[3D游戏开发实践] Cocos Cyberpunk 源码解读-游戏逻辑框架全解

这篇具有很好参考价值的文章主要介绍了[3D游戏开发实践] Cocos Cyberpunk 源码解读-游戏逻辑框架全解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

游戏逻辑,Cocos Cyberpunk,麒麟子Cocos Creator 3D研究笔记,Cocos Creator实用技巧,游戏引擎,Cocos Creator,TA Shader,3D游戏开发

Cocos Cyberpunk 是 Cocos 引擎官方团队以展示引擎重度 3D 游戏制作能力,提升社区学习动力而推出的完整开源 TPS 3D游戏,支持 Web, IOS, Android 多端发布。

本系列文章将从各个方面对源码进行解读,提升大家的学习效率。希望能够帮助大家在 3D 游戏开发的路上更进一步。

工程源码免费下载页面:
https://store.cocos.com/app/detail/4543

麒麟子觉得,这篇文章至少可以让你节约好几天的研究时间。

不信?你往下看!

目录

其实这篇文章一开始不长这样,快要写完的时候,负责 Cocos Cyberpunk Gameplay 部分的大佬告诉我,这个部分会持续调整。

麒麟子这才意识到,过多的逻辑细节讲解会随着版本的更新而失去参考意义。

在向大佬了解了一些后续规划后,麒麟子决定推翻重来,从代码编写路数和逻辑运转机制的角度来做一个导读:

  • 1、预加载流程
  • 2、【划重点】Data 与 Action
  • 3、Game 模块导读
  • 4、Level 模块导读
  • 5、Actor 模块导读
  • 6、主角控制
  • 7、角色动画掩码与IK
  • 8、怪物生成机制
  • 9、物品掉落机制
  • 10、…

预加载流程

游戏逻辑,Cocos Cyberpunk,麒麟子Cocos Creator 3D研究笔记,Cocos Creator实用技巧,游戏引擎,Cocos Creator,TA Shader,3D游戏开发

入口脚本 init.ts

上一篇文章简单提到过这个入口脚本 init.ts,它会检测设备是否支持 WEBGL,并在不支持的时候给予提示,还会将 init 节点设置为常驻,以确保游戏逻辑正常执行。

接下来,我们主要关注下面这段初始化函数:

...
// Load the resource cache data and execute the initialize game function.
ResCache.Instance.load(async () => {
    console.time('loadTextures')
    await loadTextures();
    console.timeEnd('loadTextures')
    Game.Instance.init();
});
...

可以看到,init.ts 中会先调用 ResCache.load 进行初始化,初始化完成后执行回调。

在回调函数中,调用 loadTextures 函数预加载 resources/textures 目录下的所有纹理。

最后再调用 Game.Instance.init 启动游戏逻辑。

ResCache

ResCache 是一个单例类,它负责加载并缓存所需资源。

除了提供 loadXXX 系列方法,也提供了 getXXX 系列方法,所有预加载成功的资源都可以直接使用。

进入 ResCache.load 函数可以看到,它先是加载了 data/data-res-cache.json 文件,再根据文件中的配置,预加载对应的 jsonspritesound

同时,ResCache.load 会向外发布 msg_loading 事件,UILoading 监听到此事件后,就会显示出来。

游戏逻辑,Cocos Cyberpunk,麒麟子Cocos Creator 3D研究笔记,Cocos Creator实用技巧,游戏引擎,Cocos Creator,TA Shader,3D游戏开发

loading 阶段,ResCache 也承担了加载进度统计的职责,查看 addLoadremoveLoad 方法被调用的地方就可以一目了然。

当资源预加载结束,场景会由 scene-game-start 切换为 scene 场景,并且显示出 resources/ui/ui-logo.prefab

游戏逻辑,Cocos Cyberpunk,麒麟子Cocos Creator 3D研究笔记,Cocos Creator实用技巧,游戏引擎,Cocos Creator,TA Shader,3D游戏开发

【划重点】Data 与 Action

应该有很多朋友和麒麟子一样,尝试去寻找场景切换和 UI 显示的相关的代码,但没有找到。

麒麟子咨询了负责 Gameplay 的大佬后,才恍然大悟:整套游戏逻辑代码,参考了行为树的节点设计机制,基于数据(data)和行为(action)来驱动

