Advertisement
  1. Code
  2. Games

Gerando Fantasmas Que Seguem os Seus Passos

Scroll to top
Read Time: 23 min

() translation by (you can also view the original English article)

Path following is a simple concept to grasp: the object moves from point A to point B to point C, and so on. But what if we want our object to follow the path of the player, like ghosts in racing games? In this tutorial, I'll show you how to achieve this with waypoints in AS3.


Final Result Preview

Clique no SWF e, em seguida, use as teclas de seta para mover ao redor. Pressione espaço para alternar para o fantasma, que seguirá o caminho que você criou.


The Logic Behind Path Following

Let's suppose the player moves 4 units left and 2 units down from our point of origin. For our ghost to end up in the same location it will have to also move 4 units left and 2 units down from the same point of origin. Now let's say our player is moving at a speed of 2; for the path following to remain accurate our ghost will also have a speed rate of 2.

What if our player decides to take a pause before continuing on? The obvious solution is for the ghost to keep track of the player's exact position every tick - but this will involve storing a lot of data. Instead, what we'll do is simply store data every time the player presses different keys - so if the player moves right for ten seconds, we'll store the same amount of data as if the player moved right for half a second.

For this technique to work our ghost must abide by the following rules:

  • The ghost and player have the same point of origin.
  • The ghost must follow the exact same path as the player.
  • The ghost should move at the same speed as the player.
  • The ghost has to store the current time each time the player's motion changes.

Step 1: Setting Up

Start by creating a new Flash file (ActionScript 3.0). Set the width to 480, the height to 320 and frames per second to 30. Leave the background color as white and save the file as CreatingGhosts.fla; Por último, defina sua classe como CreatingGhosts.

Before we move into the classes we need to create a pair of MovieClips. Start by drawing two separate 20px squares without a stroke. Convert the first fill to a MovieClip, setting its registration to the center, naming it player and exporting it for ActionScript with the class name Player. Now repeat the same process, except replace the name with ghost and the class with Ghost. Remove these MovieClips from the stage.

Create your document class with the following code:

1
2
package{
3
  import flash.display.*;
4
	import flash.events.*;
5
	
6
	public class CreatingGhosts extends MovieClip{
7
		public var player:Player = new Player();
8
		public function CreatingGhosts(){
9
			addChild(player);
10
		}
11
	}
12
}

Self explanatory; our next step will be to set up the Player class:

1
2
package{
3
	import flash.display.*;
4
	import flash.events.*;
5
	import flash.geom.Point;
6
	import flash.ui.Keyboard;
7
	import flash.utils.Timer;
8
	import flash.utils.getTimer;
9
	
10
	public class Player extends MovieClip{
11
		public var startPos:Point;
12
		public var startTime:int;
13
		public var speed:Number = 2;
14
		public var currentLife:int;
15
		public var keyPressLeft:Boolean = false;
16
		public var keyPressRight:Boolean = false;
17
		public var keyPressUp:Boolean = false;
18
		public var keyPressDown:Boolean = false;
19
		public function Player(){
20
			
21
		}
22
	}
23
}

The first three variables are used to help meet the rules; startPos is our point of origin, startTime is the time when the Player was added to the stage and speed is our our rate of movement. currentLife is an addition used to check how many times the player has died, accordingly each path is stored and obtainable through that value. The last four variables are used to check key presses.

It's time to create the Ghost class:

1
2
package{
3
	import flash.display.*;
4
	import flash.events.*;
5
	import flash.geom.Point;
6
	import flash.utils.getTimer;
7
	import flash.utils.Timer;
8
	
9
	public class Ghost extends MovieClip{
10
		static public var waypoints:Array = new Array();
11
		static public var times:Array = new Array();
12
		public var i:int = 0;
13
		public var startTime:int;
14
		public var speed:Number = 2;
15
		public var selectedLife:int;
16
		public function Ghost(){
17
			
18
		}
19
	}
20
}

