Sunday, December 2, 2007

10 Rules that Age of Empires Teaches about Development

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/10_rules_that_age_of_empires_teaches_about_development.htm]

I think that Age of Empires II (AoE) is arguably the best computer game of all time. A close second would be Civilization IV. I'm not much of a computer game player, so the best way for me to justify playing a game is that it's somehow educational. Smile Here are 10 rules, that Age of Empires clearly demonstrates, which also apply to software engineering:

  1. You need to invest in research: This means the occasional big investment, as well as many frequent upgrades as resources allow.  If you neglect your research, it won't matter if you have a hundred units - they'll all be obsolete and practically useless. It may even backfire because you'll still have to maintain them (via population limits or distracting your "medics" to get healed), which will prevent you from focusing on the new, important units that can actually win the game.

  2. You need the right tools: Don't even try taking down a castle with just stone-age sling shots. Often you need a combination of complementary units - i.e. you need to coordinate siege, cavalry, medics, etc... Likewise, software engineering needs the right tools - good luck with team development if you don't at least have a good source control system and a build server.

  3. Strike a problem while it's still small: Like in real life, problems grow in AoE. An enemy team gets bigger and stronger, resources dwindle, or the clock runs outs. This is why a common strategy is to rush your opponents while they're still small. (You may also be small at the moment, but you're still big enough to beat them now). In development, problems grow too - bad code propagates. Knock it out early before it comes back to bite you.

  4. Protect your home base: Don't rush out on some adventure and leave you home base defenseless. Likewise, in development, your core application is your home base, and it should be protected by a suite of automated unit tests. So if you get distracted with some other "adventure", at least your code still has some defense against bad additions that would break it.

  5. Not all units are equal: Although a foot-solder and a horse-archer are each just a single unit, one horse-archer could beat ten foot-solders. Furthermore, the horse-archer, who can shoot at range (like across a river), could accomplish things that a million foot-soldiers could never do. Likewise, in development, there are real "stars" - a star dev will produce not just more than 10x an average dev, they'll create things that an average dev won't even comprehend. Granted, sometimes a company's technical needs are simple enough that they don't need stars, but it's good to at least be aware of the discrepancy.

  6. Your plan can be limited by mental energy: You may have 50 units, but it doesn't matter if it's impossible to manage them. AoE is an arcade game, and you may make dumb decisions simply because there wasn't enough time to think out the perfect solution. I personally prefer a handful of powerful units that are easy to manage, as opposed to an army of weak units that just get plucked off because they're too hard to coordinate. Same applies to software engineering, but even more so. Most of what you do is limited by mental energy (code being hard to maintain, a purist algorithm taking too much thinking to figure out, etc...). Mental energy is as real a resource as gold.

  7. Don't rely on cheat codes: AoE has cheat codes. However,  don't depend on them because they could be disabled or make you miss the bigger picture. In software engineering, the equivalent is to use hacks - bad code that solves the immediate problem now, only to break tens times as much later.

  8. Expect the unexpected. In AoE, there are other teams actively working against you. The enemy may not attack your straight on (where all your defenses are), but may instead be creative and attack from the side, or ambush behind, or siege you, or something else. Likewise, in software engineering, there are constant business changes, miscommunications, developer bugs, new technologies, all causing unexpected things to happen.

  9. Recognize the common patterns and solutions: AoE has perhaps hundreds of units. With many different civs (each with their own unique unit), it's a lot to have a special plan for every single unit that you may encounter.  However, there are comparatively only a few categories for them - like infantry, ranged, cavalry, siege, naval units, etc... By looking at the common patterns (ranged units usually beat infantry, cavalry usually beats siege), you can abstract out the details and make a flexible plan. Same thing in software engineering - there are common design patterns and ways to abstract out complexity via domain-specific-languages. Be prepared to look at the high-level abstract issues, then drill down into the details.

  10. It should be fun: AoE is a good, old-fashioned-fun kind of computer game. It has huge replay value. Likewise, development should be fun, especially if you have the right process and schedule in place.
     

Wednesday, November 28, 2007

Have a child trigger a method in its parent

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/have_a_child_trigger_a_method_in_its_parent.htm]

Often you'll want to have a child object trigger some method in its parent. For example, you'll click a user control, but want that to change something in the parent control.  .Net 2.0 makes it very easy to do this. In .Net 1.1 (at least to my knowledge), this required a bunch of steps. Now, you can just add two lines in the object (declare a delegate, and then create an instance of that), and then use just one line in that object's consumer to specify the method to-be-called. In this case, we create an object "MyObject", with an event to be

