Cuộn camera XNA 2d - tại sao sử dụng biến đổi ma trận?


18

Tôi đang làm một trò chơi thử nghiệm, nơi tôi muốn cấp độ liên tục được cuộn. Để tạo hiệu ứng này, tôi đã thiết lập một lớp camera chỉ đơn giản là lưu trữ vị trí vector2 và hướng enum. Nó cũng chứa một phương thức công khai để 'di chuyển', đơn giản là thay đổi vị trí ở một tỷ lệ cố định. Sau đó tôi sử dụng vị trí này khi lặp qua mảng gạch của mình khi vẽ. Tất cả điều này hoạt động tốt.

Tuy nhiên, tôi đã được thông báo rằng tôi nên sử dụng ma trận Biến đổi để di chuyển máy ảnh và tôi nên cung cấp điều này khi tôi khởi động spritebatch. Tôi hơi bối rối a.) Làm thế nào điều này hoạt động? như thể tôi chỉ cho nó khi spritebatch bắt đầu làm thế nào để biết thay đổi vị trí? b.) Tại sao tôi chắc chắn vẫn cần vị trí máy ảnh khi tôi lặp qua các ô?

Hiện tại tôi không thể làm cho nó hoạt động được, nhưng điều đó không có gì đáng ngạc nhiên vì tôi không hoàn toàn hiểu nó hoạt động như thế nào. Hiện tại trong nỗ lực của tôi (mã để theo dõi) các ô được vẽ thay đổi có nghĩa là vị trí camera đang thay đổi, nhưng vị trí của khung nhìn vẫn không thay đổi (tức là tại điểm gốc của camera). Tôi thực sự sẽ đánh giá cao một số lời khuyên / hướng dẫn về cách sử dụng nó?

Máy ảnh:

 class Camera {

    // The position of the camera.
    public Vector2 Position {
        get { return mCameraPosition; }
        set { mCameraPosition = value; }
    }
    Vector2 mCameraPosition;

    public Vector2 Origin { get; set; }

    public float Zoom { get; set; }

    public float Rotation { get; set; }

    public ScrollDirection Direction { get; set; }

    private Vector2 mScrollSpeed = new Vector2(20, 18);

    public Camera() {
        Position = Vector2.Zero;
        Origin = Vector2.Zero;
        Zoom = 1;
        Rotation = 0;
    }


    public Matrix GetTransform() {
        return Matrix.CreateTranslation(new Vector3(mCameraPosition, 0.0f)) *
               Matrix.CreateRotationZ(Rotation) *
               Matrix.CreateScale(Zoom, Zoom, 1.0f) *
               Matrix.CreateTranslation(new Vector3(Origin, 0.0f));
    }




    public void MoveCamera(Level level) {
        if (Direction == ScrollDirection.Up)
        {
            mCameraPosition.Y = MathHelper.Clamp(mCameraPosition.Y - mScrollSpeed.Y, 0, (level.Height * Tile.Height - level.mViewport.Height));
        }
    }

Cấp độ:

 public void Update(GameTime gameTime, TouchCollection touchState) {

            Camera.MoveCamera(this);
 }


 public void Draw(SpriteBatch spriteBatch) {
        //spriteBatch.Begin();
        spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.LinearClamp, DepthStencilState.Default, RasterizerState.CullCounterClockwise, null, mCamera.GetTransform());


        DrawTiles(spriteBatch);

        spriteBatch.End();
    }

Trò chơi - chỉ cần gọi rút thăm trong cấp độ:

  protected override void Draw(GameTime gameTime)  {
        mGraphics.GraphicsDevice.Clear(Color.Black);

        //mSpriteBatch.Begin();

        // Draw the level.
        mLevel.Draw(mSpriteBatch);

        //mSpriteBatch.End();

        base.Draw(gameTime);
    }

================================================== =============================== CHỈNH SỬA:

Đầu tiên, cảm ơn bạn craftworkgames cho sự giúp đỡ của bạn cho đến nay.

Tôi chơi xung quanh với lời đề nghị. Khi tôi vẽ tất cả các ô, số fr nghiêng đến khoảng 15 từ 30 - có lẽ vì các cấp khá lớn.

Vì vậy, những gì tôi đã làm là áp dụng ma trận và di chuyển trong bản cập nhật (như được đề xuất) nhưng trong bản vẽ, tôi sử dụng vị trí camera để lặp qua các ô (tức là bộ đếm bắt đầu ở bên trái và kết thúc ở ô bên phải). Tất cả đều hoạt động tốt và tôi hài lòng với nó :-)

Vấn đề mới của tôi nằm ở người chơi. Rõ ràng là bây giờ tôi đang di chuyển máy ảnh chứ không phải cấp độ, người chơi bị người quay lại bỏ lại phía sau vì vị trí của nó vẫn cố định. Tôi đã nghĩ đến hai giải pháp cho vấn đề này, đầu tiên là chỉ cần xem xét vị trí camera khi vẽ trình phát. Tức là trong chức năng rút thăm chỉ cần thêm vị trí camera vào vị trí người chơi. Thứ hai là bắt đầu một đợt sprite mới cho người chơi không có biến đổi. tức là kết thúc spritebatch sau khi vẽ các ô sau đó bắt đầu một ô mới khi vẽ trình phát. Tôi biết cả hai sẽ hoạt động, nhưng tôi không thể tạo ra những cái đuôi sẽ tốt hơn về hiệu suất / mã hóa tốt? Tôi không chắc ý nghĩa hiệu suất là gì khi bắt đầu đợt hai lần?


