Latest  | Search | Go
Edit this page   |   Attach file 

  Home | Tutorials | Technical Reference | Runtime | API Documentation | DemoPlayer  

Creating a basic "DemoPlayer" avatar

Author:Jeremy Stieglitz

The concept behind a DemoPlayer avatar

An avatar is simply a reference used by the GameCore.cs class to make it simple for the GameCore.cs to call input processing and camera updating functions on arbritrary Actors (namely a single Actor that the user of the game is controlling). It's not required to use this "avatar" setup to control an Actor, rather it's just how the particular GameCore.cs logic included in the eval kit can make things easy for you to setup a basic controllable Actor.

The GameCore.cs is the basic app logic for the game, it has a Tick() function that is called for each World, and in that the GameCore will Tick() all of its Managed Actors in that World. It will also call ProcessInput() and UpdateCamera() on any MActor which sets itself as the GameCore.Game.LocalAvatar reference.

So to make a simple controllable Actor, like this DemoPlayer, all that we have to do is set it as the GameCore.Game.LocalAvatar, in the DemoPlayer's constructor, and then override the ProcessInput() and UpdateCamera() functions to, ahem, do the input handling and camera updating respectively.

Exploring the code: Resource Precaching and the Constructor

Let's take a look at the first part of the DemoPlayer script code to see how it initializes its resources:

public class DemoPlayer : MActor
{
   /// -----------------------------------------------
   /// PRECACHING & STATIC DATA VALUES
   /// -----------------------------------------------
   public static string ClassName = "DemoPlayer";
   private static bool HasCached = false;
   public static void Precache() { HasCached = MPrecacher.Precache(ClassName, HasCached); }
   ///
   static private MModel StaticModel = MPrecacher.PrecacheModel(ClassName, "Arrow.xml");
   /// -----------------------------------------------
   /// -----------------------------------------------
   /// 

