Есть ли простой способ сгруппировать два или более спрайтов, чтобы все они были зависимы друг от друга?

8

Я думаю, что этот вопрос очень похож на этот , но я не уверен, что ответы универсальны.

Итак, моя цель:

  • Поместите двух спрайтов в фиксированное положение, например игрока и его глаз
  • Удостоверьтесь, что всякий раз, когда игрок вращается, спрайт глаз тоже вращается и попадает в то же относительное положение от тела (чтобы глаза не были на спине игрока). Так что они будут работать как группа. - Этот шаг должен быть автоматизирован, это моя цель!

Так, например, теперь я хочу поместить пистолет в руки пользователя. Итак, теперь я говорю, что игрок в позиции, Vector2(0, 0)а пистолет в позиции Vector2(26, 16). Теперь я хочу сгруппировать их, поэтому, когда игрок вращается, пистолет тоже вращается.

введите описание изображения здесь

В настоящее время в этом примере это довольно хорошо, но в случае, если мне нужно переместить пистолет по оси y (только), я потерялся

Мартин.
источник

Ответы:

10

концепция

Я бы решил эту проблему с помощью иерархии спрайтов, используя вариацию шаблона составного проекта . Это означает, что каждый спрайт хранит список дочерних спрайтов, которые прикреплены к нему, так что любые модификации родительского объекта автоматически отражаются в них (включая перевод, вращение и масштабирование).

В моем движке я реализовал это так:

  • Каждый Spriteхранит List<Sprite> Childrenи предоставляет метод для добавления новых дочерних элементов.
  • Каждый Spriteзнает, как рассчитать, Matrix LocalTransformкоторый определен относительно родителя.
  • Вызов Drawна Spriteтакже вызывает его на всех своих детей.
  • Дети умножают свои локальные преобразования на глобальные преобразования своих родителей . Результатом является то, что вы используете при рендеринге.

Благодаря этому вы сможете делать то, что просили, без каких-либо других изменений в вашем коде. Вот пример:

Sprite tank = new Sprite(tankTexture);
tank.Children.Add(new Sprite(turretTexture) {Position = new Vector2(26, 16) });

spriteBatch.Begin();
tank.Draw(spriteBatch);
spriteBatch.End();

Реализация

Для начала я просто добавлю пример проекта с этой техникой, если вы предпочитаете просто посмотреть на код и разобраться с ним:

  • Sprite Class - класс, который заботится обо всем.
  • CompositeSpriteGame Class - демонстрационная демонстрационная версия игры класса XNA предыдущего класса.
  • Видео - видео предыдущего проекта в действии.

введите описание изображения здесь

Примечание: я выбрал ясность вместо производительности здесь. В серьезной реализации можно выполнить множество оптимизаций, большинство из которых включают кэширование преобразований и их пересчет только по мере необходимости (например, кэширование локальных и глобальных преобразований для каждого спрайта и их пересчет только тогда, когда спрайт или один из его предков меняется). Кроме того, версии матричных и векторных операций XNA, которые принимают значения по ссылке, работают немного быстрее, чем те, которые я использовал здесь.

Но я опишу процесс более подробно ниже, так что читайте дальше для получения дополнительной информации.


Шаг 1 - внесите несколько коррективов в класс Sprite

Предполагая, что у вас уже есть Spriteкласс (и вы должны это сделать), вам нужно будет внести в него несколько изменений. В частности, вам нужно добавить список дочерних спрайтов, матрицу локальных преобразований и способ распространения преобразований по иерархии спрайтов. Я нашел самый простой способ сделать это просто передать их в качестве параметра при рисовании. Пример:

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

Шаг 2 - Расчет матрицы LocalTransform

LocalTransformМатрица просто обычный мир матрица строится из значений положения, поворота и масштаба спрайта. В качестве источника я взял центр спрайта:

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

Шаг 3 - Знание того, как передать Матрицу в SpriteBatch

Одна проблема с SpriteBatchклассом состоит в том, что его Drawметод не знает, как получить мировую матрицу напрямую. Вот вспомогательный метод для решения этой проблемы:

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

Шаг 4 - Рендеринг Спрайта

