GDC2025 |《双影奇境》的“功能模式”

266 阅读8分钟

在前几天通关了《双影奇境》,实在是很舍不得。玩法设计构思奇妙,惊喜迭出,画面精美,剧情节奏也很棒。在游玩过程中,形式丰富且操作丝滑的关卡令人惊叹:究竟是如何做到高效实现且迭代各种灵活性极高的玩法呢?

刚好刷到了Hazelight工作室的玩法程序设计师Ylva Werner在GDC2025上的分享,这是一个十分激动人心的学习契机。(GDC2025|13人团队1万个玩法,双影奇境主创透露爆款法则

这位设计师提到的架构是“功能模式”(Capabilities),并提出这是不同于当前主流游戏引擎采用的ECS结构的新型组件架构。为了对功能模式的设计思路有更清晰的理解,本文先详细回顾ECS的相关内容。

什么是ECS

ECS结构

ECS,全称Entity-Component-System,由由数据组件组成的实体组成,以及对这些组件进行运作的系统 。(Entity component system - Wikipedia

  • 实体 :实体表示通用对象。例如,在游戏引擎上下文中,每个粗略的游戏对象都表示为一个实体。通常,它仅包含一个唯一 ID。
  • 组件 :组件将实体描述为具有特定方面,并保存对该方面进行建模所需的数据。例如,每个可能受到伤害的游戏对象都可能具有与其实体关联的 Health 组件。
  • 系统 :系统是一个过程,它作用于具有所需组件的所有实体。例如,物理系统可以查询具有质量、速度和位置分量的实体,并迭代结果,对每个实体的组件集进行物理计算。

拓展资料:如何在Unity实现ECS架构。 游戏开发中的ECS 架构概述

实体、组件与系统的相互关系

  • 游戏内的每一个基本单元都是一个实体,每个实体又由一个或多个组件构成,每个组件仅仅包含代表其特性的数据(即在组件中没有任何方法),例如:移动相关的组件MoveComponent包含速度、位置、朝向等属性,一旦一个实体拥有了MoveComponent组件便可以认为它拥有了移动的能力;
  • 系统便是来处理拥有一个或多个相同组件实体集合的工具,其只拥有行为(即在系统中没有任何数据),在这个例子中,处理移动的系统仅仅关心拥有移动能力的实体,它会遍历所有拥有MoveComponent组件实体,并根据相关的数据(速度、位置、朝向等),更新实体的位置。
  • 实体组件是一个一对多的关系,实体拥有怎样的能力,完全是取决于其拥有哪些组件,通过动态添加或删除组件,可以在(游戏)运行时改变实体的行为。

image.png (图源:游戏开发中的ECS 架构概述 - 知乎 (zhihu.com)

为什么要用ECS?

面向数据的编程

游戏往往是关于许多快速变化的状态,玩家、非玩家角色、子弹和激光等游戏对象,可能包括位置、旋转、速度、加速度、重量、颜色、意图、目标等等。状态可以封装在大量不断变化的数据中,在技术层面上,游戏完全是关于这些数据是什么以及这些数据如何变化。

比如一个玩家角色具有表示角色在游戏世界中的位置的 position 属性,它可能会被用于渲染系统、摄像机系统、输入系统、物理系统……如果仍用面向对象的思路去封装数据和操作,这个玩家对象将被各个系统引用,因此造成复杂的依赖关系

于是基于组件和系统的实体架构将离散系统的概念推向了合乎逻辑的结论。所有行为逻辑都作为独立的系统进行编程,所有游戏状态都单独存储在一组数据组件中,这些数据组件根据系统的需要提供给系统。(Why use an Entity Component System architecture for game development? (richardlord.net)

解除依赖

ECS 没有面向对象编程中常见的依赖问题,因为组件是简单的数据桶,它们没有依赖。系统只在具有所有这些组件的任何实体上执行其逻辑。其他实体只需跳过,无需复杂的依赖关系树。但是,这可能是隐藏 bug 的地方,因为通过组件将值从一个系统传播到另一个系统可能很难调试。ECS 可用于需要将解耦数据绑定到给定生命周期的情况。

使用组合

ECS 使用组合,而不是继承树。实体通常由 ID 和附加到它的组件列表组成。可以通过向实体添加正确的组件来创建任何游戏对象。这使开发人员可以轻松地向实体添加功能,而不会出现任何依赖项问题。

功能模式

开发面临的问题

这个设计源于《逃出生天》(A Way  Out)项目的技术复盘,当时程序设计师们由于多重组件相互干扰而深受困扰,尤其是在联机同步时还存在大量不可控因素。

在未对演讲源视频进行分析之前,先尝试简单理解:虽然ECS强调组件只用作数据存储,但在UE里面组件的设计通常还是数据与行为的封装。这样对于灵活的关卡设计来讲,依赖性是很高的。比如关卡1的玩法涉及到十个游戏对象,关卡2的玩法涉及到另外五种游戏对象,都需要重新写组件脚本,可重用性低,而且会相互依赖。

数据驱动+功能单元

因此,在《双影奇境》立项之初就确立了的核心准则:组件仅作为数据容器,所有行为逻辑必须上移至游戏对象(Game  Objects)层实现。

这一准则显然是实现《双影奇境》各种玩法的关键前提,为此,Ylva特意又强调了一遍: “做决策的不是组件,而是游戏对象。”

  • 数据驱动:组件仅作为数据容器,可被共享。
  • 功能单元:把游戏行为抽离成功能单元,由游戏对象做决策。这样比系统控制有更高的灵活性。

功能单元的事件流

image.png (图源:GDC2025|13人团队1万个玩法,双影奇境主创透露爆款法则

触发轴:5个关键函数(ShouldActivate, ShouldDeactivate, OnActivated, OnDeactivated and TickActive),几乎可以套用所有行为逻辑。

  • 功能单元结构化和规范化,有利于统一调度。
  • 为每个功能抽象出5个触发轴的关键函数,类似于传统的状态机的每个状态,符合“事件发生-持续-终止”的逻辑。

image.png (图源:GDC2025|13人团队1万个玩法,双影奇境主创透露爆款法则

功能模式能够实现多功能并行激活且具有优先级转换能力,可以智能协调冲突的行为。例如,当玩家忽然切换武器时,相关功能模块将被自动触发而不需要先回到预设状态。

  • 对于线性关卡为主的设计来说,功能单元更多的是前后触发以及条件触发关系。为了更灵活且更及时地反应,采用多功能并行和顺序触发的思路,相对于传统状态机来说更强调没有固定约束的事件流。
  • 优先级转换则可能为了解决功能冲突和优化响应的问题。少了条件约束且可以并行执行,就容易产生多个功能同时发生的情况。比如“应急坠落能力”就被设定为最低优先级,仅在所有行为相关功能均未触发时才会激活,确保了所有行为都能优先响应玩家做出的指令。

定义和总结

功能模式(Capabilities)是一种包含状态、事件、转换规则,以及具有决策性的代码系统。它与常见的状态机有本质差别,能够实现多功能并行激活且具有优先级转换能力,可以智能协调冲突的行为。

本文认为具有启发性的设计有以下几点:

  • 数据驱动:与传统的面向对象编程不同,游戏数据鼓励共享;
  • 事件流:有利于提高线性关卡的开发效率;
  • “状态”的结构:Unity的生命周期也与此类似,“开始-持续-结束”的抽象结构非常通用。