【 Cocos Creator 项目实战】益智游戏《2048》(附带完整源码工程)

本文详细介绍了如何使用CocosCreator3.8.0实现经典游戏2048,包括用户输入处理、地图设计、合并和移动逻辑、动画效果,以及完整源码分享。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文乃Siliphen原创,转载请注明出处

目录

游戏介绍

概述

游戏整体流程

游戏框架设计

主要流程控制类

本文项目的代码组织结构

构建游戏世界

数字方块

地图

 触摸手势识别

防触摸抖动

判断用户输入的方向

地图

任意大小的地图

初始化地图大小

地图绘制

合并和移动

合并和移动的逻辑

丝滑的合并和移动动画

本文的完整实现源码工程


游戏介绍

《2048》是一款曾经风靡全球的数字益智游戏。

目前(2023.08.14)在 App Store 的情况如下图:

关于这个游戏的更多情况可看看百度百科:百度百科-验证

概述

本文讲解用 Cocos Creator 实现经典《2048》的核心流程和算法。

Cocos Creator 版本:Cocos Creator 3.8.0

本文实现的游戏效果如下:

 

可以随意调整地图大小。可随意调整方块移动速度。

上图分别演示了 4 x 4 ,7 x 10 地图大小的效果。

运行体验本文的实现:

* Web 体验地址:Cocos Creator | 2048

* 微信小游戏体验(使用微信扫码下图):

文本末尾给出完整实现的源码工程。

游戏整体流程

游戏执行一轮玩家操作的流程:等待玩家输入操作 -> 用户滑动屏幕 -> 移动数字方块 -> 合并方块 -> 空白地方随机出现一个数字方块 -> 等待玩家输入操作

以上流程是游戏玩家操作一次,游戏执行一轮的分解动作循环。

游戏通关条件:合成数字方块2048。

游戏失败条件:当整个棋盘都填满数字方块,且没有可以合并的方块。

游戏框架设计

主要流程控制类

从调用先后顺序开始依次如下:

类名

作用

UiTouch

处理用户触摸输入

Merge

处理移动、合并的逻辑和动画。

FlowRound.fillEntity()

在地图空白处随机生成一个数字方块。

FlowRound.judge()

判断输赢。

本文项目的代码组织结构

构建游戏世界

《2048》的游戏世界只有2个实体:数字方块、棋盘地图。

棋盘是数字方块的容器。后面的移动和合并算法,都是作用在棋盘上计算的。

数字方块

// 实体
export class Entity {

    // 实体所代表的数值
    public val : number ;

    // 表现
    public presentation = new EntityPresentation() ;

}

// 实体表现
export class EntityPresentation {

    public root : Node ;

}

地图

地图数据本质是个二维数组。定义如下:

export class Map {

    // 单件
    public static ins : Map = null ;

    // 地图单元格
    public grid = new Array< Array< MapCell > >() ;

    // 格子宽高
    public size = new Size() ; 

    // 表现
    public presentation = new MapPresentation() ;

}

// 地图表现
export class MapPresentation {

    // 根节点
    public root : Node ;

}


// 地图单元格
export class MapCell {

    // 单元格上的实体
    public entity : Entity = null ;

    // 单元格所在的局部空间的坐标
    public pos : Vec3 = null ;

}

我们规定地图单元格(0,0)的位置在地图显示的左下角。x , y 的增长分别向右边和上边延伸。如下图:

 触摸手势识别

防触摸抖动

在触摸按下时记录按下的坐标,在触摸结束时用结束时的坐标减去按下时的坐标,得到一个向量。

判断这个向量的长度,大于某个数值后,就认为是有效的输入。

如果只是个很小的滑动,可能是抖动造成的,为了防止玩家误操作,可以丢弃这种输入。

判断用户输入的方向

用上一步减法得到的向量就可以判断用户操作的方向。

主要是用到 Math.atan2 这个系统函数。atan2 判断一个向量与 x 轴正方向的夹角,单位是弧度。

注意:atan2 的参数是 ( y , x ) , 不是( x , y ),y 是第一个参数。

不习惯使用弧度的话,可以转换成角度。

判断角度代码如下:

// 手势识别
export class GestureRecognition {

    // 返回:上左下右 1234 从上开始顺时针。0 无效方向
    public static exe( v : Vec2 ) : number {

        let rad = Math.atan2( v.y , v.x ) ;
        let degree = rad * ( 180 / Math.PI ) ;

        if( 45 < degree && degree < 135 ){
            return 1 ;
        } else if( -45 < degree && degree < 45  ){
            return 2 ;
        } else if( -135 < degree && degree < -45 ){
            return 3 ;
        }else if( 135 < degree && degree < 180 || 
            -180 < degree && degree < -135 ){
            return 4 ;
        }

        // console.log( "度数:" + degree ) ;
        
        return 0 ;

    }

}

地图

任意大小的地图

本文的实现,可设置任意地图大小。

如下图:

上图展示了这些尺寸的地图大小效果:3 x 3 , 5 x 5 , 6 x 4 , 7 x 10

不同地图尺寸对应不同的地图根结点缩放值。

要实现可指定任意大小的地图的前提是,动态绘制地图。

先初始化地图二维数组结构的大小,然后,地图绘制类再处理地图的绘制。

初始化地图大小

// 地图数据初始化
export class MapInit {

    public static exe( map : Map ) {

        map.presentation.root = find( "Canvas/Map" ) ;

        // 地图宽高
        let mapWidth = 3 ; 
        let mapHeight = 3 ; 

        map.size = new Size( mapWidth , mapHeight ) ;
        for( let y = 0 ; y < mapHeight ; ++y ) {
            let row = new Array< MapCell >( ) ;
            map.grid.push(row) ;
            for( let x = 0 ; x < mapWidth ; ++x ){
                let cell = new MapCell() ; 
                row.push( cell ) ;
            } // end for
        } // end for
        
    }

}

地图绘制

本文实现的中心对其的地图布局,地图的几何中心点与其父节点的原点重叠。

算法是,先算出整个地图的大小,然后宽高分别除以2,先算出 ( 0 , 0 ) 起始逻辑坐标单元格的位置。

先算出左下角的起始单元格的位置,后续可以统一处理其他单元格位置。仅仅是通过不断累加间隔就行。

地图绘制 具体实现查看源码工程的类 MapDraw

设置地图大小位置:MapInit.exe 函数

合并和移动

这个是2048的核心玩法实现,也是最难的部分。

合并和移动的逻辑

可以先算方块逻辑上的合并,后算方块逻辑上的移动。

也可以合并和移动合并在一起计算。

上下左右的合并和移动要分别处理。

这里列举用户向左( <- )滑动的处理算法,其他3个方向的以此类推。为了说明原理和简单起见以下为描述性伪代码。

// 一行行遍历地图。从左到右(->)
for( let y = 0 ; y < map.size.height ; ++y ) {
    for( let x = 0 ; x < map.size.width ; ++x ) {

        let cell = map.grid[ y ][ x ] ;
        // 如果单元格上没有实体,略过。因为我们只处理实体。不处理空格。
        if( cell.entity == null ) continue ;
        
        let cell2 = 向右(->)查找一个最近的实体所在的单元格。
        if( cell2 != null && cell2 和 cell 的实体数字相同 )
        {   // 这表示找到一个可以合并的实体
            2个实体合并,合并和的实体放在 cell 单元格的位置上。
        }
        
        以 cell 单元格为起点向左(<-)查找一个连续的空位的最右边的那个空位
        这个空位便是 cell 上的方块实体要移动到的位置。

    } // end for
} // end for

算法图示:

丝滑的合并和移动动画

如果只是按照上一步说的先在逻辑上计算合并和移动的结果,然后直接更新画面显示,会显得很生硬。

大部分的瞬间更新结果都会让画面显得生硬。好的做法是,有个滑动和合并的移动缓动动画。

加入动画后,流程就变成了:

所有的方块都会先移动到一边,然后进行合并,如果合并后留出了空位,需要再移动。保证移动后,中间不留空位。

这个流程需要对以上的逻辑处理进行改造。

具体实现查看源码工程的类 Merge

本文的完整实现源码工程

源码工程下载地址:Cocos Store

作者创作不易,您的支持让我创造出更多更好的作品。​:)

【 Cocos Creator 项目实战】系列文章链接:

【 Cocos Creator 项目实战】益智游戏《2048》

​​​​​​【Cocos Creator 项目实战 】消灭星星加强版

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值