The two static variables, waypoints and times, will be used to store arrays; the first will store coordinates of the player's positions whenever the player changes motion, and the second will store the times at which each change occurred. The other variables match those from the Player class.


Step 2: Initializing the Player

Within the Player's constructor add the following line:

1
2
addEventListener(Event.ADDED_TO_STAGE, init);

Next create the init() function:

1
2
public function init(e:Event){
3
4
}

First, we need to obtain the startTime and push a new time array to the Ghost's times array. (This is a little confusing; the ghost has multiple time arrays to allow it to deal with multiple lives in the future.)

1
2
startTime = flash.utils.getTimer();
3
Ghost.times.push(new Array);
4
currentLife = Ghost.times.length - 1;
5
Ghost.times[currentLife].push(flash.utils.getTimer() - startTime);

startTime is set to the current time (a value in milliseconds); we add a new child array to the Ghost's times array; our currentLife is set to the index of this new array; and we push the time that has elapsed during this function to the first element of this new array.

Now we set up the starting position:

1
2
startPos = new Point(stage.stageWidth/2, stage.stageHeight/2);
3
this.x = startPos.x;
4
this.y = startPos.y;
5
Ghost.waypoints.push(new Array);
6
Ghost.waypoints[currentLife].push(startPos);

Our point of origin is set to the center of the stage; we reposition our Player to the origin; a new array is added to the waypoints array in the Ghost class; and the first position is pushed to that array.

So, at the moment, Ghost.times[0][0] contains the number of milliseconds since the SWF was set up (practically zero), and Ghost.waypoints[0][0] contains a Point set to the center of the stage.

Our aim is to code this so that if, after one second, the player presses a key, then Ghost.times[0][1] will be set to 1000, and Ghost.waypoints[0][1] will be another Point, again set to the center (because the player will not have moved yet). When the player lets go of that key (or presses another), Ghost.times[0][2] will be set to the current time, and Ghost.waypoints[0][2] will be a Point that matches the player's position at that time.

Now, here are the three event listeners:

1
2
addEventListener(Event.ENTER_FRAME, enterFrame);
3
stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDown);
4
stage.addEventListener(KeyboardEvent.KEY_UP, keyUp);

Step 3: Key Events

For now let's ignore the enterFrame and focus on the key presses.

1
2
public function keyDown(e:KeyboardEvent){
3
	if (e.keyCode == Keyboard.LEFT && keyPressLeft == false){
4
		updateWaypoints();
5
		keyPressLeft = true;
6
	}else if (e.keyCode == Keyboard.RIGHT  && keyPressRight == false){
7
		updateWaypoints();
8
		keyPressRight = true;
9
	}
10
11
	if (e.keyCode == Keyboard.UP  && keyPressUp == false){
12
		updateWaypoints();
13
		keyPressUp = true;
14
	}else if (e.keyCode == Keyboard.DOWN  && keyPressDown == false){
15
		updateWaypoints();
16
		keyPressDown = true;
17
	}
18
19
	if (e.keyCode == Keyboard.SPACE){
20
		destroy();
21
	} 
22
}

Just a few simple if-statements to prevent bugs in key presses, and two new functions that are being called. updateWaypoints() will be called every time new points and times are to be pushed to the ghost arrays, and destroy() is used to remove the Player and add the Ghost to the stage. But before we go to those functions let's finish off the key press functions.

1
2
public function keyUp(e:KeyboardEvent){
3
	if (e.keyCode == Keyboard.LEFT  && keyPressLeft == true){
4
		updateWaypoints();
5
		keyPressLeft = false;
6
	}else if (e.keyCode == Keyboard.RIGHT  && keyPressRight == true){
7
		updateWaypoints();
8
		keyPressRight = false;	
9
	}
10
11
	if (e.keyCode == Keyboard.UP  && keyPressUp == true){
12
		updateWaypoints();
13
		keyPressUp = false;
14
	}else if (e.keyCode == Keyboard.DOWN  && keyPressDown == true){
15
		updateWaypoints();
16
		keyPressDown = false;
17
	}
18
}

This time we do the opposite: the variables are set to false when the key is released and the waypoints are updated.

I will elaborate in more detail on what is happening between those functions. Each time you press a key the waypoints and times are updated, so if you press another to cause a change a point and its corresponding time are added to the ghost arrays.

But what happens if the player decides to randomly release a key and cause change again? Well we account for that by updating the waypoints and times again. If this was not done the Ghost would not be able to account for 90 degree turns; instead it would move on an angle towards the next point.


Step 4: Updating and Destroying

Our updateWaypoints() function is fairly simple, seeing as it consists of code that we have already written:

1
2
public function updateWaypoints(){
3
	Ghost.times[currentLife].push(flash.utils.getTimer() - startTime);
4
	Ghost.waypoints[currentLife].push(new Point(this.x, this.y));
5
}

The destroy() function is just as simple! Waypoints are updated, a Ghost is added, event listeners are stopped and our Player is removed:

1
2
public function destroy(){
3
	updateWaypoints();
4
	var ghost:Ghost = new Ghost();
5
	parent.addChild(ghost);
6
	removeEventListener(Event.ENTER_FRAME, enterFrame);
7
	stage.removeEventListener(KeyboardEvent.KEY_DOWN, keyDown);
8
	stage.removeEventListener(KeyboardEvent.KEY_UP, keyUp);
9
	parent.removeChild(this);
10
}

Step 5: The Player's enterFrame

Begin by creating the function:

1
2
public function enterFrame(e:Event){
3
	
4
}

For the purposes of this tutorial we will add some simple collision with borders, to show how the waypoints are updated on this change:

1
2
if((this.x-(this.width/2)) > 0){
3
		
4
}
5
if((this.x+(this.width/2)) < stage.stageWidth){
6
		
7
}
8
if((this.y-(this.height/2)) > 0){
9
		
10
}
11
if((this.y+(this.height/2)) < stage.stageHeight){
12
	
13
}

Now are player should only move in the specified direction while it isn't touching a border. Inside the first if-statement add the following code for moving left:

1
2
if(keyPressLeft == true){
3
	if((this.x-(this.width/2)) <= 0){
4
		updateWaypoints();
5
		this.x = this.width/2;
6
	}else{
7
		this.x -= speed;
8
	}
9
}

First we check if the left key is currently down, then we check to see if the Player's position is greater than or equal to 0; if so we update our waypoints and reposition the player to the edge of the left side; if not we continue to move the player left.

The exact same thing is done for the other three sides:

1
2
if(keyPressRight == true){
3
	if((this.x+(this.width/2)) >= stage.stageWidth){
4
		updateWaypoints();
5
		this.x = (stage.stageWidth - (this.width/2));
6
	}else{
7
		this.x += speed;
8
	}
9
}
1
2
if(keyPressUp == true){
3
	if((this.y-(this.height/2)) <= 0){
4
		updateWaypoints();
5
		this.y = this.height/2;
6
	}else{
7
		this.y -= speed;
8
	}
9
}
1
2
if(keyPressDown == true){
3
	if((this.y+(this.height/2)) >= stage.stageHeight){
4
		updateWaypoints();
5
		this.y = (stage.stageHeight - (this.height/2));
6
	}else{
7
		this.y += speed;
8
	}
9
}

And with that we are finished with the Player Class!


Step 6: Initializing the Ghost

Add the following line inside the Ghost's constructor:

1
2
addEventListener(Event.ADDED_TO_STAGE, init);

Antes de criar a função init()

1
2
public function init(e:Event){
3
	selectedLife = times.length - 1;
4
	this.x = waypoints[selectedLife][0].x;
5
	this.y = waypoints[selectedLife][0].y;
6
	startTime = flash.utils.getTimer();
7
	addEventListener(Event.ENTER_FRAME, enterFrame);
8
}

