Tutorials/03Player

From NeoAxis Engine Wiki

Jump to: navigation, search

Contents

Tutorial 3 - Adding the player's paddle

Introduction

The following step in our BreakOut tutorial series is to put the player's paddle on the screen. This step involves a more indeep look at the entity system, and we will be using the Resource Editor for the first time.

NeooAxis example application provides a very professional aproach to game logic. For instance, NeoAxis proposes that player logic must be splited into three categories:

  • Pure player logic: The player's name, scoring information...
  • Player's behavior related: The player's actions, input handling...
  • Player's representation in the game: A Unit that is rendered in the 3D world...

In the example aplication you will find three classes that represent a player: Player, PlayerIntellect and PlayerCharacter.

To create the our own BreakOut player, we are creating new versions of these three classes.

Creating the Player

Open the NeoAxis Visual Studio solution, and locate the GameEntities project. Create a new folder within the project called BreakOut. We are placing all our new files inside this folder. This will mantain our own code separated from Example Application's, which will help us a lot in the future. For example, will have an easier integration with new versions of the engine.

The first class you will be creating today should be called BOPlayer. Add this new class to the sollution, on the BreakOut folder, and paste in the code:

using System;
using System.Collections.Generic;
using System.Text;

namespace GameEntities.BreakOut
{

    class BOPlayerType : PlayerType
    {
    }

    class BOPlayer : Player
    {
        BOPlayerType _type = null; public new BOPlayerType Type { get { return _type; } }
    }

}

The first thing you need to notice about the code is there actually two classes being defined there. All NeoAxis entities must define an aditional class called the type class. This class will allow the game designers to create different Entity Types using the Resource Editor. This special classes will only contain the properties you want to be editable in the Resource Editor, allowing the designers to configure different values for them. Will be describing this mechanism in detail later. For now, all you need to know is that is mandatory to define them, and that they must be called exactly like the main class, but with the Type suffix.

The second thing to mention here, is that both classes are inheriting from existing classes, in this case from Player and PlayerType. This base classes provide our player with a few usefull properties, like its name, and a reference to the an Intellect object (that will be creating soon).

Finally, there's that lonelly and strange line inside the BOPlayer class:

        BOPlayerType _type = null; public new BOPlayerType Type { get { return _type; } }

This line is also mandatory, must be provided for each new entity we create. It allows NeoAxis engine to associate each class with their corresponding special type-class. Please note that this define a property which type must match the name of the special class, so this line will look very similar on all classes.

Creating the Intellect

Now we are going to create the Player's intellect, which will be in charge of handling the keyboard input in our BreakOut game.

Create a new class file called BOPlayerIntellect with this code:

using System;
using System.Collections.Generic;
using System.Text;
using Engine.EntitySystem; 

namespace GameEntities.BreakOut
{
    class BOPlayerIntellectType : IntellectType
    {
    } 

    class BOPlayerIntellect : Intellect
    {
        BOPlayerIntellectType _type = null; public new BOPlayerIntellectType Type { get { return _type; } } 

        /// <summary>
        /// Initialization
        /// </summary>
        /// <param name="loaded"></param>
        protected override void OnPostCreate(bool loaded)
        {
            base.OnPostCreate(loaded);

            //This entity will accept commands of the player
            if (Controllers.Instance != null)
                Controllers.Instance.PlayerControlCommandListener = this;
        }

        /// <summary>
        /// Translate control commands into control keys
        /// </summary>
        protected override void OnPlayerControlCommand(PlayerControlCommand command)
        {
            base.OnPlayerControlCommand(command);

            ControlKey key = (ControlKey)command.ControlKey;

            if (command.KeyPressed)
                ControlKeyPress(key);

            if (command.KeyReleased)
                ControlKeyRelease(key);

        }

    }

}

Again, you can see the two classes being defined here, BOPlayerIntellect and BOPlayerIntellectType. Now we are subclassing from Intellect which is a useful class that implements a mechanism capable of controlling units in the game. Intellect classes can be of two families:

  • Input handling: Like the one we have created, we take commands in (from the user input), and send them into a unit.
  • AI: Some AI logic will decide which actions should be taken, and then send commands into a unit.

Our firs method is the initialization one, called OnPostCreate. All entities will execute this method after they are instance in the world. For this case, it just subscribe itself to receive controller commands:

            //This entity will accept commands of the player
            if (Controllers.Instance != null)
                Controllers.Instance.PlayerControlCommandListener = this;