   private float m_WalkSpeed = 6.0f;
   [Description("The speed at which the DemoPlayer walks, in meters per second.")]
   public float WalkSpeed
   {
      get { return m_WalkSpeed; }
      set { m_WalkSpeed = value; }
   }

First, as you can see, the DemoPlayer derives from class MActor which is contained in the ScriptingSystem Reality API wrapper. Not all your C# classes have to be MActors, you can create any type of classes you want and instance them in your GameCore script or any other script (as I do for simple Visual FX in the eval kit framework), but the bulk of objects -- the ones that you can spawn directly, edit, and save in RealityBuilder -- will be MActors.

What the above code does first is declare the standard Precache information. This consists of the ClassName which you should fill out, the static Precache() function which sets the HasCached bool and thus only caches once (the Wrapper API's Precache function will immediately return if the passed bool is true), and finally the static Model resource. The "StaticModel" static model resource is a reference set equal to the MPrecacher.PrecacheModel(), which takes the ClassName (hence why it's important you fill that out with the appropriate name of your class) and the filename of the Model you want to load (it'll automatically look for it in the "Models" subdirectory, though you can specify an alternate directory path if you want). The precacher will associate the reference with your classname immediately, and actually load it when the Precache() function above is called. For this DemoPlayer, we're going to load Arrow.xml so that we have some visual representation of this Actor in Reality Builder.

Below that media code block, you see the first Property of the DemoPlayer, WalkSpeed. Properties are variables that are directly accessible in Reality Builder when the object is selected, in Reality Builder's Property window. They can be edited by the user of Reality Builder, and their values saved into the scene along with the Actor and loaded when the map is loaded. They're extremely useful for creating highly configurable Actors for your game, and effective use of properties will make complex scripted level construction a much more intuitive tasks for artists. But note, a Property does not actually store any variable information, in actuality it just provides Get() and Set() methods that you use to handle the data in whatever way you choose. The value that Get() returns will be displayed in the Property window, and the value that Set(vartype) passes will come from changes the user inputs into the Property Window (don't worry, it has automatic var type protection and will display a message to the user should an invalid type be entered, such as a string where a numeral is required).

For display & editability in the Reality Builder Property panel, the currently supported C# var types are:

  • int
  • bool
  • float
  • string
  • enum (enumerations)
  • Color

Note you can also provide a "Description" above the Property, as depicted, which will be displayed in the Property Window when that Property is selected. This is a good way of documenting/commenting your Properties in a way that artists can see, to help them understand what each Property does.

Now that we've gone through the basics of precaching static resources and using Properties, let's look at what the DemoPlayer's constructor does (bear in mind the crucial line where it sets itself as the GameCore's LocalAvatar):

public DemoPlayer(MWorld world) : base(world)
   {
      Precache();

      // we'll create the DemoPlayer in slightly different ways
      // depending on whether we're in Reality Builder or in the actual Game
      if (!MHelpers.IsGameApp())
      {
         // In Reality Builder, let's load up the arrow model as this Actor's MyModel so that we have some
         // visual representation of where our relatively abstract DemoPlayer is going to begin
         // We won't bother creating this MyModel in Game-mode however, 
         // because the DemoPlayer is just a very basic avatar with no external view
         MyModel = new MModel();
         StaticModel.CreateNewInstance(MyModel);

         // In Reality Builder, we want these basic Engine Physics states, 
         // they're for dragging around the DemoPlayer representative model with no gravity on it
         CollisionFlags = COLLISION_FLAGS.CF_PASSABLE_BBOX;
         PhysicsFlags = PHYSICS_FLAGS.PHYS_NOT_AFFECTED_BY_GRAVITY;
      }
      else
      {
         // In-game, let's set this DemoPlayer as the GameCore's avatar, 
         // so that the GameCore will call its input and camera updating functions during the World Tick!
         GameCore.Game.LocalAvatar = this;

         // In-game, we want these basic Engine Physics states, 
         // they're ideal for a wall-sliding, stair-climbing, pushable character in the world
         CollisionFlags = COLLISION_FLAGS.CF_BBOX;
         PhysicsFlags = PHYSICS_FLAGS.PHYS_PUSHABLE | PHYSICS_FLAGS.PHYS_ONBLOCK_CLIMBORSLIDE;
      }

      // set the demoplayer's collision box size
      SetCollisionBox(new MVector(-DemoPlayerBoxWidth, -DemoPlayerBoxHeight / 2, -DemoPlayerBoxWidth),
         new MVector(DemoPlayerBoxWidth, DemoPlayerBoxHeight / 2, DemoPlayerBoxWidth));
   }

First, the constructor calls the Precache() function to load the static resources once; even though it's always called, it returns instantly due to the HasCached bool. If you want full Precaching of the static resources, you can insert the PrecacheClasses object into your scene and add this class to its PrecacheTheseClasses function or modify GameCore to Precache() it, but that's the subject for another tutorial. As it is here, the StaticModel will be loaded once upon first construction of this Actor, and then remain in memory for the lifetime of the application until it is shut down or PurgeCache is called for this classname.

Moving on, we have an if/else branch where we want to give the DemoPlayer Actor slightly different functionality depending on whether it's in Reality Builder or actually in the Game app. Namely, we don't want to set the DemoPlayer as the LocalAvatar in Reality Builder -- because you're in a flycam mode in Reality Builder, you don't use a LocalAvatar (unless you enter Play mode, in which case the GameCore in the eval kit automatically creates a Player for you). But we do want the DemoPlayer visible in some fashion, so that it can easily be selected and moved around in RB -- so if we're not running the "GameApp" according to the wrapper API (and thus we're running Reality Builder) we create a new MyModel for this DemoPlayer and Instance it from the StaticModel. Now we'll see an Arrow at the DemoPlayer's transformation in Reality Builder!

Furthermore, we'll set some different physics states for the object in Reality Builder, making it "CF_PASSABLE" so it doesn't have the potential of interfering with other dynamic object's collisions in RB, and making it "PHYS_NOT_AFFECTED_BY_GRAVITY" so that it floats in the air. These two flags will make it easy to move the DemoPlayer object in RB, so that the level designer can place it where he wants without any fuss.

But when IsGameApp() is true, we're in the actual game, so we want to do some different things in creating the DemoPlayer. First, we want to set this DemoPlayer Actor as the GameCore's LocalAvatar -- this ensures it will get the ProcessInput and UpdateCamera functions called by the GameCore.cs at the appropriate times during the GameCore's Tick. Next, we'll set collision and physics flags appropriate to a moving player object, namely BBox collisions, and PHYS_PUSHABLE (so that other Actors can push you if they need to) & PHYS_CLIMB_OR_SLIDE (so that it climbs stairs and slides along walls). Note we do not create a MyModel and Instance our StaticModel resource in the GameApp, because this simple DemoPlayer FPV avatar doesn't need them, the Arrow model is just to help for placement in Reality Builder.

Finally, we'll set this Actor's collision box size based on the DemoPlayer's BoxWidth and BoxHeight Properties.

Exploring the code: Processing Input and Updating the Camera

Now that we've constructed the DemoPlayer and set it as the LocalAvatar, it will get the virtual MActor.ProcessInput and MActor.UpdateCamera functions called on it by the GameCore script. So we need to override those functions, and make them actually do what we want. For DemoPlayer.ProcessInput, we'll have it check the pressed states the movement keys and Accelerate the DemoPlayer in the desired direction. Let's look at the ProcessInput() code to see how this is done:

