前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[Cocos Creator] 制作简版消消乐(三):实现方块的生成与交换

[Cocos Creator] 制作简版消消乐(三):实现方块的生成与交换

作者头像
陈皮皮
发布2020-07-10 16:50:11
1.8K1
发布2020-07-10 16:50:11
举报
文章被收录于专栏:菜鸟小栈菜鸟小栈

前言

在上一篇文章中我们实现部分基础组件和管理脚本,那么本篇文章将和大家一起实现方块的生成与交换的逻辑

温馨提醒:本文含有大量代码和注释,请提前做好心理准备并认真阅读

话不多说,冲鸭!!!

正文

生成方块

1. 新建脚本 GameUtil ,用来实现游戏中的各种算法,是游戏中最重要的模块之一。我这里暂时只实现了一个获取随机类型的函数:

代码语言:javascript
复制
import { TileType } from "../type/Enum";
import GameConfig from "../../data/GameConfig";

export default class GameUtil {

    /**
     * 获取随机类型
     * @param exclude 需排除的类型
     */
    public static getRandomType(exclude: TileType[] = []):TileType {
        let types = GameConfig.types.concat();
        for (let i = 0; i < exclude.length; i++) {
            types.splice(types.indexOf(exclude[i]), 1);
        }
          return types[Math.floor(types.length * Math.random())];
    }

}

2. 新建脚本 TileManager ,用来管理所有方块逻辑和操作实现,也是游戏最重要的模块之一。

2-1. 用二维数组变量 typeMap 和 tileMap 分别来装所有方块类型和组件,我们就可以根据二维坐标来获取特定的类型和组件;

2-2. 根据 GameConfig 中的配置遍历生成类型表,然后再根据类型表生成方块:

代码语言:javascript
复制
import Tile from "../component/Tile";
import { TileType } from "../type/Enum";
import GameConfig from "../../data/GameConfig";
import GameUtil from "../util/GameUtil";
import PoolManager from "./PoolManager";
import MapManager from "./MapManager";

const { ccclass, property } = cc._decorator;

@ccclass
export default class TileManager extends cc.Component {

    @property(cc.Node)
    private container: cc.Node = null; // 所有方块的容器

    private typeMap: TileType[][] = null; // 类型表:二维数组,保存所有方块的类型,方便计算

    private tileMap: Tile[][] = null; // 组件表:二维数组,保存所有方块 Tile 组件,方便读取

    private static instance: TileManager = null

    protected onLoad() {
        TileManager.instance = this;
    }

    public static init() {
        this.instance.generateInitTypeMap();
        this.instance.generateTiles();
    }

    /**
     * 生成初始的类型表
     */
    private generateInitTypeMap() {
        this.typeMap = [];
        for (let c = 0; c < GameConfig.col; c++) {
            let colSet: TileType[] = [];
            for (let r = 0; r < GameConfig.row; r++) {
                colSet.push(GameUtil.getRandomType());
            }
            this.typeMap.push(colSet);
        }
    }

    /**
     * 根据类型表生成方块
     */
    private generateTiles() {
        this.tileMap = [];
        for (let c = 0; c < GameConfig.col; c++) {
            let colTileSet: Tile[] = [];
            for (let r = 0; r < GameConfig.row; r++) {
                colTileSet.push(this.getTile(c, r, this.typeMap[c][r]));
            }
            this.tileMap.push(colTileSet);
        }
    }

    /**
     * 生成并初始化方块
     * @param x 横坐标
     * @param y 纵坐标
     * @param type 类型
     */
    private getTile(x: number, y: number, type: TileType): Tile {
        let node = PoolManager.get();
        node.setParent(this.container);
        node.setPosition(MapManager.getPos(x, y));
        let tile = node.getComponent(Tile);
        tile.init();
        tile.setCoord(x, y);
        tile.setType(type);
        tile.appear();
        return tile;
    }
}

3. 将我们写好的 Tile 组件挂在 tile 预制体上,并将子节点 sprite 拖到 Tile 组件上,然后保存:

预制体 tile

4. 在场景中新建一个节点 managers ,然后把 ResManager 、 PoolManager 和 TileManager 脚本拖到 managers 节点下,并设置好他们各自的属性:

managers 节点

左:ResManager | 中:PoolManager | 右:TileManager

5. 新建脚本 Game ,这个脚本为游戏的入口,启动游戏就靠它:

代码语言:javascript
复制
import MapManager from "./manager/MapManager";
import TileManager from "./manager/TileManager";

const { ccclass, property } = cc._decorator;

@ccclass
export default class Game extends cc.Component {

    protected start() {
        MapManager.init();
        TileManager.init();
    }

}

