Workshop - Graduação em Jogos Digitais
Transcrição
Workshop - Graduação em Jogos Digitais
Workshop Jogos em HTML5 usando PhaserJS Quem sou eu ? Fernando Lunardelli ● Técnico em Eletrônica pela ETP ● Graduado em Gestão de TI pela Unisul ● Pós Graduado em Desenvolvimento de Jogos Digitais pela PUCRS Desenvolvo sistemas desde 2000 e jogos desde 2010. Email: [email protected] Github: https://github.com/flunardelli Twitter: https://twitter.com/flunardelli Sobre este workshop - Destinado a iniciantes Faremos um jogo em 2D Usando tecnologia HTML5 Para fazer jogos digitais é necessário programar !!! ? Serão vistos alguns princípios básicos Uma versão alternativa deste treinamento pode ser encontrado em: https://leanpub. com/html5shootemupinanafternoon Por que jogos em 2D e HTML5 ? 2D ● Simplicidade ● Importância (ex: retro, top 10 nas mobile stores) HTML5 ● Portabilidade - Web, Desktop, Mobile ● Facilidade - Rápida prototipagem, Javascript, Typescript ● Muitas funcionalidades - Canvas, WebGL, Audio, Video, Gamepad, Websockets, Workers O que faremos ? Shoot 'em up baseado em 1942 da Capcom Veremos: ● ● ● ● ● ● sprites animações física movimentação colisões grupos Nosso motor PhaserJS ● Game engine para jogos em HTML5 ● Criado em 2013 por Richard Davey @photomstorm, usando TypeScript e depois portado para JS puro. ● Baseado no engine Flixel (AS3) ● Open Source http://phaser.io Configuração inicial Precisamos de: 1. 2. 3. 4. Arquivos iniciais: http://jogos.faccat.br/workshop-html5/game.zip Editor de texto / código - Sublime Servidor web - Wamp Navegador web - Google Chrome Instalação - Após a instalação do servidor web, descompacte o arquivo game.zip para a raiz do servidor. Ex: c: /wamp/www/game (verifique se este caminho é válido no seu computador) - Todos os testes do jogo devem ser feitos através do link (abra no navegador web): http://localhost/game Arquivos da pasta "game" index.html: Arquivo inicial a partir de onde o web browser irá carregar todos os demais conteúdos do jogo. Phaser-arcade-physics.min.js: Arquivo js minificado contendo o phaser e o plugin de arcade-physics. game.js: O conteúdo do nosso jogo. assets: Diretório contendo as imagens e sons que serão usados no jogo. game/index.html (abra um editor, copie o conteúdo abaixo e salve o arquivo. Teste no navegador.) <html> <script src="phaser-arcade-physics.min.js"></script> <script src="game.js"></script> </html> Javascript básico (para conhecimento) var nome_variavel = 'valor tipo texto'; // Variável do tipo string var outravariavel = 1234.40; // Variável do tipo numérica var um_vetor = [3,4,99,'cool']; //Array contendo 3 itens um_vetor.push(77); //adiciona um valor um_vetor.pop(); //Remove o último valor var um_objeto = {prop1: 1234, prop2: 'boa noite'}; //objeto console.log(um_objeto.prop1); // 1234 function minhaFuncao(v) { var msg = 'teste' + v; return msg; }; //definição de função minhaFuncao(123); // teste123 If (outravariavel === 123){ console.log('É verdade'); } else { console.log('Não é verdade'); } //Condicional game/game.js (abra um editor, copie o conteúdo abaixo e salve o arquivo. Teste no navegador.) window.onload = function() { //cria o jogo após o carregamento da página. function preload() { }; function create() { }; function update() { }; function render() { }; var game = new Phaser.Game(800, 600, Phaser.CANVAS, null, {preload: preload, create: create, update: update, render: render}); //objeto global do jogo }; Game loop preload create update render http://gameprogrammingpatterns.com/game-loop.html ● Preload Carregamento de arquivos (imagens, mapas, audios, etc) ● Create Ação de início do jogo. Criação de sprites, definição de ambiente, física, etc ● Update É onde acontece o laço do jogo (game loop), ou seja, toda a lógica que precisa ser atualizada constantemente, movimentações, controle de colisões, etc… ● Render Usada para efeitos de visualização (render), debug, plugins ... Sprite É o elemento primordial do jogo 2D. São as imagens que compõem o jogo. Normalmente os jogos utiliza agrupamentos de sprites em uma única imagem - Spritesheets. Animações são feitas com vários Sprites apresentados em sequência. Adicionando sprites // preload - coloca código abaixo dentro da função preload de game.js e salvar. game.load.image('sea', 'assets/sea.png'); game.load.image('bullet', 'assets/bullet.png'); // create - idem ao código anterior só que função create. sea = game.add.tileSprite(0, 0, 800, 600, 'sea'); bullet = game.add.sprite(400, 300, 'bullet'); Coordenadas x,y Sistema cartesiano Sistema de tela Animações // preload game.load.spritesheet('enemy', 'assets/enemy.png', 32, 32); // create enemy = game.add.sprite(400, 200, 'enemy'); enemy.animations.add('fly', [ 0, 1, 2 ], 20, true); enemy.play('fly'); Âncora - centraliza o eixo do sprite // create enemy.anchor.setTo(0.5, 0.5); bullet.anchor.setTo(0.5, 0.5); Atualização - movimentação inicial // update //movimento da bala bullet.y -= 1; //depois de visualizado na tela comentar usando "//bullet.y -= 1;". //movimento do mar sea.tilePosition.y += 0.2; Física //create //adicionando física game.physics.enable(enemy,Phaser.Physics.ARCADE); game.physics.enable(bullet,Phaser.Physics.ARCADE); bullet.body.velocity.y = -500; //render //informações de debug (análise de código) game.debug.body(bullet); //comentar depois game.debug.body(enemy); //comentar depois Colisão //update game.physics.arcade.overlap(bullet, enemy, enemyHit); //Esta função deve ser copiada abaixo da função render function enemyHit(bullet, enemy) { bullet.kill(); enemy.kill(); } Player //preload game.load.spritesheet('player', 'assets/player.png', 64, 64); //create player = game.add.sprite(400, 550, 'player'); player.anchor.setTo(0.5, 0.5); player.animations.add('fly', [ 0, 1, 2 ], 20, true); player.play('fly'); game.physics.enable(player, Phaser.Physics.ARCADE); player.speed = 300; Movimentação //create - adicionando física cursors = game.input.keyboard.createCursorKeys(); player.body.collideWorldBounds = true; //update player.body.velocity.x = 0; player.body.velocity.y = 0; if (cursors.left.isDown) { player.body.velocity.x = -player.speed; } else if (cursors.right.isDown) { player.body.velocity.x = player.speed; } if (cursors.up.isDown) { player.body.velocity.y = -player.speed; } else if (cursors.down.isDown) { player.body.velocity.y = player.speed; } Tiros //create bullets = []; nextShoot = 0; //update // intercepta o comando de teclado e executa a função de tiro if (game.input.keyboard.isDown(Phaser.Keyboard.Z)) { fire(); } function fire() { //copiar para abaixo da função enemyHit var bullet = game.add.sprite(player.x, player.y - 20, 'bullet'); bullet.anchor.setTo(0.5, 0.5); game.physics.enable(bullet, Phaser.Physics.ARCADE); bullet.body.velocity.y = -500; bullets.push(bullet); } Frequência de tiros //deve ser copiado para dentro da função fire //verifica se o disparo pode ser realizado if (nextShoot > game.time.now) { return; } nextShoot = game.time.now + 300; Grupos de Sprites // create bullets = game.add.group(); bullets.enableBody = true; bullets.physicsBodyType = Phaser.Physics.ARCADE; // Adiciona 100 sprites do tipo "bullet" ao jogo bullets.createMultiple(100, 'bullet'); bullets.setAll('anchor.x', 0.5); bullets.setAll('anchor.y', 0.5); // Restringe os sprites ao mundo do jogo bullets.setAll('outOfBoundsKill', true); bullets.setAll('checkWorldBounds', true); Reutilizando Sprites function fire() { //substitui a função fire anterior if (bullets.countDead() === 0) { //verifica se existem sprites inativos ("mortos") return; } if (nextShoot > game.time.now) { return; } nextShoot = game.time.now + 300; //reutiliza o primeiro sprite sem uso encontrado var bullet = bullets.getFirstExists(false); bullet.reset(player.x, player.y - 20); bullet.body.velocity.y = -500; } //update game.physics.arcade.overlap(bullets, enemy, enemyHit); //comente a função overlap anterior Aumentando o desafio //create enemies = game.add.group(); enemies.enableBody = true; enemies.physicsBodyType = Phaser.Physics.ARCADE; enemies.createMultiple(50, 'enemy'); enemies.setAll('anchor.x', 0.5); enemies.setAll('anchor.y', 0.5); enemies.setAll('outOfBoundsKill', true); enemies.setAll('checkWorldBounds', true); // adiciona uma animação para cada sprite enemies.forEach(function (enemy) { enemy.animations.add('fly', [ 0, 1, 2 ], 20, true); }); nextEnemy = 0; //copiar abaixo da função "fire" function enemyCreate() { if (nextEnemy < game.time.now && enemies.countDead() > 0) { nextEnemy = game.time.now + 3000; var enemy = enemies.getFirstExists(false); // posiciona o inimigo em uma coordenada aleatória no topo da tela enemy.reset(game.rnd.integerInRange(20, 780), 0); // define uma velocidade aleatória enemy.body.velocity.y = game.rnd.integerInRange(30, 60); enemy.play('fly'); } } //update game.physics.arcade.overlap(bullets, enemies, enemyHit); enemyCreate(); Adicionando Placar //create game.score = 0; scoreText = game.add.text( game.width / 2, 30, '' + game.score,{font: '20px monospace', fill: '#fff', align: 'center' }); scoreText.anchor.setTo(0.5, 0.5); //update scoreText.text = game.score; //enemyHit game.score += 1; Game Over //adicionar nova função function playerHit(player, enemy) { player.kill(); endText = game.add.text( game.width / 2, game.height / 2 - 60, 'Game Over!!!', {font: '72px serif', fill: '#fff' } ); endText.anchor.setTo(0.5, 0); } //update game.physics.arcade.overlap(player, enemies, playerHit); Jogo Final arquivo gamefinal.js Próximos passos ... ● ● ● ● ● ● ● Tiros dos inimigos Chefe de fase Múltiplas vidas Dificuldade gradativa Animações Efeitos sonoros Suporte Gamepad Acesse o site phaser.io/examples para mais idéias. Algumas Ferramentas Gráficos Piskel - Editor de Pixel Art online http://www.piskelapp.com/ Gimp - Editor de imagens https://www.gimp.org/ ShoeBox - Ferramenta para edição de spritesheets http://renderhjs.net/shoebox/ Online Tile Map Editor http://apps.elias.media/Online-Tile-Map-Editor Tiled Map Editor http://www.mapeditor.org/ Audio Bfxr - Sound effect generator http://www.bfxr.net/ sfMaker - Sound effect maker https://www.leshylabs.com/apps/sfMaker/ Audacity - Sound editor http://www.audacityteam.org/ Obrigado