上篇已经实现了ECS框架下的IBeginDragHandler
、IDragHandler
、IEndDragHandler
这几个拖动事件,使得可以任意给ECS框架下的UI(2D entity)响应拖动事件。本篇分享下在前篇实现的功能的基础上再实现一个常用的摇杆控制角色移动的功能。
需要注意的一点,目前Unity最新的Entities包是无法纯ECS写代码的,还有很多GameObject和组件无法转成Entity和IComponentData,所以要体验纯ECS写代码,得下载那个很久没更新过的ProjectTiny包,它的Entities版本还停留在0.17阶段。
代码不多,直接上代码:
using Unity.Entities;
using Unity.Mathematics;
using Unity.Tiny.UI;
using wangtal.EventSystem;
using TinyUI;
namespace wangtal.Joystick {
public struct Joystick : IComponentData {
public readonly struct Settings : IComponentData {
// 可拖动的范围,可以大于背景的圆形
public readonly float Radius;
public Settings(float radius) {
Radius = radius;
}
}
public Entity Entity {get; private set;}
public Entity Knob {get; private set;}
public Entity Highlight {get; private set;}
// 拖动距离
public float Distance;
public float2 Direction;
public Joystick(Entity joystickEntity, Entity knob, Entity highlight) {
Entity = joystickEntity;
Knob = knob;
Highlight = highlight;
Distance = 0;
Direction = float2.zero;
}
}
public abstract class JoystickSystem : SystemBase, IBeginDragHandler, IDragHandler, IEndDragHandler
{
// 上上篇实现的ECS下的UI分辨率适配系统-用来实现分辨率变化后,UI大小跟着变
private CanvasScalerSystem canvasScalerSystem;
protected override void OnCreate()
{
base.OnCreate();
RequireSingletonForUpdate<Joystick>();
RequireSingletonForUpdate<Joystick.Settings>();
canvasScalerSystem = World.GetExistingSystem<CanvasScalerSystem>();
}
protected override void OnUpdate()
{
var joystick = GetSingleton<Joystick>();
var settings = GetSingleton<Joystick.Settings>();
var entity = joystick.Entity;
var stickRectTransform = GetComponent<RectTransform>(entity);
var highlight = joystick.Highlight;
var higtlightRectTransform = GetComponent<RectTransform>(highlight);
var knob = joystick.Knob;
var knobRectTransform = GetComponent<RectTransform>(knob);
if (this.BeginDrag(entity)) {
higtlightRectTransform.Hidden = false;
OnBeginDrag(joystick);
}
if (this.EndDrag(entity)) {
higtlightRectTransform.Hidden = true;
knobRectTransform.AnchoredPosition = float2.zero;
joystick.Direction = float2.zero;
joystick.Distance = 0;
OnEndDrag(joystick);
}
if (this.Drag(entity, out DragEventData dragEventData)) {
// Debug.Log("drag position:" + dragEventData.Position.ToString() + " pressPosition:" + dragEventData.PressPosition.ToString());
var pressPosition = dragEventData.PressPosition;
var position = dragEventData.Position;
// 减去的是joystick的中心位置,所以要根据它的pivot来计算,如果是(0,0)的话,就要加上它宽高的一半
var dstPos = position - (stickRectTransform.AnchoredPosition + stickRectTransform.SizeDelta * (-(stickRectTransform.Pivot - 0.5f)));
// Debug.Log("距离:" + math.length(dstPos));
var direction = math.normalize(dstPos);
float distance = 0;
float radius = canvasScalerSystem.WithScale(settings.Radius); // 这个半径要根据分辨率来,不然分辨率小了,可拖动距离就会变大
if (math.length(dstPos) <= radius) {
knobRectTransform.AnchoredPosition = dstPos;
distance = math.length(dstPos);
} else {
var maxPos = direction * radius;
knobRectTransform.AnchoredPosition = maxPos;
distance = math.length(maxPos);
}
joystick.Direction = direction;
joystick.Distance = distance;
OnDrag(joystick);
}
SetComponent<RectTransform>(highlight, higtlightRectTransform);
SetComponent<RectTransform>(knob, knobRectTransform);
SetSingleton<Joystick>(joystick);
}
protected abstract void OnBeginDrag(in Joystick joystick);
protected abstract void OnDrag(in Joystick joystick);
protected abstract void OnEndDrag(in Joystick joystick);
}
}
主要功能就是上面那些,代码不多,还有一个Authoring脚本,用来挂在场景中的遥感GameObject上,并赋值好所需要的数据。
using UnityEngine;
using Unity.Entities;
// using Joystick;
namespace Authoring {
[DisallowMultipleComponent]
public class JoystickAuthoring : MonoBehaviour, IConvertGameObjectToEntity
{
public float radius;
public GameObject Joy;
public GameObject Knob;
public GameObject Highlight;
public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
{
var joy = conversionSystem.GetPrimaryEntity(Joy);
var knob = conversionSystem.GetPrimaryEntity(Knob);
var highlight = conversionSystem.GetPrimaryEntity(Highlight);
dstManager.AddComponentData<Joystick.Joystick>(entity, new Joystick.Joystick(joy, knob, highlight));
dstManager.AddComponentData<Joystick.Joystick.Settings>(entity, new Joystick.Joystick.Settings(radius));
}
}
}
这是Unity编辑器里的节点设置。场景会在打包后,转成纯ECS的Entity和ComponentData的,在编辑器里运行无法看到效果,必须打包后才能正确执行DOTSruntime代码。
具体用法的示例代码:
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using wangtal.Joystick;
using wangtal.EventSystem;
namespace Tiny3D {
[UpdateInGroup(typeof(InputSystemGroup))]
[UpdateAfter(typeof(EventSystem))]
public class HeroJoystickSystem : JoystickSystem
{
protected override void OnBeginDrag(in Joystick.Joystick joystick)
{
// 这里可以设置遥感到点击的位置,这样就不会一点击,角色就移动了,而是在拖动后才移动
}
protected override void OnDrag(in Joystick.Joystick joystick)
{
var direction = joystick.Direction;
// Unity.Tiny.Debug.Log("onDrag direction:" + direction);
Entities.WithAll<Player>().ForEach((ref Moveable moveable) =>
{
if (math.all(direction == float2.zero)) {
return;
}
moveable.moveDirection = new float3(direction.x, 0, direction.y);
}).Run();
}
protected override void OnEndDrag(in Joystick.Joystick joystick)
{
// Unity.Tiny.Debug.Log("endDrag direction:" + joystick.Direction);
Entities.WithAll<Player>().ForEach((ref Moveable moveable) =>
{
moveable.moveDirection = float3.zero;
}).Run();
}
}
}
打包WASM后到浏览器上的运行效果图: