太强了
首屏展示
在完成屏幕适配后,我们现在首先需要一些资源。在 github 上搜索一下关键字:“Cocos 羊了个羊”,找到工程:https://github.com/blakeyi/yanglegeyang-client/tree/master/assets/res
拿到里面的羊了个羊的背景相关资源。
或者在 Cocos 商店上找点免费或者付费资源 https://store.cocos.com/app/
这里我从资源库里找了点素材(素材皆来自互联网免费资源,或者付费资源,当然有些是盗版资源,请使用前进行换皮,谢谢!)。
在导入资源前,我们先设置一下项目默认导入图片的格式,方便我们在 2D 游戏中使用(因为默认导入到图片资源类型是 texture,无法在 2D 界面中使用)。在 Mac 下点击【Cocos Creator】,点击【设置】,在打开的【偏好设置】里,选择【资源数据库】,将【默认资源导入类型】中的【图片】【类型】更改为 sprite-frame。
这个操作后,引擎在隐藏文件夹 CCYang/.creator/ 下会新建了一个文件 default-meta.json 文件,持久化了我们的设置:
最后,在 assets 目录下,我们新建 yang 文件夹,将资源导入。
并添加一个自动图集并命名为 auto-atlas-match3。(参考官方文档-如何添加自动图集)
并且设置不剔除未引用的资源。
添加背景图和游戏名称,并添加健康游戏忠告
添加背景图
首先声明一下,我们要添加的背景图是纯色的。
点击【场景编辑器】,然后在左侧【层级管理器】中选中 Canvas 节点,右键【创建】 【2D 对象】【Sprite 精灵】 ,并将其命名为 BG。
这时候,我们注意到,官方会默认为图片设置了一个 sprite-frame ,这个 sprite-frame 是 internal 文件夹下的资源。
这里注意一个原则,官方的资源,一定要替换成游戏内自己的资源。哪怕是复制一份到 assets 文件夹下。
然后将这个 sprite-frame 替换成我们的纯色资源 bg-2x2。
这里我提前对 bg-2x2 的 sprite-frame 进行了操作:更改了其九宫格的 4 个边距 Border Top、 Border Bottom、Border Left 和 Border Right 4 个属性分别为 1。具体可点击 bg-2x2 查看。
将 BG 节点的 Sprite 组件的 Size Mode 更改为 Custom,Type 更改为 Slice,然后将其尺寸设置为 750x1334,颜色值更改为 CFFB9A
。最后添加一个 Widget 组件(在 BG 节点的【属性检查器】 中,点击【添加组件】,输入 Widget 后,选中 Widget 组件即可),并设置水平约束和垂直约束。
添加游戏名称
将【BG】节点拖拽到【Label】节点和【Camera_UI_2D】节点之间;
调整【Label】节点的 y 值为 160;
更改 Label 的字符串为羊了个羊
;
【字体大小】和【行高】都调整为 80;
勾选【启用描边】;
【描边宽度】更改为4。
新增健康游戏忠告
尤其是游戏备案和版本审核,都会要求首屏显示健康游戏忠告。这是每个游戏人应尽的义务。
Canvas 下创建 advice 节点
设置文本:
健康游戏忠告
抵制不良游戏 拒绝盗版游戏 注意自我保护 谨防受骗上当
适度游戏益脑 沉迷游戏伤身 合理安排时间 享受健康生活
字体大小和字高调整到 24,颜色更改为黑色。
新增 Widget 对齐组件,对齐Canvas节点的下边缘,距离 8 个像素。如图所示:
点击运行,我们完成了首屏展示
下一篇 游戏界面
游戏界面展示
首先我们用最简单的方式展示游戏主界面:在 Canvas 节点下添加一个名为【Match3UI】的精灵(Sprite) 节点,设置尺寸为 750x1334。设置颜色为红色,调整透明度为 80。(我们已经知道如何添加精灵节点了,这里不复述了)并且这个节点上,我们暂时不挂载 Widget 对齐组件。
羊了个羊本质是一个三消类型的游戏, 因此取名 Match3
上面这个做法,是为了演示,在 iPad 设备情况下,首屏界面和游戏界面尺寸的一个对比情况。
因此运行游戏的时候,我们需要在浏览器中选中模拟器。
这时候,我们发现在 iPad 的屏幕适配出了问题,这不是我预期的情况。
原因是适配策略未生效。
因此这里做了一个不是很优雅的修复。
在 Boost.ts 脚本的 adapterScreen 方法中,最后一行添加
adapterScreen() {
// 保持不变....
return isScreenWidthLarger;
}
然后修改 start 方法中的内容为:
start() {
const WIN_SIZE_W = screen.windowSize.width;
const WIN_SIZE_H = screen.windowSize.height;
let isScreenWidthLarger = this.adapterScreen();
if (isScreenWidthLarger) {
screen.windowSize = new Size(WIN_SIZE_W + 1, WIN_SIZE_H);
screen.windowSize = new Size(WIN_SIZE_W, WIN_SIZE_H);
}
}
重新运行后,发现符合我们的预期,游戏界面确实是高度适配。
下一篇 游戏界面的控制
很用心的教程
游戏界面的控制
我们希望能够在进入首页,延迟 1 秒后,再显示游戏界面(半透明的红色部分)。用来模拟加载后进入游戏的情形。
因此,我们需要先将【游戏界面节点 Match3UI】绑定到组件 Boost 的属性上,然后在 Boost 脚本上控制界面的显示。
首先在 Boost.ts 脚本中,添加属性:
export class Boost extends Component {
// 下面这行为新添加的代码
@property(Node) private match3Node: Node = null;
}
新添加的属性是 Cocos Creator 提供的绑定机制(参考 官方文档)。
然后点击【层级管理器】中的【Canvas】节点,将 Match3UI 节点拖拽到该属性中。
接下来在 Boost.ts 脚本的 start 方法中添加控制逻辑:
start() {
// 省略旧逻辑
this.match3Node.active = false;
this.scheduleOnce(()=> {
this.match3Node.active = true;
}, 1)
}
运行后发现,确实在延迟 1 秒后,出现了游戏界面。
但是目前 游戏界面的尺寸,并没有覆盖全屏。因此我们需要设置 Match3UI 节点的尺寸到全屏(我们已经知道全屏对于 Canvas 画布的尺寸,这个尺寸经过计算已经被我们填充在 G_VIEW_SIZE 变量中)。
因此在 start 方法中继续添加逻辑
start() {
// 省略旧逻辑
this.match3Node.active = false;
this.scheduleOnce(()=> {
this.match3Node.active = true;
}, 1)
this.match3Node.getComponent(UITransform).setContentSize(G_VIEW_SIZE.clone());
}
重新运行,符合预期!
实例化预制体方式创建游戏界面
在上个小节中,我们是直接在场景 Main 中的【Canvas】节点下,直接创建好【Match3UI】节点的方式去实例化游戏界面的。
这种方式其实是不好的。
为什么说这种方式不好?
Main场景我们认为是启动场景,启动场景只关心启动逻辑,Match3UI 是一个玩法界面,玩法界面为什么要在启动时就创建好呢?它应该是玩家点击,或者是逻辑脚本控制其【动态创建】。
可以通过脚本编辑器VSCode 去定位到逻辑触发的位置。
所以我们需要动态去创建这个 【Match3UI】。因此,我们需要将 Match3UI 节点制作成一个预制体(官方文档-预制体制作)。
在制作预制体前,我们先移除之前脚本 Boost.ts 中绑定的 match3Node
接下来将【Match3UI】节点拖拽到 assets 目录下,制作成一个预制体。
然后把层级管理器中的【Canvas】节点下的 【Match3UI】节点删除。
然后在 Boost.ts 脚本中,将原来的
@property(Node) private match3Node: Node = null;
更改为
@property(Prefab) private match3Prefab: Prefab = null;
记得需要从 ‘cc’ 模块 import 一下 Prefab
同步修改 start 方法中的逻辑:
start() {
const WIN_SIZE_W = screen.windowSize.width;
const WIN_SIZE_H = screen.windowSize.height;
let isScreenWidthLarger = this.adapterScreen();
if (isScreenWidthLarger) {
screen.windowSize = new Size(WIN_SIZE_W + 1, WIN_SIZE_H);
screen.windowSize = new Size(WIN_SIZE_W, WIN_SIZE_H);
}
this.scheduleOnce(()=> {
let match3Node = instantiate(this.match3Prefab);
this.node.addChild(match3Node);
match3Node.getComponent(UITransform).setContentSize(G_VIEW_SIZE.clone());
}, 1)
}
记得需要从 ‘cc’ 模块 import 一下 instantiate
再将之前制作好的预制体,拖到新增加的属性 match3Prefab 上:
点击运行。就完成了通过 instantiate 实例化游戏界面的功能了。
顺便再补充说明一下上面的脚本逻辑:
instantiate(this.match3Prefab); 是通过预制体创建节点的方法。预制体本质就是一个结构说明(json)文件,它记录了这个节点有哪些属性,属性值是多少,有哪些组件,各个组件的属性又是怎么样的,仅此而以。
this.node.addChild(match3Node);
实例化的节点必须添加到某个节点下,才会被渲染出来。
到这里,我们发现,启动的时候,仍然需要在 Boost.ts 中将预制体提前绑定到组件属性上。这显然也是有问题的。那么预制体 Prefab 究竟要如何动态加载的?这就是下一节的内容了。
等待大佬更新
动态加载预制体
我们在上节中遇到了一个问题:如何动态加载预制体的问题。
在官方的 API 中,通过代码动态加载预制体的方式是:通过 bundle 提供的加载 API。
参考官方文档 加载资源
我们先使用官方的方案去完成功能,在后面的一节,我再来说一下,为什么我们不要去用官方的 resources 这个 bundle 包,而应该去用一个更通用的解决方案。
首先,我们在 assets 目录下新建 resources 目录,编辑器自动识别文件夹名称后,把它标记为一个 bundle 包,并默认其优先级为 8。
然后我们将预制体 Match3UI.prefab 拖拽到 resources 文件夹中。
然后删除 Canvas 节点的 Boost 组件中关联的预制体:
在 Boost.ts 脚本文件中,我们删除这行:
@property(Prefab) private match3Prefab: Prefab = null;
将 this.scheduleOnce
方法中的内容更改为:
this.scheduleOnce(() => {
resources.load("Match3UI", Prefab, (err, prefab: Prefab) => {
if (err) {
console.error(err);
return;
}
let match3Node = instantiate(prefab);
this.node.addChild(match3Node);
match3Node.getComponent(UITransform).setContentSize(G_VIEW_SIZE.clone());
})
}, 1)
重新运行后,发现预制体被动态加载了。
这样,我们就通过动态加载预制体的方式,移除了启动场景的资源依赖。
但是为什么我仍然说不要去用 resources 包呢?这个观点,我在下一节中会进行阐述。
等有空了再更新吧,写教程比较费时间。争取一天一篇左右的节奏
支持一下!
昨天傍晚的时候,修改历史的文章,经常出现 502 Bad Gateway ,很抓狂。早上起来又不会了。
牛逼mark
牛逼牛逼mark
资源整理
在上节中,我们使用了官方的 resources bundle 包动态加载了 Prefab 资源。
resources 是一个 bundle 包,它既然在 Boost.ts 脚本中可以直接使用,说明其是在启动环节加载的。
想了解引擎是如何加载的,可以自行查看官方的引擎源码: 获取引擎源码
git clone -b v3.8.5 https://github.com/cocos/cocos-engine.git --depth 1
现在的第一个问题是:resources 包里面如果脚本、预制体、json 相关的配置等资源很多的话,它会影响首屏的启动时间。
或者说直接一点, resources 凭什么要在首场景加载?
以下是个人看法:
因为这个官方给新手菜鸟去使用的,新手会遇到很多问题,其中一个就是资源动态加载(尤其是图片资源动态加载),如果不提供 resources 目录,新手要先加载 bundle,然后将这个 bundle 保存在某个地方,使用的时候再去用这个 bundle 去加载。
不要小看多了这一层,直接劝退部分新手:加载个东西还要这么麻烦?垃圾引擎。
所以官方不得不加这层,降低使用门槛。
所以,作为老鸟,可以这么说吧,如果使用 bundle 包有鄙视链,那还在使用 resources 包的开发者,就很容易被鄙视。
继续刚刚的话题,resources 最优的情况下,是不应该在首屏的时候加载好的。它应该是我们业务逻辑层去动态加载的。
所以,这里我们将这个 resources 包替换成另一个包,这里不妨叫【Match3BN】包。
因此,我们在 assets 文件夹下创建一个新的文件夹,命名为 Match3,选中 Match3 文件夹,勾选上【配置为 Bundle】复选框。
Bundle 的名称更改为 Match3BN,优先级我们先保持不变(关于优先级,它影响到一些资源的打包归属,特别是被多个包共用的资源,以及被其它包引用的代码的情况,这个是后话,可参考官方文档 Asset Bundle 优先级)。
接下来我们吧之前的 Match3UI.prefab 预制体移动到这个 Match3BN 包中。
同时也把 yang 文件夹移动到这个 Match3 文件夹中。
删除 resources 文件夹
然而我们的首场景的 BG 节点,引用了 bundle 包 Match3BN 中的资源 bg_2x2,因此。我们的这个资源,需要复制一份出来。
因此我们在 assets 文件夹下,新建 Boost 文件夹,然后点击选中 bg_2x2,Ctrl+C 复制它,然后在 Boost 文件夹中 Ctrl + V 创建一个副本。最后,将这个新的bg_2x2副本,拖拽到 Canvas 节点下的 BG 精灵的 sprite-frame 属性上。
这样,我们就完成了资源的解耦:
首场景只用 Boost 文件夹下的资源。
Match3BN 包中的预制体 Match3UI.prefab 只用到了自己包中的资源。
最后,我们要修改一下之前的加载脚本:
this.scheduleOnce(() => {
assetManager.loadBundle("Match3BN", (e, bundle) => {
bundle.load("Match3UI", Prefab, (err, prefab: Prefab) => {
if (err) {
console.error(err);
return;
}
let match3Node = instantiate(prefab);
this.node.addChild(match3Node);
match3Node.getComponent(UITransform).setContentSize(G_VIEW_SIZE.clone());
})
})
}, 1)
刷新后重新运行,完美的实现了。
支持
后面会有游戏逻辑的教程吗,大佬。
会,整个游戏的实现。但是要先构建出 UI 管理器、资源管理器,抽象成框架,这个是怎么从 0 到 1 的。要把这个东西讲清楚,要很多节呢。
赞赞赞赞赞赞赞赞赞
赞赞赞赞赞赞赞赞赞
老哥,拿你这个项目作为我找工作demo了,期待更新