   /// <summary>
   /// This function is called by the GameCore.cs in Tick when you declare this Actor to be the GameCore's avatar
   /// </summary>
   public override void ProcessInput()
   {
      // if the user currently has a gui active, just bail out and ensure the cursor is visible
      if (MHelpers.HasAnyGUIopen())
      {
         MHelpers.SetCursorVisible(true);
         return;
      }

      // no cursor when actually controlling the Player
      MHelpers.SetCursorVisible(false);

      // calculate Player Rotation according to mouse yaw and pitch
      Rotation = MMatrix.LookTowards(MVector.MakeDirection(MInput.mouseYaw, MInput.mousePitch, 0));

      MVector newDirection = new MVector(); // User's new attempted direction

      //basic movement inputs

      // strafing left and right
      if (GameInput.ControlDown(GameInput.KEY_STRAFE_LEFT))
         newDirection += 0.5f * (-Rotation.GetRight());
      else if (GameInput.ControlDown(GameInput.KEY_STRAFE_RIGHT))
         newDirection += 0.5f * (Rotation.GetRight());

      // moving forward and backwards
      if (GameInput.ControlDown(GameInput.KEY_WALK_FORWARD))
         newDirection += Rotation.GetDir();
      else if (GameInput.ControlDown(GameInput.KEY_WALK_BACKWARD))
         newDirection -= Rotation.GetDir();

      newDirection.y = 0; // demoplayer movement has no Y component

      if (CurrentState == STATE_ONGROUND)
         Accelerate(newDirection.Normalized(), WalkSpeed, 46); // ground acceleration
      else
         Accelerate(newDirection.Normalized(), WalkSpeed, 7); // air acceleration

      // check if the jump key is pressed, and if the user's feet are on the ground, jump him
      if (GameInput.ControlJustPressed(GameInput.KEY_JUMP) && CurrentState == STATE_ONGROUND)
         Velocity.y = JumpVelocity;
   }

First, the code checks Wrapper API's MHelpers.HasAnyGUIOpen() to see if the user currently has either the console or the main menu open (custom menu activity states could be extended in that function), and if true, then the DemoPlayer should not receive input. Otherwise, you'd continue moving the DemoPlayer when you're interacting with the console or the menu system, which isn't desirable. So what the DemoPlayer does when HasAnyGUIOpen(), is just ensure the cursor is unhidden (you always want it when in a menu) and return. This ensures the input code following those statements will not be executed.

If the GUI is not open, then the DemoPlayer proceeds to handle input. First, it hides the cursor, since we don't want to see it when controlling the DemoPlayer in-game. Next, very crucially, it calculates the DemoPlayer's Rotation Matrix based on the Mouse Yaw and Pitch values (degrees). It uses the Matrix::LookTowards function, which takes a forward direction vector, on the Vector::MakeDirection, which takes Yaw and Pitch to generate a normalized direction vector. The MInput.MouseYaw and MInpuse.MousePitch (which are read/write if the programmer wants to manually set them) properties are incremented by the Engine every frame according to the mouse movement. Thus, the DemoPlayer's Rotation is set to correspond to the mouse movement every frame.

Now that looking is taken care of, we have to deal with key presses for movement. First we create a new MVector that's going to store our desired movement direction. Next we use the GameInput script and a GameInputHandle defined in it (a GameInputHandle, contained in GameInput.cs, is an object that encapsulates both a Reality key handle taken from an INI key entry, and an auto-incremented network key ID for synchronization in multiplayer). We use the GameInput's ControlDown function to determine whether each of these particular GameInputHandle keys is currently pressed, and if it is pressed, we add that key's movement to our desired movement direction vector.

So for KEY_STRAFE_LEFT (defined in GameInput.cs, including its INI entry name), we add some -Rotation.GetRight() to our desired direction vector. And for KEY_WALK_FORWARD, we add some Rotation.GetDir() to the direction Vector. Once all that's done, we ensure the desired direction vector has no Y component (this DemoPlayer will always want to walk along the XZ plane, though actually this isn't truly necessary since the DemoPlayer will never have a "roll" component to the Rotation unless we add that). Now, we're ready to Accelerate the DemoPlayer in the desired direction! So we call the DemoPlayer's Accelerate() function (described in the next section) passing in our desired direction, our WalkSpeed Property (discussed earlier), and an Acceleration magnitude that is different whether the Player is walking on the ground (where he has friction) or in the air (for the sake of gameplay).

