Статьи

Разработка Snowfight — базовый движок частиц

Я и некоторые другие инсайдеры из Microsoft начали работать над игрой для Windows Phone 7 (конечно, на XNA). После некоторого мозгового штурма мы решили разработать игру, которая будет чем-то напоминать игру Worms старого стиля, но в то же время мы подумали об ее адаптации к зимнему сезону. Так что это будет Snowfight — дети, бросающие снежки друг в друга.

Проект размещен на CodePlex, и вы можете проверить его здесь .

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

ПРИМЕЧАНИЕ. Это очень базовая реализация, и я буду расширять ее по пути.

Прежде всего, вот структура, которую я решил использовать:

образ

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

Для базового «скелетона» я создал простую структуру классов, разделенную на папки:

образ

Если вы заинтересованы в реальной диаграмме классов, прежде чем я объясню каждый класс в отдельности, вот он:

образ

Основной Частицей здесь является фундаментальный атом — сама частица (не требует объяснений). Его структура определяется этими свойствами:

public class BasicParticle
{
public Vector2 CurrentPosition { get; set; }
public Vector2 Velocity { get; set; }
public float Size { get; set; }
}

Обратите внимание, что скорость также определяется вектором в двумерном пространстве, который определяет, насколько быстро частица будет двигаться по осям X и Y.

Теперь, когда есть основная частица, пришло время объединить их в систему. Система будет непосредственно управлять частицами, которые будут удерживаться в ней. Я начал с набора основных свойств:

public class ParticleSystem
{
public int SystemID { get; set; }
public Texture2D Texture { get; set; }
public List<BasicParticle> Particles { get; set; }
private SpriteBatch SpriteManager {get; set;}
private Vector2 CenterPosition { get; set; }
}

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

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

В конструкторе класса ParticleSystem я прошу пользователя указать некоторые детали:

  • Что будет SpriteBatch?
  • Какой идентификатор системы?
  • Где разместить систему изначально?
  • Какую текстуру наносить на каждую частицу?

Вот как это выглядит на самом деле:

public ParticleSystem(SpriteBatch SpriteManager, int SystemID, Vector2 CenterPosition, Texture2D Texture)
{
this.SystemID = SystemID;
this.Texture = Texture;
this.Particles = new List<BasicParticle>();
this.SpriteManager = SpriteManager;
this.CenterPosition = CenterPosition;
}

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

for (int i = 0; i < 200; i++)
{
BasicParticle particle = new BasicParticle();
particle.CurrentPosition = CenterPosition;
particle.Size = new Random().Next(128);
float randomX = (float)new Random().NextDouble() - 0.5f;
float randomY = (float)new Random().NextDouble() - 0.5f;
particle.Velocity = new Vector2(randomX,randomY);
Particles.Add(particle);
}

У меня будет ровно 200 частиц. Размер генерируется случайным образом, так же как и скорость. Обратите внимание на тот факт, что скорость также может быть отрицательной — я не хочу, чтобы все частицы двигались в одном направлении.

Как только основание установлено, пришло время рисовать частицы. На данный момент я решил оставить эту задачу в менеджере, который содержит эти:

public void StartDrawing()
{
SpriteManager.Begin();
foreach (BasicParticle particle in Particles)
{
SpriteManager.Draw(Texture, new Rectangle((int)particle.CurrentPosition.X, (int)particle.CurrentPosition.Y,
(int)particle.Size, (int)particle.Size), Color.White);
particle.CurrentPosition += particle.Velocity;
}
SpriteManager.End();
}

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

Менеджер частиц (определенный классом ParticleManager) на данный момент является самым простым элементом в движке:

public class ParticleManager
{
private List<ParticleSystem> Systems { get; set; }
public void Initialize()
{
Systems = new List<ParticleSystem>();
}
public void AddSystem(ParticleSystem SystemToAdd)
{
Systems.Add(SystemToAdd);
}
public ParticleSystem GetSystem(int SystemID)
{
return (from c in Systems where c.SystemID == SystemID select c).FirstOrDefault();
}
} 

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

Теперь, если вы хотите поэкспериментировать с этим движком, попробуйте сделать это.

1) Объявите экземпляр Texture2D и ParticleManager в основном классе:

образ

2) Загрузите текстуру в методе LoadContent:

образ

ПРИМЕЧАНИЕ: здесь я использую простое растровое изображение 16 × 16 красной формы. Вы можете загрузить свою собственную текстуру.

3) В том же методе инициализируйте экземпляр ParticleManager и добавьте в него новую систему:

образ

4) В методе Draw найдите нужную систему и начните рисовать ее:

образ

Теперь, если вы запустите игру (либо на эмуляторе, либо на телефоне), вы должны увидеть результаты, подобные этим:

образ

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