Começamos selecionando o caminho que desejamos usar (por padrão iremos escolher a última matriz); em seguida, posicione o fantasma na origem e defina a hora de inicio do nosso Fantasma. Em seguida, é criado um "ouvinte de evento" para o enterFrame.


enterFrame do Fantasma

Naturalmente, nós criaremos nossa função enterFrame:

1
2
public function enterFrame(e:Event){
3
4
}

Agora teremos de fazer um loop, através de nossa matriz de tempo. Fazemos isso através da variável i; Nós verificamos se é menor que o comprimento da matriz e também verificamos se o tempo decorrido é maior ou igual ao tempo atual na matriz:

1
2
while (i < times[selectedLife].length - 1 && flash.utils.getTimer() - startTime >= times[selectedLife][i]) {
3
	i++;
4
}

A próxima coisa a fazer, é mover o Fantasma caso o tempo decorrido seja menor que o tempo atual da matriz:

1
2
if (flash.utils.getTimer() - startTime < times[selectedLife][i]) {
3
	updatePosition();
4
}

Atualizando a posição do Fantasma

Vamos começar esta etapa criando a função updatePosition():

1
2
public function updatePosition(){
3
4
}

Em seguida, adicione duas variáveis, para representar a diferença e a distância entre a antiga e a nova posição:

1
2
var diff:Point = waypoints[selectedLife][i].subtract(new Point(this.x, this.y)); 
3
var dist = diff.length;

Nós subtraimos os pontos uns dos outros para encontrar a distância. Agora, vamos mover o fantasma:

1
2
if (dist <= speed){
3
	this.x = waypoints[selectedLife][i].x;
4
	this.y = waypoints[selectedLife][i].y;
5
}else{
6
	diff.normalize(1);
7
	this.x += diff.x * speed;
8
	this.y += diff.y * speed;
9
}

Primeiro vamos verificar se a distância é menor que a velocidade (ou seja, a distância, que o fantasma se move a cada tique); Se assim for, nós moveremos o Fantasma diretamente ao ponto. No entanto, se a distância for menor então nós normalizaremos a diferença ("significa fazer com que sua magnitude seja igual a 1, preservando-se ainda a direção e sentido do vetor" - Vetores Euclidianos em Flash), e incrementaremos a posição do Fantasma ao longo da direção do ponto.


Um Lembrete

Uma coisa a observar sobre este método, é que ele usa um monte de recursos da CPU, para carregar continuamente momentos e pontos, e às vezes pode produzir algum retardo, embora a lógica seja correta. Contudo, Encontramos duas maneiras de contornar isto:

A primeira é configurando seu SWF nas Configurações de Publicação, para ser descompactado; Isso resultará em um maior tempo de carregamento para iniciar, no entanto, o desempenho será mais suave. A segunda, é mais preferível se você planeja compilar seu projeto como um exe para uso offline: simplesmente aumente a velocidade dos quadros para algo em torno de 60.


Conclusão:

Obrigado por dedicar um tempo para ler este tutorial! Se você tiver qualquer pergunta ou comentário, por favor, escreva-os abaixo. E se você quer um desafio extra, tente configurar a classe do Fantasma para seguir os caminhos no sentido inverso, ou em câmara lenta.Caminho a seguir é um conceito simples de compreender: o objeto se move do ponto A ao ponto B ao ponto C e assim por diante. Mas e se nós quisermos que nosso objeto siga o caminho do jogador, como fantasmas em jogos de corrida? Neste tutorial, vou te mostrar como conseguir isso com waypoints em AS3.


Visualização do Resultado Final

Click the SWF, then use the arrow keys to move around. Press space to switch to the ghost, which will follow the path you've created.


A lógica por trás do caminho a seguir

