Я и некоторые другие инсайдеры из 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 найдите нужную систему и начните рисовать ее:
Теперь, если вы запустите игру (либо на эмуляторе, либо на телефоне), вы должны увидеть результаты, подобные этим:
Из-за не очень случайного механизма формы летят во многих направлениях, но не точно вокруг центра, а скорее по диагональным векторам. Это изменится, как только я осуществлю генерацию вектора на основе триггерных функций, но сейчас (чтобы продемонстрировать возможные возможности) это то, что у нас есть.