新春“码”启 | Cocos 3D 开发微信小游戏(第4天):游戏资源设计和框架核心源代码

放飞自我的时光总是过得很快,一晃春节就过去 3 天了,在这几天里老牛同学玩了不少款微信小游戏,在体验这些小游戏的同时,也分析了大佬们是如何设计小游戏的。对于一个 0 基础来研发小游戏的技术来说,市面上的这些小游戏就是最好的老师。

老牛同学的“新春小游戏计划”感觉这几天取得了一定的进展,因此本文分享给大家:

  1. 关于游戏资源的设计,几天走了不少的弯路,有了自己心得体会,提供了几个参考建议。
  2. 针对游戏控制逻辑,老牛同学抽取了共用核心代码,包括设计思路、配置中心、消息中心等,后续游戏可直接复用。

提示:本游戏所有的资源和代码均可下载,打开“老牛同学”微信小程序->点击“更多”Tab->“源代码”获取下载链接。

老牛同学的小游戏目前进展如下图,包括原始:UI 组件自适应,背景蓝天白云在飘动,背景音乐在播放、可点击暂停启动等:

小游戏进展

游戏资源的建议

老牛同学为了设计开始菜单的背景、和游戏主场景中的 3D 桥梁,浪费了近 2 天时间,几个小的建议,仅供参考。

建议一:UI 组件满屏背景的自适应问题

游戏的开始场景是一个 UI 组件,老牛同学为了美观,花费大精力通过 AI 大模型设计了好几款背景图片,并且经过精心处理和裁剪,保证图片大小完美匹配小游戏设计的尺寸(宽:720像素,高:1280像素)。

在 Cocos Creator 场景编辑器中,效果确实达到了预期(组件:SpriteFrame)。可是,当运行预览时,由于不同运行设备的尺寸差异较大,而背景图又无法自动拉伸,会导致背景图片无法做到满屏,没有覆盖的地方就太突兀、太难看了。

那么最好的办法就是:不做无法确定尺寸的满屏背景图片,直接使用原生背景即可!比如就像下面的这个背景,使用默认的蓝天白云就行了。

设备尺寸的多样性

建议一:需要碰撞检测的组件使用原生组件

老牛同学想在游戏场景中展示一座 3D 桥梁,并且需要对桥梁增加碰撞组件(组件:Collider)。Cocos 原生的碰撞组件列表如下:

Cocos碰撞组件

老牛同学不会 3D 建模,于是就直接到网上买了一座 3D 桥梁(FBX格式),桥梁本身没有问题,看起来还挺不错。可接下来问题就来了:由于桥梁设计比较复杂,Cocos 自有的碰撞组件一个也用不了!

那么最好的办法就是:对于游戏中的核心资源,使用 Cocos 自带的组件进行搭建!比如下面是老牛同学采用 Cocos 自带的圆柱体和立方体组件搭建的桥梁组件,丑是丑了一点,但是功能全部具备:

Cocos桥梁组件

游戏控制逻辑设计

在上一篇中,老牛同学采用director.loadScene()来进行场景控制,但遇到个问题:场景切换比较生硬,没有连贯性,体验不好!

老牛同学这几天在玩游戏过程中,发现绝大多数的游戏设计,应该都是在同一个场景中,通过“显示/隐藏”不同区块的组件来实现控制游戏场景界面,并不是采用director.loadScene()来进行场景页面控制。

游戏组件层级设计

也就是说,小游戏中虽然有不同的场景、需要控制不同的界面,其实在大多数情况下,只需要设计一个Scene即可。

注意:这只是老牛同学理解的一般情况,并非绝对,如果游戏比较复杂,或不同的场景地图有着明显差别,建议还是设计多个Scene,以便于进行设计和管理!

那么如何控制不同组件的显示和隐藏呢?这就是“层级管理器”发挥威力的地方了:我们可以根据区块,把子组件放到不同的父层级组件中,然后程序控制父组件的显示和隐藏即可!

下面是老牛同学设计的层级:

  1. 总体设计思路:游戏组件由“3D 主场景”和“UI 组件”组成。
  2. GameRoot是一个空节点,由它控制整个游戏逻辑,包括:GameView游戏主场景、CanvasUI游戏 UI 组件。
  3. GameView是一个空节点,由它控制游戏主场景,包括主摄像头、游戏背景、游戏节点等。
  4. CanvasUI是一个Canvas组件,由它控制游戏 UI 组件,包括 UI 摄像头、游戏导航栏、开始菜单、游戏成功和失败界面等。

层级设计

注意:这只是老牛同学自己根据自己经验的设计结果,并非标准!

核心代码框架设计

游戏中存在大量组件显示/隐藏、组件实例化/销毁、动画执行、音乐和音效播放/暂停、UI 交互、碰撞检测等等大量逻辑控制。

可能的方案:组件之间通过函数相互调用直接触发逻辑,它们相互依赖;优点是单独的 2 个组件之间的关系比较直观,缺点是整个游戏组件直接深度耦合,依赖关系可能是一张蜘蛛网,随着游戏复杂度上升,维护会越来越难。老牛同学认为这种设计不可取。

优化的方案:也是老牛同学推荐的办法,就像“层级管理器”一样,分层管理,上层只依赖(或控制)它的直接下层,下层不能依赖上层,或同层之间不可直接依赖,但它们可以通过发送消息给顶层,有顶层来驱动游戏逻辑。

优化的方案也是老牛同学的设计原则,下面是一张依赖关系图:

框架设计

通过上述设计框架,层级分明,职责明确,维护方便!

核心代码:配置中心

配置中心核心职责是数据存储,包括:用户偏好设计、游戏运行状态数据等。

  1. 配置中心为单聊模式,初始化则缓存当前数据,存储的内容就是一个 Map 对象。
  2. 当发送数据更新时,则自动完成数据存储,通过setInterval每 500 毫秒存储一次。

设计优点:

  1. 数据缓存内存,读取效率高。
  2. 数据更新,自动存储,失败重试,调用者无需关系中间异常等。
// 游戏存储配置ID
const GAME_CONFIG_KEY: string = 'GameConfigID';/*** 存储工具类*/
export class Configuration {// 自动保存标private autoSaveFlag = false;/*** 数据缓存*/private data = {};/*** 单例实例化*/static _instance: Configuration = null;public static instance() {if (!this._instance) {this._instance = new Configuration();this._instance.init();}return this._instance;}/*** 构造器*/constructor() {console.log('Configuration对象初始化.');}/*** 初始化*/private init() {const storage = sys.localStorage.getItem(GAME_CONFIG_KEY);if (storage) {this.data = JSON.parse(storage);}// 缓存数据console.log(`Configuration配置数据: ${storage}`);// 定时存储setInterval(this.doScheduleSave.bind(this), 500);}/*** 定时存储数据*/private doScheduleSave() {if (!this.autoSaveFlag) {return;}console.log('Configuration开始存储数据.');try {const content = JSON.stringify(this.data);sys.localStorage.setItem(GAME_CONFIG_KEY, content);this.autoSaveFlag = false;console.log('Configuration存储数据完成.');} catch (e) {console.error(`Configuration存储数据异常 | ${e}`);// 继续存储this.autoSaveFlag = true;}}/*** 存储数据(`string`类型)*/public storeString(key: string, value: string) {this.data[key] = value;this.autoSaveFlag = true;}/*** 获取数据(`string`类型)*/public fetchString(key: string, defaultValue: string): string {const value = this.data[key];return value || defaultValue;}/*** 存储数据(`boolean`类型)*/public storeBoolean(key: string, value: boolean) {this.data[key] = value;this.autoSaveFlag = true;}/*** 获取数据(`boolean`类型)*/public fetchBoolean(key: string, defaultValue: boolean): boolean {const value = this.data[key];if (value == null) {return defaultValue;}return (value === true) || (value === 'true') || (value === 'ON');}/*** 存储数据(`number`类型)*/public storeNumber(key: string, value: number) {this.data[key] = value;this.autoSaveFlag = true;}/*** 获取数据(`number`类型)*/public fetchNumber(key: string, defaultValue: number): number {return this.data[key] || defaultValue;}
}

核心代码:消息中心

