前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[Cocos Creator] 制作简版消消乐(二):实现基础组件和管理脚本

[Cocos Creator] 制作简版消消乐(二):实现基础组件和管理脚本

作者头像
陈皮皮
修改2020-07-10 16:48:25
1.8K0
修改2020-07-10 16:48:25
举报
文章被收录于专栏:菜鸟小栈

前言

在上一篇文章中我们初步建立了项目并搭建好了场景,那么本篇文章将和大家一起实现部分基础组件和管理脚本

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

话不多说,Let's go!

正文

代码实现

1. 新建文件 Enum ,用来存放所有自定义枚举,方便管理。这里我先创建了名为 TileType 的枚举来表示方块的类型(同时我建议给值为 number 类型的枚举的初始值设为 1 ,避免使用时出现歧义):

代码语言:javascript
复制
// 以下为 Enum.ts 文件内容
/**
 * 方块类型
 */
export enum TileType {
    A = 1,
    B,
    C,
    D,
    E,
}

/**
 * 方块点击事件
 */
export enum TileEvent {
    TouchStart = 'tile_touchstart',
    TouchEnd = 'tile_touchend',
    TouchCancel = 'tile_touchcancel',
}

2. 新建脚本 GameConfig ,用来储存游戏配置。我这里定义了一些方块的基本配置:

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

export default class GameConfig {

    public static row: number = 8; // 行数

    public static col: number = 8; // 列数

    public static size: number = 70; // 方块的尺寸

    public static spacing: number = 5; // 间隔

    public static padding: number = 5; // 边距

    public static types: TileType[] = [1, 2, 3, 4, 5]; // 方格类型集合

}

3. 新建文件 DataStructure ,用来存放自定义的类和类型。这里我定义了名为 Coordinate 的类,用来表示方块的坐标,并且实现一些内置的函数;另外我还实现了一个快速创建坐标对象的函数 Coord :

代码语言:javascript
复制
// 以下为 DataStructure.ts 文件内容
/**
 * 坐标
 */
export class Coordinate {

    public x: number; // 横坐标

    public y: number; // 纵坐标

    /**
     * 构造函数
     * @param x 横坐标
     * @param y 纵坐标
     */
    constructor(x: number = 0, y: number = 0) {
        this.x = x;
        this.y = y;
    }

    /**
     * 更新赋值
     * @param x 横坐标
     * @param y 纵坐标
     */
    public set(x: number | Coordinate, y?: number) {
        if (typeof x === 'number') {
            this.x = x;
            this.y = y;
        } else {
            this.x = x.x;
            this.y = x.y;
        }
    }

    /**
     * 复制
     */
    public copy(): Coordinate {
        return new Coordinate(this.x, this.y);
    }

    /**
     * 对比
     * @param x 比较对象
     */
    public compare(x: number | Coordinate, y?: number): boolean {
        if (typeof x === 'number') return this.x === x && this.y === y;
        else return this.x === x.x && this.y === x.y;
    }

    /**
     * 是否相邻
     * @param coord 比较对象
     */
    public isAdjacent(coord: Coordinate): boolean {
        if (this.x === coord.x && (this.y === coord.y + 1 || this.y === coord.y - 1)) return true;
        else if (this.y === coord.y && (this.x === coord.x + 1 || this.x === coord.x - 1)) return true;
        else return false;
    }

    /**
     * 转换为方便阅读的字符串
     */
    public toString(): string {
        return '(x:' + this.x + ', ' + 'y:' + this.y + ')';
    }
}

/**
 * 创建坐标对象
 * @param x 横坐标
 * @param y 纵坐标
 */
export function Coord(x: number = 0, y: number = 0) {
    return new Coordinate(x, y);
}

4. 新建脚本 MapManager ,关于地图的实现都在这里完成。简版不需要动态生成不同的地图,只用来生成和储存每个方块位置,所以我直接做成静态脚本了:

代码语言:javascript
复制
import GameConfig from "../../common/GameConfig";
import { Coordinate } from "../type/DataStructure";

export default class MapManager {

    private static _posMap: cc.Vec2[][] = null;
    public static getPos(x: number | Coordinate, y?: number): cc.Vec2 {
        if (typeof x === 'number') return this._posMap[x][y];
        else return this._posMap[x.x][x.y];
    }

    private static width: number = null;

    private static height: number = null;

    private static beginX: number = null;

    private static beginY: number = null;

    /**
     * 初始化
     */
    public static init() {
        this.generatePosMap();
    }

    /**
     * 生成位置表
     */
    private static generatePosMap() {
        this._posMap = [];
        // 计算宽高
        this.width = (GameConfig.padding * 2) + (GameConfig.size * GameConfig.col) + (GameConfig.spacing * (GameConfig.col - 1));
        this.height = (GameConfig.padding * 2) + (GameConfig.size * GameConfig.row) + (GameConfig.spacing * (GameConfig.row - 1));
        // 以左下角为原点,计算第一个方块的位置
        this.beginX = -(this.width / 2) + GameConfig.padding + (GameConfig.size / 2);
        this.beginY = -(this.height / 2) + GameConfig.padding + (GameConfig.size / 2);
        // 计算所有方块的位置
        // 从左到右计算每一列方块的位置
        for (let c = 0; c < GameConfig.col; c++) {
            let colSet = [];
            let x = this.beginX + c * (GameConfig.size + GameConfig.spacing);
            // 从下到上计算该列的每一个方块的位置
            for (let r = 0; r < GameConfig.row; r++) {
                let y = this.beginY + r * (GameConfig.size + GameConfig.spacing);
                colSet.push(cc.v2(x, y));
            }
            this._posMap.push(colSet);
        }
    }
}

5. 新建脚本 ResManager ,此脚本用来存放游戏中用到的方块图片资源,方便运行中快速读取(简版只有固定的5种方块类型,所以我选择直接将图片资源挂载到该组件上):

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

const { ccclass, property } = cc._decorator;

@ccclass
export default class ResManager extends cc.Component {

    @property(cc.SpriteFrame)
    private a: cc.SpriteFrame = null;

    @property(cc.SpriteFrame)
    private b: cc.SpriteFrame = null;

    @property(cc.SpriteFrame)
    private c: cc.SpriteFrame = null;

    @property(cc.SpriteFrame)
    private d: cc.SpriteFrame = null;

    @property(cc.SpriteFrame)
    private e: cc.SpriteFrame = null;

    private static instance: ResManager = null;

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

    /**
     * 获取方块图片资源
     * @param tileType 方块类型
     */
    public static getTileSpriteFrame(tileType: TileType): cc.SpriteFrame {
        switch (tileType) {
            case TileType.A:
                return this.instance.a;
            case TileType.B:
                return this.instance.b;
            case TileType.C:
                return this.instance.c;
            case TileType.D:
                return this.instance.d;
            case TileType.E:
                return this.instance.e;
        }
    }
}

