Today I want to share with you the core gameplay of a popular game, which was once very popular, Super Decompression Hall. The implementation idea of Super Decompression Hall is a bit similar to Solitaire. Why do I say that? The logic of dealing cards is basically the same. Deal cards to N positions. Cards can also be moved, but there are conditional rules and restrictions. Then there is synthesis. If the color is right and the number meets the conditions, they will be eliminated. For Solitaire, you can complete the goal with one color. The main fun of this game lies in the sound effects and vibrations as well as the activation and deactivation effects of the cards, which really hits the fun points of players.
In this issue, we will deeply analyze the implementation logic of Super Decompression Center. As usual, we will decompose the gameplay by modules. The implementation engine is still RunoseAI Creator. The core of this game has the following components:
- Slot
- Cards
- Dealing and merging cards
1. Slot
There are 12 slots in total, three rows and four columns. Of course, you can decide how many slots you want. The instinctive reaction to this kind of regular layout is to use the Grid layout of the Layout component of RunoseAI Creator to implement it. Then the slots must of course be abstracted as prefabs to facilitate logic reuse. Each slot has its own coordinates, so dealing cards is to add cards to the slots and deal them to the corresponding slots. The key is the implementation of the slot prefab. As shown in the figure below: The card slot has four child nodes, the lock flag, the videoAd video ad flag, the video whether it can be merged flag, and the cards container. The slot itself is responsible for switching textures according to the state: stateFrame.
Slot attribute status
- Unlocked, can also be subdivided into full and not full.
- Not unlocked. Unlocked also includes unlocking by watching ads. It can be unlocked in the future. One slot is unlocked for each merge and upgrade.
The Super Decompression Hall is designed to hold 10 cards per slot, so I copied it. Here, a cards attribute is given to the slot. The card design is first-in, last-out, which is naturally suitable for stacking. The Y coordinate of each card must be staggered by a certain number of pixels to have a stacking effect, which depends on the size of the card. Here it is set to 18 pixels. In order to facilitate the unification of the coordinate direction, the Y direction anchor point of the slot is set to 1, and the child elements are placed from the top.
Several methods of slots
Click to select
Select the card in the slot, traverse the stack, and stop when a card with a different color is encountered. Set the state animation of the currently selected card. Mark the slot as selected.
// 选择卡片
selectCards(){
let tempCards = []
let lastCardNum = null
for(let i = this.cards.length - 1; i >= 0; i--){
let card = this.cards[i]
// 卡牌颜色类型相等,或者有空槽的情况
if(lastCardNum === null || lastCardNum === card.getComponent(Card).getNum()){
tempCards.push(card)
lastCardNum = card.getComponent(Card).getNum()
}else{
break; // 遇到不一样的,中断循环
}
}
tempCards.forEach(node => {
node._originY = node.y; // 临时存储,因为选择后可能又不选择,要还原位置
node.y += 10;
this.setSelectAnimation(node); // 上下浮动的动画
})
this.tempSelectCards = tempCards;
}
See the video effect:
https://github.com/iamaddy/minigame-developer/assets/3387191/466ccdbe-dbe6-4166-9467-1be05bf66dbb
Push
Push a card into cards, and increase the Y coordinate of the newly added card by 18 pixels.
addCards(position, cardType, config, finishCallback){
let card = cc.instantiate(this.cardPrefab) // 卡牌预制体
let cardScript = card.getComponent(Card)
cardScript.setNum(cardType); // 设置卡牌类型
card.parent = this.cardsNode; // 入槽
cardScript._originzIndex = card.zIndex;
card.zIndex = 1000;// 暂时提高层级,可能存在遮挡卡牌
let targetPosition = cc.v2({ x: 0, y: config.targetY })
// 很关键,坐标转换
let localPos = this.cardsNode.convertToNodeSpaceAR(position);
card.setPosition(localPos);
let moveDuration = .5
let moveto = cc.moveTo(moveDuration, targetPosition);
let delayAction = cc.delayTime(config.delay);
// 创建一个回调动作,当动作执行完毕后调用指定的回调函数
let callbackAction = cc.callFunc(() => {
this.cards.push(card)
card.zIndex = cardScript._originzIndex;
finishCallback && finishCallback();
}, this);
// 将两个动作按顺序执行
let sequenceAction = cc.sequence(delayAction, moveto , callbackAction);
card.runAction(sequenceAction);
}
The core point is the animation of movement, which involves coordinate conversion. Because the card here has specified the parent as cardsNode, the starting coordinate of the card is from the card dealing button. So you can first get the world coordinates of the card dealing button, and then convert this world coordinates to the local coordinates of cardsNode to complete the movement from the card dealing point to the card slot position.
let position = this.addNewCardsButton.parent.convertToWorldSpaceAR(this.addNewCardsButton.position)
See the video effect:
https://github.com/iamaddy/minigame-developer/assets/3387191/b1978411-7616-4ace-8075-3ca2e4ceed2a
Pop
The cards array is popped out, removed from the slot, and placed in the new slot. The logic here is relatively simple, that is, the temporarily selected cards are popped out of the stack one by one.
Card Movement
The most complex logic is the movement of the card. If other slots have been selected and the color of the card at the top of the target slot you click matches, you can move it. Otherwise, you cannot move it. The logic of the movement is as follows: the card selected in the old slot is popped out of the stack, and the card is pushed into the new slot. Here you can add an animation, each card moves after a certain delay, set a moving Action, and the target position can be calculated by converting it into world coordinates.
checkCanMove(){
// 遍历槽位,找出可以合并的
let allSlotNodes = this.node.parent.children
for(let i = 0; i < allSlotNodes.length; i++){
if(this.node === allSlotNodes[i]){
continue
}
let slotCom = allSlotNodes[i].getComponent(Slot);
// 卡槽是否选中
if(slotCom.isSelect){
let lastCardNum = this.getLastCardNum()
// 颜色要一样
if(slotCom.getLastCardNum() === lastCardNum ||
lastCardNum === -1){
// 已经满了,也不能移动
if(this.cards.length >= this.totalCardCount){
return false
}
// 真正的移动逻辑
this.moveCardsToNewSlot(allSlotNodes[i])
return true
}else{
// shake error
}
}
}
return false
}
moveCardsToNewSlot(originSlotNode){
// originSlotNode是待移动的槽位
let slot = originSlotNode.getComponent(Slot);
let cardLen = this.cards.length;
let targetY = cardLen ? this.cards[cardLen - 1].position.y : this.startY;
let index = 0;
let delay = 0.05
let len = slot.tempSelectCards.length
// 待移动的槽位层级要最高,否则会出现遮挡
slot._originzIndex = originSlotNode.zIndex;
originSlotNode.zIndex = 1000;
// 能移动的卡牌的数量
let canMoveCardCount = this.totalCardCount - cardLen;
// 加起来的数量不能超出总数
while(slot.tempSelectCards.length &&
index < canMoveCardCount){
// 出栈
let card = slot.tempSelectCards.shift();
card.stopAllActions() // 停止之前的上下浮动动画
let y = targetY - (len - index) * this.offsetY; // 目标位置
let that = this;
(function(index){
// 开始移动
that.moveAction(card, new cc.Vec2(0, y), delay * index, () => {
// 全部移除后还原槽位的zIndex
if(index + 1 === len || index + 1 === canMoveCardCount){
originSlotNode.zIndex = slot._originzIndex;
}
})
})(index);
index++;
// 删除卡牌
slot.cards.splice(slot.cards.indexOf(card), 1)
}
slot.isSelect = false;
// 如果tempSelectCards有剩余,也恢复状态
slot.tempSelectCards.forEach(node => {
node.stopAllActions()
node.y = node._originY;
})
// 清空tempSelectCards
slot.tempSelectCards = [];
}
https://github.com/iamaddy/minigame-developer/assets/3387191/bfb029f6-21c7-4ff0-adab-c7f00758191e
2. Card Prefab
Cards are relatively simple. There are 10 types, 1-10, represented by different colors. A setNum and getNum method are provided.
getNum(){
return this.num
}
setNum(num){
this.num = num
// 同时改变纹理
this.numSprite.spriteFrame = this.numSpriteFrames[num - 1]
this.node.getComponent(cc.Sprite).spriteFrame = this.cardSpriteFrames[num - 1]
}
3. Card issuance/merging logic
Traverse the unlocked slots, determine the spatial length of the current slot, and randomly distribute 1-2 cards according to the remaining available length. The card prefab is also used here. After instantiation, it will move from the release button position to the specific card slot position. The starting point is the world of the button as the local coordinate of the slot, and the end point is the position of the top of the slot plus the offset. After the animation is completed, it is pushed into the stack, and the card dealing logic is completed.
The merging logic is even simpler. Select a slot. If 10 cards have the same color, you can merge them. The existing cards will be destroyed and then merged into two cards of the next level. Add an animation effect, each card will be eliminated after a delay of 0.1 seconds, and with sound effects and vibration, it will add a sense of relief for users. See the video effect
https://github.com/iamaddy/minigame-developer/assets/3387191/88ee042f-e0e0-46b1-90b9-1c4be08cccca
mergeCards(){
let children = this.cardsNode.children;
// 不满足
if(children.length < 10){
return
}
// 颜色存在不一样的
let firstNum = children[0].getComponent(Card).getNum();
for(let i = 1; i < children.length; i++){
if(children[i].getComponent(Card).getNum() !== firstNum){
return
}
}
// 播放音频
cc.audioEngine.playEffect(this.mergeAudioClip, false);
// 隔一定时间销毁一个子节点
let delay = 0.05 * children.length
let moveTime = 0.01
for(let i = 0; i < children.length; i++){
let move = cc.scaleTo(moveTime, 0);
let delayAction = cc.delayTime(delay - 0.05 * i);
let child = children[i]
let callbackAction = cc.callFunc(() => {
child.destroy();
}, this);
let sequenceAction = cc.sequence(delayAction, move, callbackAction);
child.runAction(sequenceAction);
}
// 还原状态
setTimeout(() => {
this.cardsNode.removeAllChildren()
this.cards = []
this.tempSelectCards = []
this.isSelect = false;
}, (delay + moveTime * children.length) * 1000)
}
The core gameplay is almost here, a total of 400 lines of code can be completed, you can scan the code to experience it, or click the link . What remains is the auxiliary logic to unlock more slots. Those who are interested can explore it by themselves.
Welcome to follow my official account to get more game development knowledge and game source code, and I will teach you how to make games step by step.