这一章主要是通过IExecuteSystem让我们的小熊动起来了。我们还是先介绍一下IExecuteSystem系统。
IExecuteSystem
这个系统也比较简单,和IInitializeSystem很相似,区别就是IInitializeSystem是游戏开始时执行一次Initialize()方法,IExecuteSystem时每帧执行一次Execute()方法。类似与MonoBehaviour生命周期中的Update()方法。
ArrowStateComponent.cs
这里我们主要是想收集键盘的输入信息,所以我们和之前Game Context分开,在Input Context下创建两个分别记录左键和右键状态的组件,同时左键和右键的状态组件不需要多个所以我们[Unique]属性让组件成为单例。
using Entitas;
using Entitas.CodeGeneration.Attributes;
public enum EArrowState
{
None,
Down,
Up,
}
/// <summary>
/// LeftArrowState组件
/// </summary>
[Input]
[Unique]
public class LeftArrowStateComponent : IComponent
{
public EArrowState state;
}
/// <summary>
/// RightArrowState组件
/// </summary>
[Input]
[Unique]
public class RightArrowStateComponent : IComponent
{
public EArrowState state;
}
然后回到Unity编辑器重新生成代码。
InputDeviceSystem.cs IInitializeSystem,IExecuteSystem
注意:InputDeviceSystem.cs 同时实现IInitializeSystem,IExecuteSystem两个接口,需要在IInitializeSystem接口的Initialize()方法中初始化LeftArrowStateComponent,RightArrowStateComponent两个组件。
然后在IExecuteSystem接口的Execute()方法中检测是否按下键盘的左右键。
using Entitas;
using UnityEngine;
/// <summary>
/// InputDevice系统
/// </summary>
public class InputDeviceSystem : IInitializeSystem,IExecuteSystem
{
readonly InputContext _context;
public InputDeviceSystem(Contexts contexts)
{
_context = contexts.input;
}
public void Initialize()
{
_context.SetLeftArrowState( EArrowState.None);
_context.SetRightArrowState(EArrowState.None);
}
public void Execute()
{
if (Input.GetKey(KeyCode.LeftArrow) || Input.GetKeyDown(KeyCode.LeftArrow))
{
_context.ReplaceLeftArrowState( EArrowState.Down);
}
else if (Input.GetKeyUp(KeyCode.LeftArrow))
{
_context.ReplaceLeftArrowState(EArrowState.Up);
}
else {
_context.ReplaceLeftArrowState(EArrowState.None);
}
if (Input.GetKey(KeyCode.RightArrow) || Input.GetKeyDown(KeyCode.RightArrow))
{
_context.ReplaceRightArrowState(EArrowState.Down);
}
else if (Input.GetKeyUp(KeyCode.RightArrow))
{
_context.ReplaceRightArrowState(EArrowState.Up);
}
else
{
_context.ReplaceRightArrowState(EArrowState.None);
}
}
}
MovementSystem.cs ReactiveSystem
当左右按键按下时需要移动小熊Entity。那么需要一个速度组件。
/// <summary>
/// MoveSpeed组件
/// </summary>
[Game]
public class MoveSpeedComponent : IComponent
{
public float value;
}
注意: 每次新加组件之后都需要回到Unity编辑器重新生成代码
然后在Entity有了MoveSpeedComponent组件之后需要根据每帧的间隔时间和速度确定移动距离,并改变Entity的PositionComponent最后交由RenderPositionSystem刷新位置。
using Entitas;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// Movement系统
/// </summary>
public class MovementSystem : ReactiveSystem<GameEntity>
{
public MovementSystem(Contexts contexts) : base(contexts.game)
{
}
protected override void Execute(List<GameEntity> entities)
{
foreach (var item in entities)
{
float interval = Time.deltaTime;
//移动距离
float moveInterval = interval * item.moveSpeed.value;
Vector2 positon = item.position.value;
//移动完成后的坐标信息
positon.x += moveInterval;
item.ReplacePosition(positon);
}
}
protected override bool Filter(GameEntity entity)
{
return entity.hasMoveSpeed;
}
protected override ICollector<GameEntity> GetTrigger(IContext<GameEntity> context)
{
return context.CreateCollector(GameMatcher.MoveSpeed);
}
}
CommandMoveSystem.cs ReactiveSystem
通过InputDeviceSystem.cs可以检测到左右按键按下松开状态了,但是Game Context中的Entity还并没有速度值所以现在还没有办法移动,所以需要在LeftArrowStateComponent,RightArrowStateComponent两个组件发生变化时改变GameEntity的速度值。
这里我们简单处理了,如果小熊向左移动速度值设置成负值,向右移动速度值设置成正值,同时设置小熊的DirectionComponent组件。如果左右两个按键都没有按下时把小熊的MoveSpeedComponent移除小熊就会停下,并把DirectionComponent设置成站立状态。
using Entitas;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// CommandMove系统 extends ReactiveSystem
/// </summary>
public class CommandMoveSystem : ReactiveSystem<InputEntity>
{
readonly IGroup<GameEntity> moveEntitys;
public CommandMoveSystem(Contexts contexts) : base(contexts.input)
{
//找到
int[] allof = { GameComponentsLookup.View, GameComponentsLookup.Direction};
moveEntitys = contexts.game.GetGroup(GameMatcher.AllOf(allof));
}
protected override void Execute(List<InputEntity> entities)
{
foreach (var item in entities)
{
if ((item.hasLeftArrowState && item.leftArrowState.state == EArrowState.Up)
|| ((item.hasRightArrowState && item.rightArrowState.state == EArrowState.Up)))
{
foreach (var entity in moveEntitys)
{
entity.ReplaceDirection( DirectionComponent.EDirection.stand );
entity.RemoveMoveSpeed();
}
}
else if (item.hasLeftArrowState && item.leftArrowState.state == EArrowState.Down) {
foreach (var entity in moveEntitys)
{
entity.ReplaceDirection(DirectionComponent.EDirection.left);
entity.ReplaceMoveSpeed(-3.0f);
}
}
else if (item.hasRightArrowState && item.rightArrowState.state == EArrowState.Down)
{
foreach (var entity in moveEntitys)
{
entity.ReplaceDirection(DirectionComponent.EDirection.right);
entity.ReplaceMoveSpeed(3.0f);
}
}
}
}
protected override bool Filter(InputEntity entity)
{
return (entity.hasLeftArrowState && entity.leftArrowState.state != EArrowState.None)
|| (entity.hasRightArrowState && entity.rightArrowState.state != EArrowState.None);
}
protected override ICollector<InputEntity> GetTrigger(IContext<InputEntity> context)
{
int[] anyOf = { InputComponentsLookup.LeftArrowState, InputComponentsLookup.RightArrowState};
return context.CreateCollector(InputMatcher.AnyOf(anyOf));
}
}
最后还是一样需要将新建的System加到GameSystems中。
public class GameSystems : Feature
{
public GameSystems(Contexts contexts) : base("Game Systems")
{
...
Add(new InputDeviceSystem(contexts));
Add(new CommandMoveSystem(contexts));
Add(new MovementSystem(contexts));
}
}
现在可以回到Unity运行一下,按下左右按键时小熊已经可以移动了。现在小案例已经基本完成了,只差最后一步在小熊移动时实时打印位置了,通过处理小熊移动的过程可以看出Entitas的核心思想是Component只需记录数据或者状态,专门的事交由专门的System处理,Entity只是为了承载Component并无其他作用。
总计: IExecuteSystem 在游戏运行后每帧执行一次,可以在这里处理类似Input等事件。