Comunidad orientada al desarrollo de videojuegos

Tutorial: “Pong” con Wave Engine

OBJETIVOS

El principal objetivo de este tutorial es crear el clásico videojuego “Pong” con Wave Engine. Nuestro Pong tendrá un simple menú una escena de juego.

Aprenderemos a crear diferentes capas de juego y a navegar entre ellas, crearemos entidades que actúan como botones, incluiremos sonidos, crearemos una IA sencilla, tendremos un control de jugador sencillo y otras cosas que veremos a continuación.

CREAR UN PROYECTO NUEVO

Creamos un proyecto nuevo desde la plantilla de Wave Engine, y lo llamamos Pong:

01CreateProject

AÑADIENDO LOS RECURSOS

Lo primero que vamos a hacer es añadir todos los recursos que necesitaremos en nuestro juego. Podemos descargarlos desde aquí. Necesitamos un sprite del jugador, un fondo, una pelota, una imagen de ganador y paredes. También tenemos una fuente y dos sonidos para reproducirlos al marcar un gol y al hacer colisión la pelota con el entorno.

02AddingResources

Nota: Podemos aprender a exportar todos los assets en los primeros post sobre Wave Engine. Con la herramienta que proporciona, Assets Exporter.

Os recomiendo separar los assets por tipo en la carpeta Content, como vemos en la imagen anterior.

Nota: No olvidemos entrar en las propiedades de todos assets y elegir la opción copiar siempre o copiar si es posterior, para evitar el error FileNotFoundException.

CREAMOS LA ESCENA MENÚ

Renombramos la clase por defecto MyScene.cs con el nombre de MenuScene.cs:

En el método CreateScene() creamos el título del menú:

RenderManager.BackgroundColor = Color.Black;
int offset = 100;

var title = new Entity("Title")
               .AddComponent(new Sprite("Content/TitlePong.wpk"))
               .AddComponent(new SpriteRenderer(DefaultLayers.Alpha))
               .AddComponent(new Transform2D()
               {
                   Y = WaveServices.Platform.ScreenHeight / 2 - offset,
                   X = WaveServices.Platform.ScreenWidth / 2 - 150
               });

EntityManager.Add(title);

Esto nos dará algunos errores si no hemos añadido antes los usings correspondientes, en este caso necesitamos los siguientes:

using WaveEngine.Common.Graphics;
using WaveEngine.Components.Gestures;
using WaveEngine.Components.Graphics2D;
using WaveEngine.Components.UI;
using WaveEngine.Framework;
using WaveEngine.Framework.Graphics;
using WaveEngine.Framework.Physics2D;
using WaveEngine.Framework.Services;

03TitlePong

Después creamos dos entidades, ambas serán dos botones para entrar de forma diferente al juego, en la versión multiplayer o single player:

var multiplayerButtonEntity = new Entity("MultiplayerButton")
                                .AddComponent(new Transform2D() 
                                { 
                                    Y = WaveServices.Platform.ScreenHeight / 2 + 50,
                                    X = WaveServices.Platform.ScreenWidth / 2 - offset,
                                    XScale = 2f,
                                    YScale = 2f
                                })
                                .AddComponent(new TextControl()
                                {
                                    Text = "Multiplayer",
                                    Foreground = Color.White,
                                })
                                .AddComponent(new TextControlRenderer())
                                .AddComponent(new RectangleCollider())
                                .AddComponent(new TouchGestures());

multiplayerButtonEntity.FindComponent<TouchGestures>().TouchPressed += new EventHandler<GestureEventArgs>(Multiplayer_TouchPressed);

EntityManager.Add(multiplayerButtonEntity);

var singleplayerButtonEntity = new Entity("SingleplayerButton")
                                .AddComponent(new Transform2D() 
                                { 
                                    Y = WaveServices.Platform.ScreenHeight / 2,
                                    X = WaveServices.Platform.ScreenWidth / 2 - offset,
                                    XScale = 2f,
                                    YScale = 2f
                                })
                                .AddComponent(new TextControl()
                                {
                                    Text = "Single Player",
                                    Foreground = Color.White,
                                })
                                .AddComponent(new TextControlRenderer())
                                .AddComponent(new RectangleCollider())
                                .AddComponent(new TouchGestures());