This is necesary in order to get the OnPlayerCotrolCommand method called from inside the Engine. Let's take a line by line view of this method:

            base.OnPlayerControlCommand(command);

First, delegate on the base implementation, this is useful and oftenly mandatory when your are overriding methods from base classes. You will see this mechanism widelly used across the Example Game, and also in any other c# application that makes a heavy use of inheritance.

            ControlKey key = (ControlKey)command.ControlKey;

ControlKey is an enum type. It defines which keys on the keyboard are relevant for our game. Here we just typecast the parameter we have received from int to ControlKey.

            if (command.KeyPressed)
                ControlKeyPress(key);

            if (command.KeyReleased)
                ControlKeyRelease(key);

Now, we check if the received command correspond to a key being pressed, or a key being released. Then we delegate on the base class implementation of the ControlKeyPress and ControlKeyRelease methods. Calling these methods will cause a message to be sent into the player's controlled unit. We are handling these messages later, in the implementation of the player's Paddle.

Creating the Paddle

We have created two of the three necesary classes for the player to appear in the game. The last one, is the one in charge of representing the player in the 3D world. In our BreakOut game, the player controlls a simple paddle, that will be moving from side to side.

The only thing that will be implementing is that movement. We need to respond to the Intellect commands, and act accordingly.

Create a new class, in the BreakOut folder of the GameEntities project as always, called BOPaddle. This is the class code:

using System;
using System.Collections.Generic;
using System.Text;
using Engine.MathEx;

namespace GameEntities.BreakOut
{

    class BOPaddleType : UnitType
    {
    }

    class BOPaddle : Unit
    {
        BOPaddleType _type = null; public new BOPaddleType Type { get { return _type; } }

        /// <summary>
        /// Handle Intellect Commands
        /// </summary>
        /// <param name="command"></param>
        protected override void OnIntellectCommand(Intellect.Command command)
        {
            base.OnIntellectCommand(command);

            if (command.KeyPressed)
            {
                if (command.Key == ControlKey.Left)
                    MoveLeft();
                if (command.Key == ControlKey.Right)
                    MoveRight();
            }
        }

        /// <summary>
        /// Move the paddle to the left
        /// </summary>
        private void MoveLeft()
        {
            this.Position = this.Position + new Vec3(0, 1, 0);
        }

        /// <summary>
        /// Move the paddle to the right
        /// </summary>
        private void MoveRight()
        {
            this.Position = this.Position - new Vec3(0, 1, 0);
        }

    }

}

Well this is our paddle class. Notice that it derives from Unit instead of Entity. A Unit is an special type of entity that can be placed and moved in the 3D world, which what we want for our paddle.

The only thing a paddle does in a BreakOut game is move from side to side, so we are going to catch the commands that come from the intellect class, and respond to them accordingly. To catch these commands, you have to override the OnIntellectCommand method. For now, we just want to respond to keydowns on the left and right keys:

            if (command.KeyPressed)
            {
                if (command.Key == ControlKey.Left)
                    MoveLeft();
                if (command.Key == ControlKey.Right)
                    MoveRight();
            }

Then we delegate on the simple MoveLeft and MoveRight methods to modify the position of the paddle in the world:

        private void MoveLeft()
        {
            this.Position = this.Position + new Vec3(0, 1, 0);
        }

To move to the left, just add a vector with the desired speed to the Position. This is a very rudimentary movement implementation, don't worry we are going to be back on this subject on a future tutorial.

Before going on, compile your code now and ensure everything is ok.

Creating the entities

We have created three new classes (in fact 6 classes were created if take the type ones into account). These classes all derive indirectly from NeoAxis Entity class. That means that we only can use them through the Entity System.

BOPlayer, BOIntellect, and BOPaddle classes may not be created using the standard c# new mechanism. Instead, you first have to create an archetype (called an Entity Type) out of them, using the Resource Editor.

This procedure enable game programmers and designers to create different Entity Types of the same class, each one using a different set of values in their properties. Will be using this feature in future tutorials, by now, our classes are so simple that they have no expose properties values that can be customized by game designers.

We need to create at least one Entity Type from each class, let's begin with openning the Resource Editor.

Image:tutorial03_01.png

