CSS властивості
ГлавнаяCanvasСтворення гри в Canvas

Створення гри в Canvas

178

У цьому уроці ви будете використовувати анімацію та просунуті навички малювання яким ви вже навчилися для створення простої ігри у стилі Space Invaders. Щоб ви могли зосередитись на графіку я вже надав скелет гри. Користувач має космічний корабель, який він може рухати ліворуч і праворуч за допомогою стрілок на клавіатурі та вогонь через пробіл. Прибульці вгорі екрана рухаються, доки випадково стріляють ракетами. Код містить простий алгоритм зіткнень і вбиває прибульців, коли в них потрапляє бластер користувача та вбиває гравця, коли в корабель потрапляє ракета прибульця. Вся графіка відображається як простих прямокутників. Подивіться швидко спочатку, а потім ми почнемо це прикрашати.

Проста графіка на прямокутниках

Версія 1. Проста графіка на прямокутниках (клацніть для запуску)

Малювання корабля за допомогою спрайту

У папці з цим документом та файлами game*.html створіть новий HTML-файл з ім'ям mygame.html і скопіюйте в нього game1.html. Цей документ містить початкову версію гри, яку ви бачили вище.

Перше, що ми зробимо, це проведемо апгрейд космічного корабля гравця. Для цього ми скористаємося зображенням, яке я взяв із чудового сайту LostGarden.com.

Спочатку потрібно змінити розміри гравця, щоб вписати зображення. Нам потрібен тільки верхній спрайт по центру зображення, розмір якого 46x46 пікселів, так що додайте цей код у верхню частину game.html, щоб встановити розмір об'єкта гравця.

<code data-language="javascript">var can=document.getElementById("canvas"); var c=can.getContext('2d'); //новий код player.width=46; player.height=46;

Тепер нам потрібно завантажити зображення в об'єкт, щоб ми могли використовувати його. Створити змінну ship_image, потім викличте функцію loadResources(), щоб завантажити зображення на старті.

<code data-language="javascript">player.width=46; player.height=46; //новий код var ship_image; loadResources(); function loadResources() { ship_image=новий Image(); ship_image.src="images/Hunter1.png"; }

Тепер переходимо до функції drawPlayer. Ми змінимо останні два рядки, щоб замість однотонного прямокутника намалювати картинку.