singleplayerButtonEntity.FindComponent<TouchGestures>().TouchPressed += new EventHandler<GestureEventArgs>(Singleplayer_TouchPressed);

EntityManager.Add(singleplayerButtonEntity);

Si ejecutamos veremos lo siguiente:

04Menu

Ahora vamos a crear una nueva clase, se va a llamar GameScene.cs, aquí es donde se va a ver el juego.

Creada esta clase, volvemos a la clase del menú y vamos a crear dos métodos que van a funcionar como eventos:

private void Multiplayer_TouchPressed(object sender, GestureEventArgs e)
{
    WaveServices.ScreenLayers.AddScene<GameScene>()
                             .Apply("FromMultiplayer");
}

private void Singleplayer_TouchPressed(object sender, GestureEventArgs e)
{
    WaveServices.ScreenLayers.AddScene<GameScene>()
                             .Apply("FromSingleplayer");
}

Estos eventos van ligados a las entidades que hemos creado anteriormente, si hacemos click en cualquiera de las entidades la escena cambiará del Menú al juego, aunque esta escena está por ahora vacía.

CREAMOS LA ESCENA DE JUEGO

Lo primero que vamos a hacer en esta escena es poner un fondo, y lo vamos a crear en el método CreateScene():

// Create BackGround
  var background = new Entity("Background")
                            .AddComponent(new Sprite("Content/Texture/real-pong.wpk"))
                            .AddComponent(new SpriteRenderer(DefaultLayers.Alpha))
                            .AddComponent(new Transform2D()
                            {
                                XScale = 1.5f,
                                YScale = 1.45f,
                            });
// Add entities
 EntityManager.Add(background);

No olvidemos como siempre incluir los usings correspondientes. Pongo los que necesitamos para la clase entera, (aunque aún hay algunos que nos son necesarios, lo serán más adelante):

using WaveEngine.Common.Graphics;
using WaveEngine.Common.Math;
using WaveEngine.Components.Gestures;
using WaveEngine.Components.Graphics2D;
using WaveEngine.Components.UI;
using WaveEngine.Framework;
using WaveEngine.Framework.Graphics;
using WaveEngine.Framework.Managers;
using WaveEngine.Framework.Physics2D;
using WaveEngine.Framework.Services;
using WaveEngine.Framework.Sound;
using WaveEngine.Framework.UI;

ahora podemos ver la escena del juego con el fondo:

05Background

Y ahora tocan las paredes, que van a parte del fondo para poder calcular las colisiones más adelante:

//Create Borders
var barTop = new Entity("BarTop")
               .AddComponent(new Sprite("Content/Texture/wall.wpk"))
               .AddComponent(new SpriteRenderer(DefaultLayers.Alpha))
               .AddComponent(new RectangleCollider())
               .AddComponent(new Transform2D()
               {
                    XScale = 1.55f,
                    YScale = 2f,
               });

var barBot = new Entity("BarBot")
               .AddComponent(new Sprite("Content/Texture/wall.wpk"))
               .AddComponent(new SpriteRenderer(DefaultLayers.Alpha))
               .AddComponent(new RectangleCollider())
               .AddComponent(new Transform2D()
               {
                   XScale = 1.55f,
                   YScale = 2f,
                   Y = WaveServices.Platform.ScreenHeight - 25
               });

//Add entities
ntityManager.Add(barTop);
ntityManager.Add(barBot);

06Walls

Añadiendo el Jugador:

//Create Players
var player = new Entity("Player")
               .AddComponent(new Sprite("Content/Texture/player.wpk"))
               .AddComponent(new SpriteRenderer(DefaultLayers.Alpha))
               .AddComponent(new RectangleCollider())
               .AddComponent(new Transform2D()
               {
                    Origin = new Vector2(0.5f, 1),
                    X = WaveServices.Platform.ScreenWidth / 50,
                    Y = WaveServices.Platform.ScreenHeight / 2
               })
               .AddComponent(new PlayerBehavior());

