Unity中,做一个通过转动来移动的方块
2022-03-11 本文已影响0人
全新的饭
需求
一个长宽高为任意值的方块,可控制其向前、后、左、右通过翻转的方式移动。
示意图如下:
旋转移动的方块.gif
思路
image.png接受移动方向的输入
提供一个Rotate方法,要求外界传入要移动的方向(前、后、左、右)。
因为单次移动是一个过程而非瞬时,所以使用一个队列_rotateDirs将外界输入存储起来。
该队列中的元素是移动方向,每执行完一次单次移动后,尝试从前述队列中获取新的移动方向:
- 若获取不到,说明已执行完所有的移动指令,则停止
- 获取到新的移动方向后,向该方向执行一次单次移动。
单次移动
是一个过程:外界传入该过程所需时间rotateTime。
根据移动方向,准备所需数据
- 旋转时的参考点:假设当前位置是(x, y, z)
- 向前:(x, y-h/2, z+w/2)
向后:(x, y-h/2, z-w/2)
向左:(x-l/2, y-h/2, z)
向右:(x+l/2, y-h/2, z)
- 旋转轴
- 旋转角度
每次移动完成后,需确保transform的位置和旋转值变为准确的目标值
位置:假设变化前是(x, y, z),则变化后应是
- 向前:(x, w/2, z + (w/2 + h/2))
向后:(x, w/2, z - (w/2 + h/2))
向左:(x -(l/2 + h/2), l/2, z)
向右:(x +(l/2 + h/2), l/2, z)
旋转:直接四舍五入取整
更新当前的长宽高的值:长宽高的值用于计算旋转时参考点的位置、旋转后的新位置。
每次旋转后 l w h 的变化
- 前:w h 互换
后:w h 互换
左:l h 互换
右:l h 互换
代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RotatableCuboid : MonoBehaviour
{
[SerializeField]
private float _l = 3;
[SerializeField]
private float _w = 2;
[SerializeField]
private float _h = 5;
private IEnumerator _rotateCoroutine;
private Queue<RotateDir> _rotateDirs;
[SerializeField]
private float _rotateTime = 0.5f;
void Start()
{
Init();
}
void Update()
{
if (Input.GetKeyDown(KeyCode.W))
{
Rotate(RotateDir.Forward);
}
else if (Input.GetKeyDown(KeyCode.S))
{
Rotate(RotateDir.Backward);
}
else if (Input.GetKeyDown(KeyCode.A))
{
Rotate(RotateDir.Left);
}
else if (Input.GetKeyDown(KeyCode.D))
{
Rotate(RotateDir.Right);
}
}
void OnDestroy()
{
Destroy();
}
private void Init()
{
_rotateCoroutine = null;
_rotateDirs = new Queue<RotateDir>();
}
private void Destroy()
{
if (_rotateCoroutine != null)
{
StopCoroutine(_rotateCoroutine);
_rotateCoroutine = null;
}
_rotateDirs.Clear();
_rotateDirs = null;
}
private void Rotate(RotateDir dir)
{
_rotateDirs.Enqueue(dir);
if (_rotateCoroutine == null)
{
_rotateCoroutine = RotateCoroutine(_rotateTime);
StartCoroutine(_rotateCoroutine);
}
}
private Vector3 GetNextPos(RotateDir dir)
{
Vector3 pos = transform.position;
switch (dir)
{
case RotateDir.Forward:
pos = new Vector3(pos.x, _w / 2, pos.z + (_w / 2 + _h / 2));
break;
case RotateDir.Backward:
pos = new Vector3(pos.x, _w / 2, pos.z - (_w / 2 + _h / 2));
break;
case RotateDir.Left:
pos = new Vector3(pos.x - (_l / 2 + _h / 2), _l / 2, pos.z);
break;
case RotateDir.Right:
pos = new Vector3(pos.x + (_l / 2 + _h / 2), _l / 2, pos.z);
break;
default:
break;
}
return pos;
}
private void ExchangeValue<T>(ref T a, ref T b)
{
T temp;
temp = a;
a = b;
b = temp;
}
private IEnumerator RotateCoroutine(float rotateTime)
{
while (_rotateDirs.Count > 0)
{
var dir = _rotateDirs.Dequeue();
Vector3 newPos = GetNextPos(dir);
Vector3 point = Vector3.zero;
Vector3 axis = Vector3.right;
float angle = 0;
switch (dir)
{
case RotateDir.Forward:
point = new Vector3(transform.position.x, transform.position.y - _h / 2, transform.position.z + _w / 2);
axis = Vector3.right;
angle = 90;
break;
case RotateDir.Backward:
point = new Vector3(transform.position.x, transform.position.y - _h / 2, transform.position.z - _w / 2);
axis = Vector3.right;
angle = -90;
break;
case RotateDir.Left:
point = new Vector3(transform.position.x - _l / 2, transform.position.y - _h / 2, transform.position.z);
axis = Vector3.forward;
angle = 90;
break;
case RotateDir.Right:
point = new Vector3(transform.position.x + _l / 2, transform.position.y - _h / 2, transform.position.z);
axis = Vector3.forward;
angle = -90;
break;
default:
break;
}
int targetTimes = (int)(rotateTime / Time.deltaTime);
int times = 0;
float perAngle = angle / targetTimes;
while (times < targetTimes)
{
transform.RotateAround(point, axis, perAngle);
times++;
yield return null;
}
transform.position = newPos;
transform.eulerAngles = new Vector3(Round(transform.eulerAngles.x), Round(transform.eulerAngles.y), Round(transform.eulerAngles.z));
switch (dir)
{
case RotateDir.Forward:
ExchangeValue(ref _w, ref _h);
break;
case RotateDir.Backward:
ExchangeValue(ref _w, ref _h);
break;
case RotateDir.Left:
ExchangeValue(ref _l, ref _h);
break;
case RotateDir.Right:
ExchangeValue(ref _l, ref _h);
break;
default:
break;
}
}
if (_rotateCoroutine != null)
{
StopCoroutine(_rotateCoroutine);
_rotateCoroutine = null;
}
}
private int Round(float value)
{
int v = ((int)(value * 10)) % 10;
int ret = v >= 5 ? (int)value + 1 : (int)value;
return ret;
}
}
public enum RotateDir
{
None,
Forward,
Backward,
Left,
Right
}