Quaternion.Inverse() returns the inverse of a given quaternion. In practical terms, it gives you the rotation that exactly "undoes" the original rotation. When you multiply a quaternion by its inverse, you get the identity rotation (i.e., no rotation).
Here's a simple example:
// Suppose we have a quaternion representing a rotation:
Quaternion originalRotation = transform.rotation;
// Get the inverse of that rotation:
Quaternion inverseRotation = Quaternion.Inverse(originalRotation);
// Applying the inverse rotation will cancel out the original rotation:
transform.rotation = originalRotation * inverseRotation; // This results in no rotation (identity)

void Start()
{
y_axis_part_rotation_goal = y_axis_part.rotation; // 이상한 초기값으로 가버리지 않도록 현재 회전으로 초기화
hand_y_offset = -tongs_part.localPosition.y * 110; // 원 교점 구할 때 쓰이는 손 길이
r1 = (hand_part.position - x_axis_part_2.position).magnitude;
r2 = (x_axis_part_2.position - x_axis_part.position).magnitude;
Init_Arm();
Set_Hand_Tongs_Offset();
Fold_Arm();
}
private void Init_Arm()
{
y_axis_part.localRotation = Quaternion.Euler(-90, 0, 0);
x_axis_part.localRotation = Quaternion.Euler(-90, 0, 0);
x_axis_part_2.localRotation = Quaternion.Euler(90, 0, 0);
hand_part.localRotation = Quaternion.Euler(90, 0, 0);
}
private void Set_Hand_Tongs_Offset()
{
Vector3 handPosXZ = hand_part.position;
Vector3 tongsPosXZ = tongs_part.position;
handPosXZ.y = 0;
tongsPosXZ.y = 0;
hand_tongs_distance = Vector3.Distance(handPosXZ, tongsPosXZ);
Vector3 handToXAxis = hand_part.position - x_axis_part.position;
Vector3 tongsToHand = tongs_part.position - hand_part.position;
handToXAxis.y = 0;
tongsToHand.y = 0;
hand_tongs_angle = Vector3.Angle(handToXAxis, tongsToHand);
bonus_h = hand_tongs_distance * Mathf.Cos(hand_tongs_angle * Mathf.Deg2Rad);
bonus_w = hand_tongs_distance * Mathf.Sin(hand_tongs_angle * Mathf.Deg2Rad);
}
private void Fold_Arm()
{
x_axis_part_rotation_goal = Quaternion.Euler(-90, 0, 0);
x_axis_part_2_rotation_goal = Quaternion.Euler(0, 0, 0);
hand_part_rotation_goal = Quaternion.Euler(180, 0, 0);
}
void Update()
{
if (target_to_reach == null) return;
Rotate_To_Goal_Continously();
}
private void Rotate_To_Goal_Continously()
{
y_axis_part.rotation = Quaternion.RotateTowards(y_axis_part.rotation, y_axis_part_rotation_goal, move_time); // local이 아니라 월드 좌표계 기준 회전 해야 함
x_axis_part.localRotation = Quaternion.RotateTowards(x_axis_part.localRotation, x_axis_part_rotation_goal, move_time);
x_axis_part_2.localRotation = Quaternion.RotateTowards(x_axis_part_2.localRotation, x_axis_part_2_rotation_goal, move_time);
hand_part.localRotation = Quaternion.RotateTowards(hand_part.localRotation, hand_part_rotation_goal, move_time);
}
public void SetTarget(Transform target, float time)
{
target_to_reach = target;
move_time = time;
Set_Angle_Offset();
Set_Y_Rotation();
Set_X_Axis_Angles();
}
private void Set_Angle_Offset()
{
Vector3 a = target_to_reach.position - x_axis_part.position;
a.y = 0;
float h = a.magnitude;
angle_offset = Mathf.Atan2(bonus_w, h + bonus_h) * Mathf.Rad2Deg;
}
private void Set_Y_Rotation()
{
Vector3 direction = target_to_reach.position - y_axis_part.position;
direction.y = 0; // 불연속적 계산 방지
float angle = Vector3.SignedAngle(Vector3.forward, direction, Vector3.up);
y_axis_part_rotation_goal = Quaternion.Euler(-90, angle - angle_offset, 0);
}
private void Set_X_Axis_Angles()
{
(float angle2, float angle3) = Find_Angle_Set();
if (float.IsNaN(angle2) || float.IsNaN(angle3)) return;
x_axis_part_rotation_goal = Quaternion.Euler(angle2, 0, 0);
x_axis_part_2_rotation_goal = Quaternion.Euler(angle3, 0, 0);
float angle1 = 90 - angle2 - angle3;
hand_part_rotation_goal = Quaternion.Euler(angle1, 0, 0);
}

public void SetTarget(Vector3 point, float time)
{
move_time = time;
Sequence seq = DOTween.Sequence();
seq.AppendCallback(() => Set_Course_Dot(point + new Vector3(0, 0.5f, 0), move_time));
seq.AppendInterval(move_time + 0.2f);
seq.AppendCallback(() =>
{
Set_Course_Dot(point, move_time / 2);
});
}
Transform을 이용하던 기존 코드를 Vector3로 변경하여 좀 더 유연하게 타겟 설정하게 바꿈.
DoTween의 Sequnece로 메서드 순차 진행 제어.
private void Fold_Arm()
{
x_axis_part.DOLocalRotate(new Vector3(-90, 0, 0), 1f);
x_axis_part_2.DOLocalRotate(Vector3.zero, 1f);
hand_part.DOLocalRotate(new Vector3(180, 0, 0), 1f);
}
코드가 지저분해지고 시간도 정확하게 맞추기 힘든 RotateToward 대신 Dotween의 DOLocalRotate 사용.
이동할 거리가 달라지면 움직이는 속도도 달라져서 부자연스러워 보일수도 있지만, 로봇 팔이 느리게 움직여서 답답해지면 더 큰 문제.
public void Open_Tongs()
{
foreach (Transform tongs in tongs_arr)
tongs.DOLocalRotate(tongs.transform.localRotation.eulerAngles - new Vector3(8, 0, 0), 1f);
}
public void Grip_Tongs()
{
foreach (Transform tongs in tongs_arr)
tongs.DOLocalRotate(tongs.transform.localRotation.eulerAngles + new Vector3(8, 0, 0), 1f);
}
tongs_part.localPosition.y을 사용해서 그랬던 것. hand_part와 tongs_part의 y 좌표 차이를 계산하는 것으로 변경. /// <summary>
/// 로봇 팔을 이용해 기물을 특정 위치로 움직인다
/// </summary>
public void MovePieceToPos(GameObject piece, Vector3 moveToPos, float time)
{
Vector3 piecePos = piece.transform.position;
float pieceHeight = piece.transform.GetComponent<MeshRenderer>().bounds.size.y;
Quaternion pieceRotation = piece.transform.rotation;
Sequence seq = DOTween.Sequence();
seq.AppendCallback(() => Set_Course_Point(piecePos + new Vector3(0, pieceHeight + 0.5f, 0), time)); // 기물 머리 위까지 간다
seq.AppendInterval(time + 0.2f);
// 기물을 집는다
seq.AppendCallback(() =>
{
Set_Course_Point(piecePos + new Vector3(0, pieceHeight, 0), time / 2);
Grip_Tongs(time / 2);
});
seq.AppendInterval(time / 2 + 0.1f);
// 기물을 원하는 위치 위까지 옮긴다
seq.AppendCallback(() =>
{
piece.transform.SetParent(tongs_part.transform);
Set_Course_Point(moveToPos + new Vector3(0, pieceHeight + 0.5f, 0), time);
});
seq.AppendInterval(time + 0.2f);
// 기물을 내려놓는다
seq.AppendCallback(() =>
{
Set_Course_Point(moveToPos + new Vector3(0, pieceHeight, 0), time / 2);
hand_part_2.DOLocalRotate((hand_part_2.localRotation * pieceRotation * Quaternion.Inverse(piece.transform.rotation)).eulerAngles, time / 2);
});
seq.AppendInterval(time / 2 + 0.1f);
// 기물을 놓고 제자리로 돌아간다
seq.AppendCallback(() =>
{
piece.transform.SetParent(null);
Open_Tongs(time / 2);
Fold_Arm();
});
}