This picture shows NeoAxis' Resource Editor default view, in this case the preview window in the middle is showing the ball's mesh. This application allows you to explore the available game resources, including models, textures, materials, particle effects and other resources. Also, you can view the available Entity Types, edit them, or create new ones.

Using the Resource tree explorer located in the left, locate the Types subfolder. This is where all the game Entity Types reside. We are creating a subfolder here named BreakOut to place our Entity Types inside it. Use the right clic on the Types folder and select New Directory to create the new folder. Then right clic on the BreakOut folder you just created, and then New Resouce.

This will show up the New Resource Wizard that will look like the following picture:

Image:tutorial03_02.png

You can now select the type of resource you want to create. Choose Entity type and click Next.

Image:tutorial03_03.png

The wizard will tranforms into New entity type wizard. Click Next to pass on the wellcome screen.

Image:tutorial03_04.png

Now comes the relevant step. In this tab, you have to choose a name for your Entity type. Type BOPlayer. Now you need to choose the entity class from which you want to create the type, choose the BOPlayer class in the dropdown list. Now click Next to create the entity.

Image:tutorial03_05.png

This screen indicates that everything went ok. Click Finish to close the wizard.

Now you see the Resource Editor, with the recently created Entity Type selected. Nothing is shown in the preview window, and you can see that the properties tool shows the available properties for our BOPlayer entity.

Image:tutorial03_06.png

Our first Entity Type is created! Take your time to explore the results in the Resource Editor, focus on the Properties tab on the right. This tabs shows the properties on each entity, grouped by the class where each property is defined into. This tool will allow programmers and gamedesigners to configure the values of the instance of these type of objects in the game.

Now yo know how to create new entities, so you will create the next one on your own! Create a BOPlayerIntellect entity type, based on the BOPlayerIntellect class. When your done, the Resource Editor will look like this:

Image:tutorial03_07.png

Now you're gettin it! Finally, follow the same process once again, to create a new entity called BOPaddle using the class BOPaddle as a base. Now the Resource Editor will have this appearance:

Image:tutorial03_08.png

Observe that when the BOPaddle entity type is selected, the Resource Editor looks slightly different. This is because the BOPaddle class inherits from from Unit class, instead of Entity, so not only the Entity properties are shown, but the Unit ones too.

A Unit derived class is useful to place animated models in the game. In this case, we are going to configure the BOPaddle unit we have just created to show up the paddle model.

To do this, the first step is to enter edit mode, you can either double click on the BOPaddle item on the resouce tree, or use right click and Edit command. When Resource Editor is in edit mode, a green marquee surrounds the preview window, like shown here:

Image:tutorial03_09.png

Now, locate the AttachedObjects properties of the BOPlayer entity in the properties tab on the right. This properties allows us to designate which model will be show along the entity. Click on the small button that appears on the right and a small dialog will appear, that will look like this one (please note this dialog is locallized by .NET Framework to you local language, so in my case it appears in spanish :D)

Image:tutorial03_10.png

Click on the Add button and a Mesh object will be added to the collection of attached objects. Now we need to custom this mesh, to indicate which mesh that we want to be shown. Locate the MeshName property on the right panel and click the small button again to show up the Resource Browser dialog:

Image:tutorial03_11.png

Search for the paddle.mesh model on the Models\BreakOut folder, and then click OK

Image:tutorial03_12.png

Now the paddle model is correctly attached to the BOPaddle entity type. Now the Resource Editor will preview our new Entity, like this:

Image:tutorial03_13.png

We are done for now, don't forget to save your changes. Close the Resource Editor and go back to the Visual Studio sollution.

Instancing the entities

Now here comes the final steps for this tutorials. We need to put our recently created Entities into action!

Locate the PlayerManager class in the GameEntities project. This class is responsible of the creation of the Player, when a map is loaded. Locate the method called AddSinglePlayer and replace it with the following code:

       public Player AddSinglePlayer(string name)
       {
           Player player;
           Intellect intellect;

           switch (GameMap.Instance.GameType)
           {
               case GameMap.GameTypes.BreakOut:

                   player = (Player)Entities.Instance.Create(EntityTypes.Instance.GetByName("BOPlayer"), this);
                   player.PostCreate();
                   player.PlayerName = name;

                   intellect = (Intellect)Entities.Instance.Create(EntityTypes.Instance.GetByName("BOPlayerIntellect"), player);
                   intellect.PostCreate();
                   player.Intellect = intellect;
                   intellect.Player = player;
                   break;

               default:

                   player = (Player)Entities.Instance.Create(EntityTypes.Instance.GetByName("Player"), this);
                   player.PostCreate();
                   player.PlayerName = name;

                   intellect = (PlayerIntellect)Entities.Instance.Create(EntityTypes.Instance.GetByName("PlayerIntellect"), player);
                   intellect.PostCreate();
                   player.Intellect = intellect;
                   intellect.Player = player;

                   break;
           }
           return player;
       }

