HTML 5 canvas първата ни анимация.
12-09-2010
След като видяхме основните функции за рисуване, вече можем да се захванем с нещо по-интересно. Една от възможностите на canvas е да се създават анимации. В този урок ще ви покажа как да накарате един предмет да се движи в canvas. След това ще го накараме да седи в границите на canvas и накрая ще направим един вид игра. Най-отдолу ще има една дъска върху, която ще трябва да тупка топчето иначе играта ще спре.

Нека да започнем с html кода. При него няма нищо ново. Просто една html 5 страница и canvas елемент на нея.

<!DOCTYPE html>
<html>
<head>
<title>Canvas Animation Demos</title>
<meta charset="utf-8">
<style>
canvas { border: 1px dotted black; }
</style>
<script>
function init() {

}
</script>
</head>
<body>
<canvas id="my_canvas" width="400" height="400"></canvas>
<button onclick="init()">Стартирай кода!</button>
</body>
</html>


Сега ще направи две функции. Едната ще създава кръг по зададени параметри x, y и r. Като x е колко пиксела наляво, a y колко пиксела надолу от горния ляв ъгъл на canvas елемента да се нарисува кръга. r е радиуса на кръга.
Другата функция ще създава правоъгълник и ще приема параметри x, y, width и height. Като x е колко пиксела наляво, a y колко пиксела надолу от горния ляв ъгъл на canvas елемента да се нарисува правоъгълника. width и height са широчината и височината на правоъгълника.


