经典三消游戏核心玩法

游戏引擎

- cocos creator 3.6

项目地址

three-tiles · GitCode

核心逻辑代码

- assets/script/game/GameScene.ts

import { _decorator, Component, Node, Prefab, assetManager, NodePool, instantiate, v3, Vec3, UITransform, game, tween, Animation, PageView, Label } from 'cc';
import { Fw } from '../../framework/manager/Fw';
import { ArrayUtil } from '../../framework/util/ArrayUtil';
import { LoadUtil } from '../../framework/util/LoadUtil';
import { GameStep, TileStatus, TileUnit } from '../config/BaseConf';
import { PageConf } from '../config/PageConf';
import { GameManager } from '../manager/GameManager';
import { Tile } from './Tile';
const { ccclass, property } = _decorator;

@ccclass('GameScene')
export class GameScene extends Component {

    public static I: GameScene

    @property(Node)
    Map: Node = null;
    @property(Node)
    Put: Node = null;
    @property(Prefab)
    TilePrefab: Prefab = null;
    @property(Prefab)
    FxTishiPrefab: Prefab = null;

    @property(Label)
    NumRefresh: Label = null;
    @property(Label)
    NumTips: Label = null;
    @property(Label)
    NumBack: Label = null;


    lvd: { type: number, cell: number[][][], idList: number[], total: number } = null;
    tilePool: NodePool = new NodePool();
    fxTishiPool: NodePool = new NodePool();

    start() {
        GameScene.I = this;
        this.preLoadLevel(() => {
            this.initLevel();
        })

        this.refreshUI();//刷新UI
    }

    update(deltaTime: number) {

    }

    //预加载关卡配置与对应的预制体
    preLoadLevel(complete: Function, progress: Function = null): void {
        let skinChildIdList: number[] = [
            0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
            10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
        ];
        let finished: number = 0;
        let total: number = 2;
        LoadUtil.load(`level:${GameManager.I.level}`, (err0, res0) => {
            console.log(err0, res0)
            let lvd: any = res0.json;
            this.lvd = lvd;
            this.lvd.idList = [];
            this.lvd.total = 0;
            for (let row of this.lvd.cell)
                this.lvd.total += row.length;
            total = lvd.type + 1 + 1;
            finished++;
            progress && progress(finished, total);
            this.lvd.idList = ArrayUtil.randomElementList(skinChildIdList, lvd.type);
            let arr = [];
            for (let id of this.lvd.idList) {
                arr.push(`icon:skin/skin${GameManager.I.curSkinId}/${id}/spriteFrame`);
            }
            arr.push(`icon:skin/kuai${GameManager.I.curSkinId}/spriteFrame`);
            LoadUtil.loadList(arr, complete, () => {
                finished++;
                progress && progress(finished, total);
            })
        })
    }
    //初始化关卡
    initLevel(): void {
        //移除Tile
        for (let i: number = this.Map.children.length - 1; i >= 0; i--) {
            for (let j: number = this.Map.children[i].children.length - 1; j >= 0; j--) {
                this.removeTile(this.Map.children[i].children[j]);
            }
        }
        //配置
        let groupTotal: number = this.lvd.total / 3;
        let idList: number[] = [];
        for (let i: number = 0; i < groupTotal; i++) {
            let id = ArrayUtil.randomElement(this.lvd.idList);
            for (let j: number = 0; j < 3; j++)
                idList.push(id);
        }
        idList = ArrayUtil.randomSort(idList);
        let tileList = [];
        for (let i: number = 0; i < this.lvd.cell.length; i++) {
            this.lvd.cell[i] = this.lvd.cell[i].sort((a, b) => { return a[1] !== b[1] ? b[1] - a[1] : a[0] - b[0] })
            for (let cell of this.lvd.cell[i])
                tileList.push(v3(...cell, i));
        }
        //生成
        let index: number = 0;
        this.schedule(() => {
            let id = idList[index];
            let pos = tileList[index];
            let node: Node = this.createTile();
            let sc: Tile = node.getComponent(Tile);
            sc.id = id;
            // sc.index = index;
            sc.pos = pos;
            sc.status = TileStatus.Show;
            node.active = true;
            index++;
            //向上检测是否被覆盖 所有显示的块
            this.checkUpAll();
        }, 0.03, this.lvd.total - 1);
    }
    //创建块
    createTile(): Node {
        let node: Node = this.tilePool.get();
        if (node) return node;
        node = instantiate(this.TilePrefab);
        let sc: Tile = node.getComponent(Tile);
        sc.status = TileStatus.Recover;
        node['TileId'] = -1;
        node['IsMask'] = false;
        return node;
    }
    //移除块
    removeTile(node: Node): void {
        node.setParent(this.node);
        node.active = false;
        let sc: Tile = node.getComponent(Tile);
        sc.status = TileStatus.Recover;
        // sc.index = -1;
        this.tilePool.put(node);
    }
    //创建提示粒子
    createFxTishi(): Node {
        let node: Node = this.fxTishiPool.get();
        if (node) return node;
        node = instantiate(this.FxTishiPrefab);
        return node;
    }
    //移除提示粒子
    removeFxTishi(node: Node): void {
        node.setParent(this.node);
        node.active = false;
        this.fxTishiPool.put(node);
    }