Finally, we'll check whether the Jump key was just pressed, so that each press corresponds to one Jump and a held-key will not continually jump. For one-time events like a Jump, a single ControlJustPressed event is likely more intuitive than a held ControlDown. When the KEY_JUMP is just pressed, and the Actor is standing on the ground, we'll set the Actor's Velocity Y-component to the JumpVelocity Property discussed earlier, so the DemoPlayer jumps.

Now that we've got the Input handled appropriately, let's take a look at the function that updates the Camera to the DemoPlayer's perspective:

   /// <summary>
   /// This function is called by the GameCore.cs in Tick when you declare this Actor to be the GameCore's avatar
   /// </summary>
   public override void UpdateCamera()
   {
      // just set the camera to the DemoPlayer's Location plus half the height of the bbox up, 
      // so that his eyes are located at the top of the bbox
      // and use his Rotation too.
      MCameraHandler.setToView(Location + new MVector(0, m_DemoPlayerBoxHeight / 2, 0), Rotation);
   }

This MActor.UpdateCamera() function is called by GameCore.cs every Tick on whatever MActor you set as its LocalAvatar, a short time after ProcessInput (all the MActors are actually Ticked first, as you can see in GameCore.Tick). The code here is very simple. What it does is use the MCaneraHandler Wrapper class to just set the current camera to the specified view. Alternatively you could call MCameraHandler.GetCamera() and set the MCamera's transformation values yourself, but it's helpful to go through MCameraHandler's own set functions since it provides for advanced features such as smooth camera transformation interpolation. So we'll use MCameraHandler.setToView() here, which simply takes a Vector Location and a Matrix Rotation to set the Camera's transformation to. In our case, we want the Camera to see through the DemoPlayer's "eyes", which will be located at the top of its CollisionBox. By default an Actor's Location is in the center of its CollisionBox, so the top of this DemoPlayer's box would be the Location plus half the BoxHeight on the Y axis. We also want to set the Camera to use Rotation of the DemoPlayer, and with this the Camera will in effect be looking through the DemoPlayer's eyes every frame.

Now we've handled the input and updated the camera, all that remains is the Acceleration function to actually increment the Player's Velocity towards the desired movement direction, renderng some basic HUD, and a look at the concept of MActor.DisposeResources.

Exploring the code: Acceleration, Rendering a HUD, and MActor.DisposeResources

The DemoPlayer.Acceleration function is called by DemoPlayer.ProcessInput. It's just a useful function we've written to add an acceleration vector to the DemoPlayer's Velocity, towards the desired movement direction vector:

   /// <summary>
   /// Our Quake-style walk acceleration function
   /// </summary>
   protected void Accelerate(MVector wishdir, float wishspeed, float accel)
   {
      // apply acceleration towards desired direction, with removal of over-acceleration.
      float addspeed, currentspeed;

      //calculate the acceleration vecetor to add to achieve the desired velocity direction
      currentspeed = Velocity.Dot(wishdir);

      if (wishspeed == currentspeed)
         return;

      // add time-scaled acceleration
      if (wishspeed > currentspeed)
         addspeed = accel * MHelpers.DeltaTime;
      else
         addspeed = -accel * MHelpers.DeltaTime;

      // limit acceleration to exact needed value to achieve desired speed
      if ((currentspeed + addspeed > wishspeed && wishspeed > currentspeed) || (currentspeed + addspeed < wishspeed && wishspeed < currentspeed))
         addspeed = wishspeed - currentspeed;

      // add the calculated acceleration amount in the desired direction to our current Velocity
      Velocity += addspeed * wishdir;
   }