function circle(x,y,r) {
ctx.beginPath();
ctx.arc(x, y, r, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();
}

function rect(x, y, width, height) {
ctx.beginPath();
ctx.fillRect(x, y, width, height);
ctx.closePath();
}


Сега ще направим една функция, която да изчиства платното. Това се налага да го направим, защото като почнем да анимираме и ако не изчистим платното, ще се изрисува всяко състояние на платното и няма да изглежда като движение:

function clear() {
ctx.clearRect(0, 0, width, height);
rect(0, 0, width, height);
}


Последната помощна функция, която ни трябва ще е:

function draw() {

}

Засега тя ще остане празна, но тук ще напишем по-голямата част от кода ни.

След като създадохме тези 4 функции, вече можем да започнем да правим самата анимация. Тези 4 функции ще са ни един вид като помощни функции.


Вече можем да създадем init функцията, която ще намира canvas елемента в DOM, след това ще присвои contexta за рисуване на променлива и ще вземе широчината и височината на canvas платното (те ще ни трябват за по-натам в анимацията).
Целият скрипт между таговете <script> и </script> ще е това:

var canvas;
var ctx;
var width; //широчината на canvas платното
var height; //височината на canvas платното
var bgColor = "black"; //background на canvas платното

function init() {
canvas = document.getElementById('my_canvas');
ctx = canvas.getContext("2d");
width = canvas.width;
height = canvas.height;
}

function draw() {

}

function clear() {
ctx.clearRect(0, 0, width, height);
rect(0, 0, width, height);
}

function rect(x, y, width, height) {
ctx.beginPath();
ctx.fillRect(x, y, width, height);
ctx.closePath();
}

function circle(x,y,r) {
ctx.beginPath();
ctx.arc(x, y, r, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();
}



ДЕМО

Засега това демо не трябва да показва нищо. Но на следващата стъпка ще създадем едно черно топче.

Първото, което е трябва да създадем няколко променливи, който ще определят какъв да ни е кръга.

Затова под

var bgColor = "black";

трябва да добавим новите променливи

var bgColor = "black";
var ballRadius = 10; //Радиус на топчето
var x = 20; //x позиция на топчето
var y = 250; //y позиция на топчето
var ballColor = "white"; //цвят на топчето


След като сме създали тези променливи вече можем да преминем към самото изрисуване на топчето. Като начало най-отдолу в init функцията трябва да извикаме draw();. Това ще стане с този код:

function init() {
canvas = document.getElementById('my_canvas');
ctx = canvas.getContext("2d");
width = canvas.width;
height = canvas.height;
draw();
}


И сега да напишем самата функция draw():

function draw() {
ctx.fillStyle = bgColor;
clear();
ctx.fillStyle = ballColor;
circle(x, y, ballRadius);
}


Тази функция сменя цвета за запълване на bgColor (в примера - черен). След това извиква функцията, clear (от помощните функции), която от своя страна изчиства платното и после го запълва с квадрат с цвета, дефиниран преди това.
След това във функцията draw отново сменяме цвета. Този път слагаме цвета на топчето. Викаме спомагателната функция circle, която изрисува кръг на позицията, зададена от нас.

Сглобения код можете да копирате от демото. Така ще е за всички кодове надолу. Ще описвам промените, а накрая в демото ще имате сглобен готов код.

ДЕМО


Нека накараме топчето да се движи. За да можем да анимирам топчето функцията draw в init не трябва да се вика директно, а да се направи да се вика през определен интервал от време. Така init функцията ще изглежда по следния начин:

function init() {
canvas = document.getElementById('my_canvas');
ctx = canvas.getContext("2d");
width = canvas.width;
height = canvas.height;
gameInterval = setInterval(draw, 10);
}


Този код ще вика функцията draw на всеки 10 милисекунди. Но още не сме накарали топчето да се движи. Това е така, защото функцията draw се вика, но тя изчертава топчето винаги на едно и също място. За да можем да местим топчето ще добавим две нови променливи веднага под :

var ballColor = "white";


и новия код ще изглежда така:

var ballColor = "white";
var dx = 1.5; //стойността, с която ще променяме местоположението на топчето по x координатата
var dy = -4; //стойността, с която ще променяме местоположението на топчето по y координатата


След като сме създали тези две променливи вече можем да направим топчето да се мести. Както се досещате ще трябва да променим draw функцията.

function draw() {
ctx.fillStyle = bgColor;
clear();
ctx.fillStyle = ballColor;
circle(x, y, ballRadius);

x += dx;
y += dy;
}


Просто в края на функцията добавяме x да е равно на стойността си + стойността на dx. Същото правим и за y. Така всеки път като init функцията извика draw топчето ще се нарисува на нова позиция. Точно затова и в началото на функцията draw викаме clear(). Ако не го извикаме ще изглежда, че топчето рисува права линия.

ДЕМО

Целия код до тук е в демото. Както се вижда топчето стига до горната част на canvas елемента и излиза от него. За да направим топчето да седи в canvas елемента трябва да сложим няколко проверки преди:

x += dx;
y += dy;


Проверките ще проверяват дали топчето е стигнало до единия край и ако е стигнало ще променя съответната стойност на dx (или dy) с отрицателна такава. Така ако например сме стигнали до началото отгоре (както става сега). Вместо dy да намалява с -4, като сложим минус пред него ще почне да се увеличава с 4.
И сега нека обновим draw функцията.


function draw() {
ctx.fillStyle = bgColor;
clear();
ctx.fillStyle = ballColor;
circle(x, y, ballRadius);

if (x + dx + ballRadius > width || x + dx - ballRadius < 0)
dx = -dx;
if (y + dy + ballRadius > height || y + dy - ballRadius < 0)
dy = -dy;

x += dx;
y += dy;
}


ДЕМО

Както се вижда от демото топчето вече седи в платното. Причината да добавяме (или съответно да вадим) ballRadius в проверката е, че ако го няма, топчето ще се скрива и чак тогава ще се връща в платното.

След като направихме топчето да се движи и да седи в платното е време да създадем и дъската отдолу. За нея ще ни трябват още няколко променливи, които ще поставим точно под

var dy = -4;


и вече ще изглежда така:


var dy = -4;
var padX; //X позиция на дъската (ще я изчисляваме динамично в init функцията
var padWidth; //широчина на дъската (ще я изчисляваме динамично в init функцията
var padHeight; //височина на дъската (ще я изчисляваме динамично в init функцията
var padColor = "white"; //Цвят на дъската


И сега нека изчислим стойностите за padX, padWidth и padHeight. Новата ни init функция ще изглежда така:


function init() {
canvas = document.getElementById('my_canvas');
ctx = canvas.getContext("2d");
width = canvas.width;
height = canvas.height;
padWidth = width/4.5;
padHeight = height/40;
padX = (width/2) - (padWidth/2);
gameInterval = setInterval(draw, 10);
}


Широчината на дъската я правим 4.5 пъти по-малка от широчината на canvas. Така трудността ще се запазва независимо дали canvas е 800 или 100 пиксела голям.
Височината на дъската я правим 40 пъти по-малка от тази на canvas.
padX е стойността, от която ще започва да се изрисува дъската. С изчисленията, които сме извърпили върху тази стойност дъската ни винаги ще е в средата

И сега просто трябв да изчертаем дъската. Това ще стане като извикаме rect метода в draw.

function draw() {
ctx.fillStyle = bgColor;
clear();
ctx.fillStyle = ballColor;
circle(x, y, ballRadius);
ctx.fillStyle = padColor;
rect(padX, height-padHeight, padWidth, padHeight);

if (x + dx + ballRadius > width || x + dx - ballRadius < 0)
dx = -dx;
if (y + dy + ballRadius > height || y + dy - ballRadius < 0)
dy = -dy;

x += dx;
y += dy;
}


ДЕМО

Имаме движещо се топче и дъска, която стои неподвижна. Сега ще накараме дъската да се движи. Първо трябва да направим две променливи, който да регистрират дали е натисната стрелката за наляво или надясно. Тях ще ги поставим веднага под

var padColor = "white";

и новия ни код ще изглежда така:


var padColor = "white";
var rightDown = false;
var leftDown = false;


Причината да и задаваме на тази променлива стойност по подразбиране false e, че нито един бутон не е натиснат в началото.
Сега ще създадем две функции, които ще променят стойностите на двете променливи ако натиснем някой бутон. После ще добавим документа като слушател на събития keydown и keyup. Предметът на урока не е свързан с основите на javascript, затова няма да се впускам в обяснения какво е събитие и т.н.

function onKeyDown(evt) {
if (evt.keyCode == 39) rightDown = true;
else if (evt.keyCode == 37) leftDown = true;
}

function onKeyUp(evt) {
if (evt.keyCode == 39) rightDown = false;
else if (evt.keyCode == 37) leftDown = false;
}
document.addEventListener('keydown',onKeyDown);
document.addEventListener('keyup',onKeyUp);


Първата функция се активира ако сме натиснали копче от клавиатурата. Тя получава параметър, копчето, което е активирало събитието. Проверяваме кода дали копчето е стрелката за наляво или надясно и ако е така - променяме съответната променлива rightDown или leftDown на true;
Другата функция се активира ако сме отпуснали копчето. Ако копчето е наляво или надясно, тогава правим съответната променлива на false, за да спрем местенето

И сега, за да направим да се движи дъската просто трябва да сложим проверка дали променливите са със стойност true и да извадим или добавим стойност към padX. Това естествено ще стане в draw функцията преди да изрисуваме дъската. И новата draw функция ще изглежда така:

function draw() {
ctx.fillStyle = bgColor;
clear();
ctx.fillStyle = ballColor;
circle(x, y, ballRadius);
if (rightDown) padX += 5;
else if (leftDown) padX -= 5;
ctx.fillStyle = padColor;
rect(padX, height-padHeight, padWidth, padHeight);

if (x + dx + ballRadius > width || x + dx - ballRadius < 0)
dx = -dx;
if (y + dy + ballRadius > height || y + dy - ballRadius < 0)
dy = -dy;

x += dx;
y += dy;
}


ДЕМО

Сега вече и дъската се мести, но проблемът е, че топчето не се влияе от тупкането на върху дъската и все още може да тупка по долната част на canvas елемента. Това ще го променим като пак в draw метода променим малко проверките дали топчето се е докоснало до стените. Така новия ни draw метод ще стане така:

function draw() {
ctx.fillStyle = bgColor;
clear();
ctx.fillStyle = ballColor;
circle(x, y, ballRadius);
if (rightDown) padX += 5;
else if (leftDown) padX -= 5;
ctx.fillStyle = padColor;
rect(padX, height-padHeight, padWidth, padHeight);

if (x + dx + ballRadius > width || x + dx - ballRadius < 0)
dx = -dx;
if (y + dy - ballRadius < 0)
dy = -dy;
else if (y + dy + ballRadius > height - padHeight) {
if (x > padX && x < padX + padWidth) {
dx = 8 * ((x-(padX+padWidth/2))/padWidth);
dy = -dy;
}
else if (y + dy + ballRadius > height)
clearInterval(gameInterval);
}

x += dx;
y += dy;
}


Няма да се впускам в дълги обяснения, който го интересува може да седи да разгледа кода и да го разбера. Просто вместо да проверяваме както преди дали y + dy + ballRadius > height, сега правим проверка дали топчето е тупнало в пространствто определено от дъската.

ДЕМО

Можем да направим играта да се игре и с мишката, но понеже кода стана твърде много ще постна само едно готово демо.

ДЕМО







/ Трябва да сте регистриран за да напишете коментар /
От: NewGuy
21:30 12-09-2010
Много благодаря. Уроците са супер. Радвам се, че внесе още малко яснота за html5. Надявам се ако имаш време да продължиш. :)
От: appmaster
1:07 13-09-2010
Само ми е много смешно, като почна да кликам няколко пъти по Стартирай код-а - и почва се движи по-фаст анимацията :D
От: Mapu0
12:24 13-09-2010
Ееее при всяко нещо си има бъгове,няма начин но иначе HTML5 има добри новости :)
От: Mapu0
12:24 13-09-2010
Ееее при всяко нещо си има бъгове,няма начин но иначе HTML5 има добри новости :)
От: I_V_O
20:59 13-09-2010
Евала, че отделяш време за подобни уроци :) Ще използвам урока да споделя един от старите си canvas експерименти.

http://voidstudio.eu/ivo/canvas_game.html
1