//Add entities
EntityManager.Add(player);

He añadido una línea que dará error de momento, ya que no existe la clase PlayerBehavior, con lo cual la crearemos. Esta clase proporcionará al jugador la movilidad por la escena, es lo que se llama Behavior o comportamiento. Entonces creamos una nueva clase y la llamamos PlayerBehavior.cs que va a heredar de Behavior :

class PlayerBehavior : Behavior
{
    private const int SPEED = 5;
    private const int UP = -1;
    private const int DOWN = 1;
    private const int NONE = 0;
    private const int BORDER_OFFSET = 25;

    [RequiredComponent]
    public Transform2D trans2D;

    /// <summary>
    /// 1 or -1 indicating up or down respectively
    /// </summary>
    private int direction;
    private PlayerState currentState, lastState;
    private enum PlayerState { Idle, Up, Down };

    public PlayerBehavior()
        : base("PlayerBehavior")
    {
        this.direction = NONE;
        this.trans2D = null;
        this.currentState = PlayerState.Idle;
    }

    protected override void Update(TimeSpan gameTime)
    {
        currentState = PlayerState.Idle;

        // Keyboard
        var keyboard = WaveServices.Input.KeyboardState;
        if (keyboard.W == ButtonState.Pressed)
        {
            currentState = PlayerState.Up;
        }
        else if (keyboard.S == ButtonState.Pressed)
        {
            currentState = PlayerState.Down;
        }

        // Set current state if that one is diferent
        if (currentState != lastState)
        {
            switch (currentState)
            {
                case PlayerState.Idle:
                    direction = NONE;
                    break;
                case PlayerState.Up:
                    direction = UP;
                    break;
                case PlayerState.Down:
                    direction = DOWN;
                    break;
            }
        }

        lastState = currentState;

        // Move sprite
        trans2D.Y += direction * SPEED * (gameTime.Milliseconds / 10);

        // Check borders
        if (trans2D.Y < BORDER_OFFSET + trans2D.YScale + 80)
        {
            trans2D.Y = BORDER_OFFSET + trans2D.YScale + 80;
        }
        else if (trans2D.Y > WaveServices.Platform.ScreenHeight - BORDER_OFFSET)
        {
            trans2D.Y = WaveServices.Platform.ScreenHeight - BORDER_OFFSET;
        }
    }
}

y estos son los usings necesarios:

using WaveEngine.Common.Input;
using WaveEngine.Framework;
using WaveEngine.Framework.Graphics;
using WaveEngine.Framework.Services;

Ahora nuestro jugador se puede mover por la escena con las teclas “w” para moverlo hacia arriba y “s” para moverlo hacia abajo.

07Player

Volvemos a la clase GameScene.cs y añadimos el segundo jugador:

var player2 = new Entity("PlayerIA")
                           .AddComponent(new Sprite("Content/Texture/player.wpk"))
                           .AddComponent(new SpriteRenderer(DefaultLayers.Alpha))
                           .AddComponent(new RectangleCollider())
                           .AddComponent(new Transform2D()
                           {
                               Origin = new Vector2(0.5f, 1),
                               X = WaveServices.Platform.ScreenWidth - 15,
                               Y = WaveServices.Platform.ScreenHeight / 2
                           });

//Add entities
EntityManager.Add(player2);

con lo que lo vemos en su posición:

08Player2

Ahora vamos a crear la bola:

//Create Ball
var ball = new Entity("Ball")
              .AddComponent(new Sprite("Content/Texture/ball.wpk"))
              .AddComponent(new SpriteRenderer(DefaultLayers.Alpha))
              .AddComponent(new RectangleCollider())
              .AddComponent(new Transform2D()
              {
                  Origin = new Vector2(0.5f, 1),
                  X = WaveServices.Platform.ScreenWidth / 2,
                  Y = WaveServices.Platform.ScreenHeight / 2
              })
              .AddComponent(new BallBehavior(player, barBot, barTop, player2));
//Add entities
EntityManager.Add(ball);

Y el behavior para mover la bola. Necesitamos crear otra clase que herede de Behavior, se llamará BallBehavior.cs y tendrá el siguiente código:

class BallBehavior : Behavior
{
    private const float SPEED = 4f;
    private const int BORDER_OFFSET = 20;

    private Entity player;
    private RectangleCollider rectPlayer;

    private Entity player2;
    private RectangleCollider rectPlayer2;

    private Entity barBot;
    private RectangleCollider rectBarBot;

    private Entity barTop;
    private RectangleCollider rectBarTop;

    private int verticalDirection = -1;
    private int horizontalDirection = -1;
    private float speed = SPEED;
    private float incrementSpeed = 0.5f;
    private int goals1 = 0;
    private int goals2 = 0;
    private bool checkGoal = false;

    [RequiredComponent]
    public Transform2D trans2D;

    public int Goal1 { get { return goals1; } private set { goals1 = value; } }
    public int Goal2 { get { return goals2; } private set { goals2 = value; } }
    public int HorizontalDirection { get {return horizontalDirection; } }

    public BallBehavior(Entity player, Entity barBot, Entity barTop, Entity playerIA)
        : base("BallBehavior")
    {
        this.trans2D = null;
        this.player = player;
        this.rectPlayer = player.FindComponent<RectangleCollider>();
        this.player2 = playerIA;
        this.rectPlayer2 = playerIA.FindComponent<RectangleCollider>();
        this.barBot = barBot;
        this.rectBarBot = barBot.FindComponent<RectangleCollider>();
        this.barTop = barTop;
        this.rectBarTop = barTop.FindComponent<RectangleCollider>();
    }

    protected override void Update(TimeSpan gameTime)
    {
        //Check Goals
        if (trans2D.X <= 0 && !checkGoal)
        {

            (Owner.Scene as GameScene).CurrentState = GameScene.State.Goal;
            checkGoal = true;
            goals2++;
            StartBall();
        }

        if (trans2D.X >= WaveServices.Platform.ScreenWidth && !checkGoal)
        {

            (Owner.Scene as GameScene).CurrentState = GameScene.State.Goal;
            checkGoal = true;
            goals1++;
            StartBall();
        }

        //Move Ball
        if (trans2D.X > 0 && trans2D.X < WaveServices.Platform.ScreenWidth)
        {
            trans2D.X += horizontalDirection * speed * (gameTime.Milliseconds / 10);
            trans2D.Y += verticalDirection * speed * (gameTime.Milliseconds / 10);
        }

        // Check collisions
        if (rectPlayer.Contain(new Vector2(trans2D.X, trans2D.Y)))
        {
            horizontalDirection = 1;
            speed += incrementSpeed;
            (Owner.Scene as GameScene).PlaySoundCollision();
        }

        if (rectPlayer2.Contain(new Vector2(trans2D.X, trans2D.Y)))
        {
            horizontalDirection = -1;
            speed += incrementSpeed;
            (Owner.Scene as GameScene).PlaySoundCollision();
        }

        if (rectBarBot.Contain(new Vector2(trans2D.X, trans2D.Y)))
        {
            verticalDirection = -1;
            (Owner.Scene as GameScene).PlaySoundCollision();
        }

        if (rectBarTop.Contain(new Vector2(trans2D.X, trans2D.Y - 15)))
        {
            verticalDirection = 1;
            (Owner.Scene as GameScene).PlaySoundCollision();
        }
    }

    //Start new ball
    public void StartBall()
    {
        trans2D.X = WaveServices.Platform.ScreenWidth / 2;
        trans2D.Y = WaveServices.Platform.ScreenHeight / 2;
        checkGoal = false;
        speed = SPEED;
    }

}

En esta clase hacemos que se mueva la bola por la escena rebotando en las paredes y con los jugadores. El movimiento es básico, se podría mejorar todo lo que queramos.

Y estos son los usings necesarios:

using WaveEngine.Common.Math;
using WaveEngine.Framework;
using WaveEngine.Framework.Graphics;
using WaveEngine.Framework.Physics2D;
using WaveEngine.Framework.Services;

