концепция
Я бы решил эту проблему с помощью иерархии спрайтов, используя вариацию шаблона составного проекта . Это означает, что каждый спрайт хранит список дочерних спрайтов, которые прикреплены к нему, так что любые модификации родительского объекта автоматически отражаются в них (включая перевод, вращение и масштабирование).
В моем движке я реализовал это так:
- Каждый
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();
Реализация
Для начала я просто добавлю пример проекта с этой техникой, если вы предпочитаете просто посмотреть на код и разобраться с ним:
Примечание: я выбрал ясность вместо производительности здесь. В серьезной реализации можно выполнить множество оптимизаций, большинство из которых включают кэширование преобразований и их пересчет только по мере необходимости (например, кэширование локальных и глобальных преобразований для каждого спрайта и их пересчет только тогда, когда спрайт или один из его предков меняется). Кроме того, версии матричных и векторных операций 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
Метод принимает глобальное преобразование родителя в качестве параметра. Есть и другие способы распространения этой информации, но я обнаружил, что этот способ прост в использовании.
- Вычислить глобальное преобразование, умножив локальное преобразование на глобальное преобразование родителя.
- Адаптируйте глобальное преобразование
SpriteBatch
и отобразите текущий спрайт.
- Визуализируйте все дочерние элементы, передав им текущее глобальное преобразование в качестве параметра.
Преобразовав это в код, вы получите что-то вроде:
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); }
Вы должны быть в состоянии сгруппировать их в SpriteBatch, и они переместят его на место и повернут с помощью матрицы.
Коды не проверены, и моя матрица умножения очень ржавая, но метод остается в силе.
источник
spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, null, null, null, null, cam.get_transformation(graphics.GraphicsDevice));
-Origin * Scale * Rotation * Translation
(где все эти значения взяты из спрайта).Я бы реализовал это немного по-другому.
Дайте вашему классу спрайтов список своих детей. Затем, когда вы обновите положение и преобразования спрайта, также примените те же переводы к потомкам с циклом for-each. Дочерние спрайты должны иметь свои координаты, определенные в модельном пространстве, а не в мировом пространстве.
Мне нравится использовать два поворота - один вокруг источника дочернего элемента (чтобы вращать спрайт на месте) и один вокруг источника родительского элемента (чтобы заставить ребенка по существу вращаться вокруг родителя).
Чтобы нарисовать, просто начните свою spritebatch, вызовите player.draw (), который будет рисовать, затем перебрать все его дочерние элементы и также нарисовать их.
При такой иерархии, когда вы перемещаете родителя, все его дочерние элементы перемещаются вместе с ним, что также позволяет вам перемещать дочерних элементов независимо.
источник