最初版本
input action设置
操作对象设置
camera
具体操作类
PlayerInputView类
using System.Collections;
using UnityEngine;
using UnityEngine.InputSystem;
using TouchPhase = UnityEngine.InputSystem.TouchPhase;
public class FingerScale : MonoBehaviour
{
private Vector3 _offset;
[SerializeField] private Camera mainCamera;
private Vector2 _firstTouchPosition;
private Vector2 _firstTouchStartPosition;
private Vector3 _firstTouchStartPositionToWorld;
private Vector2 _secondTouchPosition;
private Vector2 _secondTouchStartPosition;
public void OnFirstTouch(InputAction.CallbackContext context)
{
Debug.Log("OnFirstTouch"+context.phase);
_firstTouchStartPosition = context.ReadValue<Vector2>();
_firstTouchStartPositionToWorld = mainCamera.ScreenToWorldPoint(_firstTouchStartPosition);
}
public void OnTap(InputAction.CallbackContext context)
{
if (context.performed)
{
Vector3 worldPosition = new Vector3(_firstTouchStartPositionToWorld.x,
_firstTouchStartPositionToWorld.y, -20f);
StartCoroutine(MoveSmooth(worldPosition));
}
}
public void OnFirstPosition(InputAction.CallbackContext context)
{
_firstTouchPosition = context.ReadValue<Vector2>();
Debug.Log("onFirstPosition"+context.phase);
}
public void OnFirstMove(InputAction.CallbackContext context)
{
if (context.performed)
{
_offset = mainCamera.ScreenToWorldPoint(_firstTouchPosition)-_firstTouchStartPositionToWorld;
Debug.Log("_offset"+_offset);
mainCamera.transform.position -= _offset;
}
}
IEnumerator MoveSmooth(Vector3 targetPosition)
{
Vector3 start = mainCamera.transform.position;
Vector3 speed = Vector3.zero;
while (Vector3.Distance(start, targetPosition) > 0.001f)
{
mainCamera.transform.position = Vector3.SmoothDamp(start, targetPosition, ref speed, 0.3f);
start = mainCamera.transform.position;
yield return new WaitForEndOfFrame();
}
mainCamera.transform.position = targetPosition;
}
}
出现的问题相机位置变化后通过screenToWorldPoint获取的位置和上一帧的位置,坐标系完全不一样
如何解决:改变相机位置后再通过screentoworldpoint 修改上一帧的位置,实现丝滑拖拽
新解决:根本不需要更新lastframpos ,只需每次触发更新当前位置就可以了。因为改变相机位置后再通过screentoworldpoint 修改上一帧的位置,这一计算的结果在每一帧都一样,都是初始点击的位置。想想也确实如此。因为实现丝滑的拖拽就是要让屏幕触摸的位置相对与地图来说完全不变。在第一次接触时获取这个lastframepos的值就可以了。
ps;使用了近10多个小时才解决,不过收获颇丰。不过在input action中仍存在优化空间一个是状态机优化,一个是input action接收端接受过于冗余不利于扩展
稳定版本
更简洁的数学公式,更加清晰的输入控制,携程版本移动保证连贯
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.InputSystem;
namespace script.Runtime.View
{
public class PlayerInputView:strange.extensions.mediation.impl.View
{
private Vector2 _mousePosition;
private Vector2 _lastMousePosition;
private Vector3 _lastMouseWPosition;
private Vector3 _target;
private bool _isMoving;
private bool _isDown;
public Camera MainCamera { get; set; }
public InputActionAsset inputActionAsset;
public void AttackPreformed(InputAction.CallbackContext context)
{
Debug.Log("you shall die !>_<");
}
public void DefendPreformed(InputAction.CallbackContext context)
{
Debug.Log("you have no chance to Touch me:D");
}
public void OnPos(InputAction.CallbackContext context)
{
_mousePosition = context.ReadValue<Vector2>();
}
public void OnDown(InputAction.CallbackContext context)
{
_lastMousePosition = _mousePosition;
_lastMouseWPosition = MainCamera.ScreenToWorldPoint(_lastMousePosition);
_isDown = true;
}
public void OnUp(InputAction.CallbackContext context)
{
_isDown = false;
}
public void OnDelta(InputAction.CallbackContext context)
{
if(!_isDown) return;
var currentMousePosition = MainCamera.ScreenToWorldPoint(_mousePosition);
var offset = currentMousePosition-_lastMouseWPosition;
_target =MainCamera.transform.position - offset;
if (!_isMoving)
{
StartCoroutine(MoveSmooth());
}
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.F))
{
Debug.Log("Bye bye");
inputActionAsset.Disable();
}
}
IEnumerator MoveSmooth( )
{
var camPos = MainCamera.transform.position;
var speed = Vector3.zero;
while (Vector3.Distance(camPos, _target) > 0.01f)
{
camPos= Vector3.SmoothDamp(camPos, _target, ref speed, 0.5f);
yield return new WaitForEndOfFrame();
_isMoving = true;
MainCamera.transform.position = camPos;
}
MainCamera.transform.position = _target;
_isMoving = false;
}
}
}
依旧不是最佳版本,所有的逻辑耦合在一个类中,未来的自定义按键功能无法满足
解耦版本
大致思路
- 条件过滤
- 数据获取
- 信号传递
- 数学运算
- 携程移动
分层(使用的StrangeIOC框架)
view层负责过滤用户的输入是否满足移动的条件,当满足条件时发送信号给中介层mediator
mediator 层收到后负责发送移动信号
controller收到移动信号后,条用相应的service层完成移动
service负责具体的移动
signal负责定义信号量
具体代码
bootcontext
using script.Runtime.Controllers;
using script.Runtime.Services;
using script.Runtime.Services.IServices;
using script.Runtime.SignalDefine;
using script.Runtime.View;
using strange.extensions.command.api;
using strange.extensions.command.impl;
using strange.extensions.context.api;
using strange.extensions.context.impl;
using UnityEngine;
using UnityEngine.InputSystem.LowLevel;
namespace script.Runtime
{
public class BootContext:MVCSContext
{
public BootContext(MonoBehaviour view) : base(view)
{
}
public BootContext(MonoBehaviour view, ContextStartupFlags flags) : base(view, flags)
{
}
protected override void mapBindings()
{
injectionBinder.Bind<IInputActionsService>().To<InputActionsService>().ToSingleton();
injectionBinder.Bind<ICameraMove>().To<CameraMove>().ToSingleton();
mediationBinder.Bind<PlayerInputView>().To<PlayerInputMediator>();
commandBinder.Bind(ContextEvent.START).To<PlayerInputController>();
injectionBinder.Bind<PlayerInputActionSignal.InputActionReadSignal>().ToSingleton();
injectionBinder.Bind<PlayerInputActionSignal.CameraMoveSignal>().ToSingleton();
}
protected override void addCoreComponents()
{
base.addCoreComponents();
injectionBinder.Unbind<ICommandBinder>();
injectionBinder.Bind<ICommandBinder>().To<EventCommandBinder>().ToSingleton();
}
}
}
bootcontextRoot
using System;
using strange.extensions.context.impl;
namespace script.Runtime
{
public class BootContextRoot:ContextView
{
private void Awake()
{
GameManager.Mon = this;
context = new BootContext(this);
}
}
}
view
using System;
using System.Collections;
using strange.extensions.signal.impl;
using UnityEngine;
using UnityEngine.InputSystem;
namespace script.Runtime.View
{
public class PlayerInputView:strange.extensions.mediation.impl.View
{
private Vector2 _mousePosition;
private Vector2 _lastMousePosition;
private Vector3 _lastMouseWPosition;
private Vector3 _target;
private bool _isMoving;
private bool _isDown;
protected override void Start()
{
base.Start();
CameraMoveSignal = new Signal<Vector2, Vector2>();
}
public Signal<Vector2,Vector2> CameraMoveSignal { get; set; }
public Camera MainCamera { get; set; }
public InputActionAsset inputActionAsset;
public void AttackPreformed(InputAction.CallbackContext context)
{
Debug.Log("you shall die !>_<");
}
public void DefendPreformed(InputAction.CallbackContext context)
{
Debug.Log("you have no chance to Touch me:D");
}
public void OnPos(InputAction.CallbackContext context)
{
_mousePosition = context.ReadValue<Vector2>();
}
public void OnDown(InputAction.CallbackContext context)
{
_lastMousePosition = _mousePosition;
_lastMouseWPosition = MainCamera.ScreenToWorldPoint(_lastMousePosition);
_isDown = true;
}
public void OnUp(InputAction.CallbackContext context)
{
_isDown = false;
}
public void OnDelta(InputAction.CallbackContext context)
{
if(!_isDown) return;
CameraMoveSignal.Dispatch(_lastMousePosition,_mousePosition);
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.F))
{
Debug.Log("Bye bye");
inputActionAsset.Disable();
}
}
}
}
mediator
using script.Runtime.SignalDefine;
using strange.extensions.mediation.impl;
using strange.extensions.signal.impl;
using UnityEngine;
using UnityEngine.InputSystem;
namespace script.Runtime.View
{
public class PlayerInputMediator:EventMediator
{
[Inject] public PlayerInputView InputView{ get; set; }
[Inject]public PlayerInputActionSignal.InputActionReadSignal SignalInputActionRead { get; set; }
[Inject] public PlayerInputActionSignal.CameraMoveSignal CameraMoveSignal { get; set; }
public override void OnRegister()
{
SignalInputActionRead.AddListener(InputActionBind);
}
public override void OnRemove()
{
SignalInputActionRead.RemoveListener(InputActionBind);
InputView.CameraMoveSignal.RemoveListener(SendCameraMoveSignal);
}
private void SendCameraMoveSignal(Vector2 value, Vector2 previousValue)
{
Debug.Log("send camera move signal");
CameraMoveSignal.Dispatch(value, previousValue);
}
private void InputActionBind(InputActionAsset inputActionAsset)
{
inputActionAsset.Enable();
InputView.inputActionAsset = inputActionAsset;
InputView.MainCamera = Camera.main;
var attackAction = inputActionAsset.FindAction("Attack");
Debug.Log(attackAction.name);
attackAction.performed += InputView.AttackPreformed;
var defenseAction = inputActionAsset.FindAction("Defend");
Debug.Log(defenseAction.name);
defenseAction.performed += InputView.DefendPreformed;
var mp = inputActionAsset.FindAction("MP");
mp.performed += InputView.OnPos;
var UD = inputActionAsset.FindAction("UD");
UD.performed += InputView.OnDown;
UD.canceled += InputView.OnUp;
var OD = inputActionAsset.FindAction("OD");
OD.performed += InputView.OnDelta;
InputView.CameraMoveSignal.AddListener(SendCameraMoveSignal);
}
}
}
signal
using UnityEngine.InputSystem;
using strange.extensions.signal.impl;
using UnityEngine;
namespace script.Runtime.SignalDefine
{
public class PlayerInputActionSignal
{
public class InputActionReadSignal :Signal<InputActionAsset>
{
}
public class CameraMoveSignal : Signal<Vector2, Vector2>
{
}
}
}
service
using UnityEngine;
namespace script.Runtime.Services.IServices
{
public interface ICameraMove
{
public Camera MainCamera { get; set; }
public void CameraMoveHandle(Vector2 start, Vector2 target);
}
}
using System.Collections;
using script.Runtime.Services.IServices;
using UnityEngine;
namespace script.Runtime.Services
{
public class CameraMove:ICameraMove
{
public Camera MainCamera { get; set; }
private Vector3 _target;
private bool _isMoving ;
private Vector2 _start;
private Vector3 _startWorld;
public void CameraMoveHandle(Vector2 start, Vector2 target)
{
if (_start != start)
{
_startWorld = MainCamera.ScreenToWorldPoint(start);
_start = start;
}
var currentMousePosition = MainCamera.ScreenToWorldPoint(target);
var offset = currentMousePosition-_startWorld;
_target =MainCamera.transform.position - offset;
if (!_isMoving)
{
GameManager.Mon.StartCoroutine(MoveSmooth());
}
}
IEnumerator MoveSmooth( )
{
var camPos = MainCamera.transform.position;
var speed = Vector3.zero;
while (Vector3.Distance(camPos, _target) > 0.01f)
{
camPos= Vector3.SmoothDamp(camPos, _target, ref speed, 0.1f);
yield return new WaitForEndOfFrame();
_isMoving = true;
MainCamera.transform.position = camPos;
}
MainCamera.transform.position = _target;
_isMoving = false;
}
}
}
controller
using System.Collections;
using script.Runtime.Services.IServices;
using script.Runtime.SignalDefine;
using script.Runtime.View;
using strange.extensions.command.impl;
using strange.extensions.context.api;
using UnityEngine;
using UnityEngine.InputSystem;
namespace script.Runtime.Controllers
{
public class PlayerInputController:EventCommand
{
[Inject] public IInputActionsService InputActionService { get; set; }
[Inject] public PlayerInputActionSignal.InputActionReadSignal SignalInputActionRead { get; set; }
[Inject(ContextKeys.CONTEXT_VIEW)] public GameObject Boot { get; set; }
[Inject] public PlayerInputActionSignal.CameraMoveSignal CameraMoveSignal { get; set; }
[Inject] public ICameraMove CameraMove { get; set; }
public override void Execute()
{
Debug.Log("PlayerInputController::Execute");
CameraMove.MainCamera = Camera.main;
GameManager.Mon.StartCoroutine(InputActionCoroutine());
CameraMoveSignal.AddListener(CM);
}
private void CM(Vector2 value,Vector2 target)
{
Debug.Log("PlayerInputController::CM");
CameraMove.CameraMoveHandle(value, target);
}
private IEnumerator InputActionCoroutine()
{
var go = new GameObject()
{
name = "PlayerInputController"
};
go.transform.SetParent(Boot.transform);
go.AddComponent<PlayerInputView>();
var inputActionAsset = InputActionService.ReadActionAsset("InputActions/OriginInputAction");
yield return new WaitForEndOfFrame();
SignalInputActionRead.Dispatch(inputActionAsset);
}
}
}
满足自定义按键与业务逻辑扩展
view层无需要改变任何代码,只需修改Inputaction这个配置文件就能满足自定义按键。
而修改业务逻辑也不需要修改代码,只需要实现移动相机的接口,就能实现扩展。
避坑
所有的注入,涉及到变量的对象。如果不加singleton每次注入都会是全新的对象。
双指的拖拽
从单指到双指会出现一个平移
时序问题,应该在delta移动函数的第一帧再获取开始位置,因为inputaction各个inputmap不包含明确的先后顺序,应当在具体函数的第一帧获取到这个函数的具体的初始条件。
当手指停下后任然会保持移动
每帧都会有一个offset,手指停下后每帧都发送相同的移动信号,每帧都发送移动信号,就会不断的进行移动
修改方向
统一向量,向量在计算前需要统一否则错误将难以察觉