在本小游戏中,消息中心是小游戏逻辑控制的枢纽,它的使用方式:

  1. 游戏总控监听所有的消息事件,当子节点发生特定事件(如:点击开始游戏按钮),需要通知上游或其他节点时,他就发送消息。
  2. 游戏总控接收到消息时,开始层层驱动下游节点执行,如监听到开始游戏事件,则驱动游戏主场景显示、UI 组件隐藏开始菜单等。
/*** 事件常量*/
export enum EventName {GAME_LAUCH = 'GAME_LAUCH',GAME_START = 'GAME_START',
}/*** 游戏事件*/
interface GameEvent {// 回调函数func: Function;// 函数对象target: any;
}/*** 事件总线*/
export class EventCenter {/*** 事件处理器(1个事件,存在多个处理器)*/private static handlers: { [key: string]: GameEvent[] } = {};/*** 注册监听事件** @param eventName 事件名* @param cb 回调函数* @param target 函数所在对象(可空)*/public static on(eventName: string, cb: Function, target?: any): void {if (!this.handlers[eventName]) {this.handlers[eventName] = [];}this.handlers[eventName].push({func: cb,target: target,});console.log(`注册游戏事件:(${eventName})->(${target?.name}.${cb.name})`);}/*** 取消监听事件** @param eventName 事件名* @param cb 回调函数* @param target 函数所在对象(可空)*/public static off(eventName: string, cb: Function, target?: any): void {const eventList = this.handlers[eventName];if (!eventList || eventList.length <= 0) {return;}console.log(`取消游戏事件:(${eventName})->(${target?.name}.${cb.name})`);for (let i = 0; i < eventList.length; i++) {const event = eventList[i];if (event.func === cb && (!target || target === event.target)) {eventList.splice(i, 1);break;}}}/*** 发送事件** @param eventName 事件名* @param args 参数列表*/public static dispatch(eventName: string, ...args: any): void {const eventList = this.handlers[eventName];if (!eventList || eventList.length <= 0) {return;}console.log(`发送游戏事件:(${eventName})->(${args})`);for (let i = 0; i < eventList.length; i++) {const event = eventList[i];event.func.apply(event.target, args);}}
}

总结

以上部分只是老牛同学挑选的部分内容,小游戏完整的资源和源代码:打开“老牛同学”微信小程序->点击“更多”Tab->“源代码”获取下载链接。


Cocos 0基础小游戏:

01.技术选型 丨 02.研发流程 丨 03.小游戏框架

Transformers 框架序列:

01.包和对象加载中的设计巧思与实用技巧

02.AutoModel 初始化及 Qwen2.5 模型加载全流程

03.Qwen2.5 大模型的 AutoTokenizer 技术细节

04.Qwen2.5/GPT 分词流程与 BPE 分词算法技术细节详解

05.嵌入(Embedding)机制和 Word2Vec 实战

06.位置嵌入(Positional Embedding)

Pipeline NLP 任务序列:

零·概述 丨 01.文本转音频 丨 02.文本分类 丨 03.词元分类和命名实体识别 丨 04.问答 丨 05.表格问答 | 06.填充蒙版

往期推荐文章:

Cline 免费插件 + Qwen2.5 大模型,零经验也能开发“对联王”微信小程序

使用Cursor + Qwen2.5 大模型 零经验研发微信小程序:自由构建个性化节拍器应用实战

Bolt.new 用一句话快速构建全栈应用:本地部署与应用实战(Ollama/Qwen2.5 等)

基于 Qwen2.5-Coder 模型和 CrewAI 多智能体框架,实现智能编程系统的实战教程

vLLM CPU 和 GPU 模式署和推理 Qwen2 等大语言模型详细教程

基于 Qwen2/Lllama3 等大模型,部署团队私有化 RAG 知识库系统的详细教程(Docker+AnythingLLM)

使用 Llama3/Qwen2 等开源大模型,部署团队私有化 Code Copilot 和使用教程

基于 Qwen2 大模型微调技术详细教程(LoRA 参数高效微调和 SwanLab 可视化监控)

ChatTTS 长音频合成和本地部署 2 种方式,让你的“儿童绘本”发声的实战教程

微信公众号:老牛同学

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/877606.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