1
"Vấn đề mới của tôi nằm ở người chơi. Rõ ràng là bây giờ tôi đang di chuyển máy ảnh chứ không phải cấp độ, người chơi bị người quay lại bỏ lại phía sau vì vị trí của nó vẫn cố định." Chỉ cần đọc điều này và tôi rất bối rối tại sao bạn không di chuyển người chơi trong hệ thống điều phối của cấp độ?
ClassicThunder

Câu trả lời:


15

Biến đổi ma trận máy ảnh rất dễ dàng

Tạo một máy ảnh cơ bản là dễ dàng. Dưới đây sẽ giúp bạn bắt đầu với những điều cơ bản. Di chuyển nó xung quanh, xoay, mở rộng quy mô. Di chuyển mỗi sprite 2d không phải là vấn đề lớn nhưng nếu bạn tính đến tỷ lệ hoặc xoay thì sẽ rất khó áp dụng cho từng sprite riêng lẻ.

class Camera2D 
{
    public float Zoom { get; set; }
    public Vector2 Location { get; set; }
    public float Rotation { get; set;}

    private Rectangle Bounds { get; set; }

    private Matrix TransformMatrix
    { 
        get: {
            return 
                Matrix.CreateTranslation(new Vector3(-Location.X, -Location.Y, 0)) *
                Matrix.CreateRotationZ(Rotation) *
                Matrix.CreateScale(Zoom) *
                Matrix.CreateTranslation(new Vector3(Bounds.Width * 0.5f, Bounds.Height * 0.5f, 0));
        }
    };

    public Camera2D(Viewport viewport) 
    {
        Bounds = viewport.Bounds;
    }
}

Nó làm cho nó thực sự dễ dàng để chuyển đổi giữa các định nghĩa hệ tọa độ

Để đi từ màn hình đến không gian thế giới đơn giản. Điều này thường được sử dụng để có được vị trí của chuột trên thế giới để chọn đối tượng.

Vector2.Transform(mouseLocation, Matrix.Invert(Camera.TransformMatrix));

Để đi từ thế giới đến không gian màn hình chỉ cần làm ngược lại.

Vector2.Transform(mouseLocation, Camera.TransformMatrix);

Không có trở lại để sử dụng một ma trận khác hơn là nó cần một chút học hỏi.

Thật dễ dàng để có được khu vực có thể nhìn thấy

public Rectangle VisibleArea {
    get {
        var inverseViewMatrix = Matrix.Invert(View);
        var tl = Vector2.Transform(Vector2.Zero, inverseViewMatrix);
        var tr = Vector2.Transform(new Vector2(_screenSize.X, 0), inverseViewMatrix);
        var bl = Vector2.Transform(new Vector2(0, _screenSize.Y), inverseViewMatrix);
        var br = Vector2.Transform(_screenSize, inverseViewMatrix);
        var min = new Vector2(
            MathHelper.Min(tl.X, MathHelper.Min(tr.X, MathHelper.Min(bl.X, br.X))), 
            MathHelper.Min(tl.Y, MathHelper.Min(tr.Y, MathHelper.Min(bl.Y, br.Y))));
        var max = new Vector2(
            MathHelper.Max(tl.X, MathHelper.Max(tr.X, MathHelper.Max(bl.X, br.X))), 
            MathHelper.Max(tl.Y, MathHelper.Max(tr.Y, MathHelper.Max(bl.Y, br.Y))));
        return new Rectangle((int)min.X, (int)min.Y, (int)(max.X - min.X), (int)(max.Y - min.Y));
    }
}

Bạn chỉ có thể biến đổi các góc máy ảnh và có được vị trí của chúng trong không gian thế giới. Tối đa các giá trị x, y và bạn có thể có được một hình chữ nhật xung quanh không gian có thể xem được. Rất hữu ích để loại bỏ và tối ưu hóa các cuộc gọi rút thăm.


1
Cảm ơn. Tôi thấy điều này hữu ích khi triển khai máy ảnh trong thư viện MonoGame.Extends .
craftworkgames

6

Áp dụng ma trận cho SpriteBatch của bạn sẽ biến đổi toàn bộ lệnh gọi rút thăm cùng một lúc. Điều này có nghĩa là bạn hoàn toàn không cần sử dụng máy ảnh của mình trong phương thức DrawTiles.

