Fog of War, eis um dos elementos mais básicos de um bom jogo de RTS, estilo Age of Empires. É incrível como apenas uma mecânica ou desafio pode alterar a dinâmica de um jogo. Imagine que em um jogo como Age of Empires, você precisa caçar recursos como comida e madeira, para isso é essencial que você saiba a posição exata destes recursos para tomar decisões que poderão lhe custar o jogo, se não forem bem tomadas. Remova esta mecânica e o jogo ficará totalmente diferente, tenha certeza disto.
Fog Of War no Phaser
Como em qualquer ferramenta, o Phaser possui várias formas de implementar Fog of War, eu testei muuuitas formas, não foi à toa que levei 2 semanas para chegar à um resultado que me agradasse. Neste post vou mostrar à você como fazer uma Fog of War utilizando uma render texture.
Render Texture
Bom, como eu disse, vamos utilizar uma Render Texture para Criar o nevoeiro. Eu Criei uma Classe para Abstrair todo o código e manter o tudo mais organizado.
export class FogWar { constructor(scene) { this.scene = scene; // Scene. this.rt; // Render Texture this.gameService = new GameService(scene); // Game Service. this.delay = 0; // delay to call the update. this.team = this.scene.team; // Team to update the Texture. } }
Dentro desta classe vamos criar um método para gerar nossa textura.
createFog(tileWidth, tileHeight) { console.log(tileWidth, tileHeight); const shadowMap = this.scene.make.tilemap({ width: tileWidth, height: tileHeight, tileWidth: CONFIG.tileSize, tileHeight: CONFIG.tileSize, }); const shadowTiles = shadowMap.addTilesetImage("shadowSprite"); // For of War Map this.shadow = shadowMap.createBlankDynamicLayer(0, shadowTiles); // this.shadow.fill(1); // this.shadow.putTileAt(0, 3, 3); this.shadow.depth = 9999999; this.shadow.alpha = 0.0; // this.shadow.removeInteractive(); const width = this.scene.scale.width; const height = this.scene.scale.height; // make a RenderTexture that is the size of the screen this.rt = this.scene.make.renderTexture( { width, height, }, true ); // fill it with black this.rt.fill(0x000000, 0.5); // draw the floorLayer into it this.rt.draw(this.shadow); // set a dark blue tint this.rt.setTint(0x0a2948); this.rt.depth = 999999; }
O Código é bem simples, criamos um layer de mapa do Phaser e usamos este layer para renderizar a nossa textura. Para garantir que ela ficará sobre todos os layers de visão, colocamos o depth com um número bem alto. Isso fará com que o z-index do layer sobreponha à todos os demais.
Dentro da sua scene, não esqueça de adicionar a criação da classe que acabamos de codificar. Isso fica dentro do create().
this.fogWar = new FogWar(this); // Creates the object to add the Fog of War. this.fogWar.createFog( this.collision.layer.width, this.collision.layer.height ); // Creates the fog of War.
Eu utilizei o meu layer de colisões para pegar o tamanho do mapa. Lembre-se que não é em Pixels e sim o número de Tiles.
Alterações no Sprite
Precisamos fazer algumas alterações no sprite que você quer que tenha visão no mapa. No meu caso, estou criando um jogo de RTS e eu tenho o que eu chamo de Unidades (Dá uma olhada no processo de criação), estas Unidades possuem um container onde eu movo tudo o que eu preciso, inclusive a imagem que eu uso de Máscara, que é a seguinte.

Perceba que a imagem é como se fosse um Gradiente, isso é o que garante que a visão da unidade tenha um efeito de Blur, sem precisar aplicar nenhum filtro extra. E o Código da unidade fica assim.
this.maskImg = this.scene.add.image(0, 0, "mask"); this.maskImg.visible = false; this.container = new Phaser.GameObjects.Container(scene, x, y, [ this, this.maskImg, ]);
Eu não preciso que a imagem fique visível, por tanto eu uso o atributo como visible = false. Tenha em mente que você pode usar o que quiser como máscara, até mesmo uma foto sua 😛
Pondo tudo pra funcionar
Por fim, precisamos criar mais 2 funções para atualizar a nossa máscara.
updateFog(time) { if (this.delay < time) { this.delay = time + 300; let go = this.gameService.getGameObjects(); this.redraw(); // Recreates the Fog. go.forEach((unit) => { if (unit.spriteType != SpriteTypes.RESOURCE && unit.team == this.team) { this.rt.erase(unit.maskImg, unit.container.x, unit.container.y); } }); } } redraw() { this.rt.clear(); // fill it with black this.rt.fill(0x000000, 0.7); // set a dark blue tint this.rt.setTint(0x0a2948); }
Devido à limitação de que uma render texture pode ter apenas 1 mascara por vez, precisamos redesenhar esta Render Texture a cada loop do jogo e fazer buracos nela apagando apenas a visão da unidade (Caso você queira para apenas 1 Sprite, veja a primeira referência do Artigo). Este processo é um pouco custoso, mas é um trade off que tive que fazer para ter uma Fog of War bonita. Por isso criamos o método redraw() que recria a nossa textura e percorremos todas as nossas unidades fazendo Buracos na textura com a nossa mascara. Dentro do Loop update() da minha Scene eu preciso colocar o updateFog() que acabamos de criar. Não esqueça de passar o time para controlar a quantidade de vezes que o método é chamado, eu coloquei para à cada 300ms.
update(time, delta) { this.fogWar.updateFog(time); }
Conclusão
Não é porque o jogo é feito com Javascript que ele não pode ficar bonito!!! PhaserJS é um Framework extremamente completo, com uma comunidade super-ativa e eu não teria conseguido chegar à este resultado sem a ajuda dela. Não deixe de checar as referências, elas vão te dar muitos insights. Sério, leia as referências! 😀
Referências
- https://blog.ourcade.co/posts/2020/phaser3-fog-of-war-field-of-view-roguelike/
- https://blog.gemserk.com/2018/08/27/implementing-fog-of-war-for-rts-games-in-unity-1-2/
- https://blog.gemserk.com/2018/11/20/implementing-fog-of-war-for-rts-games-in-unity-2-2/
- https://www.emanueleferonato.com/2015/12/15/calculating-dynamic-light-and-shadows-in-tile-based-roguelike-games-part-3-let-there-be-bresenham-light/
- https://www.emanueleferonato.com/2020/04/26/vvvvvv-html5-prototype-powered-by-phaser-meets-light-and-dark-with-ray-casting-and-the-result-is-astonishing/
- https://www.emanueleferonato.com/2020/04/12/play-with-light-and-dark-using-ray-casting-and-visibility-polygons-updated-to-phaser-3/
- https://www.html5gamedevs.com/topic/28623-multiple-blendmodes-fog-of-war-question/