<code data-language="javascript">c.fillStyle="red"; c.fillRect(player.x,player.y, player.width, player.height); c.drawImage(ship_image, 25,1, 23,23, //вихідні координати player.x, player.y, player.width, player.height //кінцеві координати);

Погляньмо на те, що тут відбувається. Наше зображення насправді містить вісім версій космічного корабля, але ми хочемо намалювати лише одну з них. drawImage намалює підрозділ зображення, передаючи вихідні та кінцеві координати. Координати вихідного коду визначають, яка частина зображення буде взята. Кінцеві координати визначають, де на полотні буде малюнок і як великий. Змінюючи ці цифри, ви можете легко створити цікавий ефект розтягування, кадрування та масштабування.

Для цього прикладу ми намалюємо лише частину зображення, яка знаходиться на 25 пікселів від лівого краю та 23 пікселів за висотою. Потім ми малюємо картинку на полотні з координатами, шириною та висотою гравця. Зверніть увагу, що раніше ми задали ширину та висоту як 46x46. Це подвоєний розмір вихідного коду 23x23. Я зробив це навмисне. Це має бути гра в стилі ретро, тому я захотів збільшити графіку, щоб вона виглядала піксельною та забавною.

Тепер збережіть файл і оновіть ваш браузер. Це має виглядати так:

Корабль намальований через спрайти

Версія 2. Корабель намальований через спрайти (клацніть для запуску)

Анімація спрайтів для куль та бомб

Тепер нам потрібно кілька спрайтів для куль космічного корабля та бомб прибульців. Знову ми завантажимо зображення до змінних. Оновіть верхню частину коду так, щоб він виглядав так.

<code data-language="javascript">var ship_image; var bomb_image; var bullet_image; loadResources(); function loadResources() { ship_image=новий Image(); ship_image.src="images/Hunter1.png"; bomb_image=new Image(); bomb_image.src="images/bomb.png"; bullet_image=новий Image(); bullet_image.src="images/bullets.png"; }

Це завантажить наступні зображення.

images/bullets.png (збільшено вчетверо)

images/bomb.png (збільшено вчетверо)

Зверніть увагу, що дані зображення також містять кілька спрайтів. Однак у цьому випадку ми хочемо використати усі спрайти. Кожен кадр анімації. Зациклюючи спрайти, ми створимо ілюзію анімації на екрані. Зробимо це так само, як і раніше, малюючи фрагмент основного зображення, але цього разу ми змінюватимемо координати кожного кадру.

<code data-language="javascript">function drawPlayerBullets(c) { c. fillStyle="blue"; for(i in playerBullets) { var bullet=playerBullets[i]; var count=Math.floor(bullet.counter/4); var xoff=(count% 4) * 24; //c.fillRect(bullet.x, bullet.y, bullet.width,bullet.height); c.drawImage( bullet_image, xoff+10,0+9,8,8, bullet.x,bullet.y,bullet.width,bullet.height); } 
}

Код вище схожий на те, що ми робили раніше за винятком змінних xoff, count та bullet.counter. Кожна куля має свій лічильник counter. Це число, яке починається з 0, коли куля створюється і збільшується на 1 у кожному кадрі. count — це просто лічильник поділений на чотири. Анімація всього кількох кадрів, що працюють при 60 кадрах в сек., була б занадто швидкою, щоб її побачити, так що уповільнюємо її вчетверо.

xoff це count поділений за модулем на 4 і означає, що у нас тепер у циклі є число від 0 до 3. Потім ми множимо його на 24, це ширина кожного спрайту. xoff проходить у циклі за значеннями 0, 24, 48, 72 знову і знову, даючи нам зміщення по x, що постійно змінюється, в майстер-зображенні (зайві +10 враховують додатковий простір по лівому краю майстер-зображення).

Код вище додає анімацію спрайту куль. Тепер ми проробимо те саме для бомб змінивши в коді createEnemyBullet і drawEnemyBullets.

<code data-language="javascript">function createEnemyBullet(enemy) { return { x:enemy.x, y:enemy.y+enemy.height, width:4, height:12, width:30, height:30, counter:0, } 
} 
function drawEnemyBullets(c) { for(var i in enemyBullets) { var bullet=enemyBullets[i]; c.fillStyle="yellow"; c.fillRect(bullet.x, bullet.y, bullet.width, bullet.height); var xoff=(bullet.counter%9) * 12 + 1; var yoff=1; c.drawImage(bomb_image, xoff,yoff,11,11, bullet.x,bullet.y,bullet.width,bullet.height); } 
}

Зверніть увагу, що в коді вище ми змінили розмір ворожих бомб за замовчуванням на 30. Таким чином, функції виявлення зіткнень будуть використовувати той самий розмір, що і малюнки. Ми повинні зробити те ж саме для куль космічного корабля у функції firePlayerBullet.

<code data-language="javascript">function firePlayerBullet() { //створюємо нову кулю playerBullets.push({ x: player.x, x: player.x+14, y: player.y — 5, width:10, height:10, width:20, height:20, counter:0, }); }

Тепер наша гра виглядає так. Якщо у вас виникли проблеми, порівняйте свій код з файлом game3.html . Вони повинні бути однаковими.

Противники кидаються спрайтами

Версія 3. Противники кидаються спрайтами (клацніть для запуску)

Процедурна графіка для прибульців

Давайте змінимо спосіб малювання прибульців. Замість використання спрайтів ми зробимо це процедурно, це означає, що все малювання знаходиться в коді, а не заздалегідь намальовано. Нашою метою є зелене коло, заповнене потоком маленьких білих куль, які плавають навколо в циклі. Вони виглядають так:

Оскільки це буде радикальна зміна код малювання противника, то створимо нову функцію з ім'ям drawEnemy(). Для початку змінимо drawEnemies() щоб з неї викликалася функція drawEnemy:

<code data-language="javascript">function drawEnemies(c) { for(var i in enemies) { var enemy=enemies[i]; if(enemy.state=="alive") { c.fillStyle="green"; drawEnemy(c,enemy,15); } 
if(enemy.state=="hit") { c.fillStyle="purple"; enemy.shrink--; drawEnemy(c,enemy,enemy.shrink); } 
//ймовірно, ніколи не буде викликана if(enemy.state=="dead") { c.fillStyle="black"; c.drawEnemy(c,enemy,15); } 
} 
}

Тепер створіть функцію drawEnemy() на зразок цієї:

<code data-language="javascript">function drawEnemy(c,enemy,radius) { if(radius <=0) radius=1; var theta=enemy.counter; c.save(); c.translate(0,30); //малюємо фонове коло circlePath(c, enemy.x, enemy.y, radius*2); c.fill(); //малюємо хвилясті точки for(var i=0; i<10; i++) { var xoff=Math.sin(toRadians(theta+i*36*2))*radius; var yoff=Math.sin(toRadians(theta+i*36*1.5))*radius; circlePath(c, enemy.x + xoff, enemy.y + yoff, 3); c.fillStyle="white"; c.fill(); } 
c.restore(); } 
function toRadians(d) { return d * Math.PI * 2.0 /360.0; } 
function circlePath(c, x, y, r) { c.beginPath(); c.moveTo(x,y); c.arc(x,y, r, 0, Math.PI*2); }

Код вище кілька складний, так уважно розберемо його по кроках. Функція drawEnemy містить три аргументи: контекст малювання (c), противник для малювання та радіус закручених кульок. Для початку обчислюємо кут theta на основі внутрішнього лічильника супротивника. Це зробить позицію куль трохи зміщеною у кожному кадрі. Наступний код малює коло та заливає його поточним кольором. circlePath це маленька функція-утиліта для малювання кола.

Нарешті, в циклі десять разів малюємо маленькі білі кола. Положення кожного кола виходить із значень xoff та yoff. Виглядає складно, але насправді це досить просто. Значення х — це синус поточного кута помножений на радіус. Значення у — також синус поточного кута помножений на радіус. Щоб змістити значення у кожному кадрі ми додаємо значення theta: i*36*2. Налаштування значення схоже: i*36*1.5. Якщо налаштування виявилися б однаковими, то точки рухатимуться по прямій лінії. Роблячи їх трохи різними, ми створили шаблон кружляння. Я вибрав саме ці числа, просто граючи зі значеннями. Базова тригонометрія може створити безліч цікавих рухів, ви повинні просто пограти з ними, поки не знайдете, що сподобалося. Спробуйте змінити 1.5 до 3.0 і побачите як це вплине на результат.

Якщо невелике фінальне шліфування давайте зробимо плавно зникаючий текст «game over /swarm defeated» замість його простої появи. Ми вже маємо об'єкт overlay з лічильником, який ми можемо використовувати для налаштування альфи протягом часу. Нам просто потрібно перевизначити drawOverlay встановивши значення globalAlpha і намалювати текст:

<code data-language="javascript">function drawOverlay(c) { if(overlay.counter==-1) return; //Поява var alpha=overlay.counter/50.0; if(alpha > 1) alpha=1; c.globalAlpha=alpha; c.save(); c.fillStyle="white"; c.font="Bold 40pt Arial"; c.fillText(overlay.title,140,200); c.font="14pt Arial"; c.fillText(overlay.subtitle, 190,250); c.restore(); }

Ось як гра зараз виглядає.

Прибульці з процедурною анімацією

Версія 4. Прибульці з процедурною анімацією (клацніть для запуску)

Симулятор частинок для вибухів

Тепер давайте нарешті додамо справжній вибух з використанням частинок, коли гравець помирає. Для початку, перенесемо вибух гравця в окрему функцію, як:

<code data-language="javascript">function drawPlayer(c) { if(player.state=="dead") return; if(player.state=="hit") { c.fillStyle="yellow"; c.fillRect(player.x,player.y, player.width, player.height); drawPlayerExplosion(c); return; } 
c.drawImage(ship_image, 25,1, 23,23, //вихідні координати player.x, player.y, player.width, player.height //кінцеві координати); }

Тепер ми створимо просту систему частинок. Згадаймо з лекції, що система частинок просто список об'єктів простих частинок, які ми оновлюємо і малюємо в кожному кадрі. Для вибуху ми хочемо, щоб частки стартували там же, де гравець і розширювалися у довільному напрямку із випадковою швидкістю. Код створення частинок виглядає так.

<code data-language="javascript">var particles=[]; function drawPlayerExplosion(c) { //старт if(player.counter==0) { particles=[]; //стираємо всі старі значення for(var i=0; i<50; i++) { particles.push({ x: player.x + player.width/2, y: player.y + player.height/2, xv : (Math.random()-0.5)*2.0*5.0, //швидкість x yv: (Math.random()-0.5)*2.0*5.0, //швидкість y age: 0, }); } 
}

Зверніть увагу, що значення швидкості починаються з випадкового числа. Math.random завжди повертає значення від 0 до 1. Віднімаючи 0.5 і помножуючи на 2 ми отримуємо випадкове число від-1 до 1. Тоді ми можемо дещо масштабувати його, якщо це виглядає занадто швидко для гри. Не соромтеся підправити значення 5.0.

Тепер нам потрібно оновити та намалювати кожну частинку:

<code data-language="javascript"> //оновлюємо та малюємо if(player.counter > 0) { for(var i=0; i<particles.length; i++) { var p=particles[i]; px +=p.xv; py +=p.yv; var v=255-p.age*3; c.fillStyle="rgb("+v+","+v+","+v+")"; c.fillRect(px,py,3,3); p.age++; } 
} 
};

Нове положення кожної частинки це старе положення плюс швидкість. Потім обчислюємо значення кольору v на основі віку частки. Оскільки ми маємо справу зі значеннями rgb, то нам потрібне число, що починається з 255 і зменшує з часом. Тоді колір буде починатися з білого і плавно переходити до чорного.

Ось як виглядає фінальна гра.

Завершена гра

Версія 5. Завершена гра (клацніть для запуску)

Висновок

Ця практична глава ледве стосується всіх можливостей Canvas. Я закликаю вас попрацювати з цим зразком гри, додаючи фон, змінюючи кольори, регулюючи швидкість анімації та вибираючи нові спрайти. com/2005/03/download-complete-set-of-sweet-8-bit.html" rel="nofollow">тут. LostGarden.com містить велику колекцію безкоштовного ігрового арту, а також безліч чудових нотаток по геймдизайну. Я настійно рекомендую вам прочитати їх.