【新手教程】【羊了个羊】

坐等更新,太实用了

1赞

屏幕适配实操

修改项目的设计分辨率

点击菜单栏【项目】,点击【项目设置】,找到【设计宽度】,将它更改为 750,找到【设计高度】,将它更改为 1334。

然后点击【场景编辑器】,我们发现,屏幕已经变成 750x1334

开发添加适配策略组件

点击【资源管理器】中的 assets 目录,右键点击【创建】【脚本(TypeScript),将脚本命名为 Boost

然后双击 Boost.ts 脚本打开 VSCode 编辑器(配置请参考官方的脚本编辑器配置文档),将 Boost.ts 的内容如下

import { _decorator, Component, ResolutionPolicy, screen, Size, view } from 'cc';
const { ccclass, property } = _decorator;

/** 
 * 画布的标准化尺寸,就是之前说的
 * iPad 设备中的画布尺寸 = 1001 x 1334 (其中 1001 ≈ 1668/1.6672)
 * iPhone16设备中的画布尺寸 = 750 x1626(其中 1626 = 2556/1.572)
 */
export const G_VIEW_SIZE = new Size(0, 0);

@ccclass('Boost')
export class Boost extends Component {
    start() {
        this.adapterScreen();
    }

    adapterScreen() {
        let resolutionPolicy: ResolutionPolicy = view.getResolutionPolicy();
        let designSize = view.getDesignResolutionSize();
        let frameSize = screen.windowSize;
        let frameW = frameSize.width;
        let frameH = frameSize.height;
        /** 是否是屏幕更宽 */
        const isScreenWidthLarger = (frameW / frameH) > (designSize.width / designSize.height);
        let targetResolutionPolicy = isScreenWidthLarger ? ResolutionPolicy.FIXED_HEIGHT : ResolutionPolicy.FIXED_WIDTH;
        if (targetResolutionPolicy !== resolutionPolicy.getContentStrategy().strategy) {
            /** 保证设计分辨率的内容都能显示出来 */
            view.setDesignResolutionSize(designSize.width, designSize.height, targetResolutionPolicy);
        }

        /** 实际的尺寸会和设计分辨率在一个维度,但是宽或高更大 */
        if (isScreenWidthLarger) {
            G_VIEW_SIZE.width = Math.ceil(designSize.height * frameSize.width / frameSize.height);
            G_VIEW_SIZE.height = designSize.height;
        } else {
            G_VIEW_SIZE.width = designSize.width;
            G_VIEW_SIZE.height = Math.ceil(designSize.width * frameSize.height / frameSize.width);
        }

        console.log(`屏幕${isScreenWidthLarger ? "更宽, 高度适配" : "更高, 宽度适配"} 设计分辨率比例下的屏幕尺寸: ${G_VIEW_SIZE.width}x${G_VIEW_SIZE.height}`);
    }
}

这里除了根据屏幕尺寸去调整适配策略外,还将画布的尺寸记录在了 G_VIEW_SIZE 变量中,方便后续使用。

Canvas 节点挂载 Boost 脚本

创建的 Boost 脚本是一个组件,继承自 Component,它并不会被实例化成一个对象。我们主要有 2 种实例化组件的方式:

  1. 将脚本组件拖拽到节点上,节点实例化的时候,其挂载的组件也会被实例化;
  2. 在另一个脚本中,通过编写代码的方式,对已经实例化并激活的节点,调用其addComponent(组件)的方式进行实例化;

这里我们用第一种方式,【层级管理器】中,选中 Canvas 节点,将脚本拖拽到【属性检查器】中(或者选中 Canvas 节点后,点击【添加组件】按钮,输入 Boost,后,选中 Boost 组件即可)。


于是我们基本完成了适配的实操。

至于屏幕方向的设置,这需要等到构建的时候,才会进行设置。

下一篇 首屏展示

1赞

first star

1赞

太强了 :smiley:

1赞

首屏展示

在完成屏幕适配后,我们现在首先需要一些资源。在 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 个像素。如图所示:

点击运行,我们完成了首屏展示

下一篇 游戏界面

1赞

游戏界面展示

首先我们用最简单的方式展示游戏主界面:在 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赞

很用心的教程:+1:

1赞

游戏界面的控制

我们希望能够在进入首页,延迟 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());
    }

重新运行,符合预期!

1赞

实例化预制体方式创建游戏界面

在上个小节中,我们是直接在场景 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 究竟要如何动态加载的?这就是下一节的内容了。

等待大佬更新

1赞

动态加载预制体

我们在上节中遇到了一个问题:如何动态加载预制体的问题

在官方的 API 中,通过代码动态加载预制体的方式是:通过 bundle 提供的加载 API。

参考官方文档 加载资源

我们先使用官方的方案去完成功能,在后面的一节,我再来说一下,为什么我们不要去用官方的 resources 这个 bundle 包,而应该去用一个更通用的解决方案

首先,我们在 assets 目录下新建 resources 目录,编辑器自动识别文件夹名称后,把它标记为一个 bundle 包,并默认其优先级为 8。

然后我们将预制体 Match3UI.prefab 拖拽到 resources 文件夹中。
image

然后删除 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

牛逼牛逼:ox::beer: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)

刷新后重新运行,完美的实现了。

2赞

支持 :love_you_gesture:

1赞

后面会有游戏逻辑的教程吗,大佬。

会,整个游戏的实现。但是要先构建出 UI 管理器、资源管理器,抽象成框架,这个是怎么从 0 到 1 的。要把这个东西讲清楚,要很多节呢。

2赞