// reactive system to handle messages public class HandleDebugLogMessageSystem : ReactiveSystem<DebugEntity> {
// collector: Debug.Matcher.DebugLog
// filter: entity.hasDebugLog
publicvoidExecute (List<DebugEntity> entities) { foreach (var e in entities) { Debug.Log(e.debugLog.message); e.isDestroyed = true; } } }
最后上面代码的弊端是依赖了unity API debug,除此之外如果这里的log功能比较复杂比如json解析、网络发送等时,就会需要更多的依赖甚至关联。如果画出UML可能惨目忍睹。为了解决这个问题作者提出了使用接口。这块其实就没啥特别的了,面向接口编程本身也是OOP里面重要的概念,在上面的图里面,只要不是在虚线框内的代码多数情况是OOP的。而外部代码与ECS代码的交互是基于接口的。
// the interface publicinterfaceILogService { voidLogMessage(string message); }
// a class that implements the interface using UnityEngine; publicclassUnityDebugLogService : ILogService { publicvoidLogMessage(string message) { Debug.Log(message); } }
// another class that does things differently but still implements the interface using SomeJsonLib; publicclassJsonLogService : ILogService { string filepath; string filename; bool prettyPrint; // etc... publicvoidLogMessage(string message) { // open file // parse contents // write new contents // close file } }
// the previous reactive system becomes public class HandleDebugLogMessageSystem : ReactiveSystem<DebugEntity> {
ILogService _logService; // contructor needs a new argument to get a reference to the log service publicHandleDebugLogMessageSystem(Contexts contexts, ILogService logService) { // could be a UnityDebugLogService or a JsonLogService _logService = logService; } // collector: Debug.Matcher.DebugLog // filter: entity.hasDebugLog
publicvoidExecute (List<DebugEntity> entities) { foreach (var e in entities) { _logService.LogMessage(e.DebugLog.message); // using the interface to call the method e.isDestroyed = true; } } }
下面的例子是一个较为复杂的IInputService,可以看到是对unity api的一个封装。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
// interface publicinterfaceIInputService { Vector2D leftStick {get;} Vector2D rightStick {get;} bool action1WasPressed {get;} bool action1IsPressed {get;} bool action1WasReleased {get;} float action1PressedTime {get;} // ... and a bunch more }
// (partial) unity implementation using UnityEngine; publicclassUnityInputService : IInputService { // thank god we can hide this ugly unity api in here Vector2D leftStick {get {returnnew Vector2D(Input.GetAxis('horizontal'), Input.GetAxis('Vertical'));} } // you must implement ALL properties from the interface // ... }
publicclassEmitInputSystem : IInitalizeSystem, IExecuteSystem { Contexts _contexts; IInputService _inputService; InputEntity _inputEntity; // contructor needs a new argument to get a reference to the log service publicEmitInputSystem (Contexts contexts, IInputService inputService) { _contexts = contexts; _inputService= inputService; }
publicvoidInitialize() { // use unique flag component to create an entity to store input components _contexts.input.isInputManger = true; _inputEntity = _contexts.input.inputEntity; }
// clean simple api, // descriptive, // obvious what it does // resistant to change // no using statements publicvoidExecute () { inputEntity.isButtonAInput = _inputService.button1Pressed; inputEntity.ReplaceLeftStickInput(_inputService.leftStick); // ... lots more queries } }
var _services = new Services( new UnityViewService(), // responsible for creating gameobjects for views new UnityApplicationService(), // gives app functionality like .Quit() new UnityTimeService(), // gives .deltaTime, .fixedDeltaTime etc new InControlInputService(), // provides user input // next two are monobehaviours attached to gamecontroller GetComponent<UnityAiService>(), // async steering calculations on MB GetComponent<UnityConfigurationService>(), // editor accessable global config new UnityCameraService(), // camera bounds, zoom, fov, orthsize etc new UnityPhysicsService() // raycast, checkcircle, checksphere etc. );
/// Replaces an existing component at the specified index /// or adds it if it doesn't exist yet. /// The prefered way is to use the /// generated methods from the code generator. publicvoidReplaceComponent(int index, IComponent component) { if (!_isEnabled) { thrownew EntityIsNotEnabledException( "Cannot replace component '" + _contextInfo.componentNames[index] + "' on " + this + "!" ); }
publicvoidInitialize() { // grab the view service instance from the meta context _viewService = _contexts.meta.viewService.instance; }
publicvoidExecute(List<GameEntity> entities) { foreach (var e in entities) { // call the view service to make a new view var view = _viewService.LoadAsset(_contexts, e, e.asset.name); if (view != null) e.ReplaceView(view); } } }
// [Game, Event(true)] (Event(true) DEPRECATED as of Entitas 1.6.0) [Game, Event(EventTarget.Self)] // generates events that are bound to the entities that raise them publicsealedclassPositionComponent : IComponent { public Vector2D value; }
using Entitas; publicinterfaceIViewService { // create a view from a premade asset (e.g. a prefab) voidLoadAsset( Contexts contexts, IEntity entity, string assetName); }
publicclassUnityViewService : IViewService { // now returns void instead of IViewController publicvoidLoadAsset(Contexts contexts, IEntity entity, string assetName) {
//Similar to before, but now we don't return anything. var viewGo = GameObject.Instantiate(Resources.Load<GameObject>("Prefabs/" + assetName)); if (viewGo != null) { var viewController = viewGo.GetComponent<IViewController>(); if(viewController != null) { viewController.InitializeView(contexts, entity); }
// except we add some lines to find and initialize any event listeners var eventListeners = viewGo.GetComponents<IEventListener>(); foreach(var listener in eventListeners) { listener.RegisterListeners(entity); } } } }