“编辑语:STEAM 上的 3D 解密游戏《InOutPath》以其清新的画面,独特的玩法,受到了广大 STEAM 玩家,以及 Cocos 开发者们的关注。今天有幸邀请到了这款游戏的开发商,为大家做一次技术分享。希望能够对在用 Cocos Creator 开发 3D 游戏的朋友们,有所启发。
”
在《InOutPath》的关卡中,除了画面渲染效果外,我们还设计了许多细节和彩蛋,相信大家在玩的时候就能体会到这款游戏带来的惊喜和挑战。

今天,我们就来聊聊《InOutPath》的制作细节,看看我们团队是如何利用 Cocos 游戏引擎,实现这个游戏的画风和独特的关卡机制的,以及在打包上架 Steam 的过程中,获得的一些实用经验。

团队介绍
《InOutPath》的研发是一支小型独立游戏团队,团队成员都是身经百战的游戏老兵和游戏研发老兵,源于团队对益智解谜游戏类型的喜爱和在纪念碑谷/linelight中吸取的灵感,由此制作了《InOutPath》这款冒险解谜游戏。
项目简介
《InOutPath》是一款冒险解谜游戏,一共 7 个大章节 300+ 小章节。包含了包括初晨草原、忘竹林、黄金乐园、奥秘云谷、分界地、蔚蓝边境、水下世界等 7 种完全不同风格的场景,也包含了 20 多种迥异的解谜机制。玩家将扮演一只可爱的小猫咪寻找和主人的记忆。

不同于点击解密的解密场景完全处于静止状态,在InOutPath中由于猫咪和解密要素机关都处于动态中,这导致整个场景的状态一直在变化,进而除了考验玩家的逻辑推理能力也同时考验玩家的操作能力。在设计关卡的规则当中,遵循优先设计一条看起来是正确但是实际却是错误的原则来设计关卡。
技术分享
编辑器插件
由于关卡的复杂性和策划多变的需求,开发团队基于 Cocos 编辑器插件 API 开发了一套制作关卡功能的辅助插件,使整个游戏都架构在曲线数据上。
其次是在地图数据和渲染上进行了分离,这可以很方便地调整关卡。由于制作关卡的便利性,我们一共搭建了不下于 300 关的关卡。
接下来,我们说说是如何实现的。
首先数据部分,如果屏蔽掉渲染物体,只保留逻辑物体,场景是这样的:

如图所见,场景非常简单,但是却包含了最主要的逻辑数据,这是整个游戏驱动的关键所见。当把渲染物体显示出来的时候,场景是这样的:

当然这里要特别感谢 2youyou2 提供的思路。可以利用 gizmos.ControllerBase
创建一个控制器,然后调用gizmos.ControllerUtils.cube
创建可以在世界空间中被选择方块选择器。最后在使用 gizmos.ControllerBase.initHandle
将他们关联起来就能实现图中的效果,这样策划就可以随意编辑关卡了,程序只需要在这些曲线上实现各种功能即可。
let cube = window.cce.gizmos.ControllerUtils.cube(
SPLINE_NODE_SIZE,
SPLINE_NODE_SIZE,
SPLINE_NODE_SIZE,
Color.YELLOW
);
cube.parent = this.shape;
this.positionNode = cube;
this.initAxis(cube, SplineMoveType.Position);
cube = window.cce.gizmos.ControllerUtils.cube(
SPLINE_NODE_SIZE,
SPLINE_NODE_SIZE,
SPLINE_NODE_SIZE,
Color.GREEN
);
cube.parent = this.shape;
this.directionNode = cube;
this.initAxis(cube, SplineMoveType.Direction);
cube = window.cce.gizmos.ControllerUtils.cube(
SPLINE_NODE_SIZE,
SPLINE_NODE_SIZE,
SPLINE_NODE_SIZE,
Color.RED
);
cube.parent = this.shape;
this.invDirectionNode = cube;
this.initAxis(cube, SplineMoveType.InvDirection);
initAxis(node: Node, axisName: string ) {
return window.cce.gizmos.ControllerBase.prototype.initHandle.call(
this,
node,
axisName
);
}
提示系统
首先开发的是关卡提示系统。本身解密游戏的提示功能要做到"Show,don't tell",但要做一个"Show,don't tell"实在是太难了。
而由于游戏架构设计就是奔着多人合作架构去,游戏天然的支持帧同步,帧数据(数据中主要操作数据)播放出录像。所以我们做了一个录像系统,录制了策划的指令,当玩家打开提示系统的时候,就会实时播放这个指令。还原策划当时的场景。结构如下:
export const enum OperateEventType {
DEFALUT = 'defalut',
UP = 'up',
DOWN = 'down',
LEFT = 'left',
RIGHT = 'right',
FRONT = 'front',
BACK = 'back'
... more value
}
export interface LevelFrame {
/**帧数 */
id: number;
/**添加事件操作 */
adds?: Array<OperateEventType | string>;
/**移除事件操作 */
deletes?: Array<OperateEventType | string>;
/**帧dt时间 */
dt?: number;
}
最后录制的数据差不多如此:
'lv1-1': {
'1': { id: 1, dt: 0.016 },
'31': { id: 31, adds: ['right'] },
'101': { id: 101, adds: ['back'] },
'102': { id: 102, deletes: ['right'] },