Additionally, we override the MActor's virtual PostRender function, which is called on all MActors in the World by GameCore.PostRender(). This allows us to do some simple HUD drawing by printing a string to the screen. This is just a simple example of what can be drawn in PostRender() -- if we wanted to create a more complex HUD, we could draw textured blended boxes, Models directly transformed & drawn in screen space, or even use PostProcess Effects.

   /// <summary>
   /// Called right after the World renders, to allow custom canvas drawing with regards to this Actor
   /// In the DemoPlayer's case, we'll draw a very simple HUD
   /// </summary>
   public override void PostRender(MCamera camera)
   {
      // Some way cool HUD text
      MCanvas.TextCenteredf(MCanvas.MediumFont, MHelpers.ColorFromRGBA(200, 255, 255, 190), 512, 740, "I am a DemoPlayer.");
   }

The last function in the DemoPlayer class is the DisposeResources override. DisposeResources is a virtual function defined in the Wrapper API's MActor which is immediately called when the MActor is destroyed (either via LifeTime = 0 or Destroy()). This DisposeResources() is a useful function to override and insert your own custom destruction logic, such as freeing up any resources you have allocated such as local Textures and Sounds by calling their own Dispose() functions (though C# will also Dispose such objects automatically, when the container object like an MActor is itself finalized).

In any case, we actually don't want to Dispose() our precached StaticModel resource when a DemoPlayer instance is destroyed; remember, since StaticModel is registered with the Precacher it will automatically be freed upon app shutdown, or when you call MPrecache.PurgeCache("DemoPlayer") (which you generally will not need to do, unless you have a reason for wanting to free static cached resources).

So all we want to do in this DemoPlayer's DisposeResources() function is clear it from the LocalAvatar reference, if the LocalAvatar reference is set to this DemoPlayer. In general, it's good C# practice to nullify any references that you have to your object when disposing it, since the garbage collector uses those references as a basis for efficient memory management. As you can see in the following code, this is exactly what the DemoPlayer's DisposeResources override does:

   /// <summary>
   /// Disposal of the Actor's resources, called when the Actor is destroyed,
   /// namely by having its LifeTime set to 0, having Destroy() called on it, or upon World unloading/app shutdown
   /// </summary>
   protected override void DisposeResources()
   {
      // if this actor is currently the GameCore's local avatar,
      // it's good C# programming practice to nullify the local avatar reference
      // when this Actor is disposed of.
      if (GameCore.Game.LocalAvatar == this)
         GameCore.Game.LocalAvatar = null;

      base.DisposeResources();
   }

With this, we have completed the DemoPlayer class! In summary, the DemoPlayer serves as a very basic avatar for you to manipulate and explore the World. It has properties for editing in Reality Builder, and a model it displays only in Reality Builder for visual identification. In the Game, the DemoPlayer sets itself as the GameCore's LocalAvatar, handles input, updates the camera, renders a HUD, and clears its references upon disposal. Additionally it uses the C# XML <summary> tags for auto documentation generation and VS.NET IDE Intellisense comment display. With this simple class as a starting point for your programming endeavor, you will find that building up ever more complex game systems in C# script becomes a natural extension of your C++ programming experiences, and ultimately exceeds them.

As always, e-mail me at jeremy@artificialstudios.com with any questions concering game system implementation!



Complete DemoPlayer.cs script

//=========== (C) Copyright 2004, Artificial Studios. All rights reserved. ================
/// DemoPlayer:  A simple example avatar that processes input and updates the camera
/// 
/// A tutorial that describes the creation of this class is located at: 
/// http://reality.artificialstudios.com/twiki/bin/view/Main/DemoPlayer
/// 
/// Author: Jeremy Stieglitz
//=========================================================================================