    //加入队列的顺序
    putSortList: Node[] = [];
    //加入待消除的队列
    putTile(node: Node): void {
        this.putSortList.push(node);//加入队列的顺序
        Fw.Audio.playEffect("tile_click");
        let sc: Tile = node.getComponent(Tile);
        sc.status = TileStatus.Put;
        let index = 0;
        let len: number = this.Put.children.length;
        if (len) {
            for (let i: number = 0; i < len; i++) {
                if (this.Put.children[i]['TileId'] === node['TileId']) {
                    index = i + 1;
                }
            }
            if (index === 0) index = len;
        }
        let position: Vec3 = node.worldPosition;
        this.Put.insertChild(node, index);
        node.worldPosition = position;
        //向上检测是否被覆盖 所有显示的块
        this.checkUpAll();
        //检测消除列表
        this.scheduleOnce(this.checkPutTile, 0.3);
    }
    PutMax: number = 7;//最大待消除数量
    PutAniDuration: number = 0.3;//每一次动画持续时长(单位:秒)
    nextPutAniTime: number = 0;
    putAniList: [string, Node, any][][] = [];
    //检测消除列表
    checkPutTile(): void {
        let len: number = this.Put.children.length;
        if (len < 3) return;
        let preTileId: number = this.Put.children[0]['TileId'];
        let total: number = 1;
        let index: number = -1;
        for (let i: number = 1; i < len; i++) {
            if (preTileId === this.Put.children[i]['TileId']) {
                total++;
                if (total >= 3) {
                    index = i;
                    break;
                }
            }
            else {
                preTileId = this.Put.children[i]['TileId'];
                total = 1;
            }
        }
        if (index === -1) return this.checkLevelFail();
        Fw.Audio.playEffect("tile_match");
        for (let i: number = index; i >= index - 2; i--) {
            let node: Node = this.Put.children[i];
            //播放粒子
            let fx: Node = this.createFxTishi();
            this.node.addChild(fx);
            fx.worldPosition = node.worldPosition;
            fx.active = true;
            fx.getComponent(Animation).play('fxtishi');
            this.scheduleOnce(() => {
                this.removeFxTishi(fx);
            }, 1)
            //移除块
            this.removeTile(node);
        }
        //检测是否通关
        this.checkLevelWin();
    }
    //检测是否失败
    checkLevelFail(): void {
        if (this.Put.children.length < this.PutMax) return;
        console.log("失败")
        GameManager.I.gameStep = GameStep.End;
        GameManager.I.levelRes = false;
        Fw.View.open(PageConf.EndView);
    }
    //检测是否胜利
    checkLevelWin(): void {
        for (let child of this.Map.children)
            if (child.children.length) return;
        console.log("胜利");
        GameManager.I.gameStep = GameStep.End;
        GameManager.I.levelRes = true;
        Fw.View.open(PageConf.EndView);
    }
    //向上检测是否被覆盖 所有显示的块
    checkUpAll(): void {
        for (let row of this.Map.children) {
            for (let node of row.children) {
                let sc: Tile = node.getComponent(Tile);
                sc.checkUp();
            }
        }
    }
    //刷新道具
    onBtnRefresh(): void {
        if (GameManager.I.refreshTool < 1) {
            GameManager.I.refreshTool += 3;
            this.refreshUI();//刷新UI
            Fw.msg('数量不足');
            return;
        }
        GameManager.I.refreshTool--;
        this.refreshUI();//刷新UI

        //配置
        let idList: number[] = [];
        let tileList = [];
        for (let row of this.Map.children) {
            for (let node of row.children) {
                let sc: Tile = node.getComponent(Tile);
                idList.push(sc.id);
                tileList.push(sc.pos);
            }
        }
        idList = ArrayUtil.randomSort(idList);
        //移除之前的Tile节点
        for (let i: number = this.Map.children.length - 1; i >= 0; i--) {
            for (let j: number = this.Map.children[i].children.length - 1; j >= 0; j--) {
                this.removeTile(this.Map.children[i].children[j]);
            }
        }
        //生成
        let index: number = 0;
        this.schedule(() => {
            let id = idList[index];
            let pos = tileList[index];
            let node: Node = this.createTile();
            let sc: Tile = node.getComponent(Tile);
            sc.id = id;
            // sc.index = index;
            sc.pos = pos;
            sc.status = TileStatus.Show;
            node.active = true;
            index++;
            //向上检测是否被覆盖 所有显示的块
            this.checkUpAll();
        }, 0.017, idList.length - 1);
    }
    //回退道具
    onBtnBack(): void {
        if (GameManager.I.backTool < 1) {
            GameManager.I.backTool += 3;
            this.refreshUI();//刷新UI
            Fw.msg('数量不足');
            return;
        }
        for (let i: number = this.putSortList.length - 1; i >= 0; i--) {
            let sc: Tile = this.putSortList[i].getComponent(Tile);
            if (sc.status !== TileStatus.Put) continue;
            GameManager.I.backTool--;
            this.refreshUI();//刷新UI
            sc.reset();
            return;
        }

        Fw.msg("暂无可回退的对象");
    }
    //提示道具
    onBtnTips(): void {
        if (GameManager.I.tipsTool < 1) {
            GameManager.I.tipsTool += 3;
            this.refreshUI();//刷新UI
            Fw.msg('数量不足');
            return;
        }
        GameManager.I.tipsTool--;
        this.refreshUI();//刷新UI
        //PS:当前的推荐方法还有很大的优化空间,不支持多层堆叠推荐
        //显示的块
        let showMap = {};
        let showIdList = [];
        for (let i: number = this.Map.children.length - 1; i >= 0; i--)
            for (let node of this.Map.children[i].children) {
                if (node['IsMask']) continue;
                let id = node['TileId'];
                if (!showMap[id]) showMap[id] = [];
                showMap[id].push(node);
                if (showIdList.indexOf(id) === -1) showIdList.push(id);
            }
        showIdList = showIdList.sort((a, b) => { return showMap[b].length - showMap[a].length })
        //已推的块
        let putIdList = [];
        let putMap = {}
        for (let node of this.Put.children) {
            let id = node['TileId'];
            if (!putMap[id]) putMap[id] = [];
            putMap[id].push(node);
            if (putIdList.indexOf(id) === -1) putIdList.push(id);
        }
        putIdList = putIdList.sort((a, b) => { return putMap[b].length - putMap[a].length })
        //剩余的格子数
        let disPutNum: number = this.PutMax - this.Put.children.length;
        //情况一:已推的块+显示的块>=3
        for (let id of putIdList) {
            if (putMap[id].length + disPutNum < 3) continue;//剩余的格子数无法满足消除的需求
            if (!showMap[id]) continue;
            if (putMap[id].length + showMap[id].length < 3) continue;
            for (let i: number = 0, len: number = 3 - putMap[id].length; i < len; i++)
                this.scheduleOnce(() => { this.putTile(showMap[id][i]) }, 0.3 * i);
            return;
        }
        //情况二:显示的块>=3
        if (disPutNum >= 3 && showMap[showIdList[0]].length >= 3) {
            for (let i: number = 0; i < 3; i++)
                this.scheduleOnce(() => { this.putTile(showMap[showIdList[0]][i]) }, 0.3 * i);
            return;
        }
        //没有满足3个待消除的情况
        if (disPutNum <= 1) {
            GameManager.I.tipsTool++;
            this.refreshUI();//刷新UI
            Fw.msg("没有可提示的选项");
            return;
        }
        //情况三:推已推中有相同的块
        for (let id of putIdList) {
            if (!showMap[id]) continue;
            this.putTile(showMap[id][0]);
            return;
        }
        //情况四:推显示的第一个
        this.putTile(showMap[showIdList[0]][0]);
    }

