[Cocos Creator] 制作简版消消乐(四):实现消除算法


本文由“壹伴编辑器”提供技术支

前言

来了来了,终于来了!上篇文章我们实现了方块的生成和交换,那本篇文章就到了该系列的重头戏,我们一起来实现消消乐消除算法

别说了,冲!!!

本文由“壹伴编辑器”提供技术支

正文

思路讲解

1. 首先我们确定消除规则,这里我们采用和开心消消乐类似的消除规则(核心为超过 3 个连续的同类型即可消除),具体分为以下几点:

1-1. 横型和竖型;这两种种情况很简单,我们只需要遍历每一行每一列,找出那些连续超过 3 个的组合就可以了

普通横竖型

1-2. 十字型 T 型 L 型;这三种情况相对比较复杂了,但是实质上他们都是由一个横型加上一个竖型组合而成的,这三种组合的不同点在于他们的共同方块的上下左右有多少方块(比如十字型的共同方块上下左右都有 1 个以上的方块),我们需要进行额外的判断:

    

左:十字型和 T 型 | 右:L 型

代码实现

提示:项目已更新至码云,点击文章底部阅读原文获取该项目

1. 在 Enum 文件中定义一个组合类型的枚举:

// 以下为 Enum.ts 中添加的内容

/**
 * 组合类型
 */
export enum CombinationType {
    Horizontal = 1, // 横型
    Vertical, // 竖型
    Cross, // 十字型
    TShape, // T 型
    LShape, // L 型
}

2. 在 DataStructure 文件中定义一个组合的类:

// 以下为 DataStructure.ts 文件添加的内容

/**
 * 组合
 */
export class Combination {

    public coords: Coordinate[]; // 坐标集

    public commonCoord: Coordinate; // 共同坐标

    public type: CombinationType; // 组合类型

    constructor(coords: Coordinate[]) {
        this.coords = coords;
        this.updateType();
    }

    /**
     * 更新类型
     */
    private updateType() {
        let up = 0;
        let down = 0;
        let left = 0;
        let right = 0;
        let keyCoord = this.commonCoord ? this.commonCoord : this.coords[0]; // 关键坐标
        // 收集数量
        for (let i = 0; i < this.coords.length; i++) {
            if (this.coords[i].compare(keyCoord)) continue; // 同一个坐标时跳过
            // 判断位置
            if (this.coords[i].x === keyCoord.x) {
                if (this.coords[i].y > keyCoord.y) up++;
                else down++;
            } else {
                if (this.coords[i].x < keyCoord.x) left++;
                else right++;
            }
        }
        // 判断类型
        if (up === 0 && down === 0) this.type = CombinationType.Horizontal;
        else if (left === 0 && right === 0) this.type = CombinationType.Vertical;
        else if (up > 0 && down > 0 && left > 0 && right > 0) this.type = CombinationType.Cross;
        else if ((up > 0 && down === 0 && left === 0 && right > 0) ||
            (up > 0 && down === 0 && left > 0 && right === 0) ||
            (up === 0 && down > 0 && left === 0 && right > 0) ||
            (up === 0 && down > 0 && left > 0 && right === 0)) {
            this.type = CombinationType.LShape;
        } else if ((up === 0 && down > 0 && left > 0 && right > 0) ||
            (up > 0 && down === 0 && left > 0 && right > 0) ||
            (up > 0 && down > 0 && left === 0 && right > 0) ||
            (up > 0 && down > 0 && left > 0 && right === 0)) {
            this.type = CombinationType.TShape;
        }
    }

    /**
     * 组合是否包含坐标集中的任意一个,有得返回对应坐标
     * @param coords 查询坐标集
     */
    public include(coords: Coordinate[]): Coordinate {
        for (let i = 0; i < this.coords.length; i++) {
            for (let j = 0; j < coords.length; j++) {
                if (this.coords[i].compare(coords[j])) return coords[j];
            }
        }
        return null;
    }

    /**
     * 合并组合
     * @param coords 坐标集
     * @param commonCoord 共同坐标
     */
    public merge(coords: Coordinate[], commonCoord: Coordinate) {
        for (let i = 0; i < coords.length; i++) {
            if (!coords[i].compare(commonCoord))
                this.coords.push(coords[i]);
        }
        this.commonCoord = commonCoord;
        this.updateType();
    }
}

3. 接下来在 GameUtil 中实现获取当前所有可消除组合的函数:

/**
 * 获取可消除的组合
 */