Vamos supor que o jogador move 4 unidades para a esquerda e 2 unidades para baixo, do nosso ponto de origem Para nosso fantasma acabar no mesmo local terá que mover também 4 unidades a esquerda e 2 unidades para baixo do mesmo ponto de origem. Agora digamos que nosso jogador está se movendo a uma velocidade de 2; para se manter exato no caminho a seguir nosso fantasma também terá uma taxa de velocidade de 2.

E se o nosso jogador decide fazer uma pausa antes de continuar? A solução óbvia  para o fantasma é acompanhar a posição exata de  cada tick do jogador - mas isto implicará, em armazenar uma grande quantidade de dados. Em vez disso, o que vamos fazer é simplesmente armazenar dados cada vez que o jogador pressiona diferentes teclas - portanto, se o jogador se move bem durante 10 segundos, nós vamos armazenar a mesma quantidade de dados, como se o jogador se movesse por meio segundo.

Para esta técnica funcionar o nosso fantasma deve respeitar as seguintes regras:

  • O fantasma e o jogador devem ter o mesmo ponto de origem.
  • O fantasma deve seguir o caminho exato feito pelo jogador.
  • O fantasma deve se mover na mesma velocidade que o jogador.
  • O fantasma tem que armazenar o tempo atual, cada vez que houver alterações no movimento do jogador

Ajustes

Comece criando um novo arquivo Flash (ActionScript 3.0). Defina a largura para 480, a altura para 320 e quadros por segundo para 30. Deixe a cor de fundo em branco e salve o arquivo como CreatingGhosts.fla; lastly set its class to CreatingGhosts.

Antes de passarmos para as classes,precisamos criar um par de MovieClips. Começar desenhando dois quadrados de 20px cada, sem os traços. Converter o primeiro preenchimento em MovieClip, configure o registro para  centro, nomeando-o jogador e exportando-os para ActionScript com o nome da classe Jogador. Agora repita o mesmo processo, mas substitua o nome por fantasma e a classe também por Fantasma. Remova esses MovieClips do palco.

Criar uma classe, com o seguinte código:

1
2
package{
3
	import flash.display.*;
4
	import flash.events.*;
5
	
6
	public class CreatingGhosts extends MovieClip{
7
		public var player:Player = new Player();
8
		public function CreatingGhosts(){
9
			addChild(player);
10
		}
11
	}
12
}

Auto-explicativo; nosso próximo passo será configurar a classe  jogador:

1
2
package{
3
	import flash.display.*;
4
	import flash.events.*;
5
	import flash.geom.Point;
6
	import flash.ui.Keyboard;
7
	import flash.utils.Timer;
8
	import flash.utils.getTimer;
9
	
10
	public class Player extends MovieClip{
11
		public var startPos:Point;
12
		public var startTime:int;
13
		public var speed:Number = 2;
14
		public var currentLife:int;
15
		public var keyPressLeft:Boolean = false;
16
		public var keyPressRight:Boolean = false;
17
		public var keyPressUp:Boolean = false;
18
		public var keyPressDown:Boolean = false;
19
		public function Player(){
20
			
21
		}
22
	}
23
}

As três primeiras variáveis são usadas para ajudar a cumprir as regras; startPos é nosso ponto de origem, startTime é o momento em que o jogador foi adicionado para o palco e speed é nossa nossa taxa de movimento. currentLife é um complemento usado para verificar quantas vezes o jogador morreu, em conformidade, cada caminho é armazenado e obtido através desse valor. As últimas quatro variáveis são usadas para verificar os pressionamentos de teclas.

Está na hora de criar a classe do Fantasma:

1
2
package{
3
	import flash.display.*;
4
	import flash.events.*;
5
	import flash.geom.Point;
6
	import flash.utils.getTimer;
7
	import flash.utils.Timer;
8
	
9
	public class Ghost extends MovieClip{
10
		static public var waypoints:Array = new Array();
11
		static public var times:Array = new Array();
12
		public var i:int = 0;
13
		public var startTime:int;
14
		public var speed:Number = 2;
15
		public var selectedLife:int;
16
		public function Ghost(){
17
			
18
		}
19
	}
20
}

