Вчимося програмувати ігри на XNA для Windows Phone 7 «Mango»

Морський бій, який ми писатимемо — це не та гра, в яку багато хто грав у дитинстві на папері в клітинку. Це гра, яка була доступна в ігрових автоматах, де треба було потрапити в корабель, що пливе, кількома пострілами. У нашій грі також буде корабель, що плаває у верхній частині екрану, та снаряди, які можна буде випускати з нижньої частини екрану шляхом дотику до нього.
Архітектура XNA-програми дуже відрізняється від класичного Windows або Web-програми. У ньому не використовується модель подій, оскільки вона не дуже підходить для вирішення завдань реального часу - адже нам хочеться, щоб гра розгорталася перед нашими очима саме в реальному часі, зі швидкістю не менше ніж 25 кадрів на секунду! Тому гра має дуже просту програмну модель - цикл гри . На початку гри (або ігрового рівня) викликається спеціальна функціяLoadContent для завантаження основних ресурсів (графічних та звукових елементів), потім у циклі поперемінно викликаються методиUpdate (для оновлення стану гри залежно від дії користувача з пристроями введення) таDraw (для відтворення стану гри на екрані). Таким чином, для написання гри треба зробити кілька основних дій:
- Створити графічні та звукові елементи оформлення гри та помістити їх у проект. Для створення графічних елементів добре підійдуть інструменти Microsoft Expression. Графічні елементи можуть бути двовимірними спрайтами, так і тривимірними моделями.
- Описати змінні для зберігання всіх необхідних елементів та перевизначити методLoadContent для завантаження їх у пам'ять.
- Зрозуміти, як виглядатиме стан гри – тобто.набір змінних та структур даних, які описуватимуть гру в кожний момент часу. Стан може бути дуже простий (як у нашому прикладі - координати корабля і снаряда), або складатися з багатьох незалежних взаємодіючих об'єктів або агентів, які мають свій інтелект і логіку.
- Обробити дії користувача з пристроями введення та запрограмувати логіку зміни стану гри у методіUpdate.
- Запрограмувати відображення стану на екран методомDraw. Тут знову ж таки може використовуватися 2D або 3D-графіка, залежно від стилю гри.
- Якщо гра складніша, містить кілька рівнів і т.д. – можливо, буде корисно вдосконалити об'єктну модель, щоб відокремити кожен рівень в окремий клас – тоді для кожного рівня доведеться частково повторювати описані вище дії
- Грати, насолоджуватися, ділитися грою з іншими (сюди входять такі кроки як створення інсталятора, поширення гри через Windows Mobile Marketplace, XBox Indie Games і т.д.).
graphics.PreferredBackBufferW > graphics.PreferredBackBufferHeight = 800; * Цей source code був висвітлений з Source Code Highlighter .
Зверніть увагу – у створеному рішенні два проекти: власне гра, та ресурси гри (Content) – зображення, звуки тощо. У самій грі є два класи – Program.cs потрібен для запуску гри, а Game1.cs містить основну логіку гри (функції LoadContent/Update/Draw), і саме його ми модифікуватимемо.
Графічний вміст у нашому випадку складатиметься з трьох елементів – зображення корабля та вибуху, які ми візьмемо з колекції clipart Microsoft Office, та зображення снаряду – червоної рисочки, яку можна намалювати у Paint. Отримані графічні файли мидодамо в Content-проект нашої гри (використовуємо меню Add Existing Item) – результат можна спостерігати на малюнку вище.
Далі опишемо змінні, які відповідають за стан гри. Нам потрібно буде зберігати графічні зображення корабля, ракети та вибуху – вони будуть типу Texture2D, координати та швидкість корабля (швидкість потрібна для завдання напрямку), а також координати ракети – це будуть об'єкти типу Vector2. Додатково для відтворення вибуху знадобиться змінна explode – вона вестиме зворотний відлік числа кадрів, під час яких замість корабля показується вибух.
Texture2D ship, rocket, explosion; Vector2 ship_pos = новий Vector2(100, 100); Vector2 ship_dir = новий Vector2(3, 0); Vector2 rock_pos = Vector2.Zero;
int explode = 0; * Цей source code був висвітлений з Source Code Highlighter .
Метод LoadContent для завантаження вмісту гри буде виглядати дуже просто (опис функції та перший рядок були згенеровані автоматично при створенні проекту, нам необхідно було додати лише три рядки для завантаження описаних нами ресурсів):
protected override void LoadContent() spriteBatch = новий SpriteBatch(GraphicsDevice); ship = Content.Load
("Ship"); rocket = Content.Load
("Rocket"); explosion = Content.Load
> * Цей source code був висвітлений з Source Code Highlighter .
Метод малювання також досить простий:
protected override void Draw(GameTime gameTime) GraphicsDevice.Clear(Color.White); spriteBatch.Begin(); if (explode > 0) spriteBatch.Draw(explosion, ship_pos, Color.White); else spriteBatch.Draw(ship, ship_pos,Color.White); if (rock_pos! = Vector2.Zero)< spriteBatch.Draw(rocket, rock_pos, Color.Red); > spriteBatch.End(); base .Draw(gameTime); > * Цей source code був висвітлений з Source Code Highlighter .
Тут слід зазначити одну тонкість - при малюванні спрайтів на екрані ми малюємо картинки "порціями", що називаютьсяSpriteBatch. Відповідно, ми відкриваємо таку "малювальну транзакцію" викликом spriteBatch.Begin(), і закінчуємо викликом spriteBatch.End(), між якими розташовані виклики spriteBatch.Draw() або DrawString(..). У нашому випадку ми малюємо або корабель, або картинку вибуху – залежно від змінної explode, а також відображаємо ракету в тому випадку, якщо вона випущена і летить до корабля – це ненульове значення вектора координат ракети rock_pos.
Тепер перейдемо розгляд методу Update. Його розглядатимемо частинами. Перша частина відповідає за відтворення вибуху: коли змінна-прапор explode ненульова, єдине завдання нашої гри – відмалювати вибух. Тому ми просто зменшуємо лічильник кадрів, протягом яких показується вибух, а коли він досягає нульової позначки – повертаємо корабель у вихідне положення, щоб гра почалася знову:
protected override void Update(GameTime gameTime) if (explode > 0) explode--; if (explode == 0) ship_pos.X = 0; ship_dir.X = 3; > base .Update(gameTime); return; > . base .Update(gameTime); > * Цей source code був висвітлений з Source Code Highlighter .
Зауважте, що в кінці методу Update викликається метод Update базового класу.
Наступний фрагмент коду відповідає за рух корабля вліво-вправо:
ship_pos + = ship_dir; if (ship_pos.X + ship.Width &== 480 ship_pos.X * Цей source code був висвітлений з Source Code Highlighter .
Тут ми вУ явному вигляді використовуємо роздільну здатність екрану - практично звичайно так робити не варто, краще задати константи спочатку програми, або ж використовувати змінні, які ініціалізуються в процесі роботи гри в залежності від конкретного пристрою.
var tc = TouchPanel.GetState(); if (tc.Count>0) rock_pos.X = tc[0].Position.X; rock_pos.Y = 750; > * Цей source code був висвітлений з Source Code Highlighter .
Далі слідує код, який відповідає за рух ракети та відпрацювання зіткнення ракети з кораблем:
if (rock_pos! = Vector2.Zero) rock_pos + = new Vector2 (0, -7); if (rock_pos.Y >= 0 &&rock_pos.Y = ship_pos.X &&rock_pos.X if (rock_pos.Y == 0) rock_pos = Vector2.Zero; > * Цей source code був висвітлений з Source Code Highlighter .
Для руху ракети ми просто додаємо кожному циклі гри значення швидкості як двомірного вектора. Зіткнення визначається "вручну" по координатах (для складніших фігур має сенс використовувати інші функції розпізнавання перетинів з XNA) - у разі поразки встановлюється змінна explode, що означає, що кілька кадрів замість корабля буде показаний вибух. Якщо ж ракета досягає верхньої межі екрану – її рух припиняється (встановлюються нульові координати).
Для надання грі остаточно правдоподібності, є сенс дзеркально відображати зображення корабля в тому випадку, коли він пливе вліво (щоб він не плив «задом»). Для цього трохи змінимо код для відображення корабля, додавши можливість дзеркального відображення:
if (explode == 0) spriteBatch.Draw(ship, ship_pos, null , Color.White,0f, Vector2.Zero,1.0f, ship_dir.X>0 ? SpriteEffects.FlipHorizontally :SpriteEffects.None,0f); > lese spriteBatch.Draw(explosion, ship_pos, Color.White);
* Цей source code був highlighted with Source Code Highlighter.
Ось і все, що потрібно для написання найпростішої гри. Щоб зробити її більш привабливою, варто намалювати красиву графічну підкладку, поверх якої розгортатиметься стрілянина – для цього достатньо малювати відповідне зображення на початку spriteBatch, щоб усі подальші об'єкти відмальовувалися поверх картинки. Також можна додати лічильник попадань – при цьому для малювання рядка треба буде використовувати метод spriteBatch.DrawString, а шрифт, яким виводитиметься рядок, треба буде помістити в ресурси проекту та завантажити у методі LoadContent.
Ви можете завантажити вихідний текст всього проекту, щоб на дозвіллі розібратися з ним, або використовувати як відправну точку для створення своїх ігор. Звичайно, цей приклад був дуже простим, і не є гарним прикладом того, як треба писати ігри насправді. Багато більш реалістичні приклади можна знайти:
- Прекрасний навчальний курс англійською мовою з написання двовимірної гри на сайті create.msdn.com
- Набір англомовних лабораторних робіт з XNA, включаючи розробку 2D-ігри Catapult Wars, в якій добре показується процес структурування гри екранами. Аналогічні лабораторні роботи є локалізованою українською мовою Windows Phone 7 Training Kit
- Для любителів почитати текст є книга Чарльза Петзольда «Програмування Windows Phone 7» українською мовою.
- Відеоролики з XNA на сайті TechDays.ru
- Спільнота XNADev.ru — українськомовний ресурс, на якому ви не тільки знайдете інформацію, але й зможете поставити запитання у форумах та отримати кваліфіковану відповідьвід спільноти