Ya que tenemos nuestra bola rebotando por la escena, le vamos a añadir el sonido correspondiente a las colisiones con el entorno. Para ello creamos el siguiente método, que como hemos visto en el código anterior se llama cada vez que colisiona la bola con algo. Este método esta en GameScene.cs:

// Game Sounds
private SoundInfo soundPong;

public void PlaySoundCollision()
{
    WaveServices.SoundPlayer.Play(soundPong);
}

09Ball

Para mover al segundo jugador necesitamos añadir otro Behavior, como PlayerBehavior.cs, pero con controles distintos. Creamos otra clase, y la llamamos Player2Behavior.cs. Es igual que la clase PlayerBehavior.cs, solo cambia una parte del código, así que copiamos y pegamos la clase anterior, y ahora vemos los cambios que hay que realizar:

// Keyboard
var keyboard = WaveServices.Input.KeyboardState;
if (keyboard.Up == ButtonState.Pressed)
{
    currentState = PlayerState.Up;
}
else if (keyboard.Down == ButtonState.Pressed)
{
    currentState = PlayerState.Down;
}

simplemente hemos cambiado los controles “w” y “s” por las teclas “up” y “down”.

Ahora podemos manejar a los dos jugadores. Solo necesitamos añadir el componente a la entidad player2. Pero para seguir el flujo del menú al juego, debemos añadir otra clase nueva, que llamaremos PlayerAIBehavior.cs:

class PlayerAIBehavior : Behavior
{
    private const float SPEED = 5f;
    private const int BORDER_OFFSET = 25;

    private int direction;
    private bool move = false;

    [RequiredComponent]
    public Transform2D trans2D;

    public Entity ball;
    public Transform2D transBall2D;
    public BallBehavior ballBehavior;

    public PlayerAIBehavior(Entity ball)
        : base("PlayerIABehavior")
    {
        this.trans2D = null;
        this.ball = ball;
        this.transBall2D = ball.FindComponent<Transform2D>();
        this.ballBehavior = ball.FindComponent<BallBehavior>();
        this.direction = ballBehavior.HorizontalDirection;
    }

    protected override void Update(TimeSpan gameTime)
    {
        this.direction = ballBehavior.HorizontalDirection;

        // Move sprite
        if (this.direction > 0 && (Owner.Scene as GameScene).CurrentState == GameScene.State.Game)
            move = true;
        else
            move = false;

        if (trans2D.Y < transBall2D.Y && move)
            trans2D.Y += SPEED * (gameTime.Milliseconds / 10);
        else if (trans2D.Y > transBall2D.Y && move)
            trans2D.Y -= SPEED * (gameTime.Milliseconds / 10);

        // Check borders
        if (trans2D.Y < BORDER_OFFSET + trans2D.YScale + 80)
        {
            trans2D.Y = BORDER_OFFSET + trans2D.YScale + 80;
        }
        else if (trans2D.Y > WaveServices.Platform.ScreenHeight - BORDER_OFFSET)
        {
            trans2D.Y = WaveServices.Platform.ScreenHeight - BORDER_OFFSET;
        }
    }
}

estos sería los usings necesarios:

using WaveEngine.Framework;
using WaveEngine.Framework.Graphics;
using WaveEngine.Framework.Services;

Para que funcione el flujo completo del juego, según pulsemos una opción u otra en la escena del menú, tenemos que añadir las siguientes líneas de código en el método CreateScene() de la clase GameSecene:

// Add component AI or Second Player controller
if (WaveServices.ScreenLayers.Tag.CompareTo("FromSingleplayer") == 0)
    player2.AddComponent(new PlayerAIBehavior(ball));
else if (WaveServices.ScreenLayers.Tag.CompareTo("FromMultiplayer") == 0)
    player2.AddComponent(new Player2Behavior());

Ahora, cuando seleccionamos alguna de las opciones para jugar en el menú, se añade en la escena de juego un componente que indica al segundo jugador si va a ser manejado por otra persona o va a ser el ordenador quien lo maneje.

Lo siguiente que vamos a hacer es crear la interface, para eso necesitamos añadir algunos Texblock en el método CreateScene(), pero antes crearemos unas variables y código necesarios para más adelante:

//Game Sounds
private SoundInfo soundPong;
private SoundInfo soundGoal;

//Score and Initial TextBlocks 
public TextBlock textblockGoal1, textblockGoal2, textblockInit;

//Create Trophy
public Entity trophy = new Entity("Trophy");

//Game States
public enum State
{
    Init,
    Game,
    Goal,
    Win
}
public State CurrentState = State.Init;

esto son los sonidos, los textBlock , la entidad que enseñará quien gana y una enumeración de estados del juego.

Volvemos con el interfaz, para ello añadimos el siguiente código en CreateScene():

//Create UI
int offsetTop = 50;

textblockGoal1 = new TextBlock("Goal1")
                {
                    Margin = new Thickness((WaveServices.Platform.ScreenWidth / 2f) - 100, offsetTop, 0, 0),
                    FontPath = "Content/Font/Calisto MT.wpk",
                    Text = "0",
                    Height = 130,
                };

textblockGoal2 = new TextBlock("Goal2")
                {
                    Margin = new Thickness((WaveServices.Platform.ScreenWidth / 2f) + 50, offsetTop, 0, 0),
                    FontPath = "Content/Font/Calisto MT.wpk",
                    Text = "0",
                    Height = 130,
                };

textblockInit = new TextBlock("TextInit")
                {
                    Margin = new Thickness((WaveServices.Platform.ScreenWidth / 2f) - 50, offsetTop + 150, 0, 0),
                    FontPath = "Content/Font/Calisto MT.wpk",
                    Text = "",
                    Height = 130,
                    IsVisible = false,
                }; 

//Trophy components               
trophy.AddComponent(new Sprite("Content/Texture/trophy.wpk"));
trophy.AddComponent(new SpriteRenderer(DefaultLayers.Alpha));
trophy.AddComponent(new Transform2D());
trophy.IsVisible = false;

//Create a sound bank and sound info for game
SoundBank bank = new SoundBank(Assets);
WaveServices.SoundPlayer.RegisterSoundBank(bank);

soundPong = new SoundInfo("Content/Sound/SoundPong.wpk");
bank.Add(soundPong);

soundGoal = new SoundInfo("Content/Sound/SoundGol.wpk");
bank.Add(soundGoal);

//Add entities
EntityManager.Add(textblockInit);
EntityManager.Add(textblockGoal1);
EntityManager.Add(textblockGoal2);
EntityManager.Add(trophy);

Finalmente necesitamos crear una nueva clase, la cual vamos a llamar MySceneBehavior.cs, y que va a controlar todos los estados del juego:

public class MySceneBehavior : SceneBehavior
{
    private const int GOALS_TO_WIN = 2;
    private int time = 3;
    Entity ball;
    Entity bg;
    BallBehavior ballBehavior;

    public MySceneBehavior()
        : base("PongBehavior")
    { }

    protected override void Update(TimeSpan gameTime)
    {

        var state = (this.Scene as GameScene).CurrentState;

        switch (state)
        {
            case GameScene.State.Init:

                var textBlock = (this.Scene as GameScene).textblockInit;
                textBlock.IsVisible = true;
                textBlock.Text = time.ToString();
                ball.IsActive = false;
                bg.IsVisible = false;

                WaveServices.TimerFactory.CreateTimer("Init", TimeSpan.FromSeconds(1f), () =>
                {
                    textBlock.Text = time.ToString();
                    time--;

                });

                if (time <= 0)
                {
                    time = 3;
                    WaveServices.TimerFactory.RemoveTimer("Init");
                    textBlock.IsVisible = false;
                    bg.IsVisible = true;
                    SetState(GameScene.State.Game);
                }

                break;

            case GameScene.State.Game:

                ball.IsActive = true;

                break;

            case GameScene.State.Goal:

                (this.Scene as GameScene).PlaySoundGoal();

                ball.IsActive = false;

                var textBlock1 = (this.Scene as GameScene).textblockGoal1;
                textBlock1.Text = ballBehavior.Goal1.ToString();

                var textBlock2 = (this.Scene as GameScene).textblockGoal2;
                textBlock2.Text =  ballBehavior.Goal2.ToString();

                if (ballBehavior.Goal1 == GOALS_TO_WIN || ballBehavior.Goal2 == GOALS_TO_WIN)
                {
                    SetState(GameScene.State.Win);
                    break;
                }

                SetState(GameScene.State.Init);

                break;

            case GameScene.State.Win:

                (this.Scene as GameScene).trophy.IsVisible = true;
                ball.IsActive = false;

                if (ballBehavior.Goal1 == GOALS_TO_WIN)
                {
                    (this.Scene as GameScene).trophy.FindComponent<Transform2D>().X = WaveServices.Platform.ScreenWidth / 2 - 300;
                    (this.Scene as GameScene).trophy.FindComponent<Transform2D>().Y = WaveServices.Platform.ScreenHeight / 2 - 100;
                }
                else
                {
                    (this.Scene as GameScene).trophy.FindComponent<Transform2D>().X = WaveServices.Platform.ScreenWidth / 2 + 100;
                    (this.Scene as GameScene).trophy.FindComponent<Transform2D>().Y = WaveServices.Platform.ScreenHeight / 2 - 100;
                }

                break;
        }
    }