As duas variáveis estáticas, waypoints e times, serão usadas para armazenar matrizes; o primeiro irá armazenar as coordenadas das posições do jogador sempre que ele mudar o movimento, e o segundo armazenará o exato momento que cada mudança ocorreu. As outras variáveis coincidem com aqueles da classe Jogador.


Inicializando o Jogador

No construtor do Jogador, adicione a seguinte linha:

1
2
addEventListener(Event.ADDED_TO_STAGE, init);

Em seguida, crie a função init():

1
2
public function init(e:Event){
3
4
}

Primeiro, precisamos obter o startTime e mover uma nova matriz de tempo para a matriz times do Fantasma. (Isto é um pouco confuso, o fantasma tem várias matrizes de tempo, para permitir-lhe lidar com múltiplas vidas no futuro.)

1
2
startTime = flash.utils.getTimer();
3
Ghost.times.push(new Array);
4
currentLife = Ghost.times.length - 1;
5
Ghost.times[currentLife].push(flash.utils.getTimer() - startTime);

startTime é definido como o tempo atual (um valor em milissegundos); Nós adicionamos uma nova matriz descendente à matriz "times" do Fantasma; nosso currentLife é definido como o índice desta nova matriz; e nós movemos o tempo decorrido durante essa função para o primeiro elemento desta  nova matriz.

Agora vamos configurar a posição inicial:

1
2
startPos = new Point(stage.stageWidth/2, stage.stageHeight/2);
3
this.x = startPos.x;
4
this.y = startPos.y;
5
Ghost.waypoints.push(new Array);
6
Ghost.waypoints[currentLife].push(startPos);

Nosso ponto de origem é definido como o centro do palco; nós reposicionamos nosso Jogador na origem; uma nova matriz é adicionada à matriz de waypoints na classe Fantasma; e a primeira posição é movida para a matriz.

Então, no momento, Ghost.times[0][0] contém o número de milissegundos desde que foi criado o SWF (praticamente zero), e Ghost.waypoints[0][0] contém um Ponto definido como o centro do palco.

Nosso objetivo para este código é que, se, após um segundo, o jogador pressionar uma tecla,  Ghost.times[0][1] será definido como 1000, e Ghost.waypoints[0][1] será um outro Ponto, novamente definido ao centro (porque o jogador ainda não terá movido). Quando o jogador solta a tecla (ou pressiona outra), Ghost.Times[0][2] será definido para o tempo atual, e Ghost.waypoints[0][2] será um Ponto que corresponde a posição do jogador no momento.

Agora, aqui estão três ouvintes de evento:

1
2
addEventListener(Event.ENTER_FRAME, enterFrame);
3
stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDown);
4
stage.addEventListener(KeyboardEvent.KEY_UP, keyUp);

Eventos de Teclas

Por enquanto vamos ignorar o enterFrame e focar nos pressionamentos de teclas.

1
2
public function keyDown(e:KeyboardEvent){
3
	if (e.keyCode == Keyboard.LEFT && keyPressLeft == false){
4
		updateWaypoints();
5
		keyPressLeft = true;
6
	}else if (e.keyCode == Keyboard.RIGHT  && keyPressRight == false){
7
		updateWaypoints();
8
		keyPressRight = true;
9
	}
10
11
	if (e.keyCode == Keyboard.UP  && keyPressUp == false){
12
		updateWaypoints();
13
		keyPressUp = true;
14
	}else if (e.keyCode == Keyboard.DOWN  && keyPressDown == false){
15
		updateWaypoints();
16
		keyPressDown = true;
17
	}
18
19
	if (e.keyCode == Keyboard.SPACE){
20
		destroy();
21
	} 
22
}

Apenas algumas instruções if simples, para evitar bugs nos pressionamentos de teclas e duas novas funções que estão sendo chamadas. updateWaypoints() será chamado varias vezes e novos pontos serão enviados para as matrizes do fantasma, e destroy() é usado para remover o Player e adicionar o Fantasma ao palco. Mas antes de irmos a essas funções, vamos finalizar as funções de pressionamento de tecla.

