keras conv2d如何使用用Entitas构建游戏

如何使用Entitas构建游戏
一、优势与目的
在大型项目中,使用Entitas可以使结构更清晰、稳定、灵活,如果对Entitas不是足够了解,可以看看前面几篇,本文目的是基于Entitas设计一个合理的框架使得逻辑层与表现层分离,也可以叫视图层这样就可以使得核心代码不依赖于具体哪个游戏引擎,本文以Unity为例进行介绍,分为一下几点:
定义数据层、逻辑层、表现层,彼此独立
视图与视图控制
二、明确概念
数据:Data,表示游戏状态,体力、经验、敌人类型、AI状态、移动速度等,在Entitas中,这些数据保存在Components中,就是ECS中的C.
逻辑:Logic,不用说了吧,在Entitas中叫System,就是ECS中的S.
表现:View,就是将游戏状态进行显示,比如引擎渲染、播放动画和音乐、UI显示,在这个例子中,将由GameObjects上的MonoBehaviours负责完成.
服务:Services,主要是第三方工具或插件,比如游戏引擎本身或物理、寻路模块.
输入:Input,例如控制器/键盘/鼠标输入,网络输入.
整个结构为了保持模块之间的解耦、独立,比如说打日志,很多项目的做法是在各个系统里调用Debug.Log等,这就会导致强耦合,如果依赖了平台的API,想换平台,可能需要到处寻找对该方法的引用来重新使用新平台的API,时间成本是很大的,那么Entitas可以很好解决这个问题。
先看结构图左部分,Service与Entity的交互是通过接口来进行的,接口就是告诉编译器,这个类(实现接口)有了这个API,可以调用接口里的方法,这样核心代码就脱离了具体的执行环境或平台,可以方便进行转移。下面举几个典型的例子:
LogService
public class HandleDebugLogMessageSystem : ReactiveSystem&DebugEntity& {
ILogService _logS
public HandleDebugLogMessageSystem(Contexts contexts, ILogService logService) {
_logService = logS
public void Execute (List&DebugEntity& entities) {
foreach (var e in entities) {
_logService.LogMessage(e.DebugLog.message);
e.isDestroyed = true;
如果想打日志,就调用DebugContext来创建DebugEntity,并添加一个DebugLogComponent就可以了,至于Service如下:
public interface ILogService {
void LogMessage(string message);
using UnityE
public class UnityDebugLogService : ILogService {
public void LogMessage(string message) {
Debug.Log(message);
using SomeJsonL
public class JsonLogService : ILogService {
bool prettyP
public void LogMessage(string message) {
我不用关心LogService是如何LogMessage的,无论写文件还是通过网络发送,甚至依赖了哪个平台的API,比如Unity的Debug.Log。这样通过接口将Entitas与服务联系起来又做到了解耦,Service只是帮System对数据进行处理。
InputService
这个复杂一些,首先我要知道我需要哪些输入信息,可以定义一个接口包含一些属性,C#中的属性既可以当成字段也可以理解成方法:
public interface IInputService {
Vector2D leftStick {get;}
Vector2D rightStick {get;}
bool action1WasPressed {get;}
bool action1IsPressed {get;}
bool action1WasReleased {get;}
float action1PressedTime {get;}
如果从Unity里获取输入信息,就可以写一个这样的Service:
using UnityE
public class UnityInputService : IInputService {
Vector2D leftStick {get {return new Vector2D(Input.GetAxis('horizontal'), Input.GetAxis('Vertical'));} }
现在我们创建一个System,EmitInputSystem将 输入数据拉取进来给Entitas:
public class EmitInputSystem : IInitalizeSystem, IExecuteSystem {
Contexts _
IInputService _inputS
InputEntity _inputE
public EmitInputSystem (Contexts contexts, IInputService inputService) {
_contexts =
_inputService= inputS
public void Initialize() {
_contexts.input.isInputManger = true;
_inputEntity = _contexts.input.inputE
public void Execute () {
inputEntity.isButtonAInput = _inputService.button1P
inputEntity.ReplaceLeftStickInput(_inputService.leftStick);
其中的InputEntity的InputComponent使用了[Unique]属性,对这段代码不明白的同学建议学习一下Entitas的前几节,现在这些就是我所说的抽象,通过InputService我并不关心数据是从键盘还是鼠标或者手柄传进来的,那么对于TimeSerivice,我只需要它给我delta time,不管它是Unity、XNA还是Unreal,我只需要拿到timeDelta然后移动相应的物体就可以了。
Service接入优化
对Service的获取可能在System的各个地方,那么按照上面的方法会需要写很多不同的构造函数,所以最好的办法就是让这些Service可以全局获取,那么上文提到的[Unique]可以轻松的做到这一点。
先构造一个帮助类持有所有Service的引用Services.cs:
public class Services
public readonly IViewService V
public readonly IApplicationService A
public readonly ITimeService T
public readonly IInputService I
public readonly IAiService Ai;
public readonly IConfigurationService C
public readonly ICameraService C
public readonly IPhysicsService P
public Services(IViewService view, IApplicationService application, ITimeService time, IInputService input, IAiService ai, IConfigurationService config, ICameraService camera, IPhysicsService physics)
Application =
var _services = new Services(
new UnityViewService(),
new UnityApplicationService(),
new UnityTimeService(),
new InControlInputService(),
GetComponent&UnityAiService&(),
GetComponent&UnityConfigurationService&(),
new UnityCameraService(),
new UnityPhysicsService()
在MetaContext中 ,我们有一套唯一组件持有这些Service实例:
[Meta, Unique]
public sealed class TimeServiceComponent : IComponent {
public ITimeS
最后,我们创建一个ServiceRegistrationSystems,继承Feature,然后将Services 传给它的构造器,这样每个子系统负责将每个Service实例传给MetaContext每个独一无二的组件:
public class ServiceRegistrationSystems : Feature
public ServiceRegistrationSystems(Contexts contexts, Services services)
Add(new RegisterViewServiceSystem(contexts, services.View));
Add(new RegisterTimeServiceSystem(contexts, services.Time));
Add(new RegisterApplicationServiceSystem(contexts, services.Application));
Add(new RegisterInputServiceSystem(contexts, services.Input));
Add(new RegisterAiServiceSystem(contexts, services.Ai));
Add(new RegisterConfigurationServiceSystem(contexts, services.Config));
Add(new RegisterCameraServiceSystem(contexts, services.Camera));
Add(new RegisterPhysicsServiceSystem(contexts, services.Physics));
Add(new ServiceRegistrationCompleteSystem(contexts));
public class RegisterTimeServiceSystem : IInitializeSystem
private readonly MetaContext _metaC
private readonly ITimeService _timeS
public RegisterTimeServiceSystem(Contexts contexts, ITimeService timeService)
_metaContext = contexts.
_timeService = timeS
public void Initialize()
_metaContext.ReplaceTimeService(_timeService);
通过_contexts.meta.timeService.instance就可以在全局方便地获取每一个Service的实例。
现在来看看结构图右部分,和上面原理基本一致,目标是为了脱离具体的游戏引擎或运行环境,使得渲染显示模块与Entitas分离。我们再次使用接口,只需要考虑视图与Entitas之间需要的数据和方法,这是一个接口:
public interface IGameObjectView {
Vector2D Position {get; set;}
Vector2D Scale {get; set;}
bool Active {get; set;}
void InitializeView(Contexts contexts, IEntity Entity);
void DestroyView();
这是该接口在Unity中的实现:
public class UnityGameView : Monobehaviour, IGameObjectView{
protected Contexts _
protected GameEntity _
public Vector2D Position {
get {return transform.position.ToVector2D();}
set {transform.position = value.ToVector2();}
public Vector2D Scale // as above but with tranform.localScale
public bool Active {get {return gameObject.activeS} set {gameObject.SetActive(value);} }
public void InitializeView(Contexts contexts, IEntity Entity) {
_contexts =
_entity = (GameEntity)
public void DestroyView() {
Object.Destroy(this);
接下来,我们需要一个Service(IViewService)去创建这些View,然后将它们和Entity关联起来:
public interface IViewService {
IGameObjectView LoadAsset(Contexts contexts, IEntity entity, string assetName);
持有View的Entity组件:
public sealed class ViewComponent : IComponent {
public IGameObjectV
Unity实现ViewService:
public class UnityViewService : IViewService {
public IGameObjectView LoadAsset(Contexts contexts, IEntity entity, string assetName) {
var viewGo = GameObject.Instantiate(Resources.Load&GameObject&("Prefabs/" + assetName));
if (viewGo == null) return null;
var viewController = viewGo.GetComponent&IGameObjectView&();
if (viewController != null) viewController.InitializeView(contexts, entity);
return viewC
然后通过LoadAssetSystem 将View和Entity进行绑定:
public class LoadAssetSystem : ReactiveSystem&GameEntity&, IInitializeSystem {
readonly Contexts _
readonly IViewService _viewS
public void Initialize() {
_viewService = _contexts.meta.viewService.
public void Execute(List&GameEntity& entities) {
foreach (var e in entities) {
var view = _viewService.LoadAsset(_contexts, e, e.asset.name);
if (view != null) e.ReplaceView(view);
PositionSystem代码如下,使用了IGameObjectView 更新位置信息,而不是直接使用Unity的API:
public class SetViewPositionSystem : ReactiveSystem&GameEntity& {
public void Execute(List&GameEntity& entities) {
foreach (var e in entities) {
e.view.instance.Position = e.position.value;
以上是View部分的解耦过程,但是有个明显的缺陷,就是我们在Entitas中,不需要知道引擎的渲染过程,有一种更好的解耦方法就是Event功能。
使用[Event]属性给需要的组件:
[Game, Event(true)]
public sealed class PositionComponent : IComponent {
public Vector2D value;
将会生成PositionListenerComponent和IPositionListener,IPositionListener包含一个方法void OnPosition,再写一个接口给所有的View监听器,在Unity或其他环境下实现:
public interface IEventListener {
void RegisterListeners(IEntity entity);
重新实现View Service:
public UnityViewService : IViewService {
public void LoadAsset(Contexts contexts, IEntity entity, string assetName) {
var viewGo = GameObject.Instantiate(Resources.Load&GameObject&("Prefabs/" + name));
if (viewGo == null) return null;
var viewController = viewGo.GetComponent&IViewController&();
if (viewController != null) viewController.InitializeView(contexts, entity);
var eventListeners = viewGo.GetComponents&IEventListener&();
foreach (var listener in eventListeners) listener.RegisterListeners(entity);
现在我们可以去除SetViewXXXSystem 类了,因为我们不需要告诉View去做什么,但要实现如下MonoBehaviour并挂在相应的View上:
public class PositionListener : Monobehaviour, IEventListener, IPositionListener {
GameEntity _
public void RegisterListeners(IEntity entity) {
_entity = (GameEntity)
_entity.AddPositionListener(this);
public void OnPosition(GameEntity e, Vector2D newPosition) {
transform.position = newPosition.ToVector2();
每个View监听Entity的数据变化事件,而且每个Entity可以被多个View监听,但不建议这样做,一旦PositionComponent改变会导致EventSystem触发相应Entity的PositionHandler集合,个人觉得Event并不太好用,会生成大量的Listener文件,造成系统庞杂,通过接口已经可以做到解耦。
并不是所有项目都适合用Entitas,大型项目比较适合,而且Entitas是通过数据驱动的,只有数据变化了才会触发相应的逻辑,内部很多的Cache对性能做了很多的优化,具体源码可以在上查看。
没有更多推荐了,
加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!如何使用Entitas开发游戏(FNGGames)
原文链接:
这个帖子用来说明如何使用Entitas来构建我的游戏工程。我会假定你对Entitas如何工作已经有了相当好的理解。这并不是一篇详细介绍Entitas特性的教程。相反,它旨在解释如何使用Entitas构建代码,并且最大程度地提高清晰度、稳定性和灵活性,尤其是在大型的工程中。我当然不会声称这是权威的,这只是我学会使用Entitas开发某些项目后的一些方法。As always, YMMV.
我将尝试涵盖以下主题。我会使用Unity作为示例来说明views和views controllers的概念,但是这种思维方式可以应用到各种引擎。
定义数据、逻辑和视图层
维护层与层之间的分离
使用接口进行抽象
视图层抽象(Views与View controllers)
Data: 游戏状态。数据比如血量、物品栏、经验、敌人类型、AI状态、移动速度等等。在Entitas中这些数据在Compenents中。
Logic: 数据改变的规则。PlaceInventory()、BuildItem()、FireWeapon()等等。在Etitas中叫做Systems。
View: 负责向玩家展现游戏状态、渲染、动画、音频、UI等的代码。在示例中使用依赖于GameObject的MonoBehaviour。
Service: 外部的信息来源或者信息池。比如:寻路,排行榜,反作弊,社交,物理,甚至是引擎本身。
Input: 外部的模拟输入,通常通过有限的入口进入部分游戏逻辑。比如:控制器、键盘、鼠标、网络输入等。
任何游戏的核心都只是CPU上的数值模拟。每一款游戏都不过是一组数据(游戏状态)的集合,这些数据会经历周期性的数值变换(游戏逻辑)。这些数据包括得分、血量、经验值、NPC对话、物品栏等等等等。游戏逻辑规定了这些数据可以经历的转换规则(调用PlaceItemInInventory()用已定义的方式改变游戏状态)。整个模拟系统可以不依赖于任何额外的层而存在。
通常,一个游戏和纯粹的数值模拟器之间的区别在于循环中有外部的用户,用户有能力在外部使用模拟器已定义的逻辑改变游戏状态。随着用户的加入,就有了连接模拟器与用户的需求。这就是“视图层”。这一层代码库的一部分负责展现游戏状态,通过将角色渲染到屏幕上、播放音频、更新UI控件等方式。没有这一层用户就没有办法理解模拟器或者与模拟器进行有效的交互。
大多数游戏架构的目标在于维护logic、data和view的分层。想法就是模拟器层不应该关心或者知道他们是如果渲染到屏幕上的。当玩家受到攻击后改变血量的方法,不应该包含渲染特效或者播放音频的代码。
我维护这种分离的方式是依照下图来构建我的工程。我将试着叙述图中的每一部分,然后使用接口或helps来拓展类似于抽象这样的概念。
功能的抽象
抽象(在当前特定的语境中)是解除what与how之间强耦合关系的过程。一个非常简单的例子,想象有一个写日志的功能,日志可供用户查看。
Naive的方法
最naive的方法,我确定大多数读者已经知道如何避免了,就是在每一个需要写日志的system中调用Debug.Log。这将立刻给代码库带来很高的耦合度,以后维护起来将是一场噩梦。
幼稚方法的问题
如果有一天想要用更合适的方法,比如将日志写入文件的方法,来代替Debug.Log会发生什么?如果是添加一个游戏中的调试控制台并将信息记录下来呢?你将不得不浏览整个代码库,然后添加或者替换这些方法调用。这是一场真正的噩梦,即使是一个中等的项目中。
这些代码可读性差、易混淆。职责没有分离。如果你已经完成过一个Unity项目,你可能觉得很OK因为这些事Unity都已经做了打日志这件事。在这我告诉你,这一点都不OK。你的CharacterController不应该直接去解析用户输入,不应该跟踪物品栏,不应该播放脚步声音频,也不应该发布facebook消息,而应该控制角色移动,并且只用于控制角色移动。分离你的关注。
解耦-Entitas方法
可能你对这些问题太熟悉了,所以寻求用Entitas来解决。那现在就来谈一谈Entitas方式的好处:
在Entitas中我们通过创建LogMessageComponent(string message)然后设计一个ReactiveSystem来实现这个功能,这个system用来收集信息并且做具体的代码实现。有了这个设置,我们可以很容易地创建一个Entity,给它挂上组件以使它能够向控制台打印信息。
using UnityE
using System.Collections.G
// debug message component
public sealed class DebugLogComponent : IComponent {
// reactive system to handle messages
public class HandleDebugLogMessageSystem : ReactiveSystem&DebugEntity& {
// collector: Debug.Matcher.DebugLog
// filter: entity.hasDebugLog
public void Execute (List&DebugEntity& entities) {
foreach (var e in entities) {
Debug.Log(e.debugLog.message);
e.isDestroyed =
现在,不论什么时候想要创建日志信息,创建一个Entity然后交给System处理。具体的实现我们可以想改多少次就改多少次,并且只在代码的一个地方改(System中)。
(纯粹的)Entitas方法的问题
这种方法对某些用户来说足够了,尤其是类似于MatchOne这样的小项目。但是它本身并不是没有问题。我们添加了对UnityEngine的一个很强的依赖,因为我们在System中使用了它的API。我们也在System中直接写了功能的实现。
使用Debug.Log()在这种情况下似乎不是问题,毕竟只是一行代码。但是如果里面包含解析json文件的操作或者需要发送网络消息怎么办?现在你的系统里有很多具体的代码实现。也有很多依赖和using 声明(UnityEngine/JSON library/Networking Library等等)。代码的可读性很差,如果考虑到工具库变化的话还很容易出错。如果有一天改变了引擎,所有游戏代码需要完全重写。
使用接口的示例
在c#中,解决依赖性和提高代码清晰度的方法是使用接口。一个接口就类似于一个约定。告诉编译器你的类实现了一个接口,就好比你说“这个类作为这个接口出现时具有相同的公共的API”。
当我声明接口时我会想“我的游戏需要从这个接口中获得什么样的信息或功能”,然后我会试着为它提供一个描述性的、简单的API。对于日志功能来说我们只需要一个方法,一个简单的·LogMessage(string message) 。用接口实现如下:
public interface ILogService {
void LogMessage(string message);
using UnityE
public class UnityDebugLogService : ILogService {
public void LogMessage(string message) {
Debug.Log(message);
using SomeJsonL
public class JsonLogService : ILogService {
bool prettyP
public void LogMessage(string message) {
通过继承ILogService 来向编译器保证你的类实现了void LogMessage(string message) 方法。这意味着你可以在上文提到的ReactiveSystem中调用它。这个系统只关注接口ILogService 。如果我们向系统中传入一个JsonLogService ,我们会得到一个包含日志信息的json文件。我们不能访问JsonLogMessage 类中的公共字符串字段,因为接口中并没有定义。需要注意的是,我们向这个system的构造方法中传入了一个`ILogMessage 的实例,我会在下文中解释。
public class HandleDebugLogMessageSystem : ReactiveSystem&DebugEntity& {
ILogService _logS
public HandleDebugLogMessageSystem(Contexts contexts, ILogService logService) {
_logService = logS
public void Execute (List&DebugEntity& entities) {
foreach (var e in entities) {
_logService.LogMessage(e.DebugLog.message);
e.isDestroyed = true;
在我的工程中一个更复杂的例子是IIputService 。我又开始想了:我需要知道用户的什么输入呢?我是否可以定义一组简单的属性或方法来获取到我想得到的信息?以下是我的接口的一部分:
public interface IInputService {
Vector2D leftStick {get;}
Vector2D rightStick {get;}
bool action1WasPressed {get;}
bool action1IsPressed {get;}
bool action1WasReleased {get;}
float action1PressedTime {get;}
using UnityE
public class UnityInputService : IInputService {
Vector2D leftStick {get {return new Vector2D(Input.GetAxis('horizontal'), Input.GetAxis('Vertical'));} }
现在我可以写一个EmitInputSystem 来向Entitas中发送输入数据。现在这些数据成为了游戏数据的一部分,并且可以驱使其他模块做其他的事。这种方法的好处是我可以将使用Unity的实现替换为AssetStore中的一个解决方案,比如InControl,而不用修改任何游戏代码。注意以下的系统中,代码只关注这个特定的接口。
public class EmitInputSystem : IInitalizeSystem, IExecuteSystem {
Contexts _
IInputService _inputS
InputEntity _inputE
public EmitInputSystem (Contexts contexts, IInputService inputService) {
_contexts =
_inputService= inputS
public void Initialize() {
_contexts.input.isInputManger = true;
_inputEntity = _contexts.input.inputE
public void Execute () {
inputEntity.isButtonAInput = _inputService.button1P
inputEntity.ReplaceLeftStickInput(_inputService.leftStick);
到现在,我希望你可以理解我说的“抽象”。我正在从具体的实现中抽象 逻辑,也就是从how 中抽象what 。在Input示例中我说了,我关心的只是我可以查询用户是否按下了ButtonA,我不关系这来自于键盘还是鼠标还是网络连接。在游戏中实际读取输入的地方,这些都不重要。
对于“获取time delta”功能来说,我不需要知道是来自Unity还是XNA还是Unreal,我只需要知道它是多少,这关系到我要将角色在屏幕上移动多少距离。
现在我们要向代码中引入一种之前没有遇到过的复杂情况:现在我们的system需要一个继承自某个接口的实例的引用。在上面的例子中我是通过构造方法传入的,但是这将导致许多具有不同构造方法的system。我们想要的是这些Service实例是全局可访问的。我们也希望在代码库中只有这么一个地方,这个地方靠近应用的初始化点,在这个地方我们可以决定使用接口的哪些实现。也是在这里,我们创建实例,并使这些实例全局可访问,以便可以在system中查询到,而不必把它们传入每一个单独的构造方法中。幸运的是这使用Enitas实现起来超级简单。
我的方法是首先创建一个Helper类,其中包含每一个Service的引用。
Service.cs
public class Services
public readonly IViewService V
public readonly IApplicationService A
public readonly ITimeService T
public readonly IInputService I
public readonly IAiService Ai;
public readonly IConfigurationService C
public readonly ICameraService C
public readonly IPhysicsService P
public Services(IViewService view, IApplicationService application, ITimeService time, IInputService input, IAiService ai, IConfigurationService config, ICameraService camera, IPhysicsService physics)
Application =
现在可以在GameController里面很轻易的初始化它:
var _services = new Services(
new UnityViewService(),
new UnityApplicationService(),
new UnityTimeService(),
new InControlInputService(),
GetComponent&UnityAiService&(),
GetComponent&UnityConfigurationService&(),
new UnityCameraService(),
new UnityPhysicsService()
在MetaContext 里面有一组unique components持有这些接口的实例。比如:
[Meta, Unique]
public sealed class TimeServiceComponent : IComponent {
public ITimeS
最后有一个Feature ,它在系统层面上第一个运行,叫做ServiceRegistrationSystems 。它的构造方法里有一个额外的Services 参数,然后把这个services向下传入到initialize systems。这些system简单地将Servives 中的实例分配给MetaContext中的unique components。
ServiceRegistrationSystems.cs
public class ServiceRegistrationSystems : Feature
public ServiceRegistrationSystems(Contexts contexts, Services services)
Add(new RegisterViewServiceSystem(contexts, services.View));
Add(new RegisterTimeServiceSystem(contexts, services.Time));
Add(new RegisterApplicationServiceSystem(contexts, services.Application));
Add(new RegisterInputServiceSystem(contexts, services.Input));
Add(new RegisterAiServiceSystem(contexts, services.Ai));
Add(new RegisterConfigurationServiceSystem(contexts, services.Config));
Add(new RegisterCameraServiceSystem(contexts, services.Camera));
Add(new RegisterPhysicsServiceSystem(contexts, services.Physics));
Add(new ServiceRegistrationCompleteSystem(contexts));
一个RegistrationSystems示例
public class RegisterTimeServiceSystem : IInitializeSystem
private readonly MetaContext _metaC
private readonly ITimeService _timeS
public RegisterTimeServiceSystem(Contexts contexts, ITimeService timeService)
_metaContext = contexts.
_timeService = timeS
public void Initialize()
_metaContext.ReplaceTimeService(_timeService);
最后的结果是我们可以全局访问这些service实例,通过Contexts实例(_context.meta.timeService.instance)。而且我们只在一个地方创建它们,所以回滚、修改实现或者模拟现实用于测试都变得轻而易举。你也可以轻松使用编译指令获得指定平台的实现或者只在调试状态下有效的实现。我们使用了“控制反转”的依赖性解决方式,(依赖性)从system类的深处转到了应用的顶部(初始化处)。
View层抽象
目前为止,我们看到了上图中左侧的service接口,现在来看一看右侧的View接口。工作方式很相似。就像之前说的,View层关心的是将游戏状态展现给玩家,包括动画、声音、图片、网格、渲染等等。目标同样是消除对游戏引擎或第三方库的依赖性,得到纯粹的、描述性的system代码而没有任何具体的实现。
Naive的方法是用一个ViewComponent 里面引用一个GameObject。然后可能需要一个简单的标记componentAssignViewComponent 来说明我们需要一个新的GameObject作为Entity的view。要使用的话需要写一个reactive systerm作用于AssignView 和过滤器!entity.hasView 来确保只在需要的地方添加view。在这个system,甚至是component中,可能会直接使用Unity的API。这当然不能实现我们设置的目标。
在这里可以使用上文中提到的service形式,连同更深一层次的对view的抽象。同样,思考一下在view中需要什么数据或功能,然后为它写一个接口。这将决定system代码如何从view中get或set数据。不妨把它叫做“ViewController”——这是直接控制View对象的代码块。典型的,里面可能包含transform信息(位置/旋转/缩放),也可能有标签、层、名称、enable状态。
View,天然地,应该绑定到Entity,并且它可能需要处理这个Entity和其他游戏状态的信息。为此,需要在设置view的时候,传入entity引用和Contexts实例。也要能够在entity代码内部销毁view。示例如下:
public interface IViewController {
Vector2D Position {get; set;}
Vector2D Scale {get; set;}
bool Active {get; set;}
void InitializeView(Contexts contexts, IEntity Entity);
void DestroyView();
这是在Unity中对这个接口的一个实现:
public class UnityGameView : Monobehaviour, IViewController {
protected Contexts _
protected GameEntity _
public Vector2D Position {
get {return transform.position.ToVector2D();}
set {transform.position = value.ToVector2();}
public Vector2D Scale // as above but with tranform.localScale
public bool Active {get {return gameObject.activeS} set {gameObject.SetActive(value);} }
public void InitializeView(Contexts contexts, IEntity Entity) {
_contexts =
_entity = (GameEntity)
public void DestroyView() {
Object.Destroy(this);
在这里需要一个service用于创建这些view并与entity绑定。这是我的IViewService 接口和在它Unity中的实现。
一个component持有这个view controller
public sealed class ViewComponent : IComponent {
public IViewC
一个接口来定义我需要能够访问view service的两件事情
public interface IViewService {
IViewController LoadAsset(Contexts contexts, IEntity entity, string assetName);
view service在Unity中的实现:
public class UnityViewService : IViewService {
public IViewController LoadAsset(Contexts contexts, IEntity entity, string assetName) {
var viewGo = GameObject.Instantiate(Resources.Load&GameObject&("Prefabs/" + assetName));
if (viewGo == null) return null;
var viewController = viewGo.GetComponent&IViewController&();
if (viewController != null) viewController.InitializeView(contexts, entity);
return viewC
一个LoadAssetSystem用于加载资源并且绑定view
public class LoadAssetSystem : ReactiveSystem&GameEntity&, IInitializeSystem {
readonly Contexts _
readonly IViewService _viewS
public void Initialize() {
_viewService = _contexts.meta.viewService.
public void Execute(List&GameEntity& entities) {
foreach (var e in entities) {
var view = _viewService.LoadAsset(_contexts, e, e.asset.name);
if (view != null) e.ReplaceView(view);
以下是一个position system示例,使用了抽象的view而不直接与Unity交互。
public class SetViewPositionSystem : ReactiveSystem&GameEntity& {
public void Execute(List&GameEntity& entities) {
foreach (var e in entities) {
e.view.instance.Position = e.position.value;
代码中没有对Unity引擎的依赖,component和system只引用了接口。代码中也没有具体的实现(不用关心访问GameObject和Transform,只需要简单地去设置接口里的属性)
这种方法的问题
有一个很明显的瑕疵——我们在代码中写了一个system用来与view层交互——这破坏了我们之前的原则,也就是模拟器不应该知道自己是否被渲染。在Entitas中有另一种强制完全与view解耦的方法——就是Entitas的“事件”功能。
在游戏Match-One中,Simon并没有ViewComponent。事实上,没有任何游戏代码知道他正在被渲染。代替MonoBehaviour的是事件监听器。我将使用事件来重构上面的示例,以此展示如何简化游戏逻辑,甚至是将模拟器层与view层完全解耦。
首先,需要一个使用[Event]属性标记的component,用来生成我们需要的监听器和事件系统。这里再次以Position功能为例。
[Game, Event(true)]
public sealed class PositionComponent : IComponent {
public Vector2D value;
这个新的属性(Event(true))会生成一个PositionListenerComponent 和一个IPositionListener 接口。现在写另外一个接口来作用于全部 的事件监听器,所以就可以在它们创建的时候安全地进行初始化。
public interface IEventListener {
void RegisterListeners(IEntity entity);
现在不在需要view component或view service中的LoadAsset方法的返回值了,所以移除它们。现在需要在view service中添加代码来识别并且初始化asset中的事件监听器:
public UnityViewService : IViewService {
public void LoadAsset(Contexts contexts, IEntity entity, string assetName) {
var viewGo = GameObject.Instantiate(Resources.Load&GameObject&("Prefabs/" + name));
if (viewGo == null) return null;
var viewController = viewGo.GetComponent&IViewController&();
if (viewController != null) viewController.InitializeView(contexts, entity);
var eventListeners = viewGo.GetComponents&IEventListener&();
foreach (var listener in eventListeners) listener.RegisterListeners(entity);
现在可以摆脱所有的类似于SetViewXXXSystem 这样的类了,因为不再需要通知view执行操作。取而代之的是写monobehaviour脚本来监听位置的改变,比如:
public class PositionListener : Monobehaviour, IEventListener, IPositionListener {
GameEntity _
public void RegisterEventListeners(IEntity entity) {
_entity = (GameEntity)
_entity.AddPositionListener(this);
public void OnPosition(GameEntity e, Vector2D newPosition) {
transform.position = newPosition.ToVector2();
如果把这个脚本添加到一个预制体上然后在game controller中生成EventSystems ,那这个GameObject的位置就会与entity中的PositionComponent完美同步,而不需要systems。那view层物体的位置就完全与模拟器层中entity中的位置完全解耦了。可以轻松地向component中添加事件。重构在之前IViewContoller接口中所有的功能,使用事件监听可以完全摆脱它。
使用service来加载资源的模式,有控制view层中信息流初始化的能力。可以随意添加(IAudioPlayer,UAnimator,ICollider等等)然后将它们的引用传递给contexts或者相关的entity。你可以控制初始化的顺序和时间(不再需要知道Unity中Start()和Update()的调用时间,不再需要当Start()执行过早时检查是否为空)。
现在能够做到使view层控制自身——view controller变成了简单的事件监听器,在关注的组件改变时触发,而完全不需要向模拟器层返回信息(除了在初始化时向entity上挂一个xxxListenerComponent的情况)。可以在Unity中实现一整个动画系统,通过monobehaviour事件监听器,而不用在模拟器层中引用它。同样适用于音频、粒子、着色器等等。
很完美,我们实现了开始时设置的所有目标。
我们将entitas代码与游戏引擎和第三方库完全解耦了。
我们有一个模拟器层(数据在component中,逻辑在system中),这对引擎来说是完全不可知的。而在工程中也只有一个文件夹包含各种接口针对Unity的实现。这也是唯一一个,当我们想将引擎从Unity改为XNA时,需要改变的文件夹。
在应用的顶部,有一个地方会决定使用哪个实现。可以在这里进行测试模拟、尝试新的第三方解决方案、或者轻松的改变游戏内事物如何运行的想法,而不以任何方法改变游戏逻辑。
模拟器层与view层是完全解耦的,一旦事件系统运行起来,我们的游戏逻辑甚至不知道正在被渲染。整个模拟器可以在服务器端运行,而视图层在客户端运行。
最后,再回头看一下游戏逻辑,会发现它清晰易读。复杂的实现被隐藏,而只有一些描述性的方法和属性的调用。设计只包含关注字段的接口,再也不用看到巨大的包含无用信息的 下拉框。我们将只能访问我们真正需要的东西。
没有更多推荐了,
加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!}

我要回帖

更多关于 keras h5模型如何使用 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信