Thursday, February 21, 2013

Developers Log. Development Date 95. Controls!

The main focus of this week was controls. I started off the week working on adding the ability to slightly adjust velocity for left/right movement. Last week I locked movement when in air, but I wanted the player to be able to add small corrections to their landing position. I accomplished this by adding two methods to the inputController (well, I slightly modified one we had been using, then added the other.)

 public override Vector3 GetWorldVectorRelativeToCamera()  
 {  
    float horizontalValue = ThumbSticks.Left.X;  
    float verticalValue = ThumbSticks.Left.Y;  
   
    Matrix cam = Matrix.Invert(Camera.View);  
   
    Vector3 vec = (cam.Forward * verticalValue) + (cam.Right * horizontalValue);  
    return new Vector3(vec.X, 0, vec.Z);  
 }  
   
 public override Vector2 GetLeftThumbstickRelativeToCamera()  
 {  
    Vector3 directionVec = GetWorldVectorRelativeToCamera();  
    float horizontalValue = Vector3.Dot(directionVec, physicsBody.Orientation.Right);  
    float verticalValue = Vector3.Dot(directionVec, physicsBody.Orientation.Forward);  
            
    return new Vector2(horizontalValue, verticalValue);  
 }  

GetWorldVectorRelativeToCamera returns the vector in world space of the left thumbstick. So if the player press forward, it returns the vector of forward from the camera (the camera can rotate 360 degrees around the player.)

GetLeftThumbstickRelativeToCamera returns the value one expects from calling Thumbsticks.Left but relative to the camera and player. This came into play for when the player was locked in a certain orientation. The player could be jumping in a direction that is equivalent to the camera's right. If the player pressed down on the thumbstick, the controls need to be the same as when the player is on the ground. If the player is on the ground and presses down, he runs at the camera. If the player is jumping right and presses down, he needs to slowly move towards the camera, still keeping his velocity and orientation going to the right of the camera.

In addition to in air controls, I added a new sliding state, for when the player slides down a wall, changed horizontal wall runs to cause the player to fall after 500 milliseconds rather than auto jumping for them. Requiring the player to jump rather than doing it for him/her adds a better feeling of play. Also, I now account for thumbstick directions when jumping to slightly alter the jump direction. This value needs testing, but it might be too small of an influence currently.

Outside of controls I added a few features to our level editor to make creating triggers easier on the level designer. I finished connecting all of the trigger creation for killzones, checkpoints, and ability pickup. Earlier in the week I helped architect the way we wanted to do triggers, I wanted to ensure we were taking advantage of our component/entity system.

We have a trigger component that listens for collisions from the physics body. This trigger can also have a flag set for what can trigger it (just a player, player and AI, bullets, etc.) When triggered, it raises events so that any other component that subscribed to the trigger will be notified. This is sort of a shadow to Unity3d's broadcast functionality, but I prefer this method.

Here is an example of how this pieces together:

Trigger class
 public class Trigger : Component  
 {    
     public delegate void TriggerEnter(Entity entity);  
     /// <summary>  
    /// Methods to be invoked upon the appropriate entity colliding with this trigger.   
    /// </summary>  
     public event TriggerEnter OnTriggerEnter;   
   
     ... [within collision handling, relative to whatever physics engine you are using]  
     if (OnTriggerEnter != null)   
       OnTriggerEnter.Invoke(entity);  
     ...  
  }  

Some component that uses the trigger.
 public class ExampleComponent: Component  
  {     
     public override void Initialize()  
     {  
       Trigger trigger = OwnerEntity.GetComponent<Trigger>();  
       if (trigger == null) 
           throw new System.NullReferenceException("Trigger component did not exist on owner entity");  
       trigger.OnTriggerEnter += OnCollide;  
     }  
   
     protected override void OnCollide(Entity entity)  
     {  
         // Do something, play a sound, do damage, etc  
       OwnerEntity.Dispose(); // delete trigger  
     }  
 }  

Using this method, I can attach several components to an entity that has a Trigger component, and each one can listen for the on collide, and activate their respective code when that happens. Very dynamic, flexible, light, and powerful.

Tuesday, February 12, 2013

Developers Log. Development Date 86. The saving of a level.

Two weeks since my last update. Last weeks post sits as a draft, 90% complete, but due to things getting crazy it was never completed. Last week we realized the level we had was unusable. The artist that created the assets in Maya had not exported the models at the origin, this was causing discrepencies when we loaded the models at their positions in XNA. We had developed a tool in Unity3D to build the maps then export them as XML so that we could re-build the level in XNA. By modifying the model and setting it at the origin, the level would become messed up in Unity (as expected.)

I spent some some time scripting a Python script for Maya that would center the pivot on the model, then center the model at the origin (relative to the pivot), then it would freeze the transformation and clear the history. I created a second method to loop through all fbx files in a given folder and all sub-folders, importing the fbx into Maya, running the centering script, then re-exporting the fbx file. Next I scripted a Unity editor script that adjusted each model relative tot he original offset in Maya, so that everything matched up.

I then spent a lot of time getting quests and quest objectives to go from our unity level to our XNA level. Part of this included setting up triggers and the quest manager. The Quest Manager loads the first quest, spawns all of the objectives, then after all objectives have been completed, the next quest loads. Standard stuff.

After the playtest, I slept for 12 hours. Following that a little ranked League of Legends, a man must keep his elo up, of course.

This weekend I spent working on improving the controls. The biggest issue from the playtest was the controls are slippery. People seemed to really enjoy the game, the concept and the feel they got from doing ninja moves, but they couldn't master the controls the way that is essential to having this game succeed.

When I first sold myself on this game idea, before I pitched it, my selling point was having tight and sexy controls. I haven't had the time to sit down and make them sing, I've been needed on too many other fronts. Everything is coming together well, and I finally now have the time to really baby our controls.

First I improved our camera. It now considers more of the player to determine if the player is in view or if the camera needs to do something to get the player in view. I will be looking into either making objects transparent, or improving our logic for moving the camera in the coming days.

Next I removed rotation of the player whilst in the air (again.) This time, however, I added the ability to change direction when using the double jump. This feels great. I also added the ability to slightly shift to the left/right mid air. So while not turning and moving as freely, the landing position can be slightly altered.

The wall jumping was one of the biggest challenges during the playtest. The answer was "mash jump" to scale between the two walls. Terrible. I added a slide when holding your thumbstick towards the wall, megaman style. This slows down the jumping so the player can make precise and timed jumps. This has helped a great deal, but I will be re-visiting this soon to further improve it.

I'm excited to see these improvements coming along so well, and am also excited to finally be able to focus my time on gameplay.