Ý tưởng
Tôi sẽ giải quyết vấn đề này với hệ thống phân cấp sprite bằng cách sử dụng một biến thể của mẫu thiết kế tổng hợp . Điều này có nghĩa là mỗi sprite lưu trữ một danh sách các sprite con được gắn vào nó để mọi sửa đổi cho cha mẹ được tự động phản ánh trong chúng (bao gồm dịch, xoay và chia tỷ lệ).
Trong công cụ của tôi, tôi đã thực hiện nó như thế này:
- Mỗi
Sprite
cửa hàng một List<Sprite> Children
và cung cấp một phương pháp để thêm trẻ em mới.
- Mỗi người
Sprite
biết cách tính một Matrix LocalTransform
cái được định nghĩa liên quan đến cha mẹ.
- Gọi
Draw
cho một Sprite
cũng gọi nó trên tất cả các con của nó.
- Trẻ em nhân lên sự biến đổi cục bộ của chúng bằng sự biến đổi toàn cầu của cha mẹ chúng . Kết quả là những gì bạn sử dụng khi kết xuất.
Với điều này, bạn sẽ có thể làm những gì bạn yêu cầu mà không cần sửa đổi gì khác về mã của bạn. Đây là một ví dụ:
Sprite tank = new Sprite(tankTexture);
tank.Children.Add(new Sprite(turretTexture) {Position = new Vector2(26, 16) });
spriteBatch.Begin();
tank.Draw(spriteBatch);
spriteBatch.End();
Thực hiện
Để bắt đầu, tôi sẽ chỉ tham gia vào một dự án mẫu với kỹ thuật này được triển khai, trong trường hợp bạn chỉ muốn nhìn vào mã và tìm ra nó:

Lưu ý: Tôi đã chọn cho sự rõ ràng thay vì hiệu suất ở đây. Trong một triển khai nghiêm túc, có rất nhiều tối ưu hóa có thể được thực hiện, hầu hết trong số đó liên quan đến các biến đổi bộ đệm và chỉ tính toán lại khi cần thiết (ví dụ, lưu trữ cả biến đổi cục bộ và biến đổi toàn cục ở mỗi sprite và chỉ tính lại chúng khi sprite hoặc một trong những thay đổi tổ tiên của nó). Ngoài ra, các phiên bản hoạt động ma trận và vectơ của XNA lấy giá trị theo tham chiếu nhanh hơn một chút so với các phiên bản tôi đã sử dụng ở đây.
Nhưng tôi sẽ mô tả quá trình chi tiết hơn dưới đây, vì vậy hãy đọc tiếp để biết thêm thông tin.
Bước 1 - Thực hiện một vài điều chỉnh cho lớp Sprite
Giả sử bạn đã có một Sprite
lớp tại chỗ (và bạn nên) thì bạn sẽ cần phải thực hiện một vài sửa đổi cho nó. Cụ thể, bạn sẽ cần thêm danh sách các họa tiết con, ma trận biến đổi cục bộ và một cách để truyền bá các biến đổi xuống hệ thống phân cấp sprite. Tôi thấy cách dễ nhất để làm điều đó chỉ là vượt qua chúng như một tham số khi vẽ. Thí dụ:
public class Sprite
{
public Vector2 Position { get; set; }
public float Rotation { get; set; }
public Vector2 Scale { get; set; }
public Texture2D Texture { get; set; }
public List<Sprite> Children { get; }
public Matrix LocalTransform { get; }
public void Draw(SpriteBatch spriteBatch, Matrix parentTransform);
}
Bước 2 - Tính toán ma trận LocalTransform
Các LocalTransform
ma trận chỉ là một ma trận trên thế giới thường xuyên được xây dựng từ vị trí, luân chuyển và quy mô giá trị của sprite. Đối với nguồn gốc, tôi giả sử trung tâm của sprite:
public Matrix LocalTransform
{
get
{
// Transform = -Origin * Scale * Rotation * Translation
return Matrix.CreateTranslation(-Texture.Width/2f, -Texture.Height/2f, 0f) *
Matrix.CreateScale(Scale.X, Scale.Y, 1f) *
Matrix.CreateRotationZ(Rotation) *
Matrix.CreateTranslation(Position.X, Position.Y, 0f);
}
}
Bước 3 - Biết cách chuyển Ma trận sang SpriteBatch
Một vấn đề với SpriteBatch
lớp là Draw
phương thức của nó không biết cách lấy ma trận thế giới trực tiếp. Đây là một phương pháp trợ giúp để khắc phục vấn đề này:
public static void DecomposeMatrix(ref Matrix matrix, out Vector2 position, out float rotation, out Vector2 scale)
{
Vector3 position3, scale3;
Quaternion rotationQ;
matrix.Decompose(out scale3, out rotationQ, out position3);
Vector2 direction = Vector2.Transform(Vector2.UnitX, rotationQ);
rotation = (float) Math.Atan2(direction.Y, direction.X);
position = new Vector2(position3.X, position3.Y);
scale = new Vector2(scale3.X, scale3.Y);
}
Bước 4 - Kết xuất Sprite
Lưu ý: Draw
Phương thức lấy biến đổi toàn cục của cha mẹ làm tham số. Có nhiều cách khác để tuyên truyền thông tin này, nhưng tôi thấy cách này dễ sử dụng.
- Tính toán biến đổi toàn cầu bằng cách nhân biến đổi cục bộ với biến đổi toàn cầu của cha mẹ.
- Điều chỉnh biến đổi toàn cục thành
SpriteBatch
và hiển thị sprite hiện tại.
- Kết xuất tất cả trẻ em chuyển cho chúng biến đổi toàn cầu hiện tại làm tham số.
Dịch mã đó thành mã bạn sẽ nhận được một cái gì đó như:
public void Draw(SpriteBatch spriteBatch, Matrix parentTransform)
{
// Calculate global transform
Matrix globalTransform = LocalTransform * parentTransform;
// Get values from GlobalTransform for SpriteBatch and render sprite
Vector2 position, scale;
float rotation;
DecomposeMatrix(ref globalTransform, out position, out rotation, out scale);
spriteBatch.Draw(Texture, position, null, Color.White, rotation, Vector2.Zero, scale, SpriteEffects.None, 0.0f);
// Draw Children
Children.ForEach(c => c.Draw(spriteBatch, globalTransform));
}
Khi vẽ sprite gốc không có biến đổi cha, vì vậy bạn vượt qua nó Matrix.Identity
. Bạn có thể tạo quá tải để trợ giúp trong trường hợp này:
public void Draw(SpriteBatch spriteBatch) { Draw(spriteBatch, Matrix.Identity); }