Game design - Trocando de cenário
Postado: na M.Cassab
Estado: falta muito para as 17h30 ?
Escutando: DeepPurple - Never before
Estado: falta muito para as 17h30 ?
Escutando: DeepPurple - Never before
Atribuição: Uso Não-Comercial
O conteúdo desta página foi baseado nos tutoriais contidos no site Tile based games, autorizado sob a licença Creative Commons e não pode ser utilizado para fins comerciais.
A tradução e adaptação dos textos, códigos ActionScripts e todas as outras obras originais criadas por Tonypa foram autorizada é são de propriedade dele.
Attribution: Noncommercial
The content on this page was based tutorials in the site Tile based games, licensed under a Creative Commons License and you may not use this work for commercial purposes.
The translation and adaptation of text, ActionScripts codes and all other original works created by Tonypa were authorized is are his property.
No capítulo passado, resolvemos a questão da movimentação, impedindo que o personagem (PC) continue caminhando quando esbarrar em um obstáculo.
Agora, vamos incrementar o exemplo proposto no capítulo Declarando o terreno: nosso PC está em uma sala com duas colunas e uma porta, que levará para outra sala parecida.
No final deste capítulo, o resultado do seu trabalho deverá ser parecido com esse exemplo do Tony:
Esse SFW foi criado pelo Tony para o site Tile based games e está sendo utilizado sob sua autorização.
1º passo: Criando um Tile para a porta
A primeira coisa que deve fazer é a porta que será utilizada para transender os cenários.
Haviamos definido um movieClip chamado tile para agrupar todas as tiles que construirão o cenário.
Para simplificar nossa vida, vamos continuar seguindo o exemplo do Tony, apenas incluindo mais um key frame, somando 3 no total. Apenas duplique o quadrado que já existe, mudando sua cor.
Claro, existem formas mais interessantes de se criar uma porta, de preferência com uma animação dela abrindo ou fechando. Mas vamos prosseguir dessa forma só para finalizar esse estudo. Quem sabe, no futuro, volto a trabalhar esta questão.
2º passo: Declarar o novo cenário
A partir do momento que vamos incluir uma porta, será preciso alterar o cenário original.
Em nossa matriz, definimos que 0 seria igual ao chão e 1 seria igual a parede. Logo, vamos deixar como 2 para a porta.
myMap1 = [
[1, 1, 1, 1, 1, 1, 1, 1],
[1, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 1, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 1, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 2],
[1, 1, 1, 1, 1, 1, 1, 1]
];
Depois, veremos como será o comportamento desse novo tile. Agora, vamos declarar o cenário da nova sala.
Porém (sempre há um porém), não basta apenas alterar os tile. Na verdade, vamos adicionar uma saída no cenário.
O conceito é simples, incluímos uma saída no cenário, ao passar por ela, o jogo troca a matriz e constrói um cenário novo.
Só que, nesse novo cenário, a posição da saída se transforma em uma entrada. Ao passar por ela, o jogo retorna para a matriz anterior. Sendo assim, na nova matriz, vamos alterar o valor da entrada para 3.
myMap2 = [
[1, 1, 1, 1, 1, 1, 1, 1],
[1, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 1, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 1, 0, 1],
[3, 0, 0, 0, 0, 0, 0, 1],
[1, 1, 1, 1, 1, 1, 1, 1]
];
Claro, isso é um pouco questionável. Tenho a impressão de que dá para fazer isso de uma outra forma. Mas vamos prosseguir dessa forma só para não perder o raciocínio.
Depois, vamos alterar a linha que declaramos as primeiras propriedades do cenário, adicionando um argumento:
game={tileW:30, tileH:30, currentMap:1}
A variável currentMap será responsável por armazenar qual matriz de cenário será utilizada como padrão ao iniciar o jogo. Mais para a frente, veremos como utilizá-la para alterar o cenário.
3º passo: Declarar o novo Tile
Primeiro, vamos criar uma função que será responsável por armazenar a matriz (newMap) que será utilizada para trocar o cenário acessado pela porta e a posição no eixo X (newcharx) e no eixo Y (newchary) em que o PC ficará após a troca:
game.Doors = function (newMap, newcharx, newchary) {
this.newMap=newMap;
this.newcharx=newcharx;
this.newchary=newchary;
};
Assim como fizemos com os outros tiles, vamos declarar uma classe que será utilizada como padrão para todas as portas (ou entradas e saídas) do cenário:
game.Doors.prototype.walkable = true;
game.Doors.prototype.frame = 3;
game.Doors.prototype.door = true;
Depois, vamos declarar os novos tiles e suas propriedades:
game.Tile2 = function () { };
game.Tile2.prototype = new game.Doors(2, 1, 4);
game.Tile3 = function () { };
game.Tile3.prototype = new game.Doors(1, 6, 4);
4º passo: Atualizando o cenário e a posição do PC
Depois, vamos incluir uma verificação no final da função moveChar, para mudar a posição do PC após mudar o cenário:
if (game["t_"+ob.ytile+"_"+ob.xtile].door and ob==_root.char) {
changeMap (ob);
}
Essa função verifica se o tile em que o PC está parado em cima é uma porta (door = true). Se for, executará a função Doors, que indicará qual será o novo cenário e a nova posição do PC.
Com as coordenadas declaradas, vamos criar uma função chamada changeMap, que será responsável por realizar a troca do cenário e a nova posição do PC:
function changeMap (ob) {
Quando declaramos a função, aproveitamos para informar qual objeto será reposicionado com a troca do cenário, ou seja, ao sair da sala, todos os outros objetos e personagens serão removidos do jogo... ótima tática para fugir de algum inimigo quando estiver em apuros.
var name = "t_"+ob.ytile+"_"+ob.xtile;
game.currentMap = game[name].newMap;
Dentro da função, vamos capturar o nome do tile atual e o novo cenário que será utilizado.
Depois, vamos capturar a nova posição em que o PC deverá ficar:
ob.ytile = game[name].newchary;
ob.xtile = game[name].newcharx;
Para deicar as coisas ainda mais interativas, ao trocar de cenário, vamos fazer com que o PC entre exatamente na mesma posição do frame em que havia saído anteriormente:
ob.frame = ob.clip._currentframe;
Isso é interessante, pois evita que o personagem dê aquela parada brusca na troca do cenário. Se ele estiver quase colocando o pé direito ao passar pela porta, será deste ponto em que continuará a animação ao passá-la.
Antes de fechar a função, chamamos a função construtora, passando os argumentos capturados para montar o novo cenário:
buildMap(_root["myMap"+game.currentMap]);
Por falar na função construtora (buildMap), vamos precisar adicionar uma linha dentro dela, que será responsável por identificar em qual frame a animação de sua movimentação havia sido interrompida, passando a iniciar desse ponto:
char.clip.gotoAndStop(char.frame);
Por fim, vamos atualizar o evento responsável por construir o cenário, quase no final do código ActionScript:
buildMap(_root["myMap"+game.currentMap])
Conclusão
Até agora, para fazer tudo isso, foi preciso:
Fase de Planejamento
- Definir os tipos de cenários
- Definir o comportamento para cada elemento do cenário
- Definir o PC, seu comportamento e movimentação
Fase de Elaboração
- Preparar as imagens do cenário
- Criar um movieClip para agrupar o jogo (empty)
- Criar um movieClip para agrupar as imagens do cenário (tiles)
- Preparar as imagens do PC
- Criar um movieClip para cada movimentação do PC
- Criar um movieClip agrupar os sprite do PC (char)
Fase de Programação
- Declarar as matrizes dos cenários
- Declarar o tamanho de cada tile e a matriz inicial do cenário
- Declarar as classes padrões para os elementos do cenário
- Declarar os elementos de cenário e suas propriedades
- Declarar as propriedades do PC
- Declarar a função construtora
- Declarar a função que troca o cenário
- Declarar a função delimitadora de área de movimentação
- Declarar a função de movimentação
- Declarar a função de entrada de comando
- Montar o cenário do jogo
- Acionar um evento de verificação
Seu arquivo FLA deverá ter apenas um frame vazio, contendo o seguinte ActionScript:
myMap = [
[1, 1, 1, 1, 1, 1, 1, 1],
[1, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 1, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 1, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 1],
[1, 1, 1, 1, 1, 1, 1, 1]
];
myMap2 = [
[1, 1, 1, 1, 1, 1, 1, 1],
[1, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 1, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 1, 0, 1],
[3, 0, 0, 0, 0, 0, 0, 1],
[1, 1, 1, 1, 1, 1, 1, 1]
];
game = {tileW:30, tileH:30, currentMap:1};
game.Doors = function (newMap, newcharx, newchary) {
this.newMap=newMap;
this.newcharx=newcharx;
this.newchary=newchary;
};
game.Doors.prototype.walkable = true;
game.Doors.prototype.frame = 3;
game.Doors.prototype.door = true;
game.TileClass = function () {};
game.TileClass.prototype.walkable=false;
game.TileClass.prototype.frame=2;
game.Tile0 = function () {};
game.Tile0.prototype.__proto__ = game.TileClass.prototype;
game.Tile0.prototype.walkable=true;
game.Tile0.prototype.frame=1;
game.Tile1 = function () {};
game.Tile1.prototype.__proto__ = game.TileClass.prototype;
game.Tile2 = function () { };
game.Tile2.prototype = new game.Doors(2, 1, 4);
game.Tile3 = function () { };
game.Tile3.prototype = new game.Doors(1, 6, 4);
char = {xtile:2, ytile:1, speed:4};
function buildMap (map) {
_root.attachMovie("empty", "tiles", ++d);
game.clip=_root.tiles;
var mapWidth = map[0].length;
var mapHeight = map.length;
for (var i = 0; i < mapHeight; ++i) {
for (var j = 0; j < mapWidth; ++j) {
var name = "t_"+i+"_"+j;
game[name]= new game["Tile"+map[i][j]];
game.clip.attachMovie("tile", name, i*100+j*2);
game.clip[name]._x = (j*game.tileW);
game.clip[name]._y = (i*game.tileH);
game.clip[name].gotoAndStop(game[name].frame);
}
}
game.clip.attachMovie("char", "char", 10000);
char.clip = game.clip.char;
char.x = (char.xtile*game.tileW)+game.tileW/2;
char.y = (char.ytile*game.tileH)+game.tileH/2;
char.width = char.clip._width/2;
char.height = char.clip._height/2;
char.clip._x = char.x;
char.clip._y = char.y;
char.clip.gotoAndStop(char.frame);
}
function changeMap(ob) {
var name = "t_"+ob.ytile+"_"+ob.xtile;
game.currentMap = game[name].newMap;
ob.ytile = game[name].newchary;
ob.xtile = game[name].newcharx;
ob.frame = ob.clip._currentframe;
buildMap(_root["myMap"+game.currentMap]);
}
function getMyCorners (x, y, ob) {
ob.downY = Math.floor((y+ob.height-1)/game.tileH);
ob.upY = Math.floor((y-ob.height)/game.tileH);
ob.leftX = Math.floor((x-ob.width)/game.tileW);
ob.rightX = Math.floor((x+ob.width-1)/game.tileW);
ob.upleft = game["t_"+ob.upY+"_"+ob.leftX].walkable;
ob.downleft = game["t_"+ob.downY+"_"+ob.leftX].walkable;
ob.upright = game["t_"+ob.upY+"_"+ob.rightX].walkable;
ob.downright = game["t_"+ob.downY+"_"+ob.rightX].walkable;
}
function moveChar(ob, dirx, diry) {
getMyCorners (ob.x, ob.y+ob.speed*diry, ob);
if (diry == -1) {
if (ob.upleft and ob.upright) {
ob.y += ob.speed*diry;
} else {
ob.y = ob.ytile*game.tileH+ob.height;
}
}
if (diry == 1) {
if (ob.downleft and ob.downright) {
ob.y += ob.speed*diry;
} else {
ob.y = (ob.ytile+1)*game.tileH-ob.height;
}
}
getMyCorners (ob.x+ob.speed*dirx, ob.y, ob);
if (dirx == -1) {
if (ob.downleft and ob.upleft) {
ob.x += ob.speed*dirx;
} else {
ob.x = ob.xtile*game.tileW+ob.width;
}
}
if (dirx == 1) {
if (ob.upright and ob.downright) {
ob.x += ob.speed*dirx;
} else {
ob.x = (ob.xtile+1)*game.tileW-ob.width;
}
}
ob.clip._x = ob.x;
ob.clip._y = ob.y;
ob.clip.gotoAndStop(dirx+diry*2+3);
ob.xtile = Math.floor(ob.clip._x/game.tileW);
ob.ytile = Math.floor(ob.clip._y/game.tileH);
if (game["t_"+ob.ytile+"_"+ob.xtile].door and ob == _root.char) {
changeMap(ob);
}
return (true);
}
function detectKeys() {
var ob = _root.char;
var keyPressed = false;
if (Key.isDown(Key.RIGHT)) {
keyPressed=_root.moveChar(ob, 1, 0);
} else if (Key.isDown(Key.LEFT)) {
keyPressed=_root.moveChar(ob, -1, 0);
} else if (Key.isDown(Key.UP)) {
keyPressed=_root.moveChar(ob, 0, -1);
} else if (Key.isDown(Key.DOWN)) {
keyPressed=_root.moveChar(ob, 0, 1);
}
if (!keyPressed) {
ob.clip.char.gotoAndStop(1);
} else {
ob.clip.char.play();
}
}
buildMap(_root["myMap"+game.currentMap]);
_root.onEnterFrame = function(){
_root.detectKeys();
}
Se estiver tudo certo, o resultado final ao publicar o SWF será esse:
Esse SFW foi criado pelo Tony para o site Tile based games e está sendo utilizado sob sua autorização.
Considerações
Note que, uma das propriedades que declaramos na classe padrão da porta (game.Doors) informava qual era o tile que deveria utilizar como imagem (frame = 3). Essa foi uma medida adotada para esse exemplo especificamente.
Porém, o correto é que esse velor seja declarado nas propriedades do próprio tile. Dessa forma, será possível criar outros tipos de portas, possibilitando até criar animações da porta se abrindo e fechando.
Além disso, também permitirá utilizar essa mesma classe para a troca de matrizes em cenários abertos, como acontece nos jogos de Zelda, em que a grama no canto da tela é igual a do centro, só que conduz ao cenário do lado.
Com esse capítulo, acho que terminamos um dos ciclos de desenvolvimento. Com o que foi publicado até aqui, já é possível criar um ambiente e colocar um personagem para explorá-lo.
Penso no assunto e tente criar novos cenários para testar outras possibilidades.
Se quiser, volte ao índice para ver a lista completa do conteúdo.
Próximo assunto:
Pulo e queda
Post a Comment