    //刷新UI
    refreshUI(): void {
        this.NumBack.string = GameManager.I.backTool + '';
        this.NumRefresh.string = GameManager.I.refreshTool + '';
        this.NumTips.string = GameManager.I.tipsTool + '';
    }
}

- assets/script/game/Tile.ts

import { _decorator, Component, Node, Sprite, v3, Vec3, game, director, tween, UITransform } from 'cc';
import { LoadUtil } from '../../framework/util/LoadUtil';
import { GameStep, TileStatus, TileUnit } from '../config/BaseConf';
import { GameManager } from '../manager/GameManager';
import { GameScene } from './GameScene';
const { ccclass, property } = _decorator;

@ccclass('Tile')
export class Tile extends Component {

    v3_0: Vec3 = v3();
    v3_1: Vec3 = v3();

    @property(Sprite)
    Bg: Sprite = null;
    @property(Sprite)
    Icon: Sprite = null;
    @property(Node)
    Mask: Node = null;

    start() {

    }
    onClick() {
        if (GameManager.I.gameStep !== GameStep.Game) return;
        switch (this.status) {
            case TileStatus.Show: {
                if (this.Mask.active) return;
                GameScene.I.putTile(this.node);
                break;
            }
            case TileStatus.Put: {


                break;
            }
        }
    }
    update(deltaTime: number) {
        switch (this.status) {
            case TileStatus.Put: {
                let siblingIndex: number = this.node.getSiblingIndex();
                this.v3_0.y = 0;
                this.v3_0.x = TileUnit * 2 * siblingIndex;
                Vec3.lerp(this.v3_1, this.node.position, this.v3_0, 0.2);
                this.node.position = this.v3_1;
                break;
            }
            case TileStatus.Show: {
                //向上检测是否被覆盖
                // if (director.getTotalFrames() % GameScene.I.lvd.total !== this.index) return;
                // this.checkUp();
                break;
            }
        }
    }
    //向上检测是否被覆盖
    checkUp(): void {
        for (let z: number = this.z + 1; z < GameScene.I.Map.children.length; z++) {
            for (let node of GameScene.I.Map.children[z].children) {
                if (Math.abs(node.position.x - this.node.position.x) <= TileUnit && Math.abs(node.position.y - this.node.position.y) <= TileUnit) {
                    this.Mask.active = this.node['IsMask'] = true;
                    return;
                }
            }
        }
        this.Mask.active = this.node['IsMask'] = false;
    }
    //-----------------------------------状态-----------------------------------
    status: TileStatus = TileStatus.Recover;
    //-----------------------------------编号-----------------------------------
    get id(): number { return this.node['TileId'] }
    set id(val: number) {
        this.node['TileId'] = val;
        this.Bg.spriteFrame = LoadUtil.get(`icon:skin/kuai${GameManager.I.curSkinId}/spriteFrame`);
        this.Icon.spriteFrame = LoadUtil.get(`icon:skin/skin${GameManager.I.curSkinId}/${this.id}/spriteFrame`);
    }
    // index: number = -1;
    //-----------------------------------位置-----------------------------------
    _x: number = 0;
    get x(): number { return this._x }
    set x(val: number) {
        this._x = val;
        this.node.position = v3(this.x * TileUnit, this.y * TileUnit);
    }
    _y: number = 0;
    get y(): number { return this._y }
    set y(val: number) { this._y = val; this.node.position = v3(this.x * TileUnit, this.y * TileUnit); }
    _z: number = 0;
    get z(): number { return this._z }
    set z(val: number) {
        this._z = val;
        this.node.setParent(GameScene.I.Map.children[val]);
        this.node.position = v3(this.x * TileUnit, this.y * TileUnit);
    }
    get pos(): Vec3 { return v3(this.x, this.y, this.z) }
    set pos(val: Vec3) {
        this._x = val.x;
        this._y = val.y;
        this.z = val.z;
    }

