XNA 2d Camera Scrolling - зачем использовать матричное преобразование?

18

Я делаю тестовую игру, где хочу, чтобы уровень постоянно прокручивался. Чтобы создать этот эффект, я установил класс камеры, который просто хранит позицию вектора и направление перечисления. Он также содержит публичный метод «перемещения», который просто меняет позицию с фиксированной ставкой. Затем я использую эту позицию при циклическом просмотре массива плиток при рисовании. Это все отлично работает.

Однако мне сказали, что я должен использовать матрицу Transform для перемещения камеры, и что я должен предоставить это при запуске spritebatch. Я немного запутался.) Как это работает? как будто я даю его только тогда, когда начинается 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);
    }

================================================== =============================== РЕДАКТИРОВАТЬ:

Во-первых, спасибо вам craftworkgames за вашу помощь.

Я играл с предложением. Когда я рисовал все фишки, французы увеличивались до 15 с 30 - вероятно, потому что уровни довольно большие.

Итак, что я сделал, это применил матрицу и переместился в обновлении (как предложено), но при рисовании я использую положение камер для циклического прохода по тайлам (т. Е. Начинаю счетчик слева и заканчиваю справа). Это все работает хорошо, и я доволен этим :-)

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

Pectus Excavatum
источник
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 и вы можете получить прямоугольник вокруг видимого пространства. Очень полезно для отбраковки и оптимизации вызовов.

ClassicThunder
источник
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);
    }

В целом, я предлагаю сделать это простым. Получите это, работая самым простым способом, и основывайтесь на этом. Старайтесь не писать сильно оптимизированный код, пока не начнете работать с основами. Вы можете обнаружить, что рендеринг каждой плитки каждого кадра не так уж и плох.

РЕДАКТИРОВАТЬ: Для второй части вопроса.

Хотя это правда, что вы хотите сохранить низкий счетчик партий, наличие 2 или 3 не должно быть проблемой вообще. Так что если у вас есть веская причина для создания второй партии спрайтов, просто сделайте это.

Тем не менее, в этом случае, вероятно, нет веской причины использовать вторую партию спрайтов. Скорее всего, вы хотите нарисовать своего игрока точно так же, как вы рисуете плитки в том же пакете спрайтов с применением преобразования камеры.

Немного трудно понять, почему ваш игрок отстает, не взглянув на какой-либо код, но есть основания полагать, что если вы рисуете своего игрока в той же позиции, что и тайл, он / она появится в той же позиции с той же спрайт пакетный.

Например, если вы хотите, чтобы игрок появлялся на плитке 10, 10, вы можете сделать это:

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

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

craftworkgames
источник
Это действительно полезно и убрано! Спасибо за информацию; очень признателен. Я постараюсь выполнить ваш совет позже сегодня и дам вам знать, как я поживу. Еще раз спасибо.
Pectus Excavatum
Кроме того, просто ради интереса, есть ли способ использовать матричное преобразование, чтобы рисовать только плитки в области просмотра?
Pectus Excavatum
Есть способ сделать это, но я не знаю, с головой. Я подозреваю, что это будет связано с использованием прямоугольника области просмотра, возможно, после применения преобразования камеры к нему. Я не знаю, вам придется экспериментировать. Используйте свой отладчик.
craftworkgames
Хорошо спасибо. Я поэкспериментировал с этим и получил где-то, но столкнулся с вопросом о том, что делать с игроком - пожалуйста, см. Изменения к вопросу.
Pectus Excavatum
Спасибо, я воспользовался вашим советом и выбрал возможность использовать ту же партию спрайтов и просто получить положение камеры в обновлении и применить его к положению игроков при рисовании. Кажется, работает хорошо. Спасибо за вашу помощь, застрял на камеру на некоторое время. Теперь
стало