The new method will provide two types of entities, depending of the GameType of the map. When the GameType is set to BreakOut on the map, the game will instance a BOPlayer and BOPlayerIntellect objects. When the GameType is not set to BreakOut, the game will simply instance the standard Player and PlayerIntellect. This way, the example provided maps will continue to work with our code.

Let's take a closer look at the instancing of the entities. In NeoAxis, to create an instance of an object, you must do it using the name of the Entity Type defined with the Resource Editor, like shown in the following line:

                   player = (Player)Entities.Instance.Create(EntityTypes.Instance.GetByName("BOPlayer"), this);

The Entities.Instance refers to a singleton object available everywhere in the game. Using it's Create method, you can create instances of object of the given entity type. This way the class will be instanced but also their parameters will be initializes with the values you have configured using Resource Editor. Finally notice how we pass a reference to the current with a this pointer. With this parameter you can set the parent object of the instance being created.

After you have instanced an entity, you have to call to the PostCreate method, in order to let the entity initialize itself:

                   player.PostCreate();

Finally, we instance the PlayerIntellect and associate each other with the following code:

                   intellect = (Intellect)Entities.Instance.Create(EntityTypes.Instance.GetByName("BOPlayerIntellect"), player);
                   intellect.PostCreate();
                   player.Intellect = intellect;
                   intellect.Player = player;

Our PlayerManager class is ready. We need still have a couple of things left, locate the GameWorld class on the Entities. We need to modify the DoActionsAfterMapCreated to look like this:

        void DoActionsAfterMapCreated()
       {
           if( EntitySystemWorld.Instance.WorldSimulationType == WorldSimulationType.Single )
           {
               if( GameMap.Instance.GameType == GameMap.GameTypes.Action ||
                   GameMap.Instance.GameType == GameMap.GameTypes.TPSArcade ||
                   GameMap.Instance.GameType == GameMap.GameTypes.BreakOut )
               {
                   Player player = PlayerManager.Instance.AddSinglePlayer( "__PlayerName__" );

                   SpawnPoint spawnPoint = null;
                   if( shouldChangeMapSpawnPointName != null )
                   {
                       spawnPoint = Entities.Instance.GetByName( shouldChangeMapSpawnPointName ) as SpawnPoint;
                       if( spawnPoint == null )
                           Log.Error( "World: SpawnPoint with name \"{0}\" not defined ", shouldChangeMapSpawnPointName);
                   }

                   Unit unit;
                   if( spawnPoint != null )
                       unit = CreatePlayerUnit( player, spawnPoint );
                   else
                       unit = CreatePlayerUnit( player );

                   if( unit != null )
                   {
                       unit.Intellect = player.Intellect;
                       player.Intellect.ControlledObject = unit;
                   }

                   if( shouldChangeMapPlayerCharacterInformation != null )
                   {
                       ((PlayerCharacter)player.Intellect.ControlledObject ).ApplyChangeMapInformation( shouldChangeMapPlayerCharacterInformation, spawnPoint );
                   }

               }

               EntitySystemWorld.Instance.ResetExecutedTime();
           }

           shouldChangeMapName = null;
           shouldChangeMapSpawnPointName = null;
           shouldChangeMapPlayerCharacterInformation = null;
       }

Well this is a bit more complex, isn't it? Well most of the was already there and is necessary for the example game to work fine. We have removed the creation of the Intellect class, because we have moved it inside the AddSinglePlayer method in the previous step, so it was no longer necessary here.

Then we have added the BreakOut game type to the conditional sentence at the beggining, shown in bold below, making this code compatible with our map:

               if( GameMap.Instance.GameType == GameMap.GameTypes.Action ||
                   GameMap.Instance.GameType == GameMap.GameTypes.TPSArcade ||
                   GameMap.Instance.GameType == GameMap.GameTypes.BreakOut )