namespace AddEvent
{
  public class Program
  {
    static void Main(string[] args)
    {
      MyOject o = new MyOject();
      o.GotClickedHandler += ParentGotClickedHandler;
      o.DoStuff();  //Trigger the object for some reason (this could come from a UI event like clicking it)
    }

    public static bool ParentGotClickedHandler(int i)
    {
      //I'm in the parent, but was triggered from the child object.
      i = i * 2;
      Console.WriteLine(i);
      return true;
    }

  }

  public class MyOject
  {

    public delegate bool GotClickedDelegate(int i);
    public GotClickedDelegate GotClickedHandler;


    public void DoStuff()
    {
      //trigger event:
      //trigger an event, passing that data
      int i = DateTime.Now.Second;

      if (this.GotClickedHandler != null)
      {
        GotClickedHandler(i);
      }
    }
  }
}

Tuesday, November 27, 2007

Displaying different colored text to the console (just like MSBuild)

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/displaying_different_colored_text_to_the_console_just_like_.htm]

I really like how MSBuild displays different colored text to the console - red for errors, yellow for warnings, green for success, etc... It's just a nice perk.

You can easily do this too using Console.ForegroundColor. For example, the following code has a master method "_ConsoleWrite" that takes the text and a ConsoleColor. It then keeps track of the original color, writes the text in the new color, and restores the original. It's simple, but adds a nice touch to a console app.


    private static void _ConsoleWrite(string strText, ConsoleColor cWrite)
    {
      ConsoleColor cOriginal = Console.ForegroundColor;

      Console.ForegroundColor = cWrite;
      Console.WriteLine(strText);

      Console.ForegroundColor = cOriginal;
    }

 

    public static void ConsoleWriteNormal(string strText)
    {
      _ConsoleWrite(strText, ConsoleColor.Gray);
    }

    public static void ConsoleWriteError(string strText)
    {
      _ConsoleWrite(strText, ConsoleColor.Red);
    }

    public static void ConsoleWriteWarning(string strText)
    {
      _ConsoleWrite(strText, ConsoleColor.Yellow);
    }

    public static void ConsoleWriteSuccess(string strText)
    {
      _ConsoleWrite(strText, ConsoleColor.Green);
    }

Monday, November 26, 2007

Creating a transparent, animating, and opaque image in Silverlight 1.1

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/creating_a_transparent_animating_and_opaque_image_in_silve.htm]

Every sprite-based game eventually needs two things from images: transparency and animation. In TruckWars, I use this all over the place.

 

