淮安掼蛋源码之--选牌与出牌

本文介绍了淮安掼蛋游戏中的选牌与出牌功能实现。通过分析源码,详细解释了选牌过程中的牌组管理及出牌时的数据交换机制。

原文:淮安掼蛋源码之--选牌与出牌

“掼蛋”一直是人们茶余饭后经久不衰的娱乐项目之一,淮安掼蛋 偶尔带点“彩头”(数额不大的钱财输赢)以增加刺激也是正常的事。本文介绍:淮安掼蛋源码之--选牌与出牌。

<1>选牌.

选牌结束,检查要出的牌,添加到出牌数组中.

void SeatUnit::checkSelCardEnd()
{
    CardGroup::MODE mode = _pCards->getMode();
    _ayToCards->removeAllObjects();
    CCArray *array;CCObject *pObj;
    if(mode==CardGroup::MODE_SINGLE){
        array = _pCards->getArray();
        CCARRAY_FOREACH(array, pObj){
            CardUnit *one = (CardUnit*)pObj;
            one->setSelColor(false);
            one->setSelAction();
            if(!one->getIsActivate())continue;
            _ayToCards->addObject(one);
        }
    }else{
        array = _pCards->getArray();
        CCARRAY_FOREACH(array, pObj){
            CardUnit *one = (CardUnit*)pObj;
            if(!one->getIsSel())continue;
            _ayToCards->addObject(one);    //被选中的牌加入到要出的牌的牌组中
        }
        //展开
        if(_ayToCards->count()>0){
            _pCards->expandCardSel();
            _isExpand = true;
        }
        
    }
    codeSelCard();
}


<2>出牌.

选完牌后,点击clickGo进行出牌,出牌分为2步,1是发送出牌信息4006给服务器,2是收到服务器回调信息4001后,在其回调函数cfDesk中,调用runShowCards,在runShowCards中调用my_play_a_card进行出牌.也就是将手牌移除掉自己选中的牌.

void SeatUnit::my_play_a_card() //出牌的具体实现(调用时机是收到clickGo回调信息后,出牌)
{
    if(_ayToCards->count()==0)return;
    CCPoint showPoint = _pCardShow->getPosition();
    CCObject *pObj;
    CCARRAY_FOREACH(_ayToCards, pObj){
        CardUnit *one = (CardUnit*)pObj;
        CCPoint point = one->getLocForSrc()-showPoint;
        _pCards->removeCard(one);
        one->getSprite()->setPosition(point);
        _pCardShow->addCard(one);
    }
    _pCardShow->tidyCard(1);
    _ayToCards->removeAllObjects();
    _pCards->reviseCardLoc();
    itCardGroup();
}


总结:出牌就是将自己手中要出的牌从手牌数组中删除,同时挪动到出牌数组中.但是并没有进行removeChild和addChild这些操作,所以要想好,出牌的过程是怎样的,变化的是数据的交换,要把握本质.


