通过前面两篇的介绍,基本上完成了虚拟摇杆对物体移动旋转的控制。这篇主要是介绍如何采用ECS、JobSystem及Entitas框架实现虚拟摇杆对物体移动和旋转的控制。Entitas框架就已经实现了数据和行为的分离,那为什么还要结合Unity提供的Ecs框架呢,这里主要是要使用JoySystem实现多线程功能,提高性能。所以本文采用Entitas框架结合Unity的Ecs和JobSystem混用的方式。阅读本文之前,大家需要对Entitas、Ecs、JobSystem有个大致的掌握。本位不介绍这些框架的原理,只是大致介绍如何实现。具体原理请参阅其它文章。也可留言,一起沟通交流。
前期准备
Entitas
首先定义数据结构,由于目标移动和旋转是通过目标向量完成。所以定义个Vector2的属性即可,InputType,预留方便扩展,本文中该属性未使用。由于是检测输入,所以这里我们定义的标签为[Input]
[Input]
public class InputData : IComponent
{
public Vector2 targetPos;
public InputType inputType;
}
public enum InputType
{
Press,
Release
}
System系统定义类InputSystem继承ReactiveSystem,这样如果Entity的InputData数据发生变化就会监测到。在InputSystem初始化的方法中,初始化Ecs框架中EntityManager和GameObjectEntity,并为Ecs的GameObjectEntity赋予PostitionData。每帧execute去更新数据。
using System.Collections.Generic;
using Ecs;
using Entitas;
using Unity.Entities;
using UnityEngine;
namespace System
{
public class InputSystem : ReactiveSystem<InputEntity>,IInitializeSystem ,ICleanupSystem
{
private InputContext inputContext;
private EntityManager mEntityManager;
private Unity.Entities.Entity mEntity;
public InputSystem(Contexts context) : base(context.input)
{
inputContext = context.input;
}
protected override ICollector<InputEntity> GetTrigger(IContext<InputEntity> context)
{
return context.CreateCollector(InputMatcher.InputData);
}
protected override bool Filter(InputEntity entity)
{
return entity.hasInputData;
}
protected override void Execute(List<InputEntity> entities)
{
foreach (var e in entities)
{
var posComponentData = new PositionComponent()
{
targetDir = e.inputData.targetPos
};
mEntityManager.SetComponentData(mEntity, posComponentData);
}
}
public void Cleanup()
{
var entities = inputContext.GetEntities();
foreach (var e in entities)
{
e.Destroy();
}
}
public void Initialize()
{
mEntityManager = World.Active.GetOrCreateManager<EntityManager>();
var player = GameObject.FindWithTag("Player");
if (player)
{
var gameObjectComponent = player.GetComponent<GameObjectEntity>();
if (!gameObjectComponent)
{
gameObjectComponent = player.AddComponent<GameObjectEntity>();
}
mEntity = gameObjectComponent.Entity;
var posComponentData = new PositionComponent()
{
targetDir = Vector2.zero
};
mEntityManager.AddComponentData(mEntity, posComponentData);
}
}
}
}
Ecs实体定义
public struct PositionComponent : IComponentData
{
public Vector2 targetDir;
}
JobSystem 这里要使用到Transform组件,故这里需继承IJobParallelForTransform 。在Execute方法中根据数据去设置旋转和位移。
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Jobs;
namespace Ecs
{
public struct PositionUpdateJob : IJobParallelForTransform
{
public ComponentDataArray<PositionComponent> positionComponentArray;
public float deltaTime;
public void Execute(int index, TransformAccess transform)
{
var targetPos = positionComponentArray[index].targetDir;
var moveVector = new Vector3(targetPos.x,0f,targetPos.y);
transform.rotation = Quaternion.LookRotation(moveVector);
var moveSpeed = 0.8f;
transform.position = Vector3.Lerp(transform.position,moveVector * moveSpeed,math.smoothstep(0f,1f,deltaTime));
}
}
public class PositionJobSystem : JobComponentSystem
{
struct PositionGroup
{
public TransformAccessArray transformAccessArray;
public ComponentDataArray<PositionComponent> positionComponentArray;
}
[Inject] private PositionGroup positionGroup;
protected override JobHandle OnUpdate(JobHandle inputDeps)
{
var positionUpdateJob = new PositionUpdateJob()
{
positionComponentArray = positionGroup.positionComponentArray,
deltaTime = Time.deltaTime
};
return positionUpdateJob.Schedule(positionGroup.transformAccessArray, inputDeps);
}
}
}
在虚拟摇杆中,定义输入。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using System;
using Entity;
[RequireComponent(typeof(RectTransform))]
public class JoyStickHandle : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
public event Action OnBeginDragEvent = delegate { };
public event Action<Vector2> OnDragEvent = delegate(Vector2 vector2) { };
public event Action OnEndDragEvent = delegate { };
public RectTransform rtJoyStick;
private Transform tfHandle;
private float radius = 0f;
private float limit = 0f;
private Vector2 targetPos;
private InputContext inputContext;
public Vector2 TargetPos
{
get { return targetPos; }
private set { }
}
// Use this for initialization
void Start()
{
tfHandle = this.GetComponent<RectTransform>();
radius = (rtJoyStick.rect.xMax - rtJoyStick.rect.xMin) / 2;
limit = radius * radius;
inputContext = Contexts.sharedInstance.input;
}
public void OnBeginDrag(PointerEventData eventData)
{
if (OnBeginDragEvent != null)
{
OnBeginDragEvent();
}
}
public void OnDrag(PointerEventData eventData)
{
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(rtJoyStick, eventData.position,
eventData.pressEventCamera, out targetPos))
{
DealDragEvent();
}
}
private void DealDragEvent()
{
if (targetPos.sqrMagnitude > limit)
{
targetPos = targetPos.normalized * radius;
}
tfHandle.localPosition = targetPos;
if (OnDragEvent != null)
{
OnDragEvent(targetPos);
}
inputContext.CreateEntity().AddInputData(this.targetPos, InputType.Press);
}
public void OnEndDrag(PointerEventData eventData)
{
tfHandle.localPosition = Vector3.zero;
targetPos = Vector2.zero;
if (OnEndDragEvent != null)
{
OnEndDragEvent();
}
inputContext.CreateEntity().AddInputData(Vector2.zero, InputType.Release);
}
}
创建System
using System;
using UnityEngine;
using Entitas;
public class GameMain : MonoBehaviour
{
private Systems systems;
private void Start()
{
var contexts = Contexts.sharedInstance;
systems = new Feature("RootSystem");
systems.Add(new InputFeature(contexts));
systems.Initialize();
}
private void Update()
{
systems.Execute();
systems.Cleanup();
}
private void OnDestroy()
{
systems.TearDown();
}
}
namespace System
{
public class InputFeature : Feature
{
public InputFeature(Contexts contexts) : base("InputSystem")
{
Add(new InputSystem(contexts));
}
}
}