The first thing this method really does, is call our previouslly created AddSinglePlayer method. To do so, first it obtains a reference to the already existing instance of a PlayerManager:

                   Player player = PlayerManager.Instance.AddSinglePlayer( "__PlayerName__" );

After creating the player, this method locates an available SpawnPoint in the current map (remember how we added one on our first tutorial?):

                   SpawnPoint spawnPoint = null;
                   if( shouldChangeMapSpawnPointName!= null )
                   {
                       spawnPoint = Entities.Instance.GetByName( shouldChangeMapSpawnPointName) as SpawnPoint;
                       if( spawnPoint == null )
                           Log.Error( "World: SpawnPoint with name \"{0}\" not defined ", shouldChangeMapSpawnPointName);
                   }

Now we have the spawn point, we create a unit using it:

                   Unit unit;
                   if( spawnPoint != null )
                       unit = CreatePlayerUnit( player, spawnPoint );
                   else
                       unit = CreatePlayerUnit( player );

Finally the unit created is associated with its player and intellect:

                   if( unit != null )
                   {
                       unit.Intellect = player.Intellect;
                       player.Intellect.ControlledObject = unit;
                   }

Now, the final step in this tutorial, is to create the right type of Unit depending on the game type of the map. Locate the before mentioned CreatePlayerUnit method on this very same class, and modify it to look like this:

       public Unit CreatePlayerUnit( Player player, SpawnPoint spawnPoint )
       {
           string unitTypeName;
           if (!player.Bot)
           {
               if (GameMap.Instance.GameType == GameMap.GameTypes.BreakOut)
                   unitTypeName = "BOPaddle";
               else
                   unitTypeName = "Rabbit";
           }
           else
               unitTypeName = player.PlayerName;

           Unit unit = (Unit)Entities.Instance.Create( unitTypeName, Map.Instance );

           Vec3 posOffset = new Vec3( 0, 0, 1.5f );
           unit.Position = spawnPoint.Position + posOffset;
           unit.Rotation = spawnPoint.Rotation;
           unit.PostCreate();

           return unit;
      }

Once again, we use the GameType property to determine which type of entity must be created, this time we also take in consideration the Bot property of the Player class:

           if (!player.Bot)
           {
               if (GameMap.Instance.GameType == GameMap.GameTypes.BreakOut)
                   unitTypeName = "BOPaddle";
               else
                   unitTypeName = "Rabbit";
           }
           else
               unitTypeName = player.PlayerName;

Now that we know the name of the Entity we want to use, we just create an instance the same way we have done before:

           Unit unit = (Unit)Entities.Instance.Create( unitTypeName, Map.Instance );

And place it on the map, using the position and rotation of the SpawnPoint:

           Vec3 posOffset = new Vec3( 0, 0, 1.5f );
           unit.Position = spawnPoint.Position + posOffset;
           unit.Rotation = spawnPoint.Rotation;

Finally call the PostCreate method on the unit, so it can initialize itself:

           unit.PostCreate();


Running the game

In the last tutorial we ran our game from the Map Editor. This is convenient in many situations, but for day by day programming it will be even more convenitent to do so from Visual Studio.

The Map Editor internally launches the game, using the map name as a parameter. So simply configuring the map name as a command line argument in the Game project will do the trick. Right click on the Game project and choose Properties, then navigate to the Debug tab, and configure the BreakOut map as the command line argument, like in this picture:

Image:tutorial03_14.png

Now you are ready to clic run your game from Visual Studio. Ensure that Game project is Set as Start Up Project (if it isn't, you will have to right click on the project, on the Solution Explorer tab, and then Set as Start Up Project) and click run button (the one with the green play icon that you will find on the tool bar) or just hit F5.

This is how you game looks like now. Notice that you can actually move the paddle a bit using the left and right keys (A and D by defaults).

Image:tutorial03_15.png

The movement really need some improvement, but that will come later!

Conclussions

This has been a more complicated tutorial. These are the topics we have covered:

  • Creating of new classes the NeoAxis' way!
  • Using the Resource Editor to create new Entity Types based on these classes.
  • Learn how entities should be instanced.
  • Learn the basics about the logic behind the player.

In the following chapter, will be using the Resource Editor to create different types of bricks, which will enable basic level editing for game designers.

Personal tools