Continuamos recreando el mítico juego de Atari Telepong con Castle Game Engine. En la entrada anterior preparamos todo el entorno usando el editor. En esta entrada vamos a programar algunas líneas de código. Vamos a ello.
Moviendo los jugadores
Lo primero que programaremos es el movimiento de los jugadores. Y se producirá al pulsar unas teclas. Por ejemplo, la tecla Q y A servirán para desplazar el jugador uno, arriba y abajo. Y las teclas de los cursores, arriba y abajo harán la misma función pero para el jugador 2.
Abre el editor de código pulsando F12. En la clausula uses añade la unidad CastleScene, y añade los jugadores en la parte published.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
uses Classes, CastleVectors, CastleComponentSerialize, CastleUIControls, CastleControls, CastleKeysMouse,CastleScene; type { Main view, where most of the application logic takes place. } TViewMain = class(TCastleView) published { Components designed using CGE editor. These fields will be automatically initialized at Start. } LabelFps: TCastleLabel; Jugador1 : TCastleBox; Jugador2 : TCastleBox; |
Ahora en la función Press, gestionaremos las teclas. Cuando se pulse una tecla de las que hemos definido anteriormente, aplicaremos una velocidad en el sentido que corresponda en el caso que no tenga velocidad el jugador. Si el velocidad es en sentido contrario al pulsado la velocidad aplicada será cero. Con ello se detendrá el jugador. Define en la sección Private dos variables que contendrán la velocidad de cada jugador
1 2 3 4 5 6 |
type { Main view, where most of the application logic takes place. } TViewMain = class(TCastleView) private VelocidadPlayer1: integer; VelocidadPlayer2: integer; |
En el procedimiento start, inicializa las variables a cero. El procedimiento start se ejecuta una sola vez, cuando se inicia nuestro programa. Por ello es un buen lugar para inicializar las variables, clases, etc.
1 2 3 4 5 6 |
procedure TViewMain.Start; begin inherited; VelocidadPlayer1 := 0; VelocidadPlayer2 := 0; end; |
El evento Press recoge las pulsaciones de las teclas. Así, que tal como comentamos anteriormente, capturamos la pulsación de las teclas, y asignamos un valor a la velocidad del jugador.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
function TViewMain.Press(const Event: TInputPressRelease): boolean; begin Result := inherited; if Result then Exit; // allow the ancestor to handle keys if Event.IsKey(keyQ) then begin if VelocidadPlayer1 < 0 then begin VelocidadPlayer1 := 0; end else begin VelocidadPlayer1 := 300; end; Exit(True); end; if Event.IsKey(keyA) then begin if VelocidadPlayer1 > 0 then begin VelocidadPlayer1 := 0; end else begin VelocidadPlayer1 := -300; end; end; //Teclas jugadaor 2 if Event.IsKey(keyArrowUp) then begin if VelocidadPlayer2 < 0 then begin VelocidadPlayer2 := 0; end else begin VelocidadPlayer2 := 300; end; Exit(True); end; if Event.IsKey(keyArrowDown) then begin if VelocidadPlayer2 > 0 then begin VelocidadPlayer2 := 0; end else begin VelocidadPlayer2 := -300; end; Exit(True); end; end; |
Hasta ahora solo hemos capturado las teclas y asignado valores a las variables de la velocidad. Para que se muevan los jugadores debemos trasladarlos en el eje Y, cada vez que se actualice el juego, osea en cada frame.
El evento update se encarga de esto. Así que cambiaremos la posición de los jugadores (trasladar) , en función del valor de la variable multiplicada por una constante SecondPassed. Al multiplicar por esta constante conseguimos que la velocidad de desplazamiento sea la misma, con independencia del rendimiento del equipo dónde se esté ejecutando el juego.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
procedure TViewMain.Update(const SecondsPassed: single; var HandleInput: boolean); begin inherited; { This virtual method is executed every frame (many times per second). } Assert(LabelFps <> nil, 'If you remove LabelFps from the design, remember to remove also the assignment "LabelFps.Caption := ..." from code'); LabelFps.Caption := 'FPS: ' + Container.Fps.ToString; if (Player1.Translation.Y > 300) then begin VelocidadPlayer1 := 0; Player1.Translation := Vector3(-690, 300, 0); end; if (Player1.Translation.Y < -300) then begin VelocidadPlayer1 := 0; Player1.Translation := Vector3(-690, -300, 0); end; if (Player2.Translation.Y > 300) then begin VelocidadPlayer2 := 0; Player2.Translation := Vector3(690, 300, 0); end; if (Player2.Translation.Y < -300) then begin VelocidadPlayer2 := 0; Player2.Translation := Vector3(690, -300, 0); end; Player1.Translation := Player1.Translation + Vector3(0, VelocidadPlayer1 * SecondsPassed, 0); Player2.Translation := Player2.Translation + Vector3(0, VelocidadPlayer2 * SecondsPassed, 0); end; |
Además, debemos limitar la posición del jugador para que no se salga de los límites de campo.
Gestionando las colisiones.
Ya solo nos queda gestionar cuando la pelota colisione con uno de los lados, con ello sabremos que un jugador ha marcado un gol.
Añade en la sección published los elemento LadoDerecho y LadoIzquierdo.
1 2 3 4 5 6 7 8 9 |
published { Components designed using CGE editor. These fields will be automatically initialized at Start. } LabelFps: TCastleLabel; Player1: TCastleBox; Player2: TCastleBox; LadoDerecho: TCastleBox; LadoIzquierdo: TCastleBox; public |
En el evento Start, asignaremos a cada RigidBody de cada uno de los lados un procedimiento que se ejecutará cada vez que se produzca un colisión (OnCollisionEnter).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
procedure TViewMain.Start; var Body: TCastleRigidBody; begin inherited; VelocidadPlayer1 := 0; VelocidadPlayer2 := 0; //Lado Derecho Body := LadoDerecho.FindBehavior(TCastleRigidBody) as TCastleRigidBody; {$IFDEF FPC} Body.OnCollisionEnter := @ColisionLadoDerecho; {$ELSE} Body.OnCollisionEnter := ColisionLadoDerecho; {$ENDIF} //Lado Izqquierdo Body := LadoIzquierdo.FindBehavior(TCastleRigidBody) as TCastleRigidBody; {$IFDEF FPC} Body.OnCollisionEnter := @ColisionLadoIzquierdo; {$ELSE} Body.OnCollisionEnter := ColisionLadoIzquierdo; {$ENDIF} end; |
Añadí un condicional de compilación, de tal manera que si este código se compila con Free Pascal, la asignación del evento lleva la letra @ delante. En caso contrario, que es cuando se compila con Delphi, no lo lleva. Con esto conseguimos tener un código portable o crosscompile.
En Lazarus, si pulsas la combinación de teclas ctrl+c, en el sobre la línea en la que que se asigna el evento, por ejemplo la línea 12, el editor creará automáticamente el procedimiento.
Cada vez que se produzca una colisión, anotaremos un punto al jugador correspondiente. Así si la bola colisiona con el lado derecho, el punto será para jugador uno. Y actualizaremos la etiqueta de este jugador.
En la sección privada añade dos variables de tipo entero, para guardar la puntuación de cada jugado. En la sección published declara los elementos TCastleLabel, que representan el marcador. Algo así:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
TViewMain = class(TCastleView) private VelocidadPlayer1: integer; VelocidadPlayer2: integer; PuntosPlayer1: integer; //Añade esto PuntosPlayer2: integer; //Añade esto procedure ColisionLadoDerecho(const CollisionDetails: TPhysicsCollisionDetails); procedure ColisionLadoIzquierdo( const CollisionDetails: TPhysicsCollisionDetails); procedure ColisionPlayer1(const CollisionDetails: TPhysicsCollisionDetails); published { Components designed using CGE editor. These fields will be automatically initialized at Start. } LabelFps: TCastleLabel; Player1: TCastleBox; Player2: TCastleBox; LadoDerecho: TCastleBox; LadoIzquierdo: TCastleBox; MarcadorPlayer1: TCastleLabel; //Añade esto MarcadorPlayer2: TCastleLabel; //Añade esto |
En los eventos de colisión que creamos anteriormente añade el código para incrementar los puntos de cada jugador.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
procedure TViewMain.ColisionLadoDerecho( const CollisionDetails: TPhysicsCollisionDetails); begin //EL jugador 1 (izquierdo) consigue un punto PuntosPlayer1 := PuntosPlayer1 + 1; MarcadorPlayer1.Caption := IntToStr(PuntosPlayer1); end; procedure TViewMain.ColisionLadoIzquierdo( const CollisionDetails: TPhysicsCollisionDetails); begin //El jugador 2 (derecha) consigue un punto PuntosPlayer2 := PuntosPlayer2 + 1; MarcadorPlayer2.Caption := IntToStr(PuntosPlayer2); end; |
Probando
Antes de probar el juego, vete al editor de CGE, y cambia la propiedad Visible de los elementos LadoDerecho y LadoIzquierdo de su valor True a False. Con ello, ocultamos los lados de la pista.
Ahora, compila el juego desde el editor y observa el resultado.
Ya tenemos un juego casi funcional, aunque le quedan algunos detalles para ser totalmente funcional, como el sonido, detener el juego cuando se alcance un número de puntos, reiniciarlo, etc.
Conclusiones
En esta parte hemos visto:
- Hacer uso de los elemento creados en el editor desde código.
- Como desplazar un elemento en el evento Update.
- Como gestionar las teclas en el evento Press.
- Gestionar los eventos de colisión.
Saludos