Прокрутка камери XNA 2d - навіщо використовувати матричне перетворення?


18

Я роблю тестову гру, де хочу, щоб рівень постійно прокручувався. Для створення цього ефекту я створив клас камери, який просто зберігає положення vector2 та напрямок enum. Він також містить публічний метод "переміщення", який просто змінює позицію з фіксованою ставкою. Потім я використовую це положення під час перегляду мого масиву плиток під час малювання. Це все добре працює.

Однак мені сказали, що я повинен використовувати матрицю Трансформації для переміщення камери, і що я повинен надати це під час запуску spritebatch. Я трохи розгублений.) Як це працює? наче я даю це лише тоді, коли починається спрайтбч, як він знає, щоб зберегти зміну позиції? б.) Навіщо це робити, як напевно, мені ще потрібно положення камери, коли я перебираю плитки?

В даний момент я не можу змусити його працювати, але це не дивно, тому що я не розумію, як це означає працювати. Наразі в моїй спробі (код, який слід подати) змінюється намальована плитка, що означає, що положення камер змінюється, але положення вікна перегляду залишається незмінним (тобто у походження камери). Я дуже вдячний би деяким порадам / настановам щодо того, як його слід використовувати?

Камера:

 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));
        }
    }

Рівень:

 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();
    }

Гра - просто називає нічию в межах рівня:

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

        //mSpriteBatch.Begin();

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

        //mSpriteBatch.End();

        base.Draw(gameTime);
    }

===================================================== ================================ EDIT:

По-перше, дякую вам craftworkgames за надану допомогу.

Я розігрувався з пропозицією. Коли я намалював усі плитки, fr збився приблизно до 15 з 30 - можливо, тому, що рівні досить великі.

Тож, що я зробив, це застосувати матрицю і переміститись в оновленнях (як пропонується), але на малюнку я використовую положення камер для прокручування плиток (тобто лічильник запуску ліворуч і кінець праворуч плиткою). Це все працює добре, і я цим задоволений :-)

Мій новий випуск лежить у програвачі. Очевидно, що зараз я рухаю камеру, а не рівень, плеєр залишається позаду камери, оскільки його положення залишається фіксованим. Я подумав два рішення цієї проблеми, перше - просто розглянути положення камер під час малювання плеєра. Тобто у фуніціоні жеребкування просто додайте положення камери до позиції гравця. Друга - запустити нову партію спрайту для гравця, який не має трансформації. тобто закінчуйте spritebatch після нанесення плитки, а потім запускайте новий, коли малюєте програвач. Я знаю, що обоє будуть працювати, але я не можу скласти голови хвостів, які були б кращими з точки зору продуктивності / хорошого кодування? Я не впевнений, які наслідки для продуктивності починаються від запуску серії двічі?


1
"Моя нова проблема полягає в програвачі. Очевидно, що зараз я рухаю камеру, а не рівень, гравець залишається за камерою, коли її положення залишається фіксованим". Просто прочитайте це, і я настільки розгублений, чому б на землі ви не пересували гравця в системі координат рівня?
ClassicThunder

Відповіді:


15

Перетворення матриці камери легко

Створити основну камеру легко. Нижче ви почнете з основ. Рухаючи його навколо, обертаючи, масштабуючи. Переміщення кожного 2-га спрайту не є великою проблемою, але якщо ви враховуєте масштаб або обертання, то застосувати його до кожного спрайту стає важко.

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;
    }
}

Це робить дуже легким перетворення між визначеннями системи координат

Перейти з екрана в світовий простір просто. Це зазвичай використовується для отримання місця миші у світі для вибору предметів.

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

Щоб перейти зі світу до простору екрану, просто робіть навпаки.

Vector2.Transform(mouseLocation, Camera.TransformMatrix);

Немає можливості повернутись до використання матриці, окрім якої потрібно трохи вивчити.

Легко дістати видиму область

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));
    }
}

Ви можете просто перетворити кути камер і отримати їх розташування у світовому просторі. Мінімально значення x, y і ви можете отримати прямокутник навколо видимого простору. Дуже корисно для вимкнення та оптимізації розмовних дзвінків.


1
Спасибі. Я вважав це корисним під час впровадження камери в бібліотеці MonoGame.Extended .
craftworkgames

6

Застосування матриці до SpriteBatch перетворює одразу весь дзвінок виклику. Це означає, що вам взагалі не потрібно використовувати камеру у вашому методі DrawTiles.

Це могло б стати набагато простішим на зразок:

    // 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);
                }
        }
    }

Тож сенс використання матриці полягає в тому, що вам не доведеться думати про це. Просто намалюйте речі та перемістіть камеру самостійно.

Також ваш метод MoveCamera виглядає дещо дивно. Дуже незвично мати клас камери, який приймає рівень як залежність. Більш типова реалізація виглядатиме так:

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

Тоді у вашому методі оновлення ви можете зробити щось подібне:

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

Загалом, моя пропозиція полягає в тому, щоб зробити це просто. Зробіть його найпростішим способом кулака і будуйте на ньому. Постарайтеся не писати високооптимізований код, поки ви не почали працювати з основ. Ви можете виявити, що рендеринг кожної плитки на кожен кадр не так вже й поганий.

EDIT: Для другої частини питання.

Хоча це правда, що ви хочете, щоб ваша кількість партій була низькою, наявність 2 або 3 взагалі не повинна бути проблемою. Тож якщо у вас є вагомі підстави створити другу партію спрайту, просто зробіть це.

Однак це, мабуть, не є вагомим приводом для використання другої партії спрайту в цьому випадку. Швидше за все, ви хочете намалювати плеєр точно так само, як ви малюєте плитки в одній партії спрайтів із застосованим перетворенням камери.

Трохи важко сказати, чому ваш гравець залишається позаду, не дивлячись на якийсь код, але, очевидно, якщо ви намалюєте гравця на точно такій же позиції, як і плитка, він / вона з’явиться на тій же позиції з тим же самим спрайт партії.

Наприклад, якщо ви хочете, щоб гравець з'явився на плитці 10, 10, ви можете зробити це:

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

Постарайтеся ввійти в розум думати про те, щоб намалювати речі там, де вони є, і камера буквально переміщує всю «сцену» на вигляд. Ось що робить ваша матрична трансформація.


Це справді корисні та очищені речі навантаження! Дякую за інформацію; цінується. Я спробую втілити Ваші поради пізніше сьогодні, і я дам вам знати, як я дію. Знову дякую.
Pectus Excavatum

Крім того, лише з інтересу, чи є можливість використовувати матричне перетворення лише для малювання плиток у вікні перегляду?
Pectus Excavatum

Існує спосіб зробити це, але я не знаю, що з голови. Я підозрюю, що це буде мати щось спільне з використанням прямокутника вікна перегляду, можливо, після застосування перетворення камери до нього. Не знаю, вам доведеться експериментувати. Використовуйте свій налагоджувач.
ремісничі ігри

Добре, дякую. Я пограв з цим і десь потрапив, але у мене виникло питання про те, що робити з програвачем.
Pectus Excavatum

Дякую, я прийняв вашу пораду і пішов з можливістю використовувати ту саму партію спрайтів і просто отримати положення камери в оновленні та застосувати її до позиції гравців під час малювання. Здається, добре працює. Дякуємо за всю вашу допомогу, якусь час застрягли на камері. Набагато зрозуміліше зараз :-)
Pectus Excavatum
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.