Transparent - You can make an image be transparent by just using a transparent png (I don't think Silverlight supports transparent gifs yet), which you can easily create in Paint.Net.

Opaque - You can also make it opaque by just setting the built-in Silverlight opacity property on an image.

 

Animation is a litter harder. There are different takes on this. Two main categories of approaches are (A) have a single large image which contains all the frames, and then clip that image every x milliseconds, or (B) have a separate image for each animation frame, and then update the image source every x milliseconds. I prefer option A because it's much easier to manage, but requires a little extra coding to do the clipping.

 

About option A, for example, the smoke cloud consists of four separate frames:

 

Smoke

 

You then animate it by displaying one frame every x milliseconds. (Note that I used sprites for a smoke effect instead of a particle system because the sprite is much faster). The question becomes now - how do I clip this image? Ultimately I'll use the Silverlight clipping property - but how do you set what the clip should be?

 

Some people would just use the built-in storyboard technique with key-frames to update the clip region. My problem with that is (1) I didn't know how to manage that for multiple objects such that I could pause-on-demand and have the animation freeze, and (2) that's Silverlight-specific, so learning it wouldn't help me in other scenarios, like XNA, (3) I just didn't have the motivation to learn the new technique yet. So, instead I decided to build code to manage my own clip updating.

 

You can see the code for this in the TruckWars download: Tank\Tank.Test\Tank.Core\SpriteBase.cs, however, here's a quick overview. The code runs on the same gameClock than runs everything else. It keeps track of the current frame, and has two animation options: Repeat or RunOnce. For example, the water repeats its animation, but the smoke only runs once and then gets removed from the game. At every cycle of the gameloop, it checks if enough time has passed (in this case, 200 milliseconds), and then potentially draws the next frame.

 

To actually display the new animation requires two things: (1) clip the image to the size of a single frame (48 x 48), but then also offset the images left position such that that frame appears in the same place

 

      //set clip
      RectangleGeometry r = new RectangleGeometry();
      r.Rect = new Rect(intFrameIndex * this.Size.Width, 0, this.Size.Width, this.Size.Height);

      MainImage.Clip = r;

      //apply offset
      this.MainImage.SetValue<double>(Canvas.LeftProperty, 0 - intFrameIndex * this.Size.Width);

 

 

Here's more complete code:


    #region Animation

    protected int _intFrameIndex = -1;  //Default to -1 because Update runs first, and increments it to 0 right away
    private const int AnimationFrameCount = 4;

    private int _TimeToWaitForAnimation = 200;
    public int TimeToWaitForAnimation
    {
      get
      {
        return _TimeToWaitForAnimation; //wait x milliseconds between animation frames
      }
      set
      {
        _TimeToWaitForAnimation = value;
      }
    }

    protected double _dblTimeOfLastAnimation = 0;


    public bool FinishedAnimationCycle
    {
      get
      {
        return ((_intFrameIndex) >= AnimationFrameCount);
      }
    }


    private AnimationCycle _AnimationCycle = AnimationCycle.Repeat;
    public AnimationCycle AnimationCycle
    {
      get
      {
        return _AnimationCycle;
      }
      set
      {
        _AnimationCycle = value;
      }
    }

    protected void ResetAnimation()
    {
      _dblTimeOfLastAnimation = 0;
      _intFrameIndex = -1;
    }

    public void DrawAnimation(double dblElapsedGameTimeMS)
    {
      bool blnShouldUpdate = ShouldUpdate(dblElapsedGameTimeMS);
      if (!blnShouldUpdate)
        return;

      //-----------
      _dblTimeOfLastAnimation = dblElapsedGameTimeMS;

      _intFrameIndex++;

      if (this.AnimationCycle == AnimationCycle.Repeat)
      {
        if (_intFrameIndex + 1 > AnimationFrameCount)
          _intFrameIndex = 0;
      }
      //-------------

      DrawFrame(_intFrameIndex);
    }

    private bool ShouldUpdate(double dblElapsedGameTimeMS)
    {
      if (Convert.ToInt32(dblElapsedGameTimeMS - _dblTimeOfLastAnimation) < TimeToWaitForAnimation)
        return false;
      else
        return true;
    }

    ///


    /// Animation is done by giving a single image with multiple frames, and then displaying one frame at a time.
    /// For performance reasons, this only redraws the image if the FrameIndex has changed. Therefore repeatedly calling this
    /// method in a loop should not degrade performance, as it will just cancel out once the image is set (and until the image is changed).
    ///

    ///
    public void DrawFrame(int intFrameIndex)
    {
      if (intFrameIndex == _intLastDrawnFrame)
        return; //Already at the correct frame, don't waste resources re-drawing.

      //set clip
      RectangleGeometry r = new RectangleGeometry();
      r.Rect = new Rect(intFrameIndex * this.Size.Width, 0, this.Size.Width, this.Size.Height);

      MainImage.Clip = r;

      //apply offset
      this.MainImage.SetValue<double>(Canvas.LeftProperty, 0 - intFrameIndex * this.Size.Width);

      _intLastDrawnFrame = intFrameIndex;

    }

    private int _intLastDrawnFrame = -1;

    private Image MainImage
    {
      get
      {
        return (this.root.FindName("Image1") as Image);
      }
    }

    #endregion

 

 

Monday, November 19, 2007

Benefits of Farseer 2D Physics Engine

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/benefits_of_farseer_2d_physics_engine.htm]

I recently released Silverlight TruckWars v1.3, whose big enhancement was to incorporate the Farseer 2D Physics Engine. There are several benefits I like about Farseer:

  1. It just works - The collision algorithms just work. Before I had some code to do polygon detection. It was good code that I found online. And my use of it was okay when comparing two simple bodies, but it broke down with bigger things - like 5 units all pushing on each other. Because TruckWars is intensive on objects interacting with each other, this was important.

  2. Automatic pushing of objects - Before I had special code for the crate object (that you can push), but now, pushing objects is automatically handled.

  3. Collision categories - Not every object collides with every other object. For example, a fireball hovers over water, but a tank is blocked by it.

  4. Static objects - Farseer recognizes that sometimes, something is just impenetrable and unmovable - like the walls of a game board. Whereas in the real world, enough force will eventually push through something, I didn't want to allow that in the game world.

  5. Circular geometries - Farseer implements circular geometries via polygons, and lets you specify the number of sides for precision. My previous collision code only handled rectangles.

  6. Collision callback - You can add an extra method that fires on any collision. That method can then return true (to continue normal collision behavior) or false (to ignore collisions for that specific case).

  7. Body and Geometry have object tags, so I can associate them with a creature.

  8. API - The API is just clean, especially if you have any physics background. It both maps to standard physics knowledge, as well as provides desired method calls that you'd want to actually program something.

