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

大佬 催更 萌新学习

1赞

不是写到update里,就是start里,我打印了调用堆栈,但是看不懂, 我查了一下,说是我又其他的Node多处引用了ts脚本导致,但是没找到,我抽空把你的教程重新做一次看看到底是哪一步出问题了
Start function is called by Boost [Boost.ts:25:16](file:///Users/kangming/CCYang/assets/Boost.ts)

Call stack: start@http://localhost:7456/scripting/x/chunks/51/510049438992d1a34c87556d49a3f481cee5ec6a.js:68:38 execute/invokeStart<@http://localhost:7456/scripting/engine/bin/.cache/dev/preview/bundled/index.js:42390:16 createInvokeImpl/<@http://localhost:7456/scripting/engine/bin/.cache/dev/preview/bundled/index.js:42205:17 invoke@http://localhost:7456/scripting/engine/bin/.cache/dev/preview/bundled/index.js:42329:16 startPhase@http://localhost:7456/scripting/engine/bin/.cache/dev/preview/bundled/index.js:42565:29 tick@http://localhost:7456/scripting/engine/bin/.cache/dev/preview/bundled/index.js:16668:35 _updateCallback@http://localhost:7456/scripting/engine/bin/.cache/dev/preview/bundled/index.js:17701:22 updateCallback@http://localhost:7456/scripting/engine/bin/.cache/dev/preview/bundled/index.js:91108:22 execute/Pacer/this._handleRAF@http://localhost:7456/scripting/engine/bin/.cache/dev/preview/bundled/index.js:91088:23 FrameRequestCallback*_stTime@http://localhost:7456/scripting/engine/bin/.cache/dev/preview/bundled/index.js:91132:28 start@http://localhost:7456/scripting/engine/bin/.cache/dev/preview/bundled/index.js:91112:33 resume@http://localhost:7456/scripting/engine/bin/.cache/dev/preview/bundled/index.js:17236:103 @http://localhost:7456/preview-app/main.js:1:4370 runSceneImmediate@http://localhost:7456/scripting/engine/bin/.cache/dev/preview/bundled/index.js:16384:13 @http://localhost:7456/preview-app/main.js:1:4332 execute/loadWithJson/task<.onComplete<@http://localhost:7456/scripting/engine/bin/.cache/dev/preview/bundled/index.js:185849:23 asyncify/</<@http://localhost:7456/scripting/engine/bin/.cache/dev/preview/bundled/index.js:193104:11 callInNextTick/<@http://localhost:7456/scripting/engine/bin/.cache/dev/preview/bundled/index.js:145498:18 handleRAF@http://localhost:7456/scripting/engine/bin/.cache/dev/preview/bundled/index.js:5935:18 FrameRequestCallback*setTimeoutRAF@http://localhost:7456/scripting/engine/bin/.cache/dev/preview/bundled/index.js:5938:12 callInNextTick@http://localhost:7456/scripting/engine/bin/.cache/dev/preview/bundled/index.js:145497:20 asyncify/<@http://localhost:7456/scripting/engine/bin/.cache/dev/preview/bundled/index.js:193100:12 dispatch@http://localhost:7456/scripting/engine/bin/.cache/dev/preview/bundled/index.js:192463:22 execute/_flow/<@http://localhost:7456/scripting/engine/bin/.cache/dev/preview/bundled/index.js:191345:22 load/<@http://localhost:7456/scripting/engine/bin/.cache/dev/preview/bundled/index.js:190289:7 cb@http://localhost:7456/scripting/engine/bin/.cache/dev/preview/bundled/index.js:193003:21 onComplete@http://localhost:7456/scripting/engine/bin/.cache/dev/preview/bundled/index.js:190276:13 dispatch@http://localhost:7456/scripting/engine/bin/.cache/dev/preview/bundled/index.js:192463:22 execute/_flow/<@http://localhost:7456/scripting/engine/bin/.cache/dev/preview/bundled/index.js:191345:22 onComplete@http://localhost:7456/scripting/engine/bin/.cache/dev/preview/bundled/index.js:190374:14 dispatch@http://localhost:7456/scripting/engine/bin/.cache/dev/preview/bundled/index.js:192463:22 execute/_flow/<@http://localhost:7456/scripting/engine/bin/.cache/dev/preview/bundled/index.js:191345:22 load/<@http://localhost:7456/scripting/engine/bin/.cache/dev/preview/bundled/index.js:190289:7 cb@http://localhost:7456/scripting/engine/bin/.cache/dev/preview/bundled/index.js:193003:21 onComplete@http://localhost:7456/scripting/engine/bin/.cache/dev/preview/bundled/index.js:190276:13 dispatch@http://localhost:7456/scripting/engine/bin/.cache/dev/preview/bundled/index.js:192463:22 execute/_flow/<@http://localhost:7456/scripting/engine/bin/.cache/dev/preview/bundled/index.js:191345:2

感谢大佬无私分享

1赞

感谢大佬,萌新催更 :+1:

1赞

引入界面管理器 UIManager 的缘由

完成模拟登录后,我们将控制权交给了 Match3Entry,Match3Entry 通过以下方式打开三消界面:

ResManager.getInstance().loadPrefab("Match3BN", "Match3UI", prefab => {
    let match3Node = instantiate(prefab);
    this.node.addChild(match3Node);
    match3Node.getComponent(UITransform).setContentSize(G_VIEW_SIZE.clone());
});

1. 创建 Match3UI 脚本组件并绑定到 Match3UI.prefab 预制体

1.1 这里我们先在 Match3/Script 下创建一个 Match3UI 的脚本组件,用于和预制体绑定:

移除 update 方法,在 start 方法中添加一条控制台打印,内容如下:

import { _decorator, Component, Node } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('Match3UI')
export class Match3UI extends Component {
    start() {
        console.log(`主玩法界面`)
    }
}

1.2 将该脚本绑定到预制体上

这类操作后续省略,见图:

1.3 运行测试

控制台输出了:“主玩法界面” 的字样,符合预期

2. 打开界面的方式需要重构

之前打开界面的方式其实是不方便的: 开发侧需要先调用 ResManager 加载资源,然后才创建实例,并添加到目标节点。(说白了这种方式是:每次在使用的时候,需要先考虑依赖,然后才能执行目标,对心智的负担是很重的)

我们从需求侧入手思考更直观的调用方式是什么?

我希望的方式凝练出来就是一句话:打开界面!(界面依赖的资源是确定的,请你自动帮我加载好!别让我每次使用的时候调用!)

所以我希望的调用方式应该是这样的:

import { _decorator, Component } from 'cc';
import { UIManager } from '../../fw/ui/UIManager';
import { Match3UI } from './Match3UI';
const { ccclass } = _decorator;

@ccclass('Match3Entry')
export class Match3Entry extends Component {
    async init() {
        UIManager.getInstance().open(Match3UI)
    }
}

因此我们将要引入 UIManager 这个对象(假设其位置在 fw/ui/UIManager.ts),来控制界面的打开操作。这将在下一节实现。

1赞

UIManager 初步实现

1. 新建 UIManager

在 assets/fw 下新建文件夹 ui,ui 文件夹下新建 UIManager.ts 文件:

UIManager.ts 中的内容更改为以下内容:

import { instantiate, UITransform } from 'cc';
import { G_VIEW_SIZE } from '../../Boost';
import { ResManager } from '../res/ResManager';

export class UIManager {
    private static _instance: UIManager = null!;
    /** 获取单例的接口 */
    static getInstance() {
        if (this._instance === null) {
            this._instance = new UIManager();
        }
        return this._instance;
    }

    private constructor() {
        // 私有化的构造函数
    }

    open(uiClass: any) {
        ResManager.getInstance().loadPrefab("Match3BN", "Match3UI", prefab => {
            let match3Node = instantiate(prefab);
            this.node.addChild(match3Node);
            match3Node.getComponent(UITransform).setContentSize(G_VIEW_SIZE.clone());
        });
    }
}

这时候,我们面临着几个问题(假设 uiClass 的值是类 Match3UI ):
1、如何根据 Match3UI 获得资源所在的包名 Match3BN 和资源预制体 Match3UI.prefab 的 url ?
2、this.node.addChild(match3Node); 中的 this.node 已经不复存在,我们的这个 this.node 要如何改造?
解决了这 2 个问题,就能够完成 UIManager 的初步设计。

而这些内容,我将在后续跟贴中逐一解决。

1赞

UIManager 中的层级管理

我们先解决第二个问题,让程序先运行起来。

界面一般是分层级的。为了让大家有一个直观的印象。先展示一下,我线上《经典休闲益智单机游戏合集》这款产品的界面层级的分类以及运行时各个层级的节点示意图,其中 MainUI 是游戏列表界面:

/** UI 层类型 */
export enum EViewLayer {
    /** 场景层 */
    Scene,
    /** 默认层 */
    UI,
    /** 引导层 */
    Guide,
    /** 表现层 */
    Anim,
    /** 切换层 */
    Transform,
    /** 转圈层、遮挡层 */
    Loading,
    /** 模态对话框 */
    Modal,
    /** 提示 */
    Toast,
}

即 UIMananger 需要在画布 Canvas 下新建各个层级的根节点,然后不同的界面显示在不同层中。
比如 MainUI 是要显示在 UI 默认层,也就是 EViewLayer.UI 这一层的。

现在,知道我们要实现的目标,因此让我们改造一下 UIManager 类,它需要初步实现节点的分层逻辑。

1、首先在 fw/ui/ 下创建 EViewLayer.ts 文件,其内容为

/** UI 层类型 */
export enum EViewLayer {
    /** 场景层 */
    Scene,
    /** 默认层 */
    UI,
    /** 引导层 */
    Guide,
    /** 表现层 */
    Anim,
    /** 切换层 */
    Transform,
    /** 转圈层、遮挡层 */
    Loading,
    /** 模态对话框 */
    Modal,
    /** 提示 */
    Toast,
}

如图:

2、UIManager 定义初始化接口以及创建各个层级根节点

private m_Canvas: Canvas = null;
private m_Layers: MyLayer[] = []
init(canvas: Canvas) {
    this.m_Canvas = canvas;
    for (let layer = EViewLayer.Scene, maxLayer = EViewLayer.Toast; layer <= maxLayer; ++layer) {
        this.m_Layers.push(new MyLayer(layer, canvas, EViewLayer[layer]));
    }
}

其中 MyLayer 定义如下:

class MyLayer {
    public readonly node: Node;
    constructor(
        public readonly layer: EViewLayer,
        public readonly canvas: Canvas,
        name: string,
    ) {
        const node = this.node = new Node(name);
        node.layer = Layers.Enum.UI_2D;
        node.addComponent(UITransform);
        canvas.node.addChild(node);
    }
}

此类写在 UIManager 类上方即可。
其中 Layers.Enum.UI_2D 需要从 ‘cc’ 模块 import.

3、修改 open 方法中的 this.node.addChild(match3Node)

将 open 方法内容修改如下:

open(uiClass: any) {
    ResManager.getInstance().loadPrefab("Match3BN", "Match3UI", prefab => {
        let match3Node = instantiate(prefab);
        this.m_Layers[EViewLayer.UI].node.addChild(match3Node);
        match3Node.getComponent(UITransform).setContentSize(G_VIEW_SIZE.clone());
    });
}

其中 this.node 被替换成了 this.m_Layers[EViewLayer.UI].node

4、初始化 UIManager 的 Canvas

现在我们只差最后一步,就是调用 init 函数

首先,在 Boost.ts 中,添加一条属性定义:

@property(Canvas) private canvas2d: Canvas = null;

然后在 Boost 的 start 方法中的 this.adapterScreen() 下面添加 UIManager 的初始化调用:

        UIManager.getInstance().init(this.canvas2d);

最后,在场景中,选中 Canvas,绑定 canvas2d 属性:

运行后,表现如初!

可以看到分层和界面,都和预期一致!(这里用到了预览插件 preview-template,是从论坛里搜的,大家可以自行搜索)

现在我们先解决了第一个问题:界面所属分层和父节点。第二个问题是资源依赖,我们下一个帖子解决它。

原先:ram:了个:ram:最火的是省份地区的排名竞争,现在微信不返回了这块地区信息,大佬有啥不依赖服务器获取省份地区的做法吗

这个你自己研究吧。无非 3 条路:
1、 要么平台渠道(微信)给你 API
2、 要么网络IP或者定位相关 API 。
3、 要么玩家设置选择自己参与竞争的省份地区。

你最好克隆下工程运行,如果还是这样,有可能是你改到引擎了。建议卸载重装。

这节课开始看不懂了,看来得学习一下es6才能跟上后面的课程了

1赞

找到原因了,我在Match3UI中添加了boost的脚本导致多出引用了

1赞

每日催更!!!

最近在忙着做一个推箱子的小游戏,顺便优化了一波框架,上来展示一下成型的框架结构:

别秀了 大佬快更新新手教程

2赞

哈哈,过 2 天。

必须点赞,这是讲解最详细的教程,估计工作很多年的都不知道这个技巧!跟着大佬学习!

mark 大佬大佬

感谢大佬的分享,手把手教学。少有的优质教程。

大佬,这个羊了个羊教程 后期也会实现这个结构吗