#region Using directives
using System;
using ScriptingSystem;
using System.ComponentModel;
#endregion

public class DemoPlayer : MActor
{
   /// -----------------------------------------------
   /// PRECACHING & STATIC DATA VALUES
   /// -----------------------------------------------
   public static string ClassName = "DemoPlayer";
   private static bool HasCached = false;
   public static void Precache() { HasCached = MPrecacher.Precache(ClassName, HasCached); }
   ///
   static private MModel StaticModel = MPrecacher.PrecacheModel(ClassName, "Arrow.xml");
   /// -----------------------------------------------
   /// -----------------------------------------------
   /// 

   private float m_WalkSpeed = 6.0f;
   [Description("The speed at which the DemoPlayer walks, in meters per second.")]
   public float WalkSpeed
   {
      get { return m_WalkSpeed; }
      set { m_WalkSpeed = value; }
   }

   private float m_JumpVelocity = 8.0f;
   [Description("The Y velocity with which the DemoPlayer jumps off the ground.")]
   public float JumpVelocity
   {
      get { return m_JumpVelocity; }
      set { m_JumpVelocity = value; }
   }

   private float m_DemoPlayerBoxWidth = 0.4f;
   [Description("The width of the DemoPlayer's bounding box, in meters.")]
   public float DemoPlayerBoxWidth
   {
      get { return m_DemoPlayerBoxWidth; }
      set { m_DemoPlayerBoxWidth = value; }
   }

   private float m_DemoPlayerBoxHeight = 2;
   [Description("The height of the DemoPlayer's bounding box, in meters. \nThe DemoPlayer's eyes will be located at the top of this height.")]
   public float DemoPlayerBoxHeight
   {
      get { return m_DemoPlayerBoxHeight; }
      set { m_DemoPlayerBoxHeight = value; }
   }

   /// <summary>
   /// ctor
   /// </summary>
   public DemoPlayer(MWorld world) : base(world)
   {
      Precache();

      // we'll create the DemoPlayer in slightly different ways
      // depending on whether we're in Reality Builder or in the actual Game
      if (!MHelpers.IsGameApp())
      {
         // In Reality Builder, let's load up the arrow model as this Actor's MyModel so that we have some
         // visual representation of where our relatively abstract DemoPlayer is going to begin
         // We won't bother creating this MyModel in Game-mode however, 
         // because the DemoPlayer is just a very basic avatar with no external view
         MyModel = new MModel();
         StaticModel.CreateNewInstance(MyModel);

         // In Reality Builder, we want these basic Engine Physics states, 
         // they're for dragging around the DemoPlayer representative model with no gravity on it
         CollisionFlags = COLLISION_FLAGS.CF_PASSABLE_BBOX;
         PhysicsFlags = PHYSICS_FLAGS.PHYS_NOT_AFFECTED_BY_GRAVITY;
      }
      else
      {
         // In-game, let's set this DemoPlayer as the GameCore's avatar, 
         // so that the GameCore will call its input and camera updating functions during the World Tick!
         GameCore.Game.LocalAvatar = this;

         // In-game, we want these basic Engine Physics states, 
         // they're ideal for a wall-sliding, stair-climbing, pushable character in the world
         CollisionFlags = COLLISION_FLAGS.CF_BBOX;
         PhysicsFlags = PHYSICS_FLAGS.PHYS_PUSHABLE | PHYSICS_FLAGS.PHYS_ONBLOCK_CLIMBORSLIDE;
      }

      // set the demoplayer's collision box size
      SetCollisionBox(new MVector(-DemoPlayerBoxWidth, -DemoPlayerBoxHeight / 2, -DemoPlayerBoxWidth),
         new MVector(DemoPlayerBoxWidth, DemoPlayerBoxHeight / 2, DemoPlayerBoxWidth));
   }