It took some extra effort, but it was well worth it. The engine feels much more solid now.

 

It's also worth noting, that Farseer almost makes you to do it correctly - by applying forces instead of directly setting positions. That's how the real phsycial world works. This also makes you appreciate what's involved in seemingly simple things - moving at a constant rate, having zero-turning radius, stopping a unit on demand (perfect breaks). You can achieve each of these by setting the correct properties. While it's tempting to just set the positions because then you can control everything, that becomes a nightmare with complex collisions.

 

I see a similar parallel to Enterprise development. It's tempting to just whip out some hack, but then as you scale up, that hack always comes back to haunt you.

 

I've open-sourced TruckWars on CodePlex - so you can see all the places Farseer is used. One place is from the MoveTowardTarget
 method in Tank\Tank.Test\Tank.Core\MovementStrategies.cs:

//Always clear angular velocity
c.Body.AngularVelocity = 0f;

//update direction and position
double dist = MovementUtilities.GetDistance(c.Position, c.TargetPosition);
if (dist > c.Size.Width / 2)
{
  ReleaseBreaks(c);

  Vector2 force = new Vector2();
  force.X = (float)(c.TargetPosition.X - c.Position.X);
  force.Y = (float)((c.TargetPosition.Y - c.Position.Y));
  float forceAmount = c.Thrust * PhysicsHelper.MovementForce;
  force = force.Normalize() * forceAmount;

  //Prevent sheering --> make LinearVelocity always be in direction of force
  c.Body.LinearVelocity = force.Normalize() * c.Body.LinearVelocity.Length();
  c.Body.ApplyForce(force);


  c.UpdateDirection();
}
else
{
  //You've reached your target, so apply the breaks
  ApplyBreaks(c);
}

This clears the angular velocity to prevent spinning, normalizes the force to push you in  a straight direction, and incorporates applying & releasing breaks to allow for quick stopping. Here are the two break methods:

public static void ApplyBreaks(CreatureBase c)
{
  //Need to "release breaks" when moving again
  c.Body.ClearForce();
  c.Body.LinearDragCoefficient = 4 * PhysicsHelper.LinearDragBreaks;
  c.Body.AngularVelocity = 0f;
  c.Body.ClearTorque();
}

private static void ReleaseBreaks(CreatureBase c)
{
  c.Body.LinearDragCoefficient = (float)(c.LinearDrag * PhysicsHelper.LinearDragMovement);
}

Sunday, November 18, 2007

Releases Silverlight TruckWars v1.3 - OpenSourced on CodePlex

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/releases_silverlight_truckwars_v13__opensourced_on_codeple.htm]

Silverlight TruckWars is a real-time strategy game built entirely in Silverlight. I recently releases version 1.3. The biggest thing is that I've now open-sourced it on CodePlex.

 

Check it out:

I've also added several enhancements

  • Added hold-able objects, like a key

  • Gameplay enhancmeents.

    • The biggest one is that selecting any creature now provides help text.

    • Destroying an enemy unit now can optionally provide a "treasure", like a powerup or key.

  • Lots more powerups:

    • Bounce - projectile bounces off walls

    • Thrust - doubles your thrust

    • Clone - clones the creature that got the powerup

    • Health - plus 50 health points

  • New creatures & levels

    • LandMine

    • Giant Tank

Thursday, November 8, 2007

Don't let me hurt myself

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/dont_let_me_hurt_myself.htm]

In the real world - apart from computers - it was always understood that you could do dumb things to hurt yourself. Therefore a level of discipline was always required. You could hold a sharp knife wrong and cut yourself. You could say something dumb to a friend and ruin the relationship. A sufficiently out-of-luck person could basically mess up anything they came in contact with.

 

What I find ironic is that with software, there seems more and more a demand to reverse all that - to almost make it impossible for a user to hurt themselves:

  • Through intuitive UI and instant validation, software hinders you from doing something wrong. It won't let you type bad input, it prompts you with warnings if your intent is unclear ("Do you really want to delete this account?"), and it lets you undo your action ("Please restore that account I just deleted"). Unlike a knife, good software won't let you get cut, no matter how incorrectly you hold it.

  • Every other product seems to have a long list of disclaimers on how not to use that product. "Don't do X, or you could get hurt, and our company is not responsible."

  • If you send a bad communication that blows something up, good software lets you undo it, edit it, or roll it back.

  • Even when building software, because you can instantly back up all your work (with source control), any mistake can be reverted. Imagine baking a cake and accidentally putting in one too many eggs - you can't just "undo" it.

While it's great that software keeps improving, I find this un-paralleled expectation very interesting.