我一直致力于开发一个简单的 2D 游戏引擎,我偶然发现了一个小问题,我的层次关系几乎可以工作(只要父级没有旋转,它就可以工作),只要我将旋转添加到然而,父对象中的每个子对象都以不保持对象内聚力的方式旋转/移动,我认为问题出在我的变换组件中。
using OpenTK.Mathematics;
using System.Collections;
namespace Aurora;
/// <summary>
/// Represents the transformation of a game object in the scene.
/// </summary>
public class Transform : Component, IEnumerable<Transform>
{
#region Fields
[SerializeField] private readonly List<Transform> m_Children = new();
[SerializeField] private Vector2 m_LocalPosition = Vector2.Zero;
[SerializeField] private float m_LocalRotation;
[SerializeField] private Vector2 m_LocalScale = Vector2.One;
[SerializeField] private Transform m_Parent;
#endregion
#region Properties
/// <summary>
/// Gets the number of child transforms attached to this transform.
/// </summary>
public int ChildCount
{
get { return m_Children.Count; }
}
/// <summary>
/// Gets the child transform at the specified index.
/// </summary>
/// <param name="childIndex">The index of the child transform to retrieve.</param>
/// <returns>The child transform at the specified index.</returns>
public Transform this[int childIndex]
{
get { return m_Children[childIndex]; }
}
/// <summary>
/// Gets or sets the local position of the transform.
/// </summary>
public Vector2 LocalPosition
{
get { return m_LocalPosition; }
set { m_LocalPosition = value; }
}
/// <summary>
/// Gets or sets the local rotation of the transform in degrees.
/// </summary>
public float LocalRotation
{
get { return MathHelper.RadiansToDegrees(m_LocalRotation); }
set { m_LocalRotation = MathHelper.DegreesToRadians(value); }
}
/// <summary>
/// Gets or sets the local scale of the transform.
/// </summary>
public Vector2 LocalScale
{
get { return m_LocalScale; }
set { m_LocalScale = value; }
}
/// <summary>
/// Gets the parent transform of this transform.
/// </summary>
public Transform Parent
{
get { return m_Parent; }
}
/// <summary>
/// Gets or sets the world position of the transform.
/// </summary>
/// <summary>
/// Gets or sets the world position of the transform.
/// </summary>
public Vector2 WorldPosition
{
get
{
if ( m_Parent != null )
{
Vector2 parentPosition = m_Parent.WorldPosition;
float parentRotation = m_Parent.WorldRotation;
// Rotate the local position by the parent's rotation
Vector2 rotatedPosition = LocalPosition;
float angle = MathHelper.DegreesToRadians ( parentRotation );
float cos = (float) Math.Cos ( angle );
float sin = (float) Math.Sin ( angle );
rotatedPosition = new Vector2 (
cos * rotatedPosition.X - sin * rotatedPosition.Y,
sin * rotatedPosition.X + cos * rotatedPosition.Y
);
// Add parent's position to the rotated local position
return parentPosition + rotatedPosition;
}
return LocalPosition;
}
set
{
if ( m_Parent != null )
{
Vector2 parentPosition = m_Parent.WorldPosition;
float parentRotation = m_Parent.WorldRotation;
// Calculate the new local position by taking into account parent's rotation
Vector2 delta = value - parentPosition;
float angle = MathHelper.DegreesToRadians ( -parentRotation );
float cos = (float) Math.Cos ( angle );
float sin = (float) Math.Sin ( angle );
LocalPosition = new Vector2 (
cos * delta.X - sin * delta.Y,
sin * delta.X + cos * delta.Y
);
}
else
{
LocalPosition = value;
}
}
}
/// <summary>
/// Gets or sets the world rotation of the transform in degrees.
/// </summary>
public float WorldRotation
{
get { return LocalRotation - (m_Parent?.WorldRotation ?? 0); }
set { LocalRotation = value + (m_Parent?.WorldRotation ?? 0); }
}
/// <summary>
/// Gets or sets the world scale of the transform.
/// </summary>
public Vector2 WorldScale
{
get { return m_LocalScale * (m_Parent?.WorldScale ?? Vector2.One); }
set
{
m_LocalScale = value / (m_Parent?.WorldScale ?? Vector2.One);
if (float.IsNaN(m_LocalScale.X))
{
m_LocalScale.X = 0;
}
if (float.IsNaN(m_LocalScale.Y))
{
m_LocalScale.Y = 0;
}
}
}
/// <summary>
/// Gets the list of child transforms attached to this transform.
/// </summary>
internal List<Transform> Children
{
get { return m_Children; }
}
#endregion
#region Methods
/// <summary>
/// Clears all child transforms attached to this transform.
/// </summary>
public void ClearChildren()
{
for (int i = m_Children.Count - 1; i >= 0; i--)
{
Transform child = m_Children[i];
child.GameObject.Destroy();
m_Children.RemoveAt(i);
}
}
/// <summary>
/// Returns an enumerator that iterates through the child transforms.
/// </summary>
/// <returns>An enumerator for the child transforms.</returns>
public IEnumerator<Transform> GetEnumerator()
{
return m_Children.GetEnumerator();
}
/// <summary>
/// Sets the parent of this transform.
/// </summary>
/// <param name="parent">The new parent transform.</param>
/// <param name="worldSpaceStays">Whether to maintain world space position, rotation, and scale.</param>
public void SetParent(Transform parent, bool worldSpaceStays)
{
if (worldSpaceStays)
{
Vector2 position = WorldPosition;
float rotation = WorldRotation;
Vector2 scale = WorldScale;
m_Parent = parent;
parent?.m_Children.Add(this);
if (parent != null)
{
if (Scene.IsRegistered(GameObject))
{
Scene.UnRegister(GameObject);
}
}
else
{
if (!Scene.IsRegistered(GameObject))
{
Scene.Register(GameObject);
}
}
WorldPosition = position;
WorldRotation = rotation;
WorldScale = scale;
}
else
{
m_Parent = parent;
parent?.m_Children.Add(this);
if (parent != null)
{
if (Scene.IsRegistered(GameObject))
{
Scene.UnRegister(GameObject);
}
}
else
{
if (!Scene.IsRegistered(GameObject))
{
Scene.Register(GameObject);
}
}
}
}
/// <summary>
/// Returns an enumerator that iterates through the child transforms.
/// </summary>
/// <returns>An enumerator for the child transforms.</returns>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
}
您应该将 Transform 转换为矩阵。例如 System.Numerics.Matrix3x2,但还有很多替代方案。矩阵具有允许乘法将它们组合起来的巨大优势。因此提供了一种组合多个变换的简单方法。所以你应该有这样的属性:
public Matrix3x2 Local =>
Matrix3x2.CreateScale(m_LocalScale) *
Matrix3x2.CreateRotation(m_LocalRotation)*
Matrix3x2.CreateTranslation(m_LocalPosition );
public Matrix3x2 Global => (m_Parent?.Global ?? Matrix3x2.Identity) * Local;
请注意,我可能搞乱了乘法顺序。我不记得 system.Numerics 是否遵循常规顺序。如果不起作用,请尝试颠倒顺序。
这也应该避免计算世界位置等的所有数学。您应该能够直接使用矩阵来渲染事物。如果你想获得世界位置,只需将零向量与全局矩阵相乘即可。
我还会考虑添加一个中心向量来指定要缩放的点。还有一个 Angle 结构,以避免对弧度/度数产生任何混淆。