游戏中的 player 是对象,enemy 是对象,作用于全局的管理器 gamelevel 等也是对象。

Data

每一个对象,都拥有一个 data-xxx.json 配置文件,你可以在 resources/data下找到它们,比如:

  • game: data-game.json
  • level: data-level.json
  • player: data-player.json
  • enemy: data-enemy_1.json
  • boss: data-boss_0.json

这些文件中的数据字段,与用来解析它的类是一一对应的。比如:

  • game -> game.ts
  • level -> level.ts
  • player -> actor.ts
  • enemy -> actor.ts
  • boss -> actor.ts

所以,你会发现,data-player.jsondata-enemy_1.jsondata-boss_0.json 的内容格式是差不多的。 但与 data-game.jsondata-level.json 的差别就很大。

Action

每一个对象,都拥有一个 action-xxx.json 配置文件,你可以在 resources/action 下找到它们,比如:

  • game: action-game.json
  • level: action-level.json
  • player: action-player.json
  • enemy: action-enemy_1.json
  • boss: action-boss_0.json

Data 类似,Action 配置文件的内容也与执行它的类相关,同类会采用相近的配置格式。

每一个 Action 文件中,都定义了一系列的 Action Node

对象可以根据条件,触发不同的 Action Node

每一个 Action Node 都拥有两个命令:startend

start 会在 Action Node 进入时执行, end 会在 Action Node 离开时执行。

而每一个 startend 命令在执行时,又可以按序执行多个操作。

用于解析 action-xxx.json 配置文件的类在 action.ts 脚本中。

进入到 action.ts,我们可以看到 Action 类有两个主要的函数:

  • on: 执行 start 命令
  • off: 执行 end 命令

UtilAction 中,定义了 startend 可以执行的所有操作。 比如:

  • on_ui/off_ui: 打开/关闭 UI
  • on_inst_pool: 初始化对象池
  • on_bgm: 播放背景音乐
  • on_scene: 切换场景

接下来我们用 game 对象来讲述一下 Data & Action 的运转机制。

Game 模块

Game 模块涉及到的 3 个主要文件:

  • data-game.json
  • action-game.json
  • game.ts

先来看看两个 json 配置文件的关键内容:

data-game.json

{
    ...
    "fps":60,
    "start_node":"logo",
    "action_data":"action-game",
    "version":"version:202302271037",
    "show_version":"V1.2",
    ...
    "res_ui_root":"ui/",
    ...
}

data-game.json 中,我们可以看到它定义了许多东西,比如限定的帧率,版本号等等。

这里我们还应该关注 start_nodeaction_data 这两个值,这是游戏启动流程的关键数据。

  • action_data: 用于指定对应的 Action 配置文件
  • start_node: 用于指定默认的 Action Node。

action-game.json