Примечание. DrawМетод принимает глобальное преобразование родителя в качестве параметра. Есть и другие способы распространения этой информации, но я обнаружил, что этот способ прост в использовании.

  1. Вычислить глобальное преобразование, умножив локальное преобразование на глобальное преобразование родителя.
  2. Адаптируйте глобальное преобразование SpriteBatchи отобразите текущий спрайт.
  3. Визуализируйте все дочерние элементы, передав им текущее глобальное преобразование в качестве параметра.

Преобразовав это в код, вы получите что-то вроде:

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

При рисовании корневого спрайта родительское преобразование отсутствует, поэтому вы передаете его Matrix.Identity. Вы можете создать перегрузку, чтобы помочь в этом случае:

public void Draw(SpriteBatch spriteBatch) { Draw(spriteBatch, Matrix.Identity); }
Дэвид Гувея
источник
У меня есть проблема, с которой я сейчас имею дело. В моей текущей настройке я создаю преобразование Matrix при вызове spriteBatch, потому что я использую камеру. Так что, если моя камера на 500,50, а спрайт моего игрока на 500,50, игрок должен быть в центре. Однако в этом случае он нигде не рисует
Мартин.
@Martin Используете ли вы матрицу камеры или нет, она должна работать одинаково. В этом примере я не использовал камеру, но на своем движке я использую одну, и она работает нормально. Вы передаете матрицу камеры (и только матрицу камеры) в SpriteBatch.Begin? И если вы хотите, чтобы все было по центру, учитываете ли вы половину размера экрана при создании матрицы камеры?
Дэвид Гувея
Я использую тот же класс, что и здесь, для камеры, получаю метрику камеры, да. Теперь это работает, я постараюсь настроить его сейчас и дам вам знать
Мартин.
1
ИДЕАЛЬНО! Удивительно ИДЕАЛЬНО! И поскольку в большинстве случаев у меня были проблемы, это была моя вина. Взглянуть ! i.imgur.com/2CDRP.png
Мартин.
Я знаю, что опаздываю на вечеринку здесь, так как этот вопрос действительно старый. Но в то время как это работает, оно портит позиции, когда шкалы x и y не одинаковы: /
Эрик Скоглунд,
2

Вы должны быть в состоянии сгруппировать их в SpriteBatch, и они переместят его на место и повернут с помощью матрицы.

var matrix = 
    Matrix.CreateRotationZ(radians) *
    Matrix.CreateTranslation(new Vector3(x, y, 0));

SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, null, null, null, null, matrix);
SpriteBatch.Draw(player, Vector2.Zero, null, Color.White);
SpriteBatch.Draw(gun, Vector2(handDistance, 0), null, Color.White);
SpriteBatch.End();

Коды не проверены, и моя матрица умножения очень ржавая, но метод остается в силе.

ClassicThunder
источник
Что делать, если я действительно использую матрицу в spriteBatch для камеры? spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, null, null, null, null, cam.get_transformation(graphics.GraphicsDevice));
Мартин.
Я думаю, что вы должны быть в состоянии использовать Begin with matrix * cam.get_transformation (graphics.GraphicsDevice).
ClassicThunder
ИДЕАЛЬНО. Я не так хорошо ответил так быстро.
Мартин.
Простое решение, но оно в некоторой степени побеждает всю цель пакетной обработки спрайтов, которая состоит в том, чтобы собрать как можно больше спрайтов за один вызов. Я мог бы использовать это в простой демонстрации, но определенно не в спрайт интенсивной игре.
Дэвид Гувея
@Martin В моем ответе у меня есть пример того, как создать полную матрицу мира для спрайта. В основном порядок должен быть -Origin * Scale * Rotation * Translation(где все эти значения взяты из спрайта).
Дэвид Гувея
0

Я бы реализовал это немного по-другому.

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

Мне нравится использовать два поворота - один вокруг источника дочернего элемента (чтобы вращать спрайт на месте) и один вокруг источника родительского элемента (чтобы заставить ребенка по существу вращаться вокруг родителя).

Чтобы нарисовать, просто начните свою spritebatch, вызовите player.draw (), который будет рисовать, затем перебрать все его дочерние элементы и также нарисовать их.

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

мыльная пена
источник