麒麟子Cocos Creator 3D研究笔记二:麒麟子惯用框架分享

好久没来论坛发贴了。
今天带来的是技术贴。不知不觉《麒麟子Cocos Creator 3D研究笔记》已经写了第三篇(按程序员的习惯,本系列文章是从第零篇开始的)


##零、前言

麒麟子在开发中搞出来的框架,都是遵守“大道至简,实用至上”这两个基本原则。

接触一个引擎的第一件事,就是搞出一个实用的框架,方便在此基础上做开发。

由于目前的引擎已经是对象+组件模式,所以在场景对象管理上,不需要花太多功夫了。我们主要集中在界面管理这块。

##一、常见的几种游戏类型。
既然我们的框架想要满足日常开发,就不得不满足星辰大海般的需求。从客户端的角度,我们可以把游戏分为三类。

1.1、纯界面玩法

像一些SLG、卡牌、象棋等可以视作纯界面玩法。(也有某些大作要求3D表现效果的,我们不作讨论),比如下面的这类游戏。

1.2、某些纯界面+战斗场景


王者荣耀

1.3、从头到尾都是3D场景

魔兽世界

1.4、小结

仔细分析以后我们可以发现,假如我们的框架始终支持 2D/3D场景 + 界面 这样的能力。就可以了。


##二、单场景 vs. 多场景
单场景+Prefab 和 多场景 从Cocos 2d-x提供了replaceScene函数开始,就一直有人在争论这个话题。只要引擎提供了“场景”这个定义的,都会遇上决策问题。

麒麟子看得很明白,所谓的引擎场景管理,就是给了你一次无脑销毁所有结点的机会。

如果你是在两个截然不同的逻辑之间切换,那其实是可以使用引擎的场景切换功能的。 如果逻辑相差不大,我建议依然采用单场景方式。

而麒麟子更推荐的是单场景+Prefab方式,这样会迫使你自己更注重场景节点和资源的管理。


##三、框架内容

3.1、程序启动入口
程序启动入口包含两个部分。

3.1.1、最小场景

最小场景如果不出意外,我们只需要一个Canvas节点,一个MainCamera节点,一个MainLight节点就好了。

3.1.2、App.ts

麒麟子喜欢以App.ts作为程序入口,将这个App.ts挂在Canvas节点上即可。App.ts的内容并不多,如下所示

import { _decorator, Component, Node } from 'cc';
import { UIMgr, UILayer } from './UIMgr';
import { HUD } from './HUD';
const { ccclass, property } = _decorator;
 
@ccclass('AppTs')
export class AppTs extends Component {
    start () {
        UIMgr.inst.setup(UILayer.NUM);
        UIMgr.inst.showUI(HUD);
    }
}

我们可以清晰地看到,在这里,麒麟子只启动了UIMgr。如果还有其他游戏管理器需要一开始就初始化,那么把它们放在这里就好了。

3.2、界面管理器

界面管理器至少要包含几个功能
动态加载和销毁界面
界面层级管理
界面事件自动管理机制
界面与游戏业务逻辑通信机制
上面说的这些,框架里都自带了。


##四、分辨率自适应

很多小伙伴一定还在纠结是用fitWidth还是fitHeight吧。

如果使用fitWidth,遇上更细长的设备,界面上面部分可能被裁剪。

如果使用fitHeight,遇上更短的设备,界面左右部分可能被裁剪。

其实我们想要的只有一句话:任何时候,都不要裁剪。

基于这个目标,我们制定出的策略就是。

a、在比设计分辨率更细长的设备上,我们使用fitHeight,这样一来,他长由他长,我们只需要保证背景左右能够填充就行。

b、在比设计分辨率更短的设备上,我们使用fitWidth,这样一来,他高由他高,我们只需要保证背景上下能够填充就行。上面的适配机制,已经被麒麟子写成了一个函数。

public resize() {
                //根据屏幕大小决定适配策略
        //想明白原理,请阅读本文 https://blog.csdn.net/qq_36720848/article/details/89742451
 
        let dr = view.getDesignResolutionSize();
        var s = cc.view.getFrameSize();
        var rw = s.width;
        var rh = s.height;
        var finalW = rw;
        var finalH = rh;
 
        if((rw/rh) > (dr.width / dr.height)){
            //!#zh: 是否优先将设计分辨率高度撑满视图高度。 */
            //cvs.fitHeight = true;
            
            //如果更长,则用定高
            finalH = dr.height;
            finalW = finalH * rw/rh;
        }
        else{
            /*!#zh: 是否优先将设计分辨率宽度撑满视图宽度。 */
            //cvs.fitWidth = true;
            //如果更短,则用定宽
            finalW = dr.width;
            finalH = rh/rw * finalW;
        }
 
        view.setDesignResolutionSize(finalW,finalH,ResolutionPolicy.UNKNOWN);
        let cvs = find('Canvas').getComponent(UITransformComponent);
        cvs.node.width = finalW;
        cvs.node.height = finalH;
    }