   /// <summary>
   /// This function is called by the GameCore.cs in Tick when you declare this Actor to be the GameCore's avatar
   /// </summary>
   public override void ProcessInput()
   {
      // if the user currently has a gui active, just bail out and ensure the cursor is visible
      if (MHelpers.HasAnyGUIopen())
      {
         MHelpers.SetCursorVisible(true);
         return;
      }

      // no cursor when actually controlling the Player
      MHelpers.SetCursorVisible(false);

      // calculate Player Rotation according to mouse yaw and pitch
      Rotation = MMatrix.LookTowards(MVector.MakeDirection(MInput.mouseYaw, MInput.mousePitch, 0));

      MVector newDirection = new MVector(); // User's new attempted direction

      //basic movement inputs

      // strafing left and right
      if (GameInput.ControlDown(GameInput.KEY_STRAFE_LEFT))
         newDirection += 0.5f * (-Rotation.GetRight());
      else if (GameInput.ControlDown(GameInput.KEY_STRAFE_RIGHT))
         newDirection += 0.5f * (Rotation.GetRight());

      // moving forward and backwards
      if (GameInput.ControlDown(GameInput.KEY_WALK_FORWARD))
         newDirection += Rotation.GetDir();
      else if (GameInput.ControlDown(GameInput.KEY_WALK_BACKWARD))
         newDirection -= Rotation.GetDir();

      newDirection.y = 0; // demoplayer movement has no Y component

      if (CurrentState == STATE_ONGROUND)
         Accelerate(newDirection.Normalized(), WalkSpeed, 46); // ground acceleration
      else
         Accelerate(newDirection.Normalized(), WalkSpeed, 7); // air acceleration

      // check if the jump key is pressed, and if the user's feet are on the ground, jump him
      if (GameInput.ControlJustPressed(GameInput.KEY_JUMP) && CurrentState == STATE_ONGROUND)
         Velocity.y = JumpVelocity;
   }


   /// <summary>
   /// This function is called by the GameCore.cs in Tick when you declare this Actor to be the GameCore's avatar
   /// </summary>
   public override void UpdateCamera()
   {
      // just set the camera to the DemoPlayer's Location plus half the height of the bbox up, 
      // so that his eyes are located at the top of the bbox
      // and use his Rotation too.
      MCameraHandler.setToView(Location + new MVector(0, m_DemoPlayerBoxHeight / 2, 0), Rotation);
   }

   /// <summary>
   /// Our Quake-style walk acceleration function
   /// </summary>
   protected void Accelerate(MVector wishdir, float wishspeed, float accel)
   {
      // apply acceleration towards desired direction, with removal of over-acceleration.
      float addspeed, currentspeed;

      //calculate the acceleration vecetor to add to achieve the desired velocity direction
      currentspeed = Velocity.Dot(wishdir);

      if (wishspeed == currentspeed)
         return;

      // add time-scaled acceleration
      if (wishspeed > currentspeed)
         addspeed = accel * MHelpers.DeltaTime;
      else
         addspeed = -accel * MHelpers.DeltaTime;

      // limit acceleration to exact needed value to achieve desired speed
      if ((currentspeed + addspeed > wishspeed && wishspeed > currentspeed) || (currentspeed + addspeed < wishspeed && wishspeed < currentspeed))
         addspeed = wishspeed - currentspeed;

      // add the calculated acceleration amount in the desired direction to our current Velocity
      Velocity += addspeed * wishdir;
   }

   /// <summary>
   /// Called right after the World renders, to allow custom canvas drawing with regards to this Actor
   /// In the DemoPlayer's case, we'll draw a very simple HUD
   /// </summary>
   public override void PostRender(MCamera camera)
   {
      // Some way cool HUD text
      MCanvas.TextCenteredf(MCanvas.MediumFont, MHelpers.ColorFromRGBA(200, 255, 255, 190), 512, 740, "I am a DemoPlayer.");
   }

   /// <summary>
   /// Disposal of the Actor's resources, called when the Actor is destroyed,
   /// namely by having its LifeTime set to 0, having Destroy() called on it, or upon World unloading/app shutdown
   /// </summary>
   protected override void DisposeResources()
   {
      // if this actor is currently the GameCore's local avatar,
      // it's good C# programming practice to nullify the local avatar reference
      // when this Actor is disposed of.
      if (GameCore.Game.LocalAvatar == this)
         GameCore.Game.LocalAvatar = null;

      base.DisposeResources();
   }

}

DemoPlayer   Edit | Attach | Ref-By | Printable | Diffs | r1.7 | > | r1.6 | > | r1.5 | More