织梦网站上传到服务器,二手网站怎么做,宽带费用多少钱一年,wordpress文章加字段本文乃Siliphen原创#xff0c;转载请注明出处 目录
概述
游戏整体流程
游戏框架设计
单一职责的类
主要流程控制类
核心玩法模块
UI#xff1a;
游戏世界#xff1a;
本文项目的代码组织结构
作者项目实践总结
场景只有一个入口脚本
尽量少在节点上挂载脚本
构…本文乃Siliphen原创转载请注明出处 目录
概述
游戏整体流程
游戏框架设计
单一职责的类
主要流程控制类
核心玩法模块
UI
游戏世界
本文项目的代码组织结构
作者项目实践总结
场景只有一个入口脚本
尽量少在节点上挂载脚本
构建游戏世界
ECS 设计
消除物
棋盘地图
逻辑计算和显示分离
消除的实现
查找联通分量
逐步由内向外扩张的消除动画
掉落的实现
合并的实现
道具的实现
本文的完整实现源码工程 概述 《消灭星星》是一个爆款休闲游戏累计用户5亿。
目前2023.08.06在 App Store 上39.6万个评分评分4.6益智解谜类第7名。 参考链接App Store 上的“消灭星星全新版®” 本文讲解用 Cocos Creator 实现一款加强效果版的《消灭星星》的核心流程和算法。
本文实现的游戏效果如下 可在这个地址运行体验下本文实现的版本Cocos Creator | 消灭星星
文本末尾给出完整实现的源码工程。 游戏整体流程 游戏执行一轮玩家操作的流程等待玩家点击操作 - 用户点击 - 消除- 掉落 - 合并 - 等待玩家点击操作
以上流程是游戏玩家操作一次游戏执行一轮的分解动作循环。
整个游戏的组成游戏由N关组成一关由N轮玩家操作组成。 游戏框架设计 单一职责的类
可以把一轮中的每个动作都独立成一个控制类每个控制类只负责一种动作比如A类只负责消除控制B类只负责掉落控制。
这是敏捷开发中的重要原则单一职责。一个类的功能越是单一它就越内聚、越和其他系统解耦合。
每种控制类在它负责的单一动作执行完成后用回调通知其他系统它已经完成可以进行下一步操作。
比如消除控制系统处理消除完成后用一个 onComplete 回调通知外界已经完成了消除这个动作。
掉落控制系统监听消除控制系统的 onComplete 处理消除后下一步的掉落控制。 主要流程控制类
从调用先后顺序开始依次如下 类名 作用 UiTouch 处理用户触摸输入 Eliminate 处理消除。消除上下左右连色的实体。 Fall 消除后会留下空位控制消除实体掉落下来。 Merge 掉落后如果有空的列那么向左边靠拢合并起来。 核心玩法模块 核心玩法分成2大部分UI、游戏世界 UI
包括按钮、弹窗、分数显示、玩家输入 等部分。是所有用户界面的集合。
这个部分的开发有点类似于做 APP。可以用 MVC 等 APP 常用开发模式。 游戏世界
这是游戏开发独有的部分。处理游戏世界中游戏实体的行为、游戏实体之间的关系和交互、游戏世界的规则等。
游戏核心玩法的开发主要关注这部分。
因为游戏世界不可能简单分为几个层比如什么显示层逻辑层数据层等。
有可能实体之间的关系和交互很复杂MVC 等传统 APP 开发模式并不适用。一般大型游戏会采用 ECS 等设计。 本文项目的代码组织结构
如下图 作者项目实践总结 场景只有一个入口脚本 一个场景只挂一个入口脚本各种节点的引用使用 find、node.getChildByPath 等去查找。
就像 C/C 语言有一个唯一入口函数 main 。
这样做的好处是在代码中初始化各个系统有明确的初始化顺序。
在多个节点上挂多个脚本默认情况下有个问题哪个脚本先执行哪个脚本后执行。有时候执行顺序是非常重要的。
编辑器可以指定节点上挂的脚本的执行顺序但这是额外的维护负担。不如在代码中指定的维护性好。 尽量少在节点上挂载脚本 少挂载脚本的好处是
降低脚本Missing情况的维护成本。节约性能。提高项目移植性。比如移植到其他引擎上。
想象一个情况一个场景中有很多节点很多节点都挂有脚本。出于某些原因脚本和节点的挂载关系丢失了。
编辑器节点上要么不显示脚本要么脚本显示为丢失Missing。
场景简单还好重新手动拖脚本到节点上。场景复杂那就很麻烦。绝大部分情况只是知道节点Missing了脚本但不知道Missing的是哪个脚本。 为什么有时候会Missing脚本原因很多可能有如下几种
* 用户误操作。比如 破坏了 *.meta 文件。
* 多人协作 *.meta 文件冲突导致脚本丢失。
* 引擎版本多次低级高级来回切换。
* 一些说不清楚的莫名其妙的情况。 构建游戏世界 《消灭星星》的游戏世界只有2个实体消除物、棋盘地图。 棋盘没有画面表现。棋盘是消除物的容器棋盘限定了消除物计算规则和运动规则。
后面的查找消除算法和掉落控制都是作用在棋盘上计算的。 ECS 设计 本项目使用类似 ESC 的设计非严格意义上的 ECS 是如下定义
Entity 是 Componet 的容器。Component 只有数据没有逻辑。System 没有数据只有逻辑。
实体和游戏世界的交互实现实体和实体之间的交互实现都放在 System 中。
这种设计的好处是高扩展性。高维护性。易于移植到其他引擎。易于引擎升级。 消除物 定义如下
// 消除物实体
export class Elimination
{// 类型IDpublic kindId ;public presentaion new EliminationPresentaion() ;}// 消除物表现组件
export class EliminationPresentaion
{// 根节点public node : Node null ;// 动画public amin : Animation null ; }
Elimination 是消除物实体类。EliminationPresentaion 是消除物实体的表现组件类。
实体类只是组件类的容器。实体类和组件类都只有定义没有逻辑。 棋盘地图 棋盘数据本质是个二维数组。定义如下
// 地图数据
export class MapData
{// 单件/* */public static ins new MapData() ;// 数据网格public grid new Array Array Cell () ;// 地图大小public size new Size();// 是否是有效地坐标public isValid(coord : Vec2) : boolean{if( coord.x 0 || coord.y 0 || coord.x this.size.x || coord.y this.size.y ) return false ;return true ;}}// 地图单元格
export class Cell
{// 消除物public elimination : Elimination null ;// 坐标public coord new Vec2();// 在世界空间中单元格的位置。public pt new Vec3(); }
二位数组对应的位置如下图 左下角的索引是0,0右上角是9,9。 逻辑计算和显示分离
先计算好结果后再播放达到这个结果的过渡动画。逻辑计算和播放显示动画的分离可以让代码结构更清晰维护性更高。
后面的处理都是先在内存中计算好地图状态消除后地图哪些单元格为空掉落后消除物实体都落在哪个单元格上 等。
计算好地图状态后再处理画面显示播放消除动画播放掉落动画等。 消除的实现 先看下文本实现的消除效果 大部分《消灭星星》的实现都是点击后瞬间一起消除。
本文做了不一样的效果从点击的消除物开始逐步由内向外扩张的消除。
不管是瞬间消除还是某种控制动画消除第一步都是“查找相邻的同类消除物”。 查找联通分量 术语“查找联通分量”很多《数据结构》的书都会有介绍。此处我们用来查找相邻的同类消除物。
使用深度优先搜索DFS实现输出一颗树。树的根结点是玩家点击的那个消除物。
为什么要输出一棵树因为要按照树的层次进行消除才能实现逐步由内向外扩张的动画。 具体实现可查看工程源码的 ConnectionFind.find 函数。 这里为了讲解算法原理用伪代码说明算法的核心思想。
// start 是点击的消除物
dfs( start )
{// 结果数据结构用 Map 表示一棵树。key 是一个被发现的消除物value 是这个消除物的父节点。let ret new Map Elimination , Elimination () ; // 创建一个栈 stack q ;let q new Stack() ; // 访问记录。该数据结构是为了防止重复访问那些已经访问过的消除物let visit Set Elimination () ;q.push( start ) ; // 起始点入栈ret.set( start , null ) ; // 点击的消除物是根结点根结点没有父节点。for( ; q.count 0 ; ) // 栈不为空就一直循环{let t q.pop() ; // 出栈一个节点let list expand( t ) ; // 查找出栈节点上下左右4个方向相邻的同样的节点foreach( let t2 in list ) // 所有查找出来的节点入栈{if( visit.has( t2 ) ) continue ; // 跳过访问过的消除物q.push( t2 ) ; ret.add( t2 , t ) ; // 发现一个行节点t2它的父节点是t。visit.add( t2 ) ;} // end for} // end forreturn ret ;
}
《消灭星星》最难的算法就是这个“查找联通分量”了。
如果一下子不理解也没关系可以反复琢磨下本文作者的伪代码和具体实现。
或者是查阅数据结构或算法的书籍深入、详细的学习下。加油 逐步由内向外扩张的消除动画 在上一步中我们获得了一颗消除物节点树。是一个键值对数据结构key 表示发现的节点value 表示发现的节点的父节点。
这里我们处理这棵树结构为按照树的层次划分的数据结构let levels new Array Array Elimination ()
levels[ 0 ] 表示树第 1 层的节点集合。树根只有一个起始节点。
levels[ 1 ] 表示树第 2 层的节点集合。
... 以此类推
间隔一层层的整体消除即可。 如何把 Map Elimination , Elimination 处理成 Array Array Elimination 的层次结构呢?
遍历这个 Map对每个 key 向上查找直到查到 null 遇到根结点为止。就可以得知当前 key 所在的层次。
按照层次放入对应的 Array 数组容器中即可。
具体实现查看源码工程的类 SeqCtrl。 掉落的实现 消除后棋盘地图的一些被消除的消除物所在的单元格会被设为空。上面的消除物会掉落下来。
从棋盘底部向上一行行遍历遇到一个消除物后向下查找一个空位如果能找到一个空位就把这个消除物设置到那个空位上。
先设置棋盘的逻辑状态。后计算被移动的消除物的新的显示位置做一个移动动画即可。
具体实现查看源码工程的类 Fall。 合并的实现 本文实现的合并效果如下图 合并的处理在掉落之后。
遍历棋盘最底部的那一行遍历顺序从左到右。
因为之前已经执行了掉落最底部的一行有空位的话就说明有棋盘地图有一列为空。
如果发现了一个空位就说明需要合并向后查找一个非空列整体移动那一列的消除物到空位即可。
具体实现查看源码工程的类 Merge。 道具的实现 经典的消灭星星有3个道具指定一个消除物替换为另一个指定的消除物、九宫格炸弹全体消除物随机变换。 九宫格炸弹
具体实现查看源码工程的类 PropBombNine、TouchPropBombNine 全体消除物随机变换
遍历整个棋盘地图随机替换消除物即可。
具体实现查看源码工程的类 PropChangeAll。 单点替换
这个道具的实现相对以上2个比较特殊耦合了点击操作。
先要设置触摸模式为使用道具然后玩家点击后如果点击的是一个消除物
就在这个消除物的上方显示替换UI供玩家选择变换后的消除物。
具体实现查看源码工程的类 PropChangeOne、TouchPropChangeOne 本文的完整实现源码工程
源码工程下载地址Cocos Store
作者创作不易您的支持让我创造出更多更好的作品。