1
2
public function keyUp(e:KeyboardEvent){
3
	if (e.keyCode == Keyboard.LEFT  && keyPressLeft == true){
4
		updateWaypoints();
5
		keyPressLeft = false;
6
	}else if (e.keyCode == Keyboard.RIGHT  && keyPressRight == true){
7
		updateWaypoints();
8
		keyPressRight = false;	
9
	}
10
11
	if (e.keyCode == Keyboard.UP  && keyPressUp == true){
12
		updateWaypoints();
13
		keyPressUp = false;
14
	}else if (e.keyCode == Keyboard.DOWN  && keyPressDown == true){
15
		updateWaypoints();
16
		keyPressDown = false;
17
	}
18
}

Desta vez faremos o oposto: as variáveis são definidas como false quando a tecla é liberada e os waypoints são atualizados.

Eu vou explicar mais detalhadamente, sobre o que está acontecendo entre essas funções. Cada vez que você pressiona uma tecla os waypoints e os tempos são atualizados, Então, se você pressionar outra tecla para fazer uma mudança, um ponto e seu tempo correspondente são adicionados às matrizes do fantasma.

Mas o que acontece, se o jogador decide aleatoriamente liberar uma tecla e causar outra alteração? Bem, como explicamos, os waypoints e os tempos serão  atualizados de novo. Se isto não fosse feito, o fantasma não seria capaz de fazer curvas de 90 graus; em vez disso, ele se moveria em um ângulo em direção ao próximo ponto.


Atualizando e Destruindo

Nossa função  updateWaypoints() é bastante simples, visto que trata-se de um código que nós já escrevemos:

1
2
public function updateWaypoints(){
3
	Ghost.times[currentLife].push(flash.utils.getTimer() - startTime);
4
	Ghost.waypoints[currentLife].push(new Point(this.x, this.y));
5
}

A função destroy() é muito simples! Waypoints são atualizados, um Fantasma é adicionado, ouvintes de evento são interrompidos e nosso Jogador é removido:

1
2
public function destroy(){
3
	updateWaypoints();
4
	var ghost:Ghost = new Ghost();
5
	parent.addChild(ghost);
6
	removeEventListener(Event.ENTER_FRAME, enterFrame);
7
	stage.removeEventListener(KeyboardEvent.KEY_DOWN, keyDown);
8
	stage.removeEventListener(KeyboardEvent.KEY_UP, keyUp);
9
	parent.removeChild(this);
10
}

enterFrame do Jogador

Comece criando a função:

1
2
public function enterFrame(e:Event){
3
	
4
}

Para os fins deste tutorial, nós adicionaremos algumas colisões simples contra bordas, para mostrar como os waypoints são atualizados por essas mudanças:

1
2
if((this.x-(this.width/2)) > 0){
3
		
4
}
5
if((this.x+(this.width/2)) < stage.stageWidth){
6
		
7
}
8
if((this.y-(this.height/2)) > 0){
9
		
10
}
11
if((this.y+(this.height/2)) < stage.stageHeight){
12
	
13
}

Agora, o jogador deve avançar na direção especificada, enquanto não colidir com uma borda. Dentro da primeira instrução if, adicione o seguinte código para mover para a esquerda:

1
2
if(keyPressLeft == true){
3
	if((this.x-(this.width/2)) <= 0){
4
		updateWaypoints();
5
		this.x = this.width/2;
6
	}else{
7
		this.x -= speed;
8
	}
9
}

Em primeiro lugar, verificamos se a tecla esquerda está pressionada, então checamos se a posição do jogador é maior ou igual a 0; Se assim for, nós atualizaremos nossos waypoints e reposicionamos o jogador para a borda do lado esquerdo; Se não, continuarmos mover o jogador à esquerda.