    public void SetState(GameScene.State _State)
    {
        (this.Scene as GameScene).CurrentState = _State;
    }

    protected override void ResolveDependencies()
    {

        ball = (this.Scene as GameScene).EntityManager.Find<Entity>("Ball");
        bg = (this.Scene as GameScene).EntityManager.Find<Entity>("Background");
        ballBehavior = ball.FindComponent<BallBehavior>();
    }
}

Estos son los usings para la clase:

using WaveEngine.Framework;
using WaveEngine.Framework.Graphics;
using WaveEngine.Framework.Services;

Para conectar el juego con el menú, necesitamos añadir un botón back y para controlar la escena con su behavior necesitamos indicarlo en el método CreateScene():

//Create Back Button
var backButtonEntity = new Entity("BackButton")
               .AddComponent(new Transform2D())
               .AddComponent(new TextControl()
               {
                   Text = "Back",
                   HorizontalAlignment = WaveEngine.Framework.UI.HorizontalAlignment.Center,
                   VerticalAlignment = WaveEngine.Framework.UI.VerticalAlignment.Bottom,
                   Foreground = Color.Black,
               })
               .AddComponent(new TextControlRenderer())
               .AddComponent(new RectangleCollider())
               .AddComponent(new TouchGestures());

backButtonEntity.FindComponent<TouchGestures>().TouchPressed += new EventHandler<GestureEventArgs>(Back_TouchPressed);

//Add Scene Behavior Post Update
AddSceneBehavior(new MySceneBehavior(), SceneBehavior.Order.PostUpdate);

Y añadir el evento correspondiente al botón back. Y aprovechamos para añadir el método que ejecutará el sonido de gol:

private void Back_TouchPressed(object sender, GestureEventArgs e)
{
    WaveServices.TimerFactory.RemoveTimer("Init");
    WaveServices.ScreenLayers.AddScene<MenuScene>()
                     .Apply("FromGame");
}

public void PlaySoundGoal()
{
    WaveServices.SoundPlayer.Play(soundGoal);
}

Con esto habremos acabado este sencillo tutorial de cómo hacer un “Pong” con Wave Engine. Hay muchas maneras de hacerlo, pero esta es una de las más sencillas. Todo es mejorable. Simplemente es una guía de como montar una pequeño juego con sus correspondientes clases y flujo mediante menús.

Aquí vemos la cuenta atrás:

10Init

Y aquí el resultado final del juego, con la bola y los jugadores en movimiento y el marcador funcionando con los goles marcados:

 11Game

PROYECTO COMPLETO

Podéis descargar el proyecto completo desde aquí.

FUENTES

Tutorial original en inglés: http://blog.waveengine.net

Para descargar Wave Engine: http://www.waveengine.net

Creación original y traducción por Carlos Sánchez López

, , , , ,

8 thoughts on “Tutorial: “Pong” con Wave Engine

Leave a Reply