<think>我们正在使用Cocos Creator 2.x版本(JavaScript)来实现掼蛋游戏的二维手展示和逻辑。 掼蛋游戏通常每位玩家手数量较多(如13张),因此需要合理地布局展示,并且支持玩家择多张(如对子、顺子等)。 实现思路: 1. 手布局:将手以扇形或圆弧形排列,或者水平排列并重叠展示(常见于斗地主类游戏),但掼蛋通常采用水平展开,中时提升并放大。 2. 逻辑:点击一张,将其设为中状态,并提升到最前面(避免被其他遮挡),同时记录中的。可以支持多张连续择或者多张任意择(根据规则,一般是任意,但需要符合型规则,这里我们只处理择逻辑,型判断由其他逻辑处理)。 步骤: 1. 创建手节点:每张是一个Prefab,包含面信息(花色和点数)和点击事件。 2. 布局:根据手数量,计算每张的位置和旋转角度(如果是扇形布局),或者计算每张的偏移位置(水平布局,每张有一定重叠)。 3. 中效果:当中时,将其位置提高(y轴向上移动),并放大一点,同时调整层级(zIndex)到最前面。 4. 多处理:可以设置点击中的加入一个中数组,再次点击则取消中(恢复原位置和大小,并降低层级)。 5. 提交:提供一个出牌按钮,点击后提交当前中的组。 注意:掼蛋游戏规则中,后需要符合一定的型(如单张、对子、三张、顺子、同花顺、炸弹等),但这里我们只关注逻辑和展示。 具体实现: 1. 创建手预制体(CardPrefab): - 包含一个Sprite组件用于显示面(背和面,根据情况切换) - 在属性检查器中,我们可以添加一个自定义脚本组件(如Card.js)来保存这张的花色(suit)和点数(rank)信息,并处理点击事件。 2. 在游戏场景中,创建一个节点(如HandCardArea)作为手容器。 3. 初始化手-服务器或本地获得手数据(一个数组,包含每张的花色和点数)。 - 实例化CardPrefab,设置每张的显示(根据花色和点数设置对应的图片),并将它们添加到HandCardArea中。 4. 布局计算(水平排列): - 假设每张的宽度为100(根据实际图片大小调整),我们希望手在容器中水平居中,并且每张之间有部分重叠(比如重叠1/3,即偏移70)。 - 总宽度 = (-1) * 偏移量 + 单张宽度 - 起始X坐标 = -总宽度/2 + 单张宽度/2 - 然后依次设置每张的位置:x = 起始X + 索引 * 偏移量,y=0(或根据情况调整) 5. 点击事件处理(在Card.js中): - 在Card.js的onLoad函数中,为节点添加点击事件:`this.node.on('click', this.onClick, this);` - 定义onClick函数:切换中状态,并改变位置和大小,同时通知手管理脚本(如HandCardManager)更新中状态。 6. 手管理脚本(HandCardManager): - 负责创建手实例,布局,以及处理中的- 维护一个中的数组(selectedCards),当一张被点击时,如果它已经被中,则取消中;如果未被中,则将其加入中数组(并检查是否超过最大可张数?根据规则,一般没有限制,但出牌时需要符合型,所以这里不做限制)。 7. 中效果: - 中:将向上移动一定距离(如20像素),缩放设为1.1(原大小1.0),并将层级(zIndex)设置为当前最高(可以设置一个全局的zIndex计数器,每次中时增加,并赋值给该节点)。 - 取消中:恢复原位置,缩放恢复1.0,层级恢复默认(可以设置为0,然后重新排列层级?但是注意,取消中后,其他可能已经改变了层级,我们可以将未中的按顺序设置层级,中的层级始终高于未中的)。 8. 重新布局问题:当中一张时,它周围的可能需要重新排列(避免被遮挡)?实际上,由于我们提高了的位置和层级,所以不需要重新排列。但当我们取消中时,需要将放回原位置。 9. 另一种实现:时,将其从原位置取出,放在一个单独的中区域(比如手区域上方),这样就不会影响其他的布局。取消中时再放回原位置。这样可能更简单。 10. 我们采用的方法:在布局时,每张都有一个基础位置(包括x, y, 缩放,旋转)。当中时,我们将其基础位置向上偏移并放大,同时设置高层级。当取消中时,恢复基础位置和缩放,并设置一个较低的层级(但注意,未中的之间也有层级关系,后面的层级应该比前面的高,这样才会显示重叠效果?实际上,我们通常让后面的覆盖前面的,所以后面的层级应该更高。但为了中时不被遮挡,我们提高的层级)。 11. 层级管理:在布局时,我们可以设置每张的zIndex为它的索引值(这样后面的会覆盖前面的)。当一张中时,我们将它的zIndex设置为一个很大的值(比如1000+索引),这样它就会在最上面。当取消中时,将其zIndex重置为原来的索引。 12. 但是注意:如果多张中,它们之间也需要有层级,应该按照中的先后顺序设置层级(后中的在最上面)?或者按照原始顺序?这里我们可以按照中的顺序,后中的放在上面。所以我们需要记录中的顺序。 13. 因此,在HandCardManager中,我们维护一个中的数组(按中顺序),当一张中时,将其加入数组末尾,并设置它的层级为当前最大层级+1(这样后中的总是在上面)。当取消中时,从数组中移除,并重置层级为原始层级(即它在手中的位置索引)。 14. 但是,当中后,其他未中的的位置和层级是否需要调整?实际上,未中的我们不需要调整层级,因为它们的层级就是它们在数组中的索引。但是,当一张被取消中时,我们需要将它的层级设置回原来的索引(注意,此时手数组中,这张的位置并没有变,所以索引也没变)。 15. 具体步骤: 布局时: for (let i = 0; i < cards.length; i++) { let card = cards[i]; card.node.setPosition(起始X + i * 偏移量, 0); card.node.zIndex = i; // 这样后面的会覆盖前面的 // 记录基础位置 card.originalPos = cc.v2(起始X + i * 偏移量, 0); card.originalScale = 1.0; card.originalZIndex = i; } 中时(在Card.js中,调用HandCardManager的方法): HandCardManager.selectCard(this); // 将当前对象传给管理器 在HandCardManager的selectCard方法中: if (card.selected) { // 如果已经中,则取消中 card.selected = false; // 从selectedCards数组中移除 let index = this.selectedCards.indexOf(card); if (index !== -1) { this.selectedCards.splice(index, 1); } // 恢复基础状态 card.node.position = card.originalPos; card.node.scale = card.originalScale; card.node.zIndex = card.originalZIndex; } else { card.selected = true; this.selectedCards.push(card); // 设置中状态:位置提高,放大,层级提高 let newPos = cc.v2(card.originalPos.x, card.originalPos.y + 20); card.node.position = newPos; card.node.scale = 1.1; // 设置层级:当前最大层级+1(为了避免和未中的冲突,我们设置一个基础值,比如1000,然后加上当前中的数量) card.node.zIndex = 1000 + this.selectedCards.length; } 注意:当取消中时,我们直接恢复了原始层级,这样它就会回到原来的层级位置(被后面未中的覆盖,但覆盖关系是合理的)。但是,如果有多张中,然后取消其中一张,那么其他中的层级不会改变,所以没有问题。 16. 问题:当一张中后,它被提高了层级,但是当它取消中时,我们将其层级设置回原始层级(比如0),而其他未中的层级可能是0,1,2,...,那么这张可能会被后面的覆盖(因为它的层级0小于后面的),这符合我们的要求(后面的覆盖前面的)。但是,在中时,我们希望中的都在最上面,所以使用一个很大的层级(1000以上)来确保它们总是在未中的之上。 17. 重新布局:如果手数量发生变化(比如出牌后),我们需要重新布局。重新布局时,需要重新计算每张的位置和层级(包括更新原始位置和层级),并且将中的取消中(或者根据情况重新设置中状态?)但注意,出牌后,被中的会被移除,所以不需要考虑。 18. 出牌:当用户点击出牌按钮时,我们获取selectedCards数组,将中的从手中移除,并重新布局手。 19. 代码结构: - HandCardManager.js(挂载在HandCardArea节点上) 属性: cardPrefab: cc.Prefab // 手预制体 cardSpacing: 70 // 之间的间隔(水平方向,实际重叠部分为:-间隔) cardWidth: 100 // 单张宽度 selectedCards: [] // 中的数组 方法: initCards(cardsData) // 初始化手,cardsData是数组,如[{suit:1,rank:1}, ...] layoutCards() // 对手进行布局 selectCard(card) // 中/取消 playCards() // 出牌(将中的打出) - Card.js(挂载在每张预制体上) 属性: suit: 0 // 花色 rank: 0 // 点数 selected: false // 是否中 originalPos: cc.Vec2 // 原始位置 originalScale: 1.0 // 原始缩放 originalZIndex: 0 // 原始层级 方法: init(data) // 初始化,设置suit和rank,并更新显示 onClick() // 点击事件,调用HandCardManager的selectCard方法 20. 注意:在Card.js中,我们需要引用HandCardManager,可以通过全局变量或者查找父节点的方式。建议在Card的onLoad函数中,查找父节点上的HandCardManager组件。 21. 示例代码(HandCardManager.js的initCards方法): initCards: function (cardsData) { // 先清空手区域 this.node.removeAllChildren(); // 排序(如果需要,按掼蛋规则排序) // 这里假设cardsData已经排序 this.cards = []; // 保存所有手对象(Card组件) for (let i = 0; i < cardsData.length; i++) { let cardNode = cc.instantiate(this.cardPrefab); this.node.addChild(cardNode); let card = cardNode.getComponent('Card'); card.init(cardsData[i]); // 初始化单张 card.manager = this; // 将管理器保存到card中,方便调用 this.cards.push(card); } this.layoutCards(); } layoutCards: function () { let count = this.cards.length; let totalWidth = (count - 1) * this.cardSpacing + this.cardWidth; let startX = -totalWidth / 2 + this.cardWidth / 2; for (let i = 0; i < count; i++) { let card = this.cards[i]; card.originalPos = cc.v2(startX + i * this.cardSpacing, 0); card.originalScale = 1.0; card.originalZIndex = i; // 如果这张没有被中,则恢复原始位置和缩放 if (!card.selected) { card.node.setPosition(card.originalPos); card.node.setScale(card.originalScale); card.node.zIndex = card.originalZIndex; } else { // 如果已经被中,则使用中的位置(但布局时,我们可能希望中的仍然在基础位置上提高,所以这里我们仍然设置基础位置,但中状态会改变位置) // 注意:中的的位置是基于原始位置提高的,所以这里我们不需要改变,但层级需要重新设置(因为重新布局后,原始层级可能变了?) // 但是注意,重新布局后,我们改变了原始位置,所以的位置应该重新计算(提高的位置也是基于新的原始位置) // 因此,我们在这里重新设置中状态的位置(提高) card.node.setPosition(card.originalPos.x, card.originalPos.y + 20); // 缩放已经在中时设置过了,这里不变 // 层级:我们仍然需要保持它在中状态中的层级(在selectCard方法中设置的层级),所以这里不改变层级 } } } 注意:重新布局时,如果已经被中,我们需要重新计算它的位置(因为原始位置变了),但是中的的位置是原始位置+偏移量。所以我们在layoutCards中,对每张都设置了原始位置,然后如果是中的,我们再额外设置一个偏移。但是,这样会覆盖掉中的的位置(因为每次布局都会重新设置位置)。所以我们需要在布局后,对中的重新设置一次偏移位置。但是,我们也可以在layoutCards中,对中的直接设置偏移位置(如上面代码所示)。 22. 在HandCardManager的selectCard方法中,我们不再需要设置位置?不对,在selectCard方法中,我们仍然需要设置位置(因为点击时触发)。但是,在重新布局时(layoutCards),我们也会设置位置(包括中的)。所以我们需要在layoutCards中处理中的的位置偏移。 23. 因此,在selectCard方法中,我们只改变中的状态,并更新层级,然后调用layoutCards()来重新布局?这样也可以,但是重新布局会遍历所有,性能不是最佳。但是手最多13张,所以可以接受。 24. 修改selectCard方法: selectCard: function (card) { if (card.selected) { // 取消中 card.selected = false; let index = this.selectedCards.indexOf(card); if (index !== -1) { this.selectedCards.splice(index, 1); } } else { card.selected = true; this.selectedCards.push(card); } // 重新布局(会重新设置所有的位置和层级,包括中的) this.layoutCards(); } 然后在layoutCards中,我们根据每张的selected状态来设置不同的位置和层级: for (let i = 0; i < count; i++) { let card = this.cards[i]; card.originalPos = cc.v2(startX + i * this.cardSpacing, 0); card.originalScale = 1.0; card.originalZIndex = i; if (!card.selected) { card.node.setPosition(card.originalPos); card.node.setScale(card.originalScale); card.node.zIndex = card.originalZIndex; } else { // 中状态:位置提高,缩放变大,层级提高(在中数组中的顺序) // 在中数组中的索引(从0开始,但层级要比未中的高) let indexInSelected = this.selectedCards.indexOf(card); if (indexInSelected === -1) { // 不应该发生,说明状态不一致 card.selected = false; card.node.setPosition(card.originalPos); card.node.setScale(card.originalScale); card.node.zIndex = card.originalZIndex; } else { card.node.setPosition(card.originalPos.x, card.originalPos.y + 20); card.node.setScale(1.1); // 层级:1000 + 在中数组中的索引(这样后中的层级更高,因为后加入的索引更大) card.node.zIndex = 1000 + indexInSelected; } } } 这样,在中数组中的顺序就是中的先后顺序,而且后中的层级更高(显示在上面)。 25. 问题:在中数组中的索引是加入的顺序,所以先中的索引小,后中的索引大,这样后中的层级更高(1000+更大的索引),所以会显示在先中的的上面。这符合需求。 26. 性能:每次点击一张,就会重新布局所有(13张),但13次操作在Cocos Creator中是可以接受的。 27. 出牌后,从手中移除中的,然后重新布局: playCards: function () { // 将中的从手数组中移除 for (let i = 0; i < this.selectedCards.length; i++) { let card = this.selectedCards[i]; // 从this.cards中移除 let index = this.cards.indexOf(card); if (index !== -1) { this.cards.splice(index, 1); } // 销毁节点 card.node.destroy(); } // 清空中数组 this.selectedCards = []; // 重新布局 this.layoutCards(); } 28. 注意:在出牌后,手数组(this.cards)发生了变化,所以重新布局时,的数量和顺序都变了。 29. 排序:掼蛋游戏需要按照特定规则排序(比如按点数,再按花色),所以在初始化手时,应该先对进行排序。可以在initCards之前排序,也可以在layoutCards中按照位置排序(但最好在数据层面排序)。 30. 在initCards中,我们可以对cardsData排序,然后再创建节点。 31. 关于逻辑,我们实现了多和取消择,以及中的会抬高并放大,层级提高。 32. 最后,我们还需要处理面的显示,即根据suit和rank设置对应的图片。在Card.js的init方法中: init: function (data) { this.suit = data.suit; this.rank = data.rank; // 根据suit和rank设置图片 // 假设我们有一个图片资源数组,或者使用图集 // 这里假设有一个方法getCardSpriteFrame(suit, rank)来获取对应的SpriteFrame let sprite = this.node.getComponent(cc.Sprite); sprite.spriteFrame = this.getCardSpriteFrame(this.suit, this.rank); } 33. 获取图片资源的方法可以根据项目实际情况,比如从资源管理器中获取。 34. 另外,为了美观,可以添加一些动画效果(比如使用cc.tween)来平滑移动。 总结:通过以上步骤,我们可以在Cocos Creator 2.x中实现掼蛋游戏的二维手展示和逻辑。 注意:以上代码是思路描述,实际编写时可能需要调整。 相关问题: 1. 如何实现手的拖拽操作(比如拖动中的)? 2. 如何优化手布局的性能(当手数量较多时)? 3. 如何实现手的排序功能(按点数、花色等)? 4. 如何实现的组合提示(如自动提示顺子、对子等)?</think>在 Cocos Creator 2.x 中实现掼蛋游戏的二维手布局逻辑,需结合节点操作、触摸事件和布局算法。以下是分步实现方案: --- ### **1. 手预制体设计** 创建面预制体 `CardPrefab`: - **节点结构**: ```bash CardPrefab (cc.Sprite) ├─ Front (面图片) └─ Back (背图片) ``` - **组件脚本** `Card.js`: ```javascript cc.Class({ properties: { cardId: 0, // 唯一标识 isSelected: false, // 中状态 frontSprite: cc.Sprite, // 面组件 backSprite: cc.Sprite // 背组件 }, init(data) { this.cardId = data.id; // 加载面纹理 cc.loader.loadRes(...) this.toggleFaceUp(true); // 默认显示正面 }, toggleFaceUp(isUp) { this.frontSprite.node.active = isUp; this.backSprite.node.active = !isUp; }, setSelected(selected) { this.isSelected = selected; this.node.y = selected ? 20 : 0; // 中时上浮 } }); ``` --- ### **2. 二维手布局算法** **水平弧形布局**(在 `HandCardManager.js` 中实现): ```javascript updateLayout() { const cardCount = this.cards.length; const totalWidth = this.cardWidth * Math.min(cardCount, 10); // 最大展开宽度 const radius = totalWidth / (2 * Math.sin(Math.PI * this.arcAngle / 360)); this.cards.forEach((card, idx) => { const angle = (idx - (cardCount-1)/2) * this.arcAngle / (cardCount-1); const radian = angle * Math.PI / 180; const x = radius * Math.sin(radian); const y = radius * (1 - Math.cos(radian)) - this.yOffset; card.node.setPosition(x, y); card.node.rotation = -angle; // 扇形旋转 card.node.zIndex = idx; // 层级管理 }); } ``` - **参数说明**: - `arcAngle`:总展开角度(建议 60°-90°) - `yOffset`:垂直偏移量(避免重叠 UI) - `cardWidth`:单张宽度 --- ### **3. 逻辑实现** **多取消逻辑**(在 `Card.js` 中): ```javascript onLoad() { this.node.on(cc.Node.EventType.TOUCH_END, () => { const newState = !this.isSelected; this.setSelected(newState); // 通知手管理器 const manager = this.node.parent.getComponent('HandCardManager'); manager.onCardSelect(this.cardId, newState); }); } ``` **管理器逻辑**(在 `HandCardManager.js` 中): ```javascript onCardSelect(cardId, isSelected) { if (isSelected) { this.selectedIds.add(cardId); // 加入中集合 } else { this.selectedIds.delete(cardId); // 移除中 } // 限制最大中数(如炸弹4张) if (this.selectedIds.size > 4) { // 自动取消最早中的 const firstId = Array.from(this.selectedIds)[0]; const firstCard = this.cards.find(c => c.cardId === firstId); firstCard.setSelected(false); this.selectedIds.delete(firstId); } } ``` --- ### **4. 动态效果优化** - **缓动动画**: ```javascript // 在 updateLayout 中替换 setPosition cc.tween(card.node) .to(0.2, { position: cc.v2(x, y), rotation: -angle }) .start(); ``` - **层级管理**: 中时临时提高 `zIndex`: ```javascript card.node.zIndex = (this.isSelected ? 100 : 0) + idx; ``` --- ### **5. 出牌逻辑整合** ```javascript // HandCardManager.js submitCards() { const selectedCards = this.cards.filter(c => this.selectedIds.has(c.cardId)); // 验证型(顺子/对子/炸弹等) if (this.checkCardType(selectedCards)) { selectedCards.forEach(card => card.node.destroy()); this.cards = this.cards.filter(c => !c.isSelected); this.updateLayout(); } } ``` --- ### **常见问题解决** 1. **面重叠**: - 调整 `arcAngle` 角度值 - 增加 `cardSpacing` 水平间距参数 2. **触摸穿透**: - 添加 `cc.BlockInputEvents` 组件到手容器 3. **性能优化**: - 对象池管理 `cc.NodePool` - 避免每帧更新布局(仅在数变化时调用) --- ### **相关问题** 1. 如何实现手的拖拽排序功能? 2. 怎样优化大量手(如 27 张)的渲染性能? 3. 如何实现型自动识别(顺子/炸弹)算法? 4. 在 Cocos Creator 中如何实现卡的 3D 翻转效果? > 引用提示:掼蛋游戏规则中型组合逻辑需参考《中国掼蛋竞赛规则》[^1]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值