A mesma coisa é feita para os outros três lados:

1
2
if(keyPressRight == true){
3
	if((this.x+(this.width/2)) >= stage.stageWidth){
4
		updateWaypoints();
5
		this.x = (stage.stageWidth - (this.width/2));
6
	}else{
7
		this.x += speed;
8
	}
9
}
1
2
if(keyPressUp == true){
3
	if((this.y-(this.height/2)) <= 0){
4
		updateWaypoints();
5
		this.y = this.height/2;
6
	}else{
7
		this.y -= speed;
8
	}
9
}
1
2
if(keyPressDown == true){
3
	if((this.y+(this.height/2)) >= stage.stageHeight){
4
		updateWaypoints();
5
		this.y = (stage.stageHeight - (this.height/2));
6
	}else{
7
		this.y += speed;
8
	}
9
}

E com isso terminamos com a Classe do Jogador!


Inicializando o Fantasma

Adicione a seguinte linha dentro do construtor do Fantasma:

1
2
addEventListener(Event.ADDED_TO_STAGE, init);

Like before create the init() function:

1
2
public function init(e:Event){
3
	selectedLife = times.length - 1;
4
	this.x = waypoints[selectedLife][0].x;
5
	this.y = waypoints[selectedLife][0].y;
6
	startTime = flash.utils.getTimer();
7
	addEventListener(Event.ENTER_FRAME, enterFrame);
8
}

We start by selecting the path we want to use (by default it will choose the last array); we then position the ghost to the origin and set our Ghost's start time. Then an event listener for the enterFrame is created.


Step 7: The Ghost's enterFrame

Naturally we create our enterFrame function:

1
2
public function enterFrame(e:Event){
3
4
}

Now we have to loop through our time array. We do this through the variable i; we check if it is less than the length of the array and we also check if the time elapsed is greater than or equal to the current time in the array:

1
2
while (i < times[selectedLife].length - 1 && flash.utils.getTimer() - startTime >= times[selectedLife][i]) {
3
	i++;
4
}

The next thing to do is to move the Ghost if the time elapsed is less than the current time from the array:

1
2
if (flash.utils.getTimer() - startTime < times[selectedLife][i]) {
3
	updatePosition();
4
}

Step 8: Updating the Ghost's Position

We'll start this step off by creating the updatePosition() function:

1
2
public function updatePosition(){
3
4
}

Next add two variables, to represent the difference and the distance between the old and the new position:

1
2
var diff:Point = waypoints[selectedLife][i].subtract(new Point(this.x, this.y)); 
3
var dist = diff.length;

We subtract the points from each other to find the distance. Now, we must move the ghost:

1
2
if (dist <= speed){
3
	this.x = waypoints[selectedLife][i].x;
4
	this.y = waypoints[selectedLife][i].y;
5
}else{
6
	diff.normalize(1);
7
	this.x += diff.x * speed;
8
	this.y += diff.y * speed;
9
}

First we check whether the distance is less than the speed (i.e. the distance the ghost moves each tick); if so we move the Ghost directly to the point. However, if the distance is less then we normalize the difference ("means making its magnitude be equal to 1, while still preserving the direction and sense of the vector" - Euclidean Vectors in Flash), and we increase the Ghost's position along the direction of the point.


Step 9: A Side Note

Something to note about this method is that it uses a lot of CPU resources to continuously load times and points, and at times can produce some lag even though the logic is correct. We found two ways of countering this, though!

The first is setting your SWF to be uncompressed in the Publish Settings; this will result in a longer load time at start up however the performance will be smoother. The second is more preferable if you plan on compiling your project as an exe for offline use: simply increase the frame rate to something around 60.


Conclusion:

Thank you for taking the time to read this tutorial! If you have any questions or comment please leave them below. And if you want an extra challenge try setting up the Ghost class to follow the paths in reverse, or in slow motion.

Seja o primeiro a saber sobre novas traduções–siga @tutsplus_pt no Twitter!

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.