2a aula de laboratorio 5

Upload: fabio-duarte

Post on 02-Mar-2016

27 views

Category:

Documents


0 download

TRANSCRIPT

  • 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; } };