放飞自我的时光总是过得很快,一晃春节就过去 3 天了,在这几天里老牛同学玩了不少款微信小游戏,在体验这些小游戏的同时,也分析了大佬们是如何设计小游戏的。对于一个 0 基础来研发小游戏的技术来说,市面上的这些小游戏就是最好的老师。
老牛同学的“新春小游戏计划”感觉这几天取得了一定的进展,因此本文分享给大家:
提示:本游戏所有的资源和代码均可下载,打开“老牛同学”微信小程序->点击“更多”Tab->“源代码”获取下载链接。
老牛同学的小游戏目前进展如下图,包括原始:UI 组件自适应,背景蓝天白云在飘动,背景音乐在播放、可点击暂停启动等:
老牛同学为了设计开始菜单的背景、和游戏主场景中的 3D 桥梁,浪费了近 2 天时间,几个小的建议,仅供参考。
游戏的开始场景是一个 UI 组件,老牛同学为了美观,花费大精力通过 AI 大模型设计了好几款背景图片,并且经过精心处理和裁剪,保证图片大小完美匹配小游戏设计的尺寸(宽:720像素
,高:1280像素
)。
在 Cocos Creator 场景编辑器中,效果确实达到了预期(组件:SpriteFrame
)。可是,当运行预览时,由于不同运行设备的尺寸差异较大,而背景图又无法自动拉伸,会导致背景图片无法做到满屏,没有覆盖的地方就太突兀、太难看了。
那么最好的办法就是:不做无法确定尺寸的满屏背景图片,直接使用原生背景即可!比如就像下面的这个背景,使用默认的蓝天白云就行了。
老牛同学想在游戏场景中展示一座 3D 桥梁,并且需要对桥梁增加碰撞组件(组件:Collider
)。Cocos 原生的碰撞组件列表如下:
老牛同学不会 3D 建模,于是就直接到网上买了一座 3D 桥梁(FBX
格式),桥梁本身没有问题,看起来还挺不错。可接下来问题就来了:由于桥梁设计比较复杂,Cocos 自有的碰撞组件一个也用不了!
那么最好的办法就是:对于游戏中的核心资源,使用 Cocos 自带的组件进行搭建!比如下面是老牛同学采用 Cocos 自带的圆柱体和立方体组件搭建的桥梁组件,丑是丑了一点,但是功能全部具备:
在上一篇中,老牛同学采用director.loadScene()
来进行场景控制,但遇到个问题:场景切换比较生硬,没有连贯性,体验不好!
老牛同学这几天在玩游戏过程中,发现绝大多数的游戏设计,应该都是在同一个场景中,通过“显示/隐藏”不同区块的组件来实现控制游戏场景界面,并不是采用director.loadScene()
来进行场景页面控制。
也就是说,小游戏中虽然有不同的场景、需要控制不同的界面,其实在大多数情况下,只需要设计一个Scene
即可。
注意:这只是老牛同学理解的一般情况,并非绝对,如果游戏比较复杂,或不同的场景地图有着明显差别,建议还是设计多个Scene
,以便于进行设计和管理!
那么如何控制不同组件的显示和隐藏呢?这就是“层级管理器”发挥威力的地方了:我们可以根据区块,把子组件放到不同的父层级组件中,然后程序控制父组件的显示和隐藏即可!
下面是老牛同学设计的层级:
GameRoot
是一个空节点,由它控制整个游戏逻辑,包括:GameView
游戏主场景、CanvasUI
游戏 UI 组件。GameView
是一个空节点,由它控制游戏主场景,包括主摄像头、游戏背景、游戏节点等。CanvasUI
是一个Canvas
组件,由它控制游戏 UI 组件,包括 UI 摄像头、游戏导航栏、开始菜单、游戏成功和失败界面等。注意:这只是老牛同学自己根据自己经验的设计结果,并非标准!
游戏中存在大量组件显示/隐藏、组件实例化/销毁、动画执行、音乐和音效播放/暂停、UI 交互、碰撞检测等等大量逻辑控制。
可能的方案:组件之间通过函数相互调用直接触发逻辑,它们相互依赖;优点是单独的 2 个组件之间的关系比较直观,缺点是整个游戏组件直接深度耦合,依赖关系可能是一张蜘蛛网,随着游戏复杂度上升,维护会越来越难。老牛同学认为这种设计不可取。
优化的方案:也是老牛同学推荐的办法,就像“层级管理器”一样,分层管理,上层只依赖(或控制)它的直接下层,下层不能依赖上层,或同层之间不可直接依赖,但它们可以通过发送消息给顶层,有顶层来驱动游戏逻辑。
优化的方案也是老牛同学的设计原则,下面是一张依赖关系图:
通过上述设计框架,层级分明,职责明确,维护方便!
配置中心核心职责是数据存储,包括:用户偏好设计、游戏运行状态数据等。
setInterval
每 500 毫秒存储一次。设计优点:
// 游戏存储配置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;
}
}
在本小游戏中,消息中心是小游戏逻辑控制的枢纽,它的使用方式:
/**
* 事件常量
*/
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 (${target?.name}.${cb.name})`);
for (let i = 0; i (${args})`);
for (let i = 0; i
以上部分只是老牛同学挑选的部分内容,小游戏完整的资源和源代码:打开“老牛同学”微信小程序->点击“更多”Tab->“源代码”获取下载链接。
Cocos 0基础小游戏:
Transformers 框架序列:
02.AutoModel 初始化及 Qwen2.5 模型加载全流程
03.Qwen2.5 大模型的 AutoTokenizer 技术细节
04.Qwen2.5/GPT 分词流程与 BPE 分词算法技术细节详解
05.嵌入(Embedding)机制和 Word2Vec 实战
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 种方式,让你的“儿童绘本”发声的实战教程
本文作者:奔跑的蜗牛,转载请注明原文链接:https://ntopic.cn
参与评论
手机查看
返回顶部