6. 新建脚本 Tile ,此为方块的关键组件,每个方块上都需要挂载该组件。具体内置函数和作用看下面代码与注释(其中 GameEvent 为之前文章提到过的事件监听发射系统

代码语言:javascript
复制
import { TileType, TileEvent } from "../type/Enum";
import { Coordinate, Coord } from "../type/DataStructure";
import ResManager from "../manager/ResManager";
import PoolManager from "../manager/PoolManager";
import { GameEvent } from "../../common/GameEvent";

const { ccclass, property } = cc._decorator;

@ccclass
export default class Tile extends cc.Component {

    @property(cc.Sprite)
    private sprite: cc.Sprite = null; // 显示图片的组件

    private _type: TileType = null; // 类型
    /**
     * 获取该方块的类型
     */
    public get type() { return this._type; }

    private _coord: Coordinate = null; // 坐标
    /**
     * 获取该方块的坐标
     */
    public get coord() { return this._coord; }

    protected onLoad() {
        this.bindTouchEvents();
    }

    protected onDestroy() {
        this.unbindTouchEvents();
    }

    /**
     * 节点池复用回调
     */
    public reuse() {
        this.bindTouchEvents();
    }
    /**
     * 节点池回收回调
     */
    public unuse() {
        this.unbindTouchEvents();
    }

    /**
     * touchstart 回调
     * @param e 参数
     */
    private onTouchStart(e: cc.Event.EventTouch) {
        GameEvent.emit(TileEvent.TouchStart, this._coord.copy(), e.getLocation());
    }

    /**
     * touchend 回调
     */
    private onTouchEnd() {
        GameEvent.emit(TileEvent.TouchEnd);
    }

    /**
     * touchcancel 回调
     * @param e 参数
     */
    private onTouchCancel(e: cc.Event.EventTouch) {
        GameEvent.emit(TileEvent.TouchCancel, this._coord.copy(), e.getLocation());
    }

    /**
     * 绑定点击事件
     */
    private bindTouchEvents() {
        this.node.on('touchstart', this.onTouchStart, this);
        this.node.on('touchcancel', this.onTouchCancel, this);
        this.node.on('touchend', this.onTouchEnd, this);
    }

    /**
     * 解绑点击事件
     */
    private unbindTouchEvents() {
        this.node.off('touchstart', this.onTouchStart, this);
        this.node.off('touchcancel', this.onTouchCancel, this);
        this.node.off('touchend', this.onTouchEnd, this);
    }

    /**
     * 初始化
     */
    public init() {
        this._type = null;
        this.sprite.spriteFrame = null;
        this.setCoord(-1, -1);
        this.node.setScale(0);
    }

    /**
     * 设置类型
     * @param type 类型
     */
    public setType(type: TileType) {
        this._type = type;
        this.updateDisplay();
    }

    /**
     * 更新方块图片
     */
    private updateDisplay() {
        this.sprite.spriteFrame = ResManager.getTileSpriteFrame(this._type);
    }

    /**
     * 设置坐标
     * @param x 横坐标
     * @param y 纵坐标
     */
    public setCoord(x: number | Coordinate, y?: number) {
        if (!this._coord) this._coord = Coord();
        if (typeof x === 'number') this._coord.set(x, y);
        else this._coord.set(x);
    }

    /**
     * 显示方块
     */
    public appear() {
        cc.tween(this.node)
            .to(0.075, { scale: 1.1 })
            .to(0.025, { scale: 1 })
            .start();
    }

    /**
     * 消失并回收
     */
    public disappear() {
        cc.tween(this.node)
            .to(0.1, { scale: 0 })
            .call(() => PoolManager.put(this.node))
            .start();
    }

}

7. 新建脚本 PoolManager ,用来管理游戏中频繁用到的预制体。我这里只内置了 tile 节点池 tilePool ,并且在实例化节点池时将 Tile 组件作为参数传入,目的是让节点池复用和回收时自动调用 Tile 组件上的 reuse 和 unuse 函数:

代码语言:javascript
复制
import Tile from "../component/Tile";

const { ccclass, property } = cc._decorator;

@ccclass
export default class PoolManager extends cc.Component {

    @property(cc.Prefab)
    private tilePrefab: cc.Prefab = null;

    private tilePool: cc.NodePool = new cc.NodePool(Tile);

    private static instance: PoolManager = null;

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

    /**
     * 获取节点
     */
    public static get() {
        if (this.instance.tilePool.size() > 0) return this.instance.tilePool.get();
        else return cc.instantiate(this.instance.tilePrefab);
    }

    /**
     * 存入节点
     * @param node
     */
    public static put(node: cc.Node) {
        cc.Tween.stopAllByTarget(node);
        if (this.instance.tilePool.size() < 30) this.instance.tilePool.put(node);
        else node.destroy();
    }
}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-04-21,如有侵权请联系 cloudcommunity@tencent.com 删除

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档