Criando um build para celular para o meu jogo com o Phaser JS - Game Devlog #23
Escrito em 9 de setembro de 2021 - 🕒 5 min. de leituraFala galera, aqui estamos nós novamente com outro devlog para o jogo Super Ollie, e como o jogo está sendo feito com JavaScript, eu posso exportar facilmente para praticamente qualquer plataforma, e hoje vou criar uma versão para celular do jogo.
Controlando com o celular
O personagem no meu jogo é controlado com um gamepad ou um teclado, então a maneira mais fácil de fazer uma versão para celular é adicionar algum tipo de controle virtual, tipo uns botões na tela do celular.
Para fazer isso eu vou precisar de 3 sprites para botões, um para o botão A
, um para o botão B
e outro para os botões direcionais
, veja um exemplo do meu sprite abaixo:
Para o código, farei praticamente o mesmo que fiz para o HUD, que é criar uma classe e usar setScrollFactor(0)
para as imagens ficarem estáticas na tela.
class TouchScreenControls extends GameGroup {
constructor({
scene,
x = 0,
y = 0,
name,
dPadAsset = 'd_pad',
buttonAAsset = 'button_a',
buttonBAsset = 'button_b',
controlKeys = {},
}) {
const {
LEFT,
RIGHT,
UP,
DOWN,
SPACE,
NUMPAD_ZERO,
} = Input.Keyboard.KeyCodes;
const baseKeys = {
d_pad_left: LEFT,
d_pad_right: RIGHT,
d_pad_up: UP,
d_pad_down: DOWN,
button_a: SPACE,
button_b: NUMPAD_ZERO,
};
const keys = {
...baseKeys,
...controlKeys,
};
const children = [];
[
dPadAsset,
buttonAAsset,
buttonBAsset,
].forEach(this.assignButtonActions);
super({
scene,
children,
name,
});
this.setOrigin(0, 0);
this.setDepth(TOUCH_CONTROLS_DEPTH);
this.setControlsPositions();
}
}
export default TouchScreenControls;
Para a função assignButtonActions
, preciso escrever um código que será executado quando o jogador pressionar um dos botões na tela do celular, e a maneira mais fácil de fazer isso é simular as teclas do teclado que já estão mapeadas.
Por exemplo, eu já codifiquei que quando a barra de espaço é pressionada, o herói precisa pular, então tudo que eu preciso fazer agora é criar um código para quando o jogador pressiona o botão A
na tela do celular, que irá simular que a barra de espaço foi pressionada. Depois de pesquisar um pouco, eu encontrei um Gist no GitHub com uma função que faz exatamente isso.
/**
* source https://gist.github.com/GlauberF/d8278ce3aa592389e6e3d4e758e6a0c2
* Simulate a key event.
* @param {Number} keyCode The keyCode of the key to simulate
* @param {String} type (optional) The type of event : down, up or press. The default is down
*/
export const simulateKeyEvent = (keyCode, type) => {
const evtName = (typeof type === 'string') ? `key${type}` : 'keydown';
const event = document.createEvent('HTMLEvents');
event.initEvent(evtName, true, false);
event.keyCode = keyCode;
document.dispatchEvent(event);
};
Agora, para o código da função assignButtonActions
, o segredo aqui é disparar um evento para pointerdown
, pointerup
e pointerout
, caso contrário, não funcionará corretamente na touchscreen do celular.
assignButtonActions(asset) {
if (asset === buttonPrefix + dPadAsset) {
let angle = 0;
['left', 'up', 'right', 'down'].forEach((dPadDirection) => {
const assetName = `${asset}_${dPadDirection}`;
const child = new GameObjects.Image(
scene,
x,
y,
asset
);
child.setOrigin(0, 0);
child.setName(assetName);
child.setAngle(angle);
child.setInteractive();
child.on('pointerdown', () => {
child.setTint(0x90989e);
simulateKeyEvent(keys[assetName], 'down');
});
child.on('pointerup', () => {
child.clearTint();
simulateKeyEvent(keys[assetName], 'up');
});
child.on('pointerout', () => {
child.clearTint();
simulateKeyEvent(keys[assetName], 'up');
});
angle += 90;
children.push(child);
});
} else {
const child = new GameObjects.Image(
scene,
x,
y,
asset
);
child.setOrigin(0, 0);
child.setName(asset);
child.setInteractive();
child.on('pointerdown', () => {
child.setTint(0x90989e);
simulateKeyEvent(keys[asset], 'down');
});
child.on('pointerup', () => {
child.clearTint();
simulateKeyEvent(keys[asset], 'up');
});
child.on('pointerout', () => {
child.clearTint();
simulateKeyEvent(keys[asset], 'up');
});
children.push(child);
}
}
A função setControlsPositions
é meio sem graça, ela simplesmente define as posições de cada botão na tela:
setControlsPositions() {
const { width, height } = this.scene.cameras.main;
const paddingX = 20;
const paddingY = 60;
this.forEach((child) => {
const { name } = child;
child.setX(paddingX);
child.setY(height - paddingY);
child.setScrollFactor(0);
switch (name) {
case 'big_d_pad_left': {
child.setX(child.x);
child.setY(child.y - 20);
break;
}
case 'big_d_pad_up': {
child.setX(child.x + 58);
child.setY(child.y - 49);
break;
}
case 'big_d_pad_right': {
child.setX(child.x + 88);
child.setY(child.y + 8);
break;
}
case 'big_d_pad_down': {
child.setY(child.y + 37);
child.setX(child.x + 30);
break;
}
case 'big_button_a': {
child.setY(child.y - 25);
child.setX(width - paddingX - 35);
break;
}
case 'big_button_b': {
child.setY(child.y - 5);
child.setX(width - paddingX - 85);
break;
}
case 'd_pad_left': {
child.setX(child.x);
child.setY(child.y - 20);
break;
}
case 'd_pad_up': {
child.setX(child.x + 40);
child.setY(child.y - 40);
break;
}
case 'd_pad_right': {
child.setX(child.x + 60);
break;
}
case 'd_pad_down': {
child.setY(child.y + 20);
child.setX(child.x + 20);
break;
}
case 'button_a': {
child.setY(child.y - 35);
child.setX(width - paddingX - 35);
break;
}
case 'button_b':
default: {
child.setY(child.y - 20);
child.setX(width - paddingX - 75);
break;
}
}
});
}
Build para celular
Eu não quero ter que carregar assets que não sejam usados, então vou criar um build separado para celulares e verificar se devo ou não carregar os assets dos botões:
preload() {
if (getGlobal('IS_MOBILE_BUILD')) {
this.load.image('big_d_pad', bigDPad);
this.load.image('big_button_a', bigButtonA);
this.load.image('big_button_b', bigButtonB);
this.load.image('big_button_Y', bigButtonY);
this.load.image('d_pad', dPad);
this.load.image('button_a', buttonA);
this.load.image('button_b', buttonB);
this.load.image('button_Y', buttonY);
}
}
create() {
if (getGlobal('IS_MOBILE_BUILD')) {
this.touchScreenControls = new TouchScreenControls({
scene: this,
name: 'touchScreenControls',
dPadAsset: 'd_pad',
buttonAAsset: 'button_a',
buttonBAsset: 'button_b',
useBigButtons: true,
});
this.touchScreenControls.forEach((child) => {
this.add.existing(child);
});
}
}
Agora no meu package.json
, irei criar um script de build com "build-mobile": "webpack --mode production --config webpack.config.prod.js --env = mobile"
, e na minha configuração do Webpack eu vou exportar uma função ao em vez de um objeto, para eu poder acessar o parâmetro env
:
// webpack.config.js
module.exports = (env = {}) => {
const isMobileBuild = env === 'mobile';
if (isMobileBuild) {
BUILD_PATH = path.resolve(__dirname, 'www/build');
DIST_PATH = path.resolve(__dirname, 'www');
}
}
Além disso, vou criar a variável IS_MOBILE_BUILD
com o Webpack Define Plugin:
new webpack.DefinePlugin({
IS_MOBILE_BUILD: JSON.stringify(isMobileBuild),
})
Buildando o APK
Quero que meu jogo rode no Android por enquanto e, para isso, usarei o Cordova. Primeiro, preciso instalar o Cordova com npm install -g cordova
e, em seguida, instalar o Android Studio e yada yada e, finalmente, criar meu projeto com cordova create skate
.
Cordova espera que os arquivos de build estejam na pasta www/
, e é por isso que eu tive que mudar o meu script do Webpack antes. Agora posso simplesmente executar cordova build --release android
e um arquivo APK não assinado será gerado para mim.
E é isso! Mole mole, fácil fácil!
Nos vemos no próximo devlog, pessoal!
Tags:
- programação
- jogos
- javascript
- phaser
- phaser 3
- game devlog
- gamedev
- skate platformer
- super ollie vs pebble corp
- webpack
- tiled
- celular
- android
- build para celular
Posts relacionados
Publicar um comentário
Comentários
Nenhum comentário.