6. 在场景下新建 main 节点,并将 Game 组件拖到该节点上。保存场景,点击预览,就可以看到已经成功生成方块了:

左:main 节点 | 右:预览画面

交换方块

1. 我们交换方块有两种方式:点击和滑动。我们先在 Enum 文件中添加一个枚举 SlidDirection 来表示滑动方向:

代码语言:javascript
复制
/**
 * 滑动方向
 */
export enum SlidDirection {
    Up = 1, // 上
    Down, // 下
    Left, // 左
    Right, // 右
}

2. 向 GameUtil 中添加一个计算滑动方向的函数和一个根据坐标和方向计算目标坐标的函数:

代码语言:javascript
复制
/**
 * 获取滑动的方向
 * @param startPos 开始位置
 * @param endPos 结束位置
 */
public static getSlidDirection(startPos: cc.Vec2, endPos: cc.Vec2): SlidDirection {
    let offsetX = endPos.x - startPos.x; // x 偏移
    let offsetY = endPos.y - startPos.y; // y 偏移

    if (Math.abs(offsetX) < Math.abs(offsetY)) {
        return offsetY > 0 ? SlidDirection.Up : SlidDirection.Down
    } else {
        return offsetX > 0 ? SlidDirection.Right : SlidDirection.Left;
    }
}

/**
 * 获取指定方向的坐标
 * @param coord 坐标
 * @param direction 方向
 */
public static getCoordByDirection(coord: Coordinate, direction: SlidDirection) {
    switch (direction) {
        case SlidDirection.Up:
            return coord.y === GameConfig.row - 1 ? null : Coord(coord.x, coord.y + 1);
        case SlidDirection.Down:
            return coord.y === 0 ? null : Coord(coord.x, coord.y - 1);
        case SlidDirection.Left:
            return coord.x === 0 ? null : Coord(coord.x - 1, coord.y);
        case SlidDirection.Right:
            return coord.x === GameConfig.col - 1 ? null : Coord(coord.x + 1, coord.y);
    }
}

3. 接下来在 TileManager 中更新并添加了很多变量和函数来实现方块交换的逻辑。高能预警!!!接下来有大量代码和注释,虽然我每个函数都加了注释,但是我还是建议你直接拉到底部阅读完整文件,那样会比较好理解

代码语言:javascript
复制
private selectedCoord: Coordinate = null; // 当前已经选中的方块坐标

private tileTouchStartPos: cc.Vec2 = null; // 滑动开始位置

protected onLoad() {
    TileManager.instance = this;

    GameEvent.on(TileEvent.TouchStart, this.onTileTouchStart, this);
    GameEvent.on(TileEvent.TouchEnd, this.onTileTouchEnd, this);
    GameEvent.on(TileEvent.TouchCancel, this.onTileTouchCancel, this);
}

protected onDestroy() {
    GameEvent.off(TileEvent.TouchStart, this.onTileTouchStart, this);
    GameEvent.off(TileEvent.TouchEnd, this.onTileTouchEnd, this);
    GameEvent.off(TileEvent.TouchCancel, this.onTileTouchCancel, this);
}

/**
 * 方块的 touchstart 回调
 * @param coord 坐标
 * @param pos 点击位置
 */
private onTileTouchStart(coord: Coordinate, pos: cc.Vec2) {
    cc.log('点击 | coord: ' + coord.toString() + ' | type: ' + this.getTypeMap(coord));
    // 是否已经选中了方块
    if (this.selectedCoord) {
        // 是否同一个方块
        if (!this.selectedCoord.compare(coord)) {
            // 判断两个方块是否相邻
            if (this.selectedCoord.isAdjacent(coord)) {
                this.tryExchangeByTouch(this.selectedCoord, coord);
                this.setSelectedTile(null); // 交换后重置
            } else {
                this.tileTouchStartPos = pos;
                this.setSelectedTile(coord); // 更新选中的方块坐标
            }
        } else {
            this.tileTouchStartPos = pos;
        }
    } else {
        this.tileTouchStartPos = pos;
        this.setSelectedTile(coord);
    }
}

/**
 * 方块的 touchend 回调
 */
private onTileTouchEnd() {
    this.tileTouchStartPos = null;
}

/**
 * 方块的 touchcancel 回调
 * @param coord 坐标
 * @param cancelPos 位置
 */
private onTileTouchCancel(coord: Coordinate, cancelPos: cc.Vec2) {
    if (!this.tileTouchStartPos) return;
    this.tryExchangeBySlid(coord, GameUtil.getSlidDirection(this.tileTouchStartPos, cancelPos));
    this.tileTouchStartPos = null;
}

/**
 * 设置选中的方块
 * @param coord 坐标
 */