    reset(): void {
        let position: Vec3 = this.node.worldPosition;
        let parent: Node = GameScene.I.Map.children[this.z];
        let index: number = 0;
        for (let i: number = 0; i < parent.children.length; i++) {
            let sc: Tile = parent.children[i].getComponent(Tile);
            if (this.y < sc.y) continue;
            if (this.y === sc.y && this.x > sc.x) continue;
            index = i;
            break;
        }
        parent.insertChild(this.node, index);
        this.node.worldPosition = position;
        tween(this.node).to(0.6, { position: v3(this.x * TileUnit, this.y * TileUnit) }, { easing: 'circInOut' }).start();
        this.status = TileStatus.Show;
    }
}

内容说明

- (非业务)基础框架

- 核心玩法

- 道具(刷新、回退、提示)

提示:本项目如标题所示仅提供核心玩法的逻辑,不计划补充周边功能

页面效果

 

 

实现流程

1、根据配置(配置中包含种类数量、块位置列表)实例化关卡

2、每步操作后,【检测】所有块的被遮挡状态

3、点击(未遮挡的)块,将其移动到待消除的列表中

- 【检测】满3个执行消除

- 【检测】待消除的列表长度>=7 游戏失败

- 【检测】消除所有的块 游戏通关

比较麻烦的点:

1、块的排列(没有什么捷径,addChild前优先做好前后排序)

2、块的移动(采用lerp的方式)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值