Game design - Delimitando a movimentação do personagem
Postado: na M.Cassab
Estado: falta muito para as 17h30 ?
Escutando: DeepPurple - Fireball
Estado: falta muito para as 17h30 ?
Escutando: DeepPurple - Fireball
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, fizemos o PC andar. O problema é que ele passa por cima de tudo.
Agora, vamos fazer com que o jogo detecte se há um obstáculo no caminho do PC, verificando a propriedade do objeto, conforme declaramos no capítulo de criar cenários.
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.
O conceito é bem simples: ao montar o cenário, será verificado se a imagem possui a propriedade que permite andar ou bloquear.
Com essa varredura, serão declaradas as coordenadas em que é possível caminhar, não precisando fazer um hiTest para verificar colisão.
Porém, quem já usou a função hiTest sabe que ela possui uma falha... que não é exatamente uma falha, mas aspecto muito chato de tratar a posição dos registros.
Quando alinhamos um objeto dinamicamente, a quina de sua área fica para fora do outro objeto, como na primeira imagem do exemplo abaixo:
O ideal é fazer com que a imagem do objeto fique rente ao outro objeto, como na segunda imagem. Para isso, é preciso fazer umas gambiarras. O Tony até chegou a fazer uma série de comentários sobre isso no tutorial dele.
Até conheço uma forma de resolver isso, mas como não vamos usar nem hiTest e nem hiArea, vamos seguir o método proposto pelo Tony.
1º passo: Delimitar a área do objeto
A primeira coisa que devemos fazer é encontrar a área dos objetos que compõem o cenário. Para isso, vamos iniciar uma função que receberá os argumentos do objeto:
function getMyCorners (x, y, ob){
Quando criamos a função construtora, declaramos a posição dos objetos em relação ao cenário. Com essa nova função, vamos a posição real do objeto e calcular o seu centro.
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);
Depois que se descobre a área dos objetos, armazenaremos os valores relacionados aqueles que permitem a movimentação do PC:
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;
Lembra que declaramos uma propriedade em cada Tile (imagem que compõem parte do cenário) informando se o PC poderia caminha por cima (game.Tile0.prototype.walkable=true) ou não (game.Tile0.prototype.walkable=false)?
A ActionScript está buscando essa propriedade, indicando se a posição do Tile possibilita a movimentação do PC.
2º passo: Limitando o movimento
Agora, vamos usar os valores da área definida em que o PC poderá andar para limitar sua movimentação. Para isso, teremos que fazer algumas alterações bruscas na função de movimentação (moveChar).
Vamos inserir dois blocos parecidos na função, um para verificar o sentido da movimentação no eixo Y e outro para verificar o sentido da movimentação no eixo X:
Começaremos pelos cálculos na movimentação vertical (eixo Y), em que o valor do argumento diry é diferente de 0. Insira esse comando logo na primeira linha após declarar a função:
getMyCorners (ob.x, ob.y+ob.speed*diry, ob);
Aqui, vamos passar a posição em que o PC deverá se deslocar para a função que delimita a área, que verificará se a posição indicada está dentro de uma área válida ou não para movimentação.
Depois, será verificado o sentido da movimentação:
if (diry == -1) {
O próximo passo é saber se o Tile da posição indicada permite ou não a movimentação. Para isso, vamos verificar as variáveis que guardaram o valor da propriedade walkable:
if (ob.upleft and ob.upright) {
Se a movimentação for direcionada para uma área válida (ob.upleft and ob.upright = true), o movieClip do PC será deslocado para a posição informada.
ob.y += ob.speed*diry;
Se não, caso tenha um obstáculo na frente (ob.upleft and ob.upright = false), sua posição será centralizada no Tile atual em que se encontra.
} else {
ob.y = ob.ytile*game.tileH+ob.height;
}
Depois disso, deverá repetir o mesmo processo, mas verificando a outra direção:
if (diry == 1) {
if (ob.downleft and ob.downright) {
ob.y += ob.speed*diry;
} else {
ob.y = (ob.ytile+1)*game.tileH-ob.height;
}
}
Agora, deverá fazer o mesmo para a posição no eixo X, verificando a movimentação horizontal, em que o valor do argumento dirx é diferente de 0:
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;
}
}
Com as posições verificadas, você deverá declarar os valores nas propriedades do objeto referente ao PC:
ob.clip._x = ob.x;
ob.clip._y = ob.y;
Com as propriedades atualizadas, o restante da função de movimentação permanecerá o mesmo, continuando o restante do processo:
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);
return (true);
Dúvida pendente
O Tony fez uma série de comentários sobre o posicionamento de um movieClip no tutorial dele, ilustrando algumas hipóteses.
Quase no final da página, ele comenta sobre ter inserido dois quadrados que tem a distância do registro do movieClip do PC até o final.
O problema é que não entendi o porque disso. Fiz um teste aqui e não vi necessidade de fazer isso, o próprio desenho dentro do movieClip já resolve.
Fiz um teste com imagens e outro com vetores e em ambos não houve necessidade de gambiarra. Acho que ele deve ter alterado um movieClip que já existia e deve ter ocorrido algum problema e essa foi a solução.
De qualquer forma, se um dia descobrir porque ele comentou isso, volto aqui e atualizo a postagem.
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 a matriz do cenário
- Declarar o tamanho de cada imagem do cenário
- Declarar um objeto padrão 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 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]
];
game = {tileW:30, tileH:30};
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;
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;
}
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);
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(myMap);
_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.
Se quiser, volte ao índice para ver a lista completa do conteúdo.
Próximo assunto:
Trocando de cenário
Post a Comment