自己做了个飞行模拟的小游戏,发布到手机上需要用到摇杆来控制。原先是通过一个Joystick类来实现,摇杆位置固定,详细请参考这里
但是后面策划说要做成王者荣耀那种,摇杆的位置会根据手指的位置有一定的活动区域,所以我重新写了一份逻辑,并且对代码进行一定的逻辑细分,效果如下:
首先,我这里将摇杆分为几个部分:
- 摇杆可触发区域,紫色区域,当手指放在该区域时,可以操控摇杆
- 摇杆中心位置可活动区域,黄色区域,绿色的圆形摇杆背景图的中心点可到达的区域
- 最后是白色的摇杆和绿色摇杆背景的组合
前两个都挂载了一个区域判断的类-TriggerAreaHandler
,主要用来判断屏幕点是否在该区域内,如图所示:
详细代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| using System.Collections; using System.Collections.Generic; using UnityEngine;
public class TriggerAreaHandler : MonoBehaviour { RectTransform[] _triggerArea = null; Camera _camera = null; Canvas _canvas = null; void InitTriggerArea() { if (_triggerArea != null) return; _triggerArea = GetComponentsInChildren<RectTransform>(); _canvas = GetComponentInParent<Canvas>(); _camera = _canvas.worldCamera; } public bool CheckScreenPosInArea(Vector2 screenPos) { InitTriggerArea(); for (int i=0;i< _triggerArea.Length;++i) { if (RectTransformUtility.RectangleContainsScreenPoint(_triggerArea[i], screenPos, _camera)) return true; } return false; } public Vector2 Clamp(Vector2 screenPos) { InitTriggerArea(); Vector2 clampPos = _camera.WorldToScreenPoint(_triggerArea[0].position); float len = float.MaxValue; InitTriggerArea(); for (int i = 0; i < _triggerArea.Length; ++i) { RectTransform rectTransform = _triggerArea[i]; if (RectTransformUtility.RectangleContainsScreenPoint(_triggerArea[i], screenPos, _camera)) { return screenPos; } else {
Vector2 rectPos = _camera.WorldToScreenPoint(rectTransform.position); Rect rect = RectTransformUtility.PixelAdjustRect(rectTransform, _canvas); Vector2 clampPos_temp = Clamp(screenPos, rectPos, new Vector2(rect.width/2, rect.height/2)); float distance = (clampPos_temp - screenPos).sqrMagnitude; if (distance < len) { len = distance; clampPos = clampPos_temp; } } } return clampPos; } Vector2 Clamp(Vector2 value,Vector2 center, Vector2 halfSize) { return new Vector2(Clamp(value.x, center.x - halfSize.x, center.x + halfSize.x), Clamp(value.y, center.y - halfSize.y, center.y + halfSize.y)); } float Clamp(float value, float min, float max) { value = value > min ? value : min; value = value < max ? value : max; return value; } }
|
第三部分的摇杆组合,主要是用来显示的,所以下面挂载了一个控制其显示的脚本-JoyStickView
,该脚本控制摇杆组合的位置显示,如图所示:
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
| using System.Collections; using System.Collections.Generic; using UnityEngine;
public class JoyStickView : MonoBehaviour { [SerializeField] RectTransform _joyStickCenter; [SerializeField] RectTransform _joyStickOutline; [SerializeField] float _joyStickAreaScale = 1;
bool _initSize = false; Canvas _canvas = null; Vector3 _joyStickOriginScreenPos0 = Vector3.zero; Vector3 _joyStickOriginScreenPos = Vector3.zero; float _joyStickRadius = 0; public float GetJoyStickRadius() { return _joyStickRadius; } public void CalculateAreaSize() { if (_initSize) return; _initSize = true; if(_canvas==null) _canvas = GetComponentInParent<Canvas>(); _joyStickOriginScreenPos0 = _canvas.worldCamera.WorldToScreenPoint(_joyStickOutline.position); _joyStickOriginScreenPos = _joyStickOriginScreenPos0; SetJoyStickOriginPos(_joyStickOriginScreenPos0); Rect rect = RectTransformUtility.PixelAdjustRect(_joyStickOutline, _canvas); _joyStickRadius = _joyStickAreaScale * rect.width / 2; } public Vector2 ResetJoyStickOrigionPos() { SetJoyStickOriginPos(_joyStickOriginScreenPos0); return _joyStickOriginScreenPos0; } public void SetJoyStickOriginPos(Vector3 screenPos) { _joyStickOriginScreenPos.x = screenPos.x; _joyStickOriginScreenPos.y = screenPos.y; _joyStickOutline.position = _canvas.worldCamera.ScreenToWorldPoint(_joyStickOriginScreenPos); } public void SetJoyStick(Vector2 dir) { Vector3 dir3 = dir.sqrMagnitude > 1 ? dir.normalized : dir; Vector3 joyStickScreenPos = dir3 * _joyStickRadius + _joyStickOriginScreenPos; _joyStickCenter.position = _canvas.worldCamera.ScreenToWorldPoint(joyStickScreenPos); } public bool InJoyStickArea(Vector2 screenPos) { float len = Vector2.Distance(screenPos, _joyStickOriginScreenPos); return len < _joyStickRadius; } }
|
最后是整个摇杆逻辑实现部分-JoyStickTrigger
,这个类里面记录了触发事件,以及对外的参数,如图所示:
同样的,通过属性器可以获取当前摇杆的状态参数,包括偏移方向以及强度,方向是360度,强度是[0-1]。详细代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
| using UnityEngine; using UnityEngine.EventSystems;
public class JoyStickTrigger : MonoBehaviour, IPointerDownHandler, IPointerUpHandler, IDragHandler { public static JoyStickTrigger instance; [SerializeField] TriggerAreaHandler _joyStickCenterArea; [SerializeField] TriggerAreaHandler _joyStickTriggerArea; [SerializeField] JoyStickView _joyStick; [SerializeField] float _sensitivity = 1; [SerializeField] float _triggerAreaScale = 1; public Vector2 MoveDir { get; private set; } public float MoveLen { get; private set; } public bool IsDraging { get { return _onJoyStickActive; } } public float Horizontal { get { return MoveDir.x * MoveLen; } } public float Vertical { get { return MoveDir.y * MoveLen; } }
Vector2 _originScreenPos = Vector2.zero; Vector2 _dragScreenPos = Vector2.zero; Vector2 _interpolateScreenPos = Vector2.zero; bool _onJoyStickActive = false; void Awake() { instance = this; } void OnEnable() { _joyStick.CalculateAreaSize(); } void OnDisable() { OnPointerUp(null); }
public void OnPointerDown(PointerEventData eventData) { if (!_joyStickTriggerArea.CheckScreenPosInArea(eventData.position)) return; _onJoyStickActive = true; _originScreenPos = _joyStickCenterArea.Clamp(eventData.position); _interpolateScreenPos = _dragScreenPos = eventData.position; _joyStick.SetJoyStickOriginPos(_originScreenPos); } public void OnDrag(PointerEventData eventData) { if (!_onJoyStickActive) return; _dragScreenPos = eventData.position; }
public void OnPointerUp(PointerEventData eventData) { _onJoyStickActive = false; _dragScreenPos = _joyStick.ResetJoyStickOrigionPos(); _interpolateScreenPos = _dragScreenPos; _originScreenPos = _dragScreenPos; }
private void Update() { _interpolateScreenPos = Vector2.Lerp(_interpolateScreenPos, _dragScreenPos, Time.deltaTime * _sensitivity); Vector2 dir = _interpolateScreenPos - _originScreenPos; dir /= _joyStick.GetJoyStickRadius(); dir = dir.sqrMagnitude > 1 ? dir.normalized : dir; MoveDir = dir; MoveLen = dir.magnitude; _joyStick.SetJoyStick(dir); } }
|