public static getCombinations(typeMap: TileType[][]) {
    let combinations: Combination[] = [];
    // 逐行检测
    for (let r = 0; r < GameConfig.row; r++) {
        let count: number = 0;
        let type: TileType = null;
        for (let c = 0; c < GameConfig.col; c++) {
            if (c === 0) {
                count = 1; // 连续计数
                type = typeMap[c][r]; // 保存类型
            } else {
                if (typeMap[c][r] && typeMap[c][r] === type) {
                    // 类型相同
                    count++;
                    // 到最后一个了,是不是有 3 个以上连续
                    if (c === GameConfig.col - 1 && count >= 3) {
                        let coords = [];
                        for (let i = 0; i < count; i++) {
                            coords.push(Coord(c - i, r));
                        }
                        combinations.push(new Combination(coords));
                    }
                } else {
                    // 类型不同
                    if (count >= 3) {
                        // 已累积 3 个
                        let coords = [];
                        for (let i = 0; i < count; i++) {
                            coords.push(Coord(c - 1 - i, r));
                        }
                        combinations.push(new Combination(coords));
                    }
                    // 重置
                    count = 1;
                    type = typeMap[c][r];
                }
            }
        }
    }
    // 逐列检测
    for (let c = 0; c < GameConfig.col; c++) {
        let count: number = 0;
        let type: TileType = null;
        for (let r = 0; r < GameConfig.row; r++) {
            if (r === 0) {
                count = 1;
                type = typeMap[c][r];
            } else {
                if (typeMap[c][r] && typeMap[c][r] === type) {
                    count++;
                    if (r === GameConfig.row - 1 && count >= 3) {
                        let coords = [];
                        for (let i = 0; i < count; i++) {
                            coords.push(Coord(c, r - i));
                        }
                        // 是否可以和已有组合合并
                        let hasMerge = false;
                        for (let i = 0; i < combinations.length; i++) {
                            let common = combinations[i].include(coords);
                            if (common) {
                                combinations[i].merge(coords, common);
                                hasMerge = true;
                                break;
                            }
                        }
                        if (!hasMerge) combinations.push(new Combination(coords));
                    }
                } else {
                    if (count >= 3) {
                        let coords = [];
                        for (let i = 0; i < count; i++) {
                            coords.push(Coord(c, r - 1 - i));
                        }
                        // 是否可以和已有组合合并
                        let hasMerge = false;
                        for (let i = 0; i < combinations.length; i++) {
                            let common = combinations[i].include(coords);
                            if (common) {
                                combinations[i].merge(coords, common);
                                hasMerge = true;
                                break;
                            }
                        }
                        if (!hasMerge) combinations.push(new Combination(coords));
                    }
                    count = 1;
                    type = typeMap[c][r];
                }
            }
        }
    }
    return combinations;
}

4. 然后我们对 TileManager 进行改造;添加了 combinations 变量、更新 tryExchange 函数并加入 eliminateCombinations 和 eliminateTile 函数:

private combinations: Combination[] = null; // 可消除组合

/**
 * 尝试交换方块
 * @param coord1 1
 * @param coord2 2
 */
private async tryExchange(coord1: Coordinate, coord2: Coordinate) {
    // 交换方块
    await this.exchangeTiles(coord1, coord2);
    // 获取可消除组合
     this.combinations = GameUtil.getCombinations(this.typeMap);
    if (this.combinations.length > 0) {
        // 消除!!!
        this.eliminateCombinations();
    } else {
        // 不能消除,换回来吧
        await this.exchangeTiles(coord1, coord2);
    }
}

/**
 * 消除组合
 */
private eliminateCombinations() {
    for (let i = 0; i < this.combinations.length; i++) {
        for (let j = 0; j < this.combinations[i].coords.length; j++) {
            this.eliminateTile(this.combinations[i].coords[j]);
        }
    }
    this.combinations = [];
}

/**
 * 消除方块
 * @param coord 坐标
 */
private eliminateTile(coord: Coordinate) {
    this.getTileMap(coord).disappear(); // 方块消失
    this.setTileMap(coord, null); // 数据置空
    this.setTypeMap(coord, null); // 数据置空
}

5. 此时,我们的消除功能也实现了:

★ 但是现在还有一个问题,游戏开始时就随机出现了一些可消除的组合,理论上来说开局时是不能有任何消除但是同时又要存在可一步消除的情况,所以这就是我们下篇文章会讲到的东西了。

本文由“壹伴编辑器”提供技术支

结束语

以上皆为本菜鸡的个人观点,文采实在不太好,如果写得不好还请各位见谅。如果有哪些地方说的不对,还请各位指出,大家共同进步。

接下来我会持续分享自己所学的知识与见解,欢迎各位关注本公众号。

我们,下次见!

本文由“壹伴编辑器”提供技术支

扫描二维码

获取更多精彩

文弱书生陈皮皮

点击 阅读原文 获取完整项目

  • 4
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: Cocos Creator是一个基于JavaScript和TypeScript开发的游戏开发引擎,它提供了丰富的功能和工具来帮助开发者创建游戏。要实现一个简单的搓牌动画,可以按照以下步骤进行: 1. 创建一个新的Cocos Creator项目,并导入所需的资源。 2. 在场景编辑器中创建一个节点,作为牌的容器。 3. 创建两个精灵节点,分别用作牌的背面和正面,并将它们添加到牌的容器中。 4. 使用属性检查器设置背面精灵节点的初始位置和角度,使其完全覆盖住正面精灵节点。 5. 在脚本文件中,通过获取背面和正面精灵节点的引用,可以开始实现搓牌动画。 6. 在脚本文件中,可以使用缓动动画来实现搓牌效果。可以使用Cocos Creator提供的"cc.tween" API来创建一个缓动动画序列。 7. 在缓动动画序列中,可以先设置背面精灵节点的位置和角度,使其慢慢移动和旋转,暴露出正面精灵节点。 8. 在动画序列的最后,可以设置正面精灵节点的位置和角度,使其慢慢恢复到初始状态。 9. 将动画序列应用到牌的容器节点上,并运行动画。 通过以上步骤,就可以在Cocos Creator实现一个简单的搓牌动画。根据具体情况,可以调整动画的效果和时间等参数,以满足需求。 ### 回答2: 搓牌动画是一种常见的游戏特效,下面我将使用Cocos Creator中的TypeScript语言为您实现一个简单的搓牌动画。 首先,在Cocos Creator中创建一个新的场景,并添加一个精灵节点作为显示牌的容器。确保您已经加载了纹理资源,并将其分别设置为牌背和牌面。 接下来,创建一个脚本组件`CardAnimation`,并将其挂载到显示牌的容器节点上。在脚本中,我们需要定义牌背和牌面两个精灵节点的引用,以及搓牌动画的开始和结束状态。 ```typescript const { ccclass, property } = cc._decorator; @ccclass export default class CardAnimation extends cc.Component { @property(cc.Sprite) cardBack: cc.Sprite = null; @property(cc.Sprite) cardFront: cc.Sprite = null; private isRevealing: boolean = false; start() { // 将牌面隐藏起来 this.cardFront.node.active = false; } revealCard() { if (this.isRevealing) return; this.isRevealing = true; // 开始搓牌动画 cc.tween(this.cardBack.node) .to(0.5, { scaleX: 0 }) .call(() => { // 动画完成时,显示牌面 this.cardBack.node.active = false; this.cardFront.node.active = true; }) .to(0.5, { scaleX: 1 }) .call(() => { this.isRevealing = false; }) .start(); } } ``` 最后,在场景加载时,调用`revealCard`方法启动搓牌动画。可以通过按钮点击或其他触发机制来调用这个方法。 ```typescript const { ccclass, property } = cc._decorator; import CardAnimation from "CardAnimation"; @ccclass export default class Game extends cc.Component { @property(cc.Button) btnRevealCard: cc.Button = null; @property(CardAnimation) cardAnimation: CardAnimation = null; onLoad() { this.btnRevealCard.node.on('click', () => { this.cardAnimation.revealCard(); }); } } ``` 这样,当点击按钮时,牌背会向左平滑搓动,并在动画完成后显示牌面。通过调整动画的时间和缓动函数,您可以进一步改进搓牌动画的效果。 ### 回答3: 在Cocos Creator中使用TypeScript(TS)实现一个简单的搓牌动画可以按照以下步骤进行: 1. 首先,创建一个新的Cocos Creator项目,并且确保项目中已经安装了TypeScript插件。 2. 在编辑器中创建一张背面朝上的牌的精灵,并将其命名为cardBack。 3. 创建一个TS脚本文件,例如CurlCardAnimation.ts,并将其附加到cardBack的节点上。 4. 在脚本中,首先导入必要的模块: ```typescript import { _decorator, Component, Animation, Node } from 'cc'; ``` 5. 然后,在脚本的onLoad方法中,获取cardBack节点的Animation组件,并定义两个动画剪辑对象: ```typescript private animation: Animation | null = null; private curlClip: AnimationClip | null = null; private uncurlClip: AnimationClip | null = null; onLoad() { this.animation = this.node.getComponent(Animation); this.curlClip = this.animation.getClips()[0]; this.uncurlClip = this.animation.getClips()[1]; } ``` 6. 接下来,创建一个公共方法,用于触发搓牌动画: ```typescript public playCurlAnimation() { this.animation?.stop(); this.animation?.play(this.curlClip); this.animation?.on('finished', this.playUncurlAnimation, this); } public playUncurlAnimation() { this.animation?.stop(); this.animation?.play(this.uncurlClip); } ``` 7. 最后,在编辑器的场景中创建一个按钮,并将按钮的点击事件绑定到playCurlAnimation方法。 8. 运行项目后,点击按钮时,搓牌动画将会触发。 这样,你就可以在Cocos Creator中使用TypeScript实现一个简单的搓牌动画了。根据需求,你可以自定义动画剪辑和动画效果,使其更加丰富和炫酷。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值