View
28
Download
0
Category
Preview:
Citation preview
ISEL / DEETC
Licenciatura em Engenharia de Redes de Comunicao e Multimdia
Produo de Contedos Multimdia
Aula de Laboratrio
de
Produo de Contedos
Multimdia
Space Invaders HTML5 e JavaScript
Rui Jesus
Introduo Este trabalho visa introduzir o HTML5 e a manipulao das novas tags, de forma programtica
em JavaScript utilizando o DOM. tambm objectivo aprender a fazer animaes e a trabalhar
com imagens utilizando APIs em JavaScript e o objecto canvas. So utilizadas as tcnicas mais
comuns relativamente utilizao de udio, de eventos, do teclado e do rato. Em geral, so
introduzidos os princpios bsicos para desenvolver aplicaes multimdia (particularmente
jogos) em HTML5/JavaScript. Em baixo so enumerados dois links que deve consultar durante
o desenvolvimento deste trabalho.
w3schools.com HTML5
http://www.w3schools.com/html/html5_intro.asp
w3schools.com JavaScript
http://www.w3schools.com/js/default.asp
Trabalho Laboratorial
Space Invaders
Este guia descreve os passos principais para implementar em HTML5 uma verso do
tradicional jogo Space Invaders. O objectivo do jogo destruir um conjunto de naves inimigas
(aliens) para ganhar o maior nmero de pontos possvel. O jogador controla os movimentos de
uma nave que se movimenta na parte inferior da tela. Da parte superior marcham em direo
nave os aliens. O objectivo do jogador evitar que os aliens atinjam a parte inferior da tela, para
essa tarefa, a nave possui munio infinita para atirar.
A figura seguinte exemplifica um aspecto possvel da interface do jogo a desenvolver.
Tambm so indicadas algumas das entidades que compem o jogo:
Ship - nave controlada pelo utilizador (teclado e rato);
Enemy - nave inimigas que marcha do topo para a parte inferior;
Background - animao com partculas que descem aleatriamente do topo;
Bullet - bala da nave para destruir as naves inimigas;
Enemy_Bullet - balas das naves inimigas para destruir a nave controlada pelo utilizador;
Score - espao da interface onde so apresentados alguns dados do jogo (e.g.,
pontuao).
Game - motor de jogo;
Estas entidades representam algumas das funes construtoras a desenvolver.
necessrio implementar tambm funo construtora principal que vai servir de motor de jogo
(Game).
WebStorm JavaScript IDE
1. Faa download do WebStorm seguindo o link, http://www.jetbrains.com/webstorm/ .
2. Instale a ferramenta de desenvolvimento no seu PC.
Projecto no WebStorm
3. Execute o WebStorm. Crie um novo projecto pressionando ALT+F para activar o menu
File e seleccione a opo New Project. Na janela (ver Figura 1) indique o nome do
projecto, a directoria e o tipo de projecto (Empty project).
Ship
Score
Enemy
Bullet Background
Enemy_Bullet
Figura 1.
4. Na janela do Projecto (ver Figura 2) crie quatro directorias (MRB sobre o ttulo do
projecto, seleccione NEW e depois Directory) com os seguintes nomes:
css - directoria para guardar os ficheiros com os estilos; js - directoria para guardar os ficheiros JavaScript; imgs - directoria para guardar as imagens necessrias no projecto; sounds - directoria para guardar os ficheiros de udio necessrios no
projecto.
Figura 2.
5. Faa novamente MRB (Mouse Right Button) sobre o nome do projecto na janela de
projecto e NEW. A seguir, seleccione HTML FILE para criar um ficheiro HTML
onde iremos colocar os elementos HTML e onde faremos a ligao ao JavaScript. D o
nome de Space_Invaders ao ficheiro.
6. Faa MRB sobre a directoria css, seleccione NEW e depois FILE. D o nome de
styling.css ao ficheiro que acabou de criar.
7. Proceda da mesma forma que no ponto anterior mas agora sobre a directoria js e
seleccionando a opo JavaScript File. Crie um ficheiro JavaScript com o nome,
space_invaders.js.
8. Faa download do ficheiro data.zip na pgina da unidade curricular no Moodle. Copie as
imagens para a directoria do projecto imgs e os ficheiros de udio para a directoria
sounds.
Background do jogo e objecto game
9. Copie o seguinte cdigo para o ficheiro space_invaders.html:
No cdigo em cima, no bloco , aparecem elementos que permitem introduzir
meta-informao na pgina e as ligaes folha de estilo e ao cdigo em JavaScript. No
bloco temos a definio do canvas porque o jogo implementado utilizando o
elemento . Finalmente, chamada a funo init() (que ser definida em
JavaScript) como resposta ao evento onload que activado quando a pgina HTML
estiver totalmente carregada.
10. Coloque o seguinte cdigo na folha de estilo (styling.css):
Space Invaders Your browser does not support canvas. Please try again with a different browser. window.onload = init();
Explique as linhas anteriores relativas formatao do canvas.
11. No ficheiro space_invaders.js coloque o seguinte cdigo:
Execute o programa, ALT+SHIFT+F9. No se esquea de definir qual o browser que est
a utilizar (Chrome ou Firefox). Qual o resultado do programa? Para poder ver o
elemento altere a sua cor de fundo para vermelho. Experimente variar a
localizao e as dimenses do canvas.
12. No jogo vamos utilizar vrias imagens (e.g., background e ship) e algumas so reutilizadas
vrias vezes. Por isso, vamos criar um objecto (imageRepository) para guardar em memria
as vrias imagens. Desta forma, apenas utilizamos um objecto image e no necessrio
fazer a leitura do ficheiro cada vez que fr necessrio utilizar a imagem. Copie a funo
construtora seguinte para o ficheiro JavaScript:
13. Para desenhar os vrios objectos no canvas vamos criar o objecto Drawable. Este objecto
um objecto abstrato. Os restantes objectos do jogo vo herdar as suas propriedades e
redefinir ou criar novos mtodos. Copie o seguinte cdigo para o ficheiro JavaScript:
canvas { position: absolute; top: 150px; left: 150px; background: transparent; }
function init() { document.write("Welcome to the Space Invaders Tutorial"); }
var imageRepository = new App_images(); function App_images() { // Define images this.empty = null; this.background = new Image(); // Set images src this.background.src = "imgs/bg.png"; }
14. Coloque o cdigo em baixo no ficheiro JavaScript:
O objecto Background serve para desenhar a imagem de fundo. Vamos fazer uma
animao com o fundo. Por isso, desenhamos duas vezes a imagem, a primeira vez a
ocupar todo o espao do canvas e na segunda vez a ocupar o espao em cima do canvas.
Com a animao as duas imagens vo se movimentando para baixo, e desta forma temos
um efeito de scrolling.
15. O objecto principal do jogo o objecto game. Copie a funo construtora para o ficheiro
JavaScript:
function Background() { this.speed = .5; // Implement abstract function this.draw = function() { this.y += this.speed; this.context.drawImage(imageRepository.background, this.x, this.y); this.context.drawImage(imageRepository.background, this.x, this.y - this.canvasHeight); if (this.y >= this.canvasHeight) this.y = 0; }; } // Set Background to inherit properties from Drawable Background.prototype = new Drawable();
function Drawable() { this.init = function(x, y) { // Defualt variables this.x = x; this.y = y; } this.speed = 0; this.canvasWidth = 0; this.canvasHeight = 0; // Define abstract function to be implemented in child objects this.draw = function() { }; }
Nesta a funo construtora Game apenas tem dois mtodos. O mtodo init() que vai
buscar o elemento html , verifica se o browser o suporta e de seguida constri
o objecto Background. O mtodo start() chama a funo global animate() que responsvel
pela animao.
16. A animao realizada com o seguinte cdigo:
function Game() { this.init = function() { // Get the canvas element this.bgCanvas = document.getElementById('background'); // Test to see if canvas is supported if (this.bgCanvas.getContext) { this.bgContext = this.bgCanvas.getContext('2d'); // Initialize objects to contain their context and canvas // information Background.prototype.context = this.bgContext; Background.prototype.canvasWidth = this.bgCanvas.width; Background.prototype.canvasHeight = this.bgCanvas.height; // Initialize the background object this.background = new Background(); this.background.init(0,0); // Set draw point to 0,0 return true; } else { return false; } }; // Start the animation loop this.start = function() { animate(); }; }
A funo animate() utilizada na funo/evento requestAnimFrame() que se repete. Caso o
browser no seja compatvel utilizado um timer.
17. Antes de executar o programa, necessrio construir o objecto game (utilizando a funo
construtora Game) e na funo init() verificar se possvel iniciar o jogo (if game.init()). Em
caso afirmativo, preciso chamar o mtodo start() do objecto game.
18. Altere a velocidade do scrolling do Background. Experimente outras imagens como
background.
Ship e Bullet 19. O jogo tem vrios elementos com diversos tipos de movimento. Por isso, vamos criar 3
canvas: (1) movimento do background; (2) movimento dos inimigos (Enemy) e das balas
(Enemy_Bullet); (3) movimento da nave controlada pelo utilizador (Ship) e as balas
(Bullet). Assim, sempre que existe um movimento no preciso redesenhar todo o jogo.
O ficheiro html fica assim:
// This function must be global function animate() { requestAnimFrame( animate ); game.background.draw(); } //Finds the first API that works to optimize the animation loop, //otherwise defaults to setTimeout(). window.requestAnimFrame = (function(){ return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(/* function */ callback, /* DOMElement */ element){ window.setTimeout(callback, 1000 / 60); }; })();
Para alm de criar os trs , foi retirado o evento window.onload que permitia
chamar a funo init() aps a pgina ter sido carregada. Agora, fazemos ligao ao
ficheiro JavaScript que comea a ser executado. Vamos ver a seguir porque que tem de
ser assim.
20. O repositrio de imagens fica com trs imagens. Assim, a funo construtora
App_Images() fica com o cdigo em baixo:
Space Invaders Your browser does not support canvas. Please try again with a different browser.
this.background = new Image(); this.spaceship = new Image(); this.bullet = new Image(); var numImages = 3; var numLoaded = 0; function imageLoaded() { numLoaded++; if (numLoaded === numImages) { window.init(); } } this.background.onload = function() { imageLoaded(); } this.spaceship.onload = function() { imageLoaded(); } this.bullet.onload = function() { imageLoaded(); } this.background.src = "imgs/bg.png"; this.spaceship.src = "imgs/ship.png"; this.bullet.src = "imgs/bullet.png";
O que acontece que necessrio fazer o load das imagens antes das utilizar. Por isso,
temos de utilizar o evento onload. O jogo s comea aps as imagens terem sido
carregadas. Explique a sequncia de cdigo que executado aps a link para o JavaScript
em HTML at ao incio do jogo.
21. Actualize a folha de estilo com o cdigo em baixo:
Para cada elemento definido um z-index diferente. Recorrendo ao site do
W3Schools, diga porque razo os trs elementos devem ter um z-index diferente. No se
esquea que os trs canvas esto sobrepostos.
22. O objecto abstrato Drawable fica da seguinte forma:
canvas { position: absolute; top: 100px; left: 100px; background: transparent; } #background { z-index: -2; } #main { z-index: -1; } #ship { z-index: 0; }
function Drawable() { this.init = function(x, y, width, height) { // Defualt variables this.x = x; this.y = y; this.width = width; this.height = height; } this.speed = 0; this.canvasWidth = 0; this.canvasHeight = 0; // Define abstract function to be implemented in child objects this.draw = function() { }; this.move = function() { }; }
Foi acrescentado um novo mtodo abstracto, move(), e no mtodo init() foram
acrescentados dois novos parmetros.
23. Em baixo a funo construtora Bullet:
O objecto Bullet tem 3 mtodos:
spawn para atribuir os valores da localizao, velocidade e torna-lo vivo novamente;
draw para desenhar o bullet. utilizado um rectngulo para apagar e depois desenhado na nova posio;
clear para fazer reset aos atributos do Bullet.
24. O jogo utiliza vrios Bullets, por isso, preciso definir um pool de Bullets. Adicione a
funo construtora pool ao cdigo JavaScript.
function Bullet() { this.alive = false; this.spawn = function(x, y, speed) { this.x = x; this.y = y; this.speed = speed; this.alive = true; }; this.draw = function() { this.context.clearRect(this.x, this.y, this.width, this.height); this.y -= this.speed; if (this.y
Quando o pool inicializado, preenchido um array com objectos Bullet. Quando
preciso um Bullet utilizado o ltimo elemento do array se no estiver a ser utilizado. Se
no estiver a ser utilizado, feito o pop do fim do array e feito o push no inico do array.
Desta forma, temos elementos livres no fim do array e elementos a serem utilizados no
inico do array. Para animar um Bullet, primeiro verificado se o objecto est a ser
utilizado. Em caso afirmativo feito o draw. Se o mtodo draw() do pool retornar true,
significa que o Bullet saiu fora do cenrio. Por isso, feito clear() e com a funo splice() o
Bullet removido do inico do array e depois feito o push() no fim.
function Pool(maxSize) { var size = maxSize; // Max bullets allowed in the pool var pool = []; this.init = function() { for (var i = 0; i < size; i++) { // Initalize the bullet object var bullet = new Bullet(); bullet.init(0,0, imageRepository.bullet.width,imageRepository.bullet.height); pool[i] = bullet; } }; this.get = function(x, y, speed) { if(!pool[size - 1].alive) { pool[size - 1].spawn(x, y, speed); pool.unshift(pool.pop()); } }; this.getTwo = function(x1, y1, speed1, x2, y2, speed2) { if(!pool[size - 1].alive && !pool[size - 2].alive) { this.get(x1, y1, speed1); this.get(x2, y2, speed2); } }; this.animate = function() { for (var i = 0; i < size; i++) { // Only draw until we find a bullet that is not alive if (pool[i].alive) { if (pool[i].draw()) { pool[i].clear(); pool.push((pool.splice(i,1))[0]); } } else break; } }; }
25. Copie o cdigo em baixo:
function Ship() { this.speed = 1; this.bulletPool = new Pool(30); this.bulletPool.init(); var fireRate = 15; var counter = 0; this.draw = function() { this.context.drawImage(imageRepository.spaceship, this.x, this.y); }; this.move = function() { counter++; // Determine if the action is move action if (KEY_STATUS.left || KEY_STATUS.right || KEY_STATUS.down || KEY_STATUS.up) { this.context.clearRect(this.x, this.y, this.width, this.height); if (KEY_STATUS.left) { this.x -= this.speed if (this.x < 0) this.x = -this.speed; } else if (KEY_STATUS.right) { this.x += this.speed if (this.x >= this.canvasWidth - this.width) this.x = this.canvasWidth - this.width; } else if (KEY_STATUS.up) { this.y -= this.speed if (this.y = this.canvasHeight - this.height) this.y = this.canvasHeight - this.height; } this.draw(); } if (KEY_STATUS.space && counter >= fireRate) { this.fire(); counter = 0; } }; this.fire = function() { this.bulletPool.getTwo(this.x+6, this.y, 3, this.x+33, this.y, 3); }; } Ship.prototype = new Drawable();
Explique o cdigo em cima. Aumente o nmero de Bullets disponveis para disparar.
Altere o cdigo para que seja disparado apenas um Bullet (em vez de dois em simultneo)
do centro da nave (Ship).
26. Na funo construtora Game acrescente o seguinte cdigo ao mtodo init():
this.init = function() { this.bgCanvas = document.getElementById('background'); this.shipCanvas = document.getElementById('ship'); this.mainCanvas = document.getElementById('main'); // Test to see if canvas is supported. Only need to // check one canvas if (this.bgCanvas.getContext) { this.bgContext = this.bgCanvas.getContext('2d'); this.shipContext = this.shipCanvas.getContext('2d'); this.mainContext = this.mainCanvas.getContext('2d'); // Initialize objects to contain their context and canvas // information Background.prototype.context = this.bgContext; Background.prototype.canvasWidth = this.bgCanvas.width; Background.prototype.canvasHeight = this.bgCanvas.height; Ship.prototype.context = this.shipContext; Ship.prototype.canvasWidth = this.shipCanvas.width; Ship.prototype.canvasHeight = this.shipCanvas.height; Bullet.prototype.context = this.mainContext; Bullet.prototype.canvasWidth = this.mainCanvas.width; Bullet.prototype.canvasHeight = this.mainCanvas.height; this.background = new Background(); this.background.init(0,0); this.ship = new Ship(); var shipStartX = this.shipCanvas.width/2 - imageRepository.spaceship.width; var shipStartY = this.shipCanvas.height/4*3 + imageRepository.spaceship.height*2; this.ship.init(shipStartX, shipStartY, imageRepository.spaceship.width, imageRepository.spaceship.height); return true; } else { return false; } };
No mtodo start() preciso fazer, this.ship.draw();, antes de chamar a funo global
animate(). Execute o programa para visualizar a nave sobre o fundo. Altere a posio
inicial da nave (Ship) para o canto inferior direito canvas.
27. Para mover a nave e para disparar necessrio utilizar os eventos do teclado para receber
o input do utilizador. Assim, copie o seguinte cdigo:
Para capturar as teclas pressionadas utiliza-se os eventos onkeyup e onkeydown. As funes
handler destes eventos alteram no array KEY_STATUS, a tecla pressionada para true ou
false.
28. Antes de executar o programa preciso acrescentar funo global animate() o seguinte
cdigo:
Comente o resultado.
KEY_CODES = { 32: 'space', 37: 'left', 38: 'up', 39: 'right', 40: 'down', } KEY_STATUS = {}; for (code in KEY_CODES) { KEY_STATUS[KEY_CODES[code]] = false; } document.onkeydown = function(e) { // Firefox and opera use charCode instead of keyCode to // return which key was pressed. var keyCode = (e.keyCode) ? e.keyCode : e.charCode; if (KEY_CODES[keyCode]) { e.preventDefault(); KEY_STATUS[KEY_CODES[keyCode]] = true; } } document.onkeyup = function(e) { var keyCode = (e.keyCode) ? e.keyCode : e.charCode; if (KEY_CODES[keyCode]) { e.preventDefault(); KEY_STATUS[KEY_CODES[keyCode]] = false; } }
game.ship.move(); game.ship.bulletPool.animate();
Enemy e Enemy_Bullet
29. Na funo construtora App_Images(), crie novos objectos do tipo Image (Enemy e
enemy_Bullet) para as imagens, enemy.png e bullet_enemy. A seguir, active o
evento onload para estas duas imagens, tal como est para as restantes imagens. No se
esquea de colocar o nome do ficheiro no atribute src dos novos objectos Image e de
alterar o valor de numImages para o nmero total de imagens.
30. Copie o cdigo em baixo para definir a funo construtora das naves Enemy:
function Enemy() { var percentFire = 0.01; var chance = 0; this.alive = false; this.spawn = function(x, y, speed) { this.x = x; this.y = y; this.speed = speed; this.speedX = 0; this.speedY = speed; this.alive = true; this.leftEdge = this.x - 90; this.rightEdge = this.x + 90; this.bottomEdge = this.y + 160; }; this.draw = function() { this.context.clearRect(this.x-1, this.y, this.width+1, this.height); this.x += this.speedX; this.y += this.speedY; if (this.x = this.rightEdge + this.width) { this.speedX = -this.speed; } else if (this.y >= this.bottomEdge) { this.speed = 1 .5; this.speedY = 0; this.y -= 5; this.speedX = -this.speed; } this.context.drawImage(imageRepository.enemy, this.x, this.y); chance = Math.random(); if (chance < percentFire) { this.fire(); } }; this.fire = function() { game.enemyBulletPool.get(this.x+this.width/2, this.y+this.height, -2.5); } this.clear = function() { this.x = 0; this.y = 0; this.speed = 0; this.speedX = 0; this.speedY = 0; this.alive = false; }; } Enemy.prototype = new Drawable();
31. O jogo contm vrias naves inimigas por isso vamos criar tambm um pool de objectos
Enemy. Assim, necessrio fazer pequenas alterar o mtodo init() na funo construtora
do pool:
Para que se utilize a funo construtora pool() com diferentes objectos necessrio passar
o objecto como parmetro no mtodo init().
32. Da mesma forma, a classe Bullet tambm utilizada para gerar um pool de balas
inimigas, para alm das balas da nave controlada pelo utilizador. Por isso, necessrio
tambm introduzir um parmetro indicando o objecto. Faa as alteraes apresentadas
em baixo na funo construtora Bullet:
this.init = function(object) { if (object == "bullet") { for (var i = 0; i < size; i++) { // Initalize the object var bullet = new Bullet("bullet"); bullet.init(0,0, imageRepository.bullet.width, imageRepository.bullet.height); pool[i] = bullet; } } else if (object == "enemy") { for (var i = 0; i < size; i++) { var enemy = new Enemy(); enemy.init(0,0, imageRepository.enemy.width, imageRepository.enemy.height); pool[i] = enemy; } } else if (object == "enemyBullet") { for (var i = 0; i < size; i++) { var bullet = new Bullet("enemyBullet"); bullet.init(0,0, imageRepository.enemyBullet.width, imageRepository.enemyBullet.height); pool[i] = bullet; } } };
As mudanas so no mtodo draw(), no objecto que passado como parmetro na
funo construtora e na utilizao do self.
33. No mtodo init() do Game necessrio colocar o canvas indicado na classe Enemy,
construir as naves inimigas e as balas das naves inimigas. Acrescente o seguinte cdigo
ao mtodo init() da funo construtora Game:
function Bullet(object) { this.alive = false; var self = object; this.draw = function() { this.context.clearRect(this.x-1, this.y-1, this.width+1, this.height+1); this.y -= this.speed; if (self === "bullet" && this.y = this.canvasHeight) { return true; } else { if (self === "bullet") { this.context.drawImage(imageRepository.bullet, this.x, this.y); } else if (self === "enemyBullet") { this.context.drawImage(imageRepository.enemyBullet, this.x, this.y); } return false; } };
34. Acrescente as duas linhas de cdigo seguintes funo animate():
function Game() { this.init = function() { Enemy.prototype.context = this.mainContext; Enemy.prototype.canvasWidth = this.mainCanvas.width; Enemy.prototype.canvasHeight = this.mainCanvas.height; this.background = new Background(); this.background.init(0,0); // Set draw point to 0,0 this.ship = new Ship(); var shipStartX = this.shipCanvas.width/2 - imageRepository.spaceship.width; var shipStartY = this.shipCanvas.height/4*3 + imageRepository.spaceship.height*2; this.ship.init(shipStartX, shipStartY, imageRepository.spaceship.width, imageRepository.spaceship.height); // Initialize the enemy pool object this.enemyPool = new Pool(30); this.enemyPool.init("enemy"); var height = imageRepository.enemy.height; var width = imageRepository.enemy.width; var x = 100; var y = -height; var spacer = y * 1.5; for (var i = 1; i
35. Faa run (modo DEBUG) do programa e comente. J aparecem as naves inimigas?
provvel que no, porque falta na funo construtora ship incluir o parmetro bullet no
atributo, this.bulletPool.init("bullet");
36. Diminua o nmero de naves inimigas para 10 (2 filas de 5 inimigos). Coloque o conjunto
dos inimigos mais para cima (junto ao limite superior do background). Diminua tambm a
velocidade horizontal com que as naves inimigas se movimentam (movimento esquerda-
direita).
37. No lhe parece que as naves inimigas disparam muitas balas? Diminua a percentagem de
balas disparadas pelas naves inimigas.
Colises
38. As colises so uma das componentes mais importantes deste tipo de jogos, no que diz
respeito eficincia computacional do jogo. Para detectar colises, necessrio verificar
se a bounding box de cada objecto coincide com alguma bounding box dos restantes
objectos. Esta comparao de todos com todos pode ser pesada computacionalmente se
o nmero de objectos fr elevado. No o caso deste jogo. Contudo, por uma questo
de generalidade, a soluo apresentada procura resolver esta questo. Nos prximos 3
blocos de cdigo copie a funo construtora QuadTree correspondente ao algoritmo
quadtree (melhor soluo encontrada para este problema):
function QuadTree(boundBox, lvl) { var maxObjects = 10; this.bounds = boundBox || { x: 0, y: 0, width: 0, height: 0 }; var objects = []; this.nodes = []; var level = lvl || 0; var maxLevels = 5; this.clear = function() { objects = []; for (var i = 0; i < this.nodes.length; i++) { this.nodes[i].clear(); } this.nodes = []; }; this.getAllObjects = function(returnedObjects) { for (var i = 0; i < this.nodes.length; i++) { this.nodes[i].getAllObjects(returnedObjects); } for (var i = 0, len = objects.length; i < len; i++) { returnedObjects.push(objects[i]); } return returnedObjects; };
FbioNotaJ, pois eu j tinha visto essa situao.
O quadTree uma estrutura de dados utilizada para dividir regies 2D em regies mais pequenas
e mais fceis de gerir. Funciona com uma rvore binria mas com 4 filhos (ns) em vez de dois.
Comea por dividir o ecr em quatro regies como indicado na figura.
this.findObjects = function(returnedObjects, obj) { if (typeof obj === "undefined") { console.log("UNDEFINED OBJECT"); return; } var index = this.getIndex(obj); if (index != -1 && this.nodes.length) { this.nodes[index].findObjects(returnedObjects, obj); } for (var i = 0, len = objects.length; i < len; i++) { returnedObjects.push(objects[i]); } return returnedObjects; }; this.insert = function(obj) { if (typeof obj === "undefined") { return; } if (obj instanceof Array) { for (var i = 0, len = obj.length; i < len; i++) { this.insert(obj[i]); } return; } if (this.nodes.length) { var index = this.getIndex(obj); // Only add the object to a subnode if it can fit completely // within one if (index != -1) { this.nodes[index].insert(obj); return; } } objects.push(obj); if (objects.length > maxObjects && level < maxLevels) { if (this.nodes[0] == null) { this.split(); } var i = 0; while (i < objects.length) { var index = this.getIndex(objects[i]); if (index != -1) { this.nodes[index].insert((objects.splice(i,1))[0]); } else { i++; } } } };
this.getIndex = function(obj) { var index = -1; var verticalMidpoint = this.bounds.x + this.bounds.width / 2; var horizontalMidpoint = this.bounds.y + this.bounds.height / 2; // Object can fit completely within the top quadrant var topQuadrant = (obj.y < horizontalMidpoint && obj.y + obj.height < horizontalMidpoint); // Object can fit completely within the bottom quandrant var bottomQuadrant = (obj.y > horizontalMidpoint); // Object can fit completely within the left quadrants if (obj.x < verticalMidpoint && obj.x + obj.width < verticalMidpoint) { if (topQuadrant) { index = 1; } else if (bottomQuadrant) { index = 2; } } // Object can fix completely within the right quandrants else if (obj.x > verticalMidpoint) { if (topQuadrant) { index = 0; } else if (bottomQuadrant) { index = 3; } } return index; }; this.split = function() { // Bitwise or [html5rocks] var subWidth = (this.bounds.width / 2) | 0; var subHeight = (this.bounds.height / 2) | 0; this.nodes[0] = new QuadTree({ x: this.bounds.x + subWidth, y: this.bounds.y, width: subWidth, height: subHeight }, level+1); this.nodes[1] = new QuadTree({ x: this.bounds.x, y: this.bounds.y, width: subWidth, height: subHeight }, level+1); this.nodes[2] = new QuadTree({ x: this.bounds.x, y: this.bounds.y + subHeight, width: subWidth, height: subHeight }, level+1); this.nodes[3] = new QuadTree({ x: this.bounds.x + subWidth, y: this.bounds.y + subHeight, width: subWidth, height: subHeight }, level+1); };
De seguida vai introduzindo em cada n objectos. A partir de um nmero mximo de
objectos num n, o espao partido novamente em quatro sub-regies. Desta forma, objectos
esto em ns diferentes (correspondem a zonas do ecr diferentes) no podem colidir. O
algoritmo de coliso s verifica se h colises com objectos no mesmo n.
39. necessrio actualizar as restantes funes construtoras para esta funcionalidade.
Vamos comear pela funo abstracta Drawable. Copie o cdigo em baixo para substituir
o que tinha anteriormente para a funo construtora Drawable.
Foram adicionados 2 novos atributos, isColliding para indicar se o objecto est a colidir
com algum objecto e colliadableWith, um lista com os objectos que podem colidir com
cada objecto. Finalmente, foi tambm acrescentado um mtodo para retornar o valor do
segundo novo atributo.
function Drawable() { this.init = function(x, y, width, height) { // Defualt variables this.x = x; this.y = y; this.width = width; this.height = height; } this.speed = 0; this.canvasWidth = 0; this.canvasHeight = 0; this.collidableWith = ""; this.isColliding = false; this.type = ""; // Define abstract function to be implemented in child objects this.draw = function() { }; this.move = function() { }; this.isCollidableWith = function(object) { return (this.collidableWith === object.type); }; }
40. No objecto Bullet alterou-se o mtodo draw() e o clear(). No primeiro, preciso
verificar se o objecto est a colidir com algum. No segundo, apenas se altera o atributo
isColliding para false.
41. No caso do objecto Ship preciso atribuir um valor ao atributo collidableWith e
desenhar a nave e disparar se no est a colidir no mtodo move().
this.draw = function() { this.context.clearRect(this.x-1, this.y-1, this.width+2, this.height+2); this.y -= this.speed; if (this.isColliding) { return true; } else if (self === "bullet" && this.y = this.canvasHeight) { return true; } else { if (self === "bullet") { this.context.drawImage(imageRepository.bullet, this.x, this.y); } else if (self === "enemyBullet") { this.context.drawImage(imageRepository.enemyBullet, this.x, this.y); } return false; } }; this.clear = function() { this.x = 0; this.y = 0; this.speed = 0; this.alive = false; this.isColliding = false; };
42. Da mesma forma, alteraes idnticas so necessrias na classe Enemy.
function Ship() { this.speed = 3; this.bulletPool = new Pool(30); this.bulletPool.init("bullet"); var fireRate = 15; var counter = 0; this.collidableWith = "enemyBullet"; this.move = function() { counter++; if (KEY_STATUS.left || KEY_STATUS.right || KEY_STATUS.down || KEY_STATUS.up) { this.context.clearRect(this.x, this.y, this.width, this.height); if (!this.isColliding) { this.draw(); } } if (KEY_STATUS.space && counter >= fireRate && !this.isColliding) { this.fire(); counter = 0; } };
function Enemy() { var percentFire = .01; var chance = 0; this.alive = false; this.collidableWith = "bullet"; this.type = "enemy"; this.draw = function() { if (!this.isColliding) { this.context.drawImage(imageRepository.enemy, this.x, this.y); chance = Math.floor(Math.random()*101); if (chance/100 < percentFire) { this.fire(); } return false; } else { return true; } }; this.clear = function() { this.x = 0; this.y = 0; this.speed = 0; this.speedX = 0; this.speedY = 0; this.alive = false; this.isColliding = false; }; }
43. A funo construtora Pool fica:
44. Na classe Game apenas uma linha no final da funo init():
45. Actualize a funo animate()
function Pool(maxSize) { var size = maxSize; // Max bullets allowed in the pool var pool = []; this.getPool = function() { var obj = []; for (var i = 0; i < size; i++) { if (pool[i].alive) { obj.push(pool[i]); } } return obj; } this.init = function(object) { if (object == "bullet") { for (var i = 0; i < size; i++) { // Initalize the object var bullet = new Bullet("bullet"); bullet.init(0,0, imageRepository.bullet.width,imageRepository.bullet.height); bullet.collidableWith = "enemy"; bullet.type = "bullet"; pool[i] = bullet; } } else if (object == "enemy") { for (var i = 0; i < size; i++) { var enemy = new Enemy(); enemy.init(0,0, imageRepository.enemy.width,imageRepository.enemy.height); pool[i] = enemy; } } else if (object == "enemyBullet") { for (var i = 0; i < size; i++) { var bullet = new Bullet("enemyBullet"); bullet.init(0,0, imageRepository.enemyBullet.width,imageRepository.enemyBullet.height); bullet.collidableWith = "ship"; bullet.type = "enemyBullet"; pool[i] = bullet; } } }; }
this.quadTree = new QuadTree({x:0,y:0,width:this.mainCanvas.width,height:this.mainCanvas.height}); return true; } else { return false; } };
this.quadTree = new QuadTree({x:0,y:0,width:this.mainCanvas.width,height:this.mainCanvas.height}); return true; } else { return false; } };
Recommended