private setSelectedTile(coord: Coordinate) {
    this.selectedCoord = coord;
}

/**
 * 尝试点击交换方块
 * @param coord1 1
 * @param coord2 2
 */
private tryExchangeByTouch(coord1: Coordinate, coord2: Coordinate) {
    cc.log('尝试点击交换方块 | coord1: ' + coord1.toString() + ' | coord2: ' + coord2.toString());
    this.tryExchange(coord1, coord2);
}

/**
 * 尝试滑动交换方块
 * @param coord 坐标
 * @param direction 方向
 */
private tryExchangeBySlid(coord: Coordinate, direction: SlidDirection) {
    cc.log('点击交换方块 | coord1: ' + coord.toString() + ' | direction: ' + direction);
    let targetCoord = GameUtil.getCoordByDirection(coord, direction);
    if (targetCoord) {
        this.tryExchange(coord, targetCoord);
        this.setSelectedTile(null);
    }
}

/**
 * 尝试交换方块
 * @param coord1 1
 * @param coord2 2
 */
private async tryExchange(coord1: Coordinate, coord2: Coordinate) {
    await this.exchangeTiles(coord1, coord2);
}

/**
 * 交换方块
 * @param coord1 1
 * @param coord2 2
 */
private async exchangeTiles(coord1: Coordinate, coord2: Coordinate) {
    // 保存变量
    let tile1 = this.getTileMap(coord1);
    let tile2 = this.getTileMap(coord2);
    let tile1Type = this.getTypeMap(coord1);
    let tile2Type = this.getTypeMap(coord2);
    // 交换数据
    tile1.setCoord(coord2);
    tile2.setCoord(coord1);
    this.setTypeMap(coord1, tile2Type);
    this.setTypeMap(coord2, tile1Type);
    this.setTileMap(coord1, tile2);
    this.setTileMap(coord2, tile1);
    // 交换方块
    cc.tween(tile1.node).to(0.1, { position: MapManager.getPos(coord2) }).start();
    cc.tween(tile2.node).to(0.1, { position: MapManager.getPos(coord1) }).start();
    await new Promise(res => setTimeout(res, 100));
}

/**
 * 设置类型表
 * @param x 横坐标
 * @param y 纵坐标
 */
private getTypeMap(x: number | Coordinate, y?: number): TileType {
    return typeof x === 'number' ? this.typeMap[x][y] : this.typeMap[x.x][x.y];
}

/**
 * 获取类型
 * @param x 横坐标
 * @param y 纵坐标
 * @param type 类型
 */
private setTypeMap(x: number | Coordinate, y: number | TileType, type?: TileType) {
    if (typeof x === 'number') this.typeMap[x][y] = type;
    else this.typeMap[x.x][x.y] = <TileType>y;
}

/**
 * 获取组件
 * @param x 横坐标
 * @param y 纵坐标
 */
private getTileMap(x: number | Coordinate, y?: number): Tile {
    return typeof x === 'number' ? this.tileMap[x][y] : this.tileMap[x.x][x.y]; }

/**
 * 设置组件表
 * @param x 横坐标
 * @param y 纵坐标
 * @param type 组件
 */
private setTileMap(x: number | Coordinate, y: number | Tile, tile?: Tile) {
    if (typeof x === 'number') this.tileMap[x][<number>y] = tile;
    else this.tileMap[x.x][x.y] = <Tile>y;
}

4. 以上逻辑写好之后,我们就已经实现了交换方块的逻辑了:

点点点,滑滑滑

5. 为了更直观的让我们知道当前选中了哪个方块,我们接着作。

5-1. 在 tileContainer 节点上方添加一个空节点 underLayer ,将资源中的 selectFrame 图片拖到 underLayer 节点下,调整为合适的大小,然后将 selectFrame 节点关闭:

selectFrame

5-2. 给 TileContainer 添加一个属性,并对 setSelectedTile 函数进行升级:

代码语言:javascript
复制
@property(cc.Node)
private selectFrame: cc.Node = null; // 选中框

/**
 * 设置选中的方块
 * @param coord 坐标
 */
private setSelectedTile(coord: Coordinate) {
    this.selectedCoord = coord;
    if (coord) {
        this.selectFrame.active = true;
        this.selectFrame.setPosition(MapManager.getPos(coord));
    } else {
        this.selectFrame.active = false;
    }
}

5-3. 将 selectFrame 节点拖到 TileContainer 组件上之后,再预览游戏:

wow awsome

★ 到这里本篇文章内容就结束了,不知道你看懵了没,反正我是写懵了。下篇文章是将会是这个系列最重要的一篇,消消乐消除算法的实现!!!

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-04-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 菜鸟小栈 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档