{
    "logo": {
        "start": [ ... ],
        "end": [ ... ]
    },
    "level": {
        "start": [ ... ],
        "end": [ ... ]
    },
    ...

可以看到在 action-game.json 定义了许多 action,最关键的两个:

  • logo: 显示 Logo 和开始按钮时的状态
  • level: 关卡状态,此时可以操作主角进行战斗

game.ts

game.tsinit 方法中,我们可以看到下面两条代码

// Initialize the game data.
this._data = dataCore.DataGameInst._data;
// Initialize game action data.
this._action = new Action(this._data.action_data);
...
// Push the game initial node into the stack data.
this.push(this._data['start_node']);

第一行代码,是获得从 data-game.json 加载到的配置数据。

第二行代码,是使用 action_data(值为 ‘action-game.json’) 去创建一个 Action。

第三行代码,是使用 start_node(值为 logo) 去作为 game 的初始 action

我们再来看看, action-game.jsonlogo 节点的具体内容:

{ "time": 0, "name": "on_ui", "data": "ui_logo" },
{ "time": 0.1, "name": "on_inst_pool", "data": "gun_tracer_pool"},
{ "time": 0.15, "name": "on_inst_pool", "data": "sfx_heart"},
{ "time": 0.2, "name": "on_inst_pool", "data": "random-fly-car"},
{ "time": 0.3, "name": "on_inst_pool", "data": "level_events"},
{ "time": 0.4, "name": "on_bgm", "data": "bgm_logo"},
{ "time": 0.5, "name": "on_scene", "data": "scene"}

翻译为执行流程就是:

  • 显示 ui_logo
  • 初始化对象并放入对象池
  • 播放背景音乐 bgm_logo
  • 切换到场景 scene

Tips: 前面的 time 的单位是秒,用于分帧处理。 如果想快速处理完,可以全部填 0。

到这里,Game 模块的导读就结束了,相信有了上面的讲解,Game 模块的代码阅读会轻松不少。

Level 模块

Level 模块用于负责关卡相关的状态控制,它没有具体的行为。

它只是一个容器,将主角、怪物、物品掉落、寻路、存档等功能连接在一起。

当用户点击 START 按钮后,会执行 action-game.json 中的 level Action。

"level": {
    "start": [
        { "time": 0, "name": "on_bgm", "data": "bgm_level"},
        { "time": 0, "name": "on_ui", "data": "ui_fx" },
        { "time": 0, "name": "on_msg", "data": "msg_level_start" },
        { "time": 0.2, "name": "on_msg_str", "data": { "key":"msg_set_camera", "value": false } },
        { "time": 2, "name": "on_ui", "data": "ui_level" }
    ],
    ...
}

可以看到,这里做了以下操作:

  • 播放背景音乐 bgm_level
  • 显示UI ui_fx
  • 发送事件 msg_level_start
  • 发送事件 msg_set_camera, false
  • 显示UI ui_level

Level 模块也涉及到了三个主要文件:

  • data-level.json
  • action-level.json
  • level.ts

data-level.json

{
    "name":"Cyberpunk Scene",
    "total_time":300,
    "close_blood_fx": true,
    "each_rate_value":10000,
    "killed_to_score":100,
    "survival_time_to_score":10,
    "level_events":"level_events",
    "prefab_player":"player-tps",
    "prefab_enemy":"enemy-tps",
    "prefab_drop_item":"drop_item",
    "enemies":["enemy_0", "enemy_1", "enemy_2", "boss_0"],
    "fx_dead":"fx_dead_white",
    "score_level":[...],
    "items":[...],
    "probability_drop_enemy":{...},
    "probability_drop_items":{...},
    "cards":["life", "attack", "defense", "special"],
    "probability_drop_card":{...}
}

我们可以看到,在 Level 模块的数据配置文件中,包含了:

  • 击杀分数
  • 重生时间
  • prefab信息
  • 怪物信息
  • 掉落物品信息
  • 分数评级

通过修改这些配置,就可以改变关卡数据和玩法。

action-level.json

{
    "start":{...},
    "warning":{ ... },
    "end":{...}
}

而关卡的 Action 配置文件,则相对简单,只有 startwarningend 三种情况。

"start":{
    "start":[
        { "time": 0.2, "name": "on_msg_str", "data": { "key":"level_do", "value":"addPlayer" } },
        { "time": 0.5, "name": "on_inst", "data": "level_events_enemy" },
        { "time": 0.6, "name": "on_inst", "data": "level_events_items" }, 
        { "time": 0.7, "name": "on_inst", "data": "level_events_card" }
    ]
},

start 命名中我们可以看到,当关卡加载时,会执行以下操作:

  • on_msg_str:level_do, addPlayer
  • on_inst:level_events_enemy
  • on_inst:level_events_items
  • on_inst:level_events_card

on_msg_str

action.tsUtilAction 类中,我们可以看到, on_msg_str 的方法原型为:

  public static on_msg_str (data: key_type_string) {
      Msg.emit(data.key, data.value);
  }

不难看出,它的功能就是向外发出一个事件,并带有一个参数。

接下来我们看看在脚本代码中,是如何执行这些操作的。

{ "time": 0.2, "name": "on_msg_str", "data": { "key":"level_do", "value":"addPlayer" }

在这里就是:发出一个 level_do 消息, 参数为 addPlayer

打开 level.ts 我们可以看到,在它的 init 函数中,监听了这个消息:

Msg.on('level_do', this.do.bind(this));

level.tsdo 函数的原型如下:

public do (fun: string) {
    this[fun]();
}

由此可以看出, action-level.json 中的第一个操作,其实就是调用 LeveladdPlayer 函数。

on_inst

action.tsUtilAction 类中,我们可以看到, on_msg_str 的方法原型为:

    public static on_inst (key: string, actor: Actor) {
        var asset = ResCache.Instance.getPrefab(key);
        var obj = Res.inst(asset, Level.Instance._objectNode);
        if (actor && actor._viewRoot) {
            obj.parent = actor._viewRoot!;
            obj.setPosition(0, 0, 0);
        }
    }

从上面的原型中可以看出,on_inst 方法就是实例化一个指定的对象。

这个指定对象参数是 prefab 的名称。

  • level_events_enemy
  • level_events_items
  • level_events_cards

都是 prefab,对应的资源在 assets/resources/obj 目录下。

ResCache 在预加载阶段会将目录下的 prefab 都加载完,并按 prefab 名称存储下来。 使用的时候直接使用名称查询就可以了。

具体逻辑请参考 ResCache 类中的 loadPrefabsetPrefabgetPrefab

Actor 模块

接下来我们来讲讲大家最关心的主角控制怪物生成以及可拾取物品生成相关内容。

但在讲这些内容之前,我们先来看看 Actor 模块。

在前面的内容中,我们提到过,当你打开 action-player.jsonaction-enemy_1.jsonaction-boss_0.json
这些文件时,你会发现它们的内容格式都差不多。
这是因为,主角怪物BOSS 对应的类都是 Actor

打开 data-player.json 或者是 data-enemy_1.json 文件,都可以看到 action 配置文件名音效最大血量等信息。

{
    "name":"actor-enemy_0",
    "action":"action-enemy_0",
    "sfx_walk_ground":"sfx_walk_ground",
    "sfx_walk_grass":"sfx_walk_grass",
    ...
    "strength": 100,
    "max_strength": 100,
    "max_hp":60,
    ...

打开对应的 action-xxx.json,你可以看到有 playjumpdeadpickup 等命令。

而每个命令会执行以下命令:

  • on_call:调用当前 actor 上指定的函数名
  • on_set:设置当前 actor 上指定的变量值
  • on_anig:设置当前 actor 上动画图的变量
  • on_sfx:播放音效

具体的函数原型,大家可以自己去 action.ts 里查看。

以上动作的载体就是 Actor

主角控制

初始化

主角使用的 prefab 路径为 resources/obj/player-tps.prefab,这个在 data-player.json 中有配置。

我们从 level.ts 中的 addPlayer 函数继续讲。

public addPlayer () {
    //获得 player 的 prefab
    const prefab = ResCache.Instance.getPrefab(this._data.prefab_player);
    //通过 prefab 生成 player 实例
    const resPlayer = Res.inst(prefab, this._objectNode!, this._data.spawn_pos);
    // 获取 Actor 组件
    this._player = resPlayer.getComponent(Actor)!;
    // 标记这个 actor 为主角
    this._player.isPlayer = true;
    // 使用 data-player.json 初始化这个 actor
    this._player.init('data-player');
}

上面是 addPlayer 中的关键步骤。接下来,我们继续看 this._player.init 这段代码做的事情。

这段代码会先调用 ActorBase.init 完成一些基本的初始化工作,然后再调用 Actor.initView

Actor.initView 中的最后一步,会调用 play Action,启动对象。

action-player.josn 中,我们可以看到 play 的内容如下:

"play":{
    "start":[
        { "time": 1, "name": "on_call", "data": "onUpdate"},
        { "time": 1.5, "name": "on_inst_scene", "data": "actor_input"},
        { "time": 1.5, "name": "on_com", "data":"ActorStatistics"},
        { "time": 1.5, "name": "on_com", "data":"ActorSound"},
        { "time": 2, "name": "on_msg_str", "data": { "key":"msg_set_input_active", "value": true} }
    ]
},

它做了以下几件事:

  • 调用 onUpdate 函数
  • 实例化 actor_input 对象
  • 为 player 添加 ActorStatisticsActorSound 组件
  • 发送 msg_set_input_active 事件,激活输入

用户输入

{ "time": 1.5, "name": "on_inst_scene", "data": "actor_input"},

上面这条语句执行的就是将 resources/obj/actor_input.prefab 实例化,并添加到场景节点上。

游戏逻辑,Cocos Cyberpunk,麒麟子Cocos Creator 3D研究笔记,Cocos Creator实用技巧,游戏引擎,Cocos Creator,TA Shader,3D游戏开发

actor_input.prefab 上有一个 ActorInput 组件,它的功能就是处理用户的输入。

进入 actor-input.ts 文件中,在 ActorInput 类的 initInput 方法中我们可以看到,它会做平台判断。 当处于移动端的时候,会启动 joysitck 方式。 反之,则使用鼠标键盘操作。

游戏逻辑,Cocos Cyberpunk,麒麟子Cocos Creator 3D研究笔记,Cocos Creator实用技巧,游戏引擎,Cocos Creator,TA Shader,3D游戏开发

input-joystick.tsinput-keyboard.ts 不会直接控制角色,而是会把所有的操作指令交给 ActorInput 组件来处理。

ActorInput 实现了所有的用户操作,如 onMoveonJumponRotationonRun 等等。

主角摄像机

游戏逻辑,Cocos Cyberpunk,麒麟子Cocos Creator 3D研究笔记,Cocos Creator实用技巧,游戏引擎,Cocos Creator,TA Shader,3D游戏开发

player-tps.prefab 中,我们可以找到 camera-root 节点,这个节点上挂了三个组件:

CameraTps:用于控制摄像机的上下旋转。由于摄像机的正方向总是与主角对齐的,所以不需要控制左右。

CameraMoveTarget:用于控制摄像机的缓动效果

SensorRayNodeToNode:用于摄像机与场景的碰撞检测

角色动画掩码与IK

射击游戏中,有两个最主要的功能:

  • 边移动边开枪
  • 根据镜头方向改变端枪姿势

Cocos Cyberpunk 中也实现了。

角色动画掩码

Cocos Creator 提供了动画图功能,并且提供了 分层动画与掩码功能。

分层动画:简单来说,就是用户可以同时播放多个动画,并且通过权重来进行混合。

动画掩码:标记动画在播放时,哪些骨骼需要更新。

Cocos Cyberpunk 中,角色(主角和怪物)的动画图有两层 basefire

游戏逻辑,Cocos Cyberpunk,麒麟子Cocos Creator 3D研究笔记,Cocos Creator实用技巧,游戏引擎,Cocos Creator,TA Shader,3D游戏开发

可以看到 fire 的权重为 1.0,由于 fire 在分层图中的索引靠后,优先级较高,所以当 basefire 同时播放的时候,fire 会替换掉动画。

假如我们想要实现移动中射击,那我们希望的是 base 层播放移动fire 层播放射击,并且 fire 只影响上半身。

这时,就需要用到动画掩码。

双击打开 anig_player_mask 可以看到,所有的上半身骨骼都被选中了。 也就是说,fire 动作在播放时,只会进行上半射更新。

游戏逻辑,Cocos Cyberpunk,麒麟子Cocos Creator 3D研究笔记,Cocos Creator实用技巧,游戏引擎,Cocos Creator,TA Shader,3D游戏开发

角色IK

游戏逻辑,Cocos Cyberpunk,麒麟子Cocos Creator 3D研究笔记,Cocos Creator实用技巧,游戏引擎,Cocos Creator,TA Shader,3D游戏开发

如上图所示,我们需要根据镜头方向改变端枪姿势,才能保证游戏的射击体验。

这就需要用到我们的 IK

player-tps.prefab 中找到 actor-player-tps 节点,可以看到它上面挂了两个组件:

AimIK:用于绑定IK相关数据。端枪描准姿势主要受脊柱上 spine01spine02spine03 三根关节的影响,组件里对它们进行了引用。

AimControl:用于实现描准控制

游戏逻辑,Cocos Cyberpunk,麒麟子Cocos Creator 3D研究笔记,Cocos Creator实用技巧,游戏引擎,Cocos Creator,TA Shader,3D游戏开发

IK 相关的代码在 scripts/core/ik 目录下,有兴趣的朋友可以深入研究。

怪物生成机制

配置

让我们从 action-level.json 中的 start Action 说起:

{ "time": 0.5, "name": "on_inst", "data": "level_events_enemy" },

Level 开始后,会创建 level_events_enemy 的实例,它在关卡中只有一个,是负责怪物生成和销毁的管理器。

与它相关的资源:

  • resources/obj/level_events_enemy.prefab
  • resources/obj/boss_0.prefab
  • resources/obj/enemy_0.prefab
  • resources/obj/enemy_1.prefab
  • resources/obj/enemy_2.prefab
  • level_events_enemy.ts

打开 level_events_enemy.ts 可以看到,在 LevelEventsEnemy 中使用了 data-level.json 中的 probability_drop_enemy 作为数据源,内容如下:

"probability_drop_enemy":{
    "interval":[2, 4],
    "interval_weight_max":1,
    "life_time":[30, 40],
    "max": 4,
    "init_count":[7, 10],
    "weights":[0.4, 0.7, 0.9, 1],
    "weights_max":[4, 4, 4, 1],
    "weights_group":[ 0, 1, 2, 3]
},

可以看到,它定义了刷新间隔,最大怪物数量等等。

生成

LevelEventsEnemy 中主要函数为 generateEvent,它会判断怪物数量是否需要生成。

如果符合生成条件,它会从 data-level.jsonenemies 中随机一种怪物的外观。

"enemies":["enemy_0", "enemy_1", "enemy_2", "boss_0"],

得到外观后,再随机一个行为组,组合成数据,发出 msg_add_enemy 事件。

const currentIndex = this.probability.weights_group[occurGroupIndex];
const res = DataLevelInst._data.enemies[currentIndex];
// Send add add enemy.
Msg.emit('msg_add_enemy', { res: res, groupID: occurGroupIndex })

还要特别注意的是,当 boss 出现时,它还会发出警告。(麒麟子觉得,这个配在 action-boss_0.json 中可能会更好)

  if (res == 'boss_0'){
      Msg.emit('level_action', 'warning');
  } 

同时,它也会响应 msg_remove_enemy 事件,处理怪物被删除的情况。

然后,在 addEnemy 中,从寻路系统中随机出一个可用的位置点,进行怪物创建:

  //从寻路系统中获取一个可用点
  const point = NavSystem.randomPoint();
  //创建 enemy
  var enemy = ResPool.Instance.pop(data.res, point.position); 
  const actor = enemy.getComponent(Actor);
  //初始化怪物数据      
  actor.init(`data-${data.res}`);

怪物AI

打开 boss_0.prefab 或者 enemy_0.prefab,可以看到两个与怪物 AI 相关的组件:

ActorBrain

真正的 怪物AI 驱动器,会根据周围环境执行相应动作。

ActorInputBrain
转接器,方便将 怪物AI 行为转递给 ActorInput

可拾取物品掉落机制

配置

level_events_enemy 相似,在 Level 加载成功后,会生成一个用于可拾取物品的管理器:

{ "time": 0.6, "name": "on_inst", "data": "level_events_items" }, 

与它相关的资源:

  • resources/obj/level_events_items.prefab
  • resources/obj/drop_item.prefab
  • level_events_items.ts
  • drop-item.ts

level_events_enemy.ts 中,会使用 data-level.json 中的 probability_drop_items 字段做为配置数据:

    "probability_drop_items":{
        "interval":[5, 30],
        "life_time":[30, 40],
        "max": 4,
        "interval_weight_max": 1,
        "init_count":[7, 10],
        "weights":[0, 0.125,0.25, 0.375,0.75,1],
        "weights_max":[1, 1, 1, 1,2,2],
        "weights_group":[0, 1,2, 3,4,5]
    },

可以看出,它和怪物生成类似,也配置了一些生成相关的参数。

生成

level_events_items.ts 中,不会处理实际的物品生成,它会监听 msg_remove_item 事件,调整自身数据。

当判定可以生成新物品时,会发出 msg_remove_item 事件。

level.ts 中,会监听这个事件并执行 addDrop 方法。

addDrop 方法中,会生成 drop_item.prefab 的实例。

拾取

在主角 player-tps.prefabsensor_detect_drop 节点上,有 SensorRaysActorSensorDropItem 两个组件。

SensorRays:会定期检查主角周围的对象,并存到 checkedNode 变量上。

ActorSensorDropItem:会做一些判断,并更新状态,同时将 checkedNode 保存为 pickedNode

ui_level.prefab 中的 grp_take_info 上有一个 UIDisplayByState 组件,它会检测这个状态,并显示出 “按E拾取” 这个提示。

当物品被拾取后,ActoronPicked 方法会被调用,并发出 picked 事件。

drop_item.prefab 上的 DropItem 组件会响应 picked 事件并销毁回收自己。

自动拾取

在主角 player-tps.prefab 上挂了一个 CheckAutoPick 组件,当处于移动端时,会自动调用 ActoronPicked 方法。

最后来几句

由于 UI系统寻路系统 涉及的内容太多,会专门用两篇新的文章来讲解。

希望这篇文章,能够帮助到想要研究 Cocos Cyberpunk 项目源码的朋友。

大佬花了几个月时间写完的 Gameplay,显然是麒麟子用两天时间无法吃透的。

希望更多朋友能够一起研究,一起产出一些学习心得、教程,让 Cocos Cyberpunk 这个开源项目发展得更好。

下一篇不出意外,会写自定义管线,敬请期待。

附1:类名 <—> 文件名

Cocos Cyberpunk 项目中,类名采用大驼峰命名法,即首字母大写的方式。

与类名对应的 ts 文件,则采用小写单词并用“-”连接的方式。

比如:

  • UILoading -> ui-loading.ts
  • CameraController -> camera-controller.ts

掌握这个对应关系,保证你能很快定位到想要的代码。

附2:两种单例写法

细心的朋友会发现,Cocos Cyberpunk 中有两种单例写法。

一类是传统的,适合所有面象对象语言的模式:Singleton

通过 类名.Instance 访问,如:

  • UI.Instance
  • ResCache.Instance
  • Level.Instance
  • GameSet.Instance

这样做的好处是:可以随时调用,会在第一次被调用时初始化

但不好的地方是:所有类都会直接被不同的文件引用,当类名修改或者移动文件时,涉及到的文件会非常多

另一类是适合 TS,JS,C++ 这种支持全局变量的语言。 参考 data-core.ts

import { DataEquip } from "../../logic/data/data-equip";
import { DataSound } from "../../logic/data/data-sound";
import { DataCamera } from "./data-camera";
...

export const DataEquipInst = new DataEquip();
export const DataSoundInst = new DataSound();
export const DataCameraInst = new DataCamera();
...

export function Init () {
    //Init data.
    DataEquipInst.init('data-equips');
    DataSoundInst.init('data-sound');
    DataCameraInst.init('data-camera');
}

这种写法,使用起来相当灵活。
可以像这样使用:

import * as dataCore from "./data-core";
dataCore.DataEquipInst.foo();

也可以像这样使用:

import { DataEquipInst } from '../data/data-core';
DataEquipInst.foo();

这种写法的好处是:所有引用的地方只依赖一个容器文件,发生重构时影响非常小

但也有一些较小的坏处:可能会有多人同时维护同一个容器文件,并且需要一个适合的地方调用初始化函数

相比而言,麒麟子更喜欢第二种,会让代码变得更易维护。

**Tips:**其实第二种方式并不新鲜,与在静态类中持有各实例对象的做法是一样的。只是得益于 TS 这类可以使用全局变量的语言特性,才可以用这样的方式编写。文章来源地址https://www.toymoban.com/news/detail-715092.html

到了这里,关于[3D游戏开发实践] Cocos Cyberpunk 源码解读-游戏逻辑框架全解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • AIGC 加持 Cocos,游戏开发需要几步?

    近日,游戏行业知名的 B2B 大会 WN 2023 大会于土耳其首都伊斯坦布尔顺利举办。本次大会邀请了来自全球的游戏开发商、媒体、发行商、分发平台等行业决策者,共同探讨游戏行业未来发展态势,进一步拓展业务,并在世界范围内寻找新的合作伙伴,Cocos 受邀出席本次大会。

    2024年02月09日
    浏览(75)
  • Unity、UE、Cocos游戏开发引擎的区别

    Unity、Unreal Engine(UE)和Cocos引擎是三个常用的游戏开发引擎,它们在功能和特性上有一些区别。以下是它们之间的主要区别: 编程语言:Unity使用C#作为主要的编程语言,开发者可以使用C#脚本进行游戏逻辑编写。Unreal Engine主要使用C++作为编程语言,但也支持蓝图系统,允许

    2024年02月22日
    浏览(66)
  • 《入门级-Cocos2d 4.0塔防游戏开发》---第二课:游戏加载界面开发

    目录 一、开发环境介绍 二、开发内容 2.1 修改窗口的大小。 2.2 添加加载场景相关代码 2.3 添加资源 三、显示效果 四、知识点  4.1 Sprite 4.2 定时器 操作系统:UOS1060专业版本。 cocos2dx:版本 环境搭建教程: 统信UOS下配置安装cocos2dx开发环境_三雷科技的博客-CSDN博客        游

    2024年02月15日
    浏览(62)
  • 全新适配鸿蒙生态,Cocos引擎助力3D应用开发

    原文链接: 全新适配鸿蒙生态,Cocos引擎助力3D应用开发,点击链接查看更多技术内容; 一、适配HarmonyOS背景 HarmonyOS 3.1版本自发布以来,备受广大开发者的好评,同时也吸引了鸿蒙生态众多伙伴的青睐。 鸿蒙生态所强调的智慧全场景、多端联动与跨设备流转等能力,与Coc

    2024年02月09日
    浏览(52)
  • Cocos Creator小游戏-文字斗争(H5、小程序)益智类 项目展示+完整项目源码

    文字斗争(H5、小程序)益智类 项目展示+完整项目源码 玩家有着自己的战场,可以作为进攻方去挑战其他战场,也可以作为防守方抵御其他玩家的进攻。 玩家可以挑战游戏里设置的各个关卡,提高自己的指挥能力和布局能力,最终可以战胜其他玩家的同时能够不被其他玩家

    2024年02月08日
    浏览(72)
  • Cocos Creator小游戏-2048(PC、安卓、H5)益智类 项目展示+完整项目源码

    Cocos Creator小游戏-2048 在棋盘上,每次会增加一个 小 动物,你可以选择四个方向 滑动 ,然后 小 动物会按方向移动,遇到相同的 小 动物就会 合并,看谁合并的最多。 1 .初始化格子小动物的位置。 2.手势滑屏移动屏幕中的小动物。 3.自动寻找棋盘中没有小动物的格子,自动

    2024年02月12日
    浏览(60)
  • 【cocos 2d微信小游戏开发教程】基础使用笔记分享(一)

    开发文档地址 https://docs.cocos.com/creator/2.4/manual/zh/ 挂载脚步 右键新建脚本 脚本解释 把类名和脚本名改为一致 允许其他脚本调用 text优先使用面板的值,property去掉则不在面板上显示 打印输出 生命周期函数解释 节点的使用 预设体 拖拽新增预设体 用代码渲染预设体 脚本内容

    2024年02月07日
    浏览(66)
  • Cocos独立游戏开发框架中的日志模块:Bug无所遁形

    本系列是《8年主程手把手打造Cocos独立游戏开发框架》,欢迎大家关注分享收藏订阅。 在Cocos独立游戏开发框架中,一个强大的日志模块是不可或缺的组成部分。日志不仅仅是记录应用程序的运行状态,还可以用于故障排除、性能监测和安全审计。本文将探讨如何设计和实现

    2024年02月09日
    浏览(43)
  • 【unity之IMGUI实践】游戏玩法逻辑实现【四】

    👨‍💻个人主页 :@元宇宙-秩沅 👨‍💻 hallo 欢迎 点赞👍 收藏⭐ 留言📝 加关注✅! 👨‍💻 本文由 秩沅 原创 👨‍💻 收录于专栏 : unityUI专题篇 🅰️ Target Texture 行为渲染 —————————————— ___________________________ 😶‍🌫️ 步骤 : 1.靠近武器,碰撞检测

    2024年02月16日
    浏览(57)
  • 游戏开发常用引擎工具介绍对比区别(UE4,Unity,Cocos,LayaAir,[egret-白鹭])

    是一套为开发实时技术而存在的引擎工具。目前广泛应用于3D建模渲染、游戏开发中。它完善的工具套件以及简易的工作流程能够使开发者快速修改或查看成果,对于代码的依赖性很低。而完整公开的源代码则能让使用者自由修改和扩展引擎功能。 是面向开发人员的 3D/2D 游戏

    2024年02月13日
    浏览(68)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包