关于这个函数,有两个地方要注意

注意1:在设置面板里面,fitWidth和fitHeight都得去掉,一个都不要勾,否则可能出现设置失效。

注意2:麒麟子在调用view.setDesignResolutionSize函数的时候,最后一个参数传的是ResolutionPolicy.UNKNOWN。这样一来,这个函数是可以重复调用且生效的。当我们处于微信浏览器的时候,横竖屏旋转会导致宽高比不一致,这就需要再次调用这个函数来重新调整布局。


##五、游戏逻辑与界面通信机制
麒麟子这里没有MVC,也没有MVVM,只有以下几个套路

  • 1、数据、数据更新方法由逻辑管理器提供

  • 2、数据变更,想让界面产生改变,则通过事件传递

  • 3、界面可以直接调用逻辑管理器

  • 4、两个界面之间,只能通过事件传递(需要两个界面联动的情况非常少,一般情况下界面之间的逻辑都是互不干扰的)

也就是说,逻辑管理器不知道界面的存在,但界面是知道逻辑管理器的存在的。

我举一个关于个人信息的例子。在这个例子中,我们会涉及到三个文件 MyInfoMgr.ts(用户信息逻辑管理器 M) 、UIMyInfoController.ts(用户信息界面控制器 C) 、MyInfo.prefab(用户信息界面布局 V)。

如果非要用MVC来对应的话,就按我后面标记的字母来对应吧。

当用户点击个人按钮时,UIMgr会实例化UIMyInfoController类,UIMyInfoController类会加载MyInfo.prefab。

当加载成功后,UIMyInfoController会有一个onCreated回调,我们可以在回调里初始化我们的显示信息。

UIMyInfoController需要监听用户信息改变相关的事件,当收到事件的时候,需要对应地做显示更新

MyInfoMgr持有用户数据并提供访问接口以及数据修改接口,当数据产生修改时,会抛出修改事件。它不关心这个事件是否有人要用。无脑抛出就行。


##六、总结

与其说是框架,不如说是麒麟子的惯用套路。这个套路没有出色的地方,也不满足很多学术性的依赖解耦标准。但这个套路陪着我走过了大大小小很多项目。

如果要给它下一个定义的话,我觉得就是两个词:“简单实用”。

哦对了,只能用Cocos Creator 3D 1.1.1打开。

源码地址:https://gitee.com/qilinzi/creator3ddemos/GFW/


##七、文末广告位(此位置长期招商)

  • 有一些备忘类型的贴子不会发到论坛,大家可移步到我的博客 https://qilinzi.blog.csdn.net

  • 有一些与技术无关的贴子,麒麟子以后会尽量不发到论坛,毕竟论坛是一个讨论技术的地方,大家可关注我的微信公众号:麒麟子TM

  • 麒麟子拥有10多年的3D游戏引擎和游戏开发经验,经过深思熟虑后,决定回归到3D领域。接下来决定与Cocos Creator 3D共同成长。欢迎大家加入 Cocos第一野生社区大本营-Cocos Creator 3D研究院,再多华丽的拉客姿势在实力面前都显得苍白无力,大家点开看看管理员阵容再决定要不要加入。

20赞

大佬竟然跟我用的是同样的套路 :grin:

第五点一样一样。

更详细一点
1、逻辑管理器关联proto,proto对接协议再调用逻辑管理器的相关方法来更新数据。
2、逻辑管理器所有数据更新都是get_xxx / set_xxx,不允许直接操作成员变量。
3、逻辑管理器提供与服务器交互的接口类似postMsgxxxxx。
4、逻辑管理器下可按需要拆分多个子模块
5、各逻辑管理器可以相互调用依赖。

英雄所见略同。哈哈哈

幸会幸会,看来是道友。。。

第五点的思路竟然一样,从flash AS3到H5就一直这么用:joy:

mark mark mark

我也是。。

从AS3开始就这个套路了

哈哈哈哈,as3开始的老套路了,我现在项目都做成这样

我草,我也是这个套路。在茫茫多的MVC,ECS,MVVM中,我用这个套路都有点怀疑人生了。。。。看了大佬也是这个套路,我就放心了

大道至简 单向依赖 就可以了

明明是套路哦。可是不承认。。。。
怕我们听不懂么?

mark…