动手学大模型应用开发,第2天:调用大模型(下)

五、调用智谱 AI 1. ChatGLM 大模型 智谱 AI 是由清华大学计算机系技术成果转化而来的公司,致力于打造新一代认知智能通用模型。公司合作研发了双语千亿级超大规模预训练模型 GLM-130B,并构建了高精度通用知识图谱,形成数据与知识双轮驱动的认知引擎,基于此模型打造了 Chat…

《浅谈树拓扑序计数相关问题的一些方法》阅读笔记

才发现原来我不会拓扑序计数。临时补一下。树拓扑序计数 叶向树拓扑序计数 朴素的做法是 dp 然后归纳,但是论文给出了新解释。 考察一个简单的拓扑序生成方式:你有一个集合,初始为空,每次选择树中不在集合里的一个点,取它最浅的不在集合里的点,加入集合,重复操作 \(n\) …

MyBatis之万能的map

在映射的mxl文件中写sql语句时,可以将parameterType设置为map,这样就可以想传什么就传什么,废话不多说,上代码 insertUser2和insertUser的区别是,前者传递的是map,后者传递的是User类,如果User类的属性很多的话,后者这种传递方式需要定义一个对象,并且实现每一个属性…

昆明理工大学计算机25考研面试真题

--昆工昆明理工大学计算机技术人工智能软件工程网络空间安全计算机系统结构计算机软件与理论计算机应用技术网络与信息安全408考研综合程序设计891计算机专业核心综合数据库系统原理

提示工程 (Prompt Engineering)

概念 提示工程也叫 指令工程。调优不知道训练数据怎么办?高质量prompt 核心要点:具体、丰富、少歧义本文来自博客园,作者:chuangzhou,转载请注明原文链接:https://www.cnblogs.com/czzz/p/18696337

线段树优化建树 CF786B

题解,线段树优化建图。看到区间操作想到用线段树优化建树,建一棵外向树一棵内向树,用线段树点代表整个区间内的所有点。 大佬的图从树上找节点然后连边就可以,最后跑个dijktra就完成了。 我一次就过样例了,改了几次内存就过了这题,太好了!!! #include <bits/stdc++…

数据库安全管理中的用户和角色管理:打造安全高效的数据环境

title: 数据库安全管理中的用户和角色管理:打造安全高效的数据环境 date: 2025/2/1 updated: 2025/2/1 author: cmdragon excerpt: 在数字化时代,数据库作为信息存储和处理的核心,安全管理显得尤为重要。用户和角色管理是保障数据库安全性的重要手段,合理的管理策略不仅能…

如何成为销售顶尖高手?这4个方法绝了!

在销售的世界里,业绩高手们总有一套独特的“制胜法则”。他们不仅对产品了如指掌,更擅长与客户沟通,能够在复杂多变的市场中游刃有余,稳操胜券。玩变化:灵活应变,精准出击 高手们深知,销售没有一成不变的模式。他们懂得根据不同客户、场合和时间,灵活调整沟通方式和销售…

分布式事务之2PC两阶段提交

1. 分布式事务概述 1.1 问题背景 在分布式系统中,业务操作可能跨越多个服务或数据库(如订单服务、库存服务、支付服务),传统单机事务(ACID)无法满足跨网络节点的数据一致性需求。网络不可靠:服务间调用可能失败或超时。 数据一致性:不同节点间的状态需最终一致。 性能与…

【SQL】存储过程、函数、触发器

存储过程 存储过程(Stored Procedure)是一种在数据库中保存的SQL语句集合,它可以执行一系列的数据库操作,例如插入、更新、查询等。存储过程可以提高数据库操作的效率,减少网络流量,并且可以封装复杂的逻辑。定义: 存储过程是一组为了完成特定功能的SQL语句集,这些语句…

[Tools] GitHub Action 部署文档网站

关于部署网站,理论上来讲,只要你有一个服务器,你要采用什么样的方式来部署都是可以的。但是前提是你需要有一个服务器(物理机、云服务器)。 这节课我们部署文档网站选择使用 github 来进行部署,因为 GitHub 为我们提供了一个免费的服务器,一个账号只有一个,只要你在 Gi…