Nó có thể trở nên đơn giản hơn rất nhiều như vậy:

    // Loop through the number of visible tiles.
    for (int y = 0; y <= tiles.GetUpperBound(1); y++) {
        for (int x = 0; x <= tiles.GetUpperBound(0); x++) {

                // If the tile is not an empty space.
                if (tiles[x, y].Texture != null) {
                     // Get the position of the visible tile within the viewport by multiplying the counters by the tile dimensions
                     // and subtracting the camera offset values incase the position of the camera means only part of a tile is visible.
                     Vector2 tilePosition = new Vector2(x * Tile.Width, y * Tile.Height);
                     // Draw the correct tile
                     spriteBatch.Draw(tiles[x, y].Texture, tilePosition, Color.White);
                }
        }
    }

Vì vậy, quan điểm của việc sử dụng ma trận là để bạn không phải suy nghĩ về nó. Chỉ cần vẽ mọi thứ và di chuyển camera xung quanh một cách độc lập.

Ngoài ra, phương thức MoveCamera của bạn trông hơi lạ. Thật là bất thường khi có một lớp máy ảnh lấy Cấp độ làm phụ thuộc. Một triển khai điển hình hơn sẽ như thế này:

public void MoveCamera(float deltaX, float deltaY) {
    mCameraPosition.X += deltaX;
    mCameraPosition.Y += deltaY;
}

Sau đó, trong phương thức Cập nhật của bạn, bạn có thể làm một cái gì đó như thế này:

    if (Direction == ScrollDirection.Up)
    {
        mCamera.MoveCamera(mScrollSpeed.Y, 0);
    }

Nhìn chung, đề nghị của tôi là giữ cho nó đơn giản. Làm cho nó hoạt động theo cách đơn giản nhất nắm tay và xây dựng trên nó. Cố gắng không viết mã được tối ưu hóa cao cho đến khi bạn có những điều cơ bản làm việc trước tiên. Bạn có thể thấy rằng kết xuất mọi ô mỗi khung hình không quá tệ.

EDIT: Đối với phần thứ hai của câu hỏi.

Mặc dù đúng là bạn muốn giữ số lượng lô của mình ở mức thấp, nhưng có 2 hoặc 3 không phải là vấn đề. Vì vậy, nếu bạn có một lý do chính đáng để tạo một đợt sprite thứ hai, hãy làm điều đó.

Điều đó nói rằng, có lẽ không có lý do chính đáng để sử dụng một đợt sprite thứ hai trong trường hợp này. Nhiều khả năng, bạn muốn vẽ trình phát của mình theo cách chính xác giống như cách bạn vẽ các ô trong cùng một đợt sprite với biến đổi camera được áp dụng.

Thật khó để nói lý do tại sao trình phát của bạn bị bỏ lại phía sau mà không nhìn vào một số mã nhưng lý do là nếu bạn vẽ trình phát của mình ở cùng một vị trí như một ô, anh ấy / cô ấy sẽ xuất hiện ở cùng một vị trí với cùng một vị trí lô sprite.

Ví dụ: nếu bạn muốn trình phát xuất hiện trên ô 10, 10 bạn có thể làm điều này:

var playerPosition = new Vector2(10 * Tile.Width, 10 * Tile.Height);
spriteBatch.Draw(player.Texture, playerPosition, Color.White);

Cố gắng đi vào suy nghĩ về việc suy nghĩ về việc vẽ những thứ mà chúng đang ở và máy ảnh thực sự di chuyển toàn bộ "cảnh" vào tầm nhìn. Đó là những gì biến đổi ma trận của bạn đang làm.


Điều đó thực sự hữu ích và dọn dẹp mọi thứ! Cảm ơn bạn về thông tin; Nhiều đánh giá cao. Tôi sẽ cố gắng thực hiện lời khuyên của bạn sau hôm nay và sẽ cho bạn biết làm thế nào tôi nhận được. Cảm ơn một lần nữa.
Pectus khai mạc

Ngoài ra, chỉ cần quan tâm, có cách nào để sử dụng biến đổi ma trận để chỉ vẽ các ô trong chế độ xem không?
Pectus khai mạc

Có một cách để làm điều đó, nhưng tôi không biết trên đỉnh đầu. Tôi nghi ngờ nó sẽ có liên quan đến việc sử dụng hình chữ nhật khung nhìn, có thể sau khi áp dụng biến đổi máy ảnh cho nó. Tôi không biết, bạn sẽ phải thử nghiệm. Sử dụng trình gỡ lỗi của bạn.
craftworkgames

Được rồi cảm ơn. Tôi đã chơi xung quanh với nó và có một nơi nào đó nhưng đã gặp phải một câu hỏi về việc phải làm gì với người chơi - vui lòng xem các chỉnh sửa để đặt câu hỏi.
Pectus khai mạc

Cảm ơn, tôi đã lấy lời khuyên của bạn và đi với tùy chọn sử dụng cùng một đợt sprite và chỉ nhận vị trí camera trong bản cập nhật và áp dụng nó vào vị trí người chơi khi vẽ. Có vẻ để làm việc tốt. Cảm ơn tất cả sự giúp đỡ của bạn, đã bị kẹt trên máy ảnh trong một thời gian. Bây giờ rõ ràng hơn nhiều :-)
Pectus khai mạc
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.