Jump to content

  • Log In with Google      Sign In   
  • Create Account

True, False, Maybe



Man of Steel, Le Bad Cinema

Posted by , in Random Thoughts 15 June 2013 - - - - - - · 971 views

Just saw Man of Steel.

It was a major disappointment for me.

Bad plot, bad story, bad writing, bad presentation.

To be fair, some of it is just Superman. Declaring patriotism with a flag waving in the background, I can live with that. Classic Superman.

Giving yourself up to potential death for the good of humanity, while standing in a church sharing the screen with a stained-glass image of Christ giving himself up for humanity... Very tacky. But it's Superman, so I guess I can live with that. Tacky, but still comic book.

It is a superhero movie so we get superhero physics. Catching someone falling from a fatal height without slowing them down first (they might as well just hit the concrete), okay, they are super heros. Flying around the world in a matter of seconds without burning up from compression off the atmosphere? Well, comic book Superman could do some of that if he tried really hard. So does Santa, I suppose. So we can tolerate it.

The computer graphics were both good and awful. The exploding cars look just like I imagine an exploding car should look. But things thrown by the Supers don't obey simple physics. Supers picking up full-ton vehicles, instantly accelerating to supersonic speed, smashing them into a building and only doing minimal damage? Yeah, that's not very believable. The Krypton scenes were pretty for still motion images, but ugly in motion. Not falling fast enough? Will yourself to fall faster! The models and artwork were usually good, but the animation of those graphics was terrible. Then the exploding planet, "We're zoomed in, everything moves at human movement speeds; we're zoomed out to the planet size, it engulfs the entire planet in seconds; were zoomed in again, slow it back down". Wrong physics to be realistic, wrong physics for comic book, so just plain wrong.

Then they went with the "everything explodes" method of action. Cars crash? Explosion (and bad news for auto body shops, good news for dealerships). Fistfight at a gas station? Explosion. (Note to self, never fill up my fuel tank again, too dangerous.) Superhero/villain looks at a building? Bricks and cinder blocks somehow explode. UFO flying out of a glacier? Explosions. That's right, the ice explodes in a collection of fireballs.

They went with the same "Michael Bay" style on locations. Remember Transformers? "We are out here in the desert at a wonderful military site, great visibility, lots of weapons, no civilians, and the enemy is seeking us out. Let's leave this great site and go to a city where the enemy can ambush us an kill millions of civilians while doing billions in property damage." Much the same. "We can fly and we can choose our battleground, so let's have it right in the center of a city of skyscrapers to destroy and to encourage millions of pointless deaths, rather than drawing them out over the neighboring ocean."

Then there is the writing of the people watching the fight. "This is the Daily Planet, world's largest news organization. A fight among aliens that will decide the fate of Earth is happening right outside our window. This is a news agency, so everybody sit at your desks, do not look out the window, do not take photos, and do not otherwise report on the globally significant news visible outside the window."

And the writing of the CONSTANT fights themselves. It was pervasive, so I don't know to blame the writers, directors, or both. The bad guys are supposed to be extremely intelligent; the apex of a race , and genetically engineered to be masterminds of warfare. Yet they are stupid. "We have weapons that can destroy a planet from orbit, but let's not use them, and instead go down in person. We have a group of skilled warriors with guns that can vaporize people (including Supers) instantly. Let's not use them, not use our snipers and get a headshot. Instead, let's drop of just two of our soldiers and engage in hand-to-hand combat. And not stick around to make sure they win." Bad writing, but the director followed it so bad directing too.

Let's compare the old, real Superman character vs this movie.

When Superman has a fight in a train yard, it is because he wants to SAVE the trains, not throw them as projectiles. When Superman has a fight outside a gas station he not only saves the building from destruction but also buys a slurpee on his way through. When Superman fights in the city, he always stops the bad guys, prevents the destruction of buildings, and never causes collateral damage. In this movie he was responsible for untold billions in property damage. In the movie, hhis own actions destroyed probably 15 skyscrapers full of people, and he did nothing to save any buildings from damage. That isn't the Superman of the comics. The Superman of the comics would find a way to protect the buildings and save the lives.

The real movie takes place between the explosions. It could be reduced to about 30 minutes. That part was mediocre. The love story wasn't there, apart from "he looks hot", but Superman is supposed to be all about a yes-but-no romance between Clark and Lois. Again, this was not Superman.

I could keep going on about it, but it doesn't help.

It is bad cinema. I recommend avoiding it unless you are a die-hard Superman fan, but in that case you have probably seen it already.


Creating gd.net articles

Posted by , in Random Thoughts 27 March 2013 - - - - - - · 1,233 views

The new Article Writing forum has some interesting discussion going on lately.

As the site administrators were asking for a list of topics, I started looking over the list.

Then I realized something...

I could probably write 2/3 of the articles they were asking for.

I've got the education and the experience. I attended graduate school after completing my bachelor's degree (cum laude, university honors, department honors, etc.) and I have kept up on the topics. I'm a reasonably good teacher and can generally communicate well in writing. I am approaching two decades of real-world experience (eww, I'm getting middle aged!) so I've seen most of the theories in action.

I even have ideal proof-readers for my target audience. My wife can review it from an educated adult perspective and check for non-technical issues. My teenagers can read it from a less-educated teenage perspective and check for readability.

So I've decided to get to work and spend some of my evenings writing articles. One is already published, two are nearly complete, and four others are in the works.

That's it for this random thought. Wish me luck.


State Machines in Games – Part 6

Posted by , in Programming 17 March 2013 - - - - - - · 1,400 views

Okay, this is the wrap-up.

I was planning on part 6 adding to the pet game having them loop back to the individual pet in determining the interest, and part 7 adding inventory items, a plot, and otherwise finishing off the dungeon explorer.

Then I realized --- it doesn't add anything new to the series. They are just new machines that replicate the behaviors already taught.

What Have We Learned

We learned that state machines are the "least powerful" of the automata in Computer Science. We also learned that they can do incredible things for games.

We can use state machines to run different code based on state.

We can use state machines to represent arbitrary meshes. These meshes can include things like maps and connectivity grids.

We don't need to hard code state machines. In fact, when you load them from data you can get substantially more power from them. Designers and artists and modelers and animators and producers can all modify the game without touching the code.

We can build complex systems, such as AI behavioral trees, out of a nested tree of very simple state machines.

How Can I Apply This

Most games apply these concepts all over the place.

One of the most common jobs in the industry is "Gameplay Engineer", or GPE. All they do is create the exact types of items we have made in this series of tutorials.

For example, in Littlest PetShop I spent months adding new behaviors and actions. They ranged from 'in the world' behaviors like I implemented in part 5, to minigames such as hide and seek where pets would run behind bushes to hide, then run up to the camera and celebrate with the player when they were found.

In The Sims every one of the tens of thousands of objects in the game required scripting. You need a pile of clothes, then you need some of these scripts: Behaviors and interactions to create the piles of clothes; interactions to drop the piles of clothes and pick them up again; interactions for a washing machine; interactions for a dryer; interactions for the maid to come clean up; and on and on and on.

Or if you are into faced paced games, you need behaviors and actions for your main characters to know to attack or run away, or decide if they should attack towers or players or minions. You need to create objects to pick up, weapons, inventories, and more. All of these behave the same way that we implemented in parts 3-5.

These little activities and behaviors grow into complex ecosystems powering most games.

The End?

I still want to add those abilities to the code, and my kids want to help me expand on the pet simulation. So I'm sure I'll be adding to this article with an addendum or two. But for now, it has been a good week. I got to spend about 6 hours writing two proof-of-concepts games, and I got to write a bunch of journal entries, and finally I was invited to turn these into an article.

So expect more on this to come. But for now, if you want more you'll need to use your own creativity and post what you did with the knowledge you have gained.

Thanks for reading.


State Machines in Games – Part 5

Posted by , 16 March 2013 - - - - - - · 1,422 views

Okay, quick review.

Part 1 - basics of a state machine; Part 2 - simple state machine interface; Part 3 - turning the simple state machine into a dungeon exploration game; Part 4 - making it data driven.

That brings us to Part 5, an artificial intelligence system.

First, I know this pattern is used in major games. How do I know? First, because I wrote almost exactly this same system in Littlest PetShop. According to VGChartz the game series sold over 4.15 million copies globally. Second, I used a very similar pattern when working on The Sims 2 and The Sims 3.

Behavioral Trees

Okay, so this gets in to a concept called a behavioral tree. There are actors and objects, and the actors do something. Here is the structure I created for this demo:

Attached Image


The game’s container is a playing field. It basically manages the state machine. It gets regular updates at about 30 frames per second, and updates all the game objects.

So far I have two classes of game objects: Pets and Toys. These work together with activities.

The Base GameObject Class

A game object fits in with the state machines. They serve as both state machines AND as state nodes.

It has an Update(), which means to run the current state, and also to advance the current state if necessary. We’ll expand on this a little later.

The GameObject represents any object we can place on our game board. They have an owner (in this case, the playing field). They have a location. They have an image.

For convenience they have a ToString() override that makes things look nicer when I view them in a property grid.

Also over development, they have evolved to have a PushToward() and a MaxSpeed() method. These would probably be integrated into a physics system or a collision system, but for now this is their best natural fit.


    public abstract class GameObject
    {
        public PlayingField Owner { get; set; }

        /// <summary>
        /// Initializes a new instance of the GameObject class.
        /// </summary>
        public GameObject(PlayingField owner)
        {
            Owner = owner;
        }

        /// <summary>
        /// Update the object
        /// </summary>
        /// <param name="seconds">seconds since last update.</param>
        /// <remarks>seconds is the easiest scale for the individual settings</remarks>
        public abstract void Update(float seconds);

        /// <summary>
        /// Location on the playing field to draw the actor
        /// </summary>
        public PointF Location { get; set; }

        /// <summary>
        /// What to draw on the playing field
        /// </summary>
        public abstract Image Image { get; }

        /// <summary>
        /// Push the game object toward a location.  Default behavior is to not move.
        /// </summary>
        /// <param name="destination">Location to push toward</param>
        /// <param name="seconds">Seconds that have passed in this movement</param>
        public virtual void PushToward(PointF destination, float seconds) { return; }

        /// <summary>
        /// Get the maximim speed of this game object. Default behavior is not to move.
        /// </summary>
        /// <returns></returns>
        public virtual float MaxSpeed() { return 0; }

        /// <summary>
        /// Simplified name for the object to display in the property browser
        /// </summary>
        /// <returns>Shorter name</returns>
        public override string ToString()
        {
            string classname = base.ToString();

            int index = classname.LastIndexOf('.');
            string shortname = classname.Substring(index+1);
            return shortname;
        }

    }




Pets and Motives

The basic pet class is pretty simple.

A pet is a game object (so it gets everything above), plus it also gets an activity and a collection of motives.

The motives are nothing more than a wrapper for the pet’s status. In this case we are only tracking fun and energy. (Note for comparison in The Sims3 there are 8 visible motives – hunger, social, bladder, hygiene, energy, and fun.)

When a pet is created we default them to the Idle activity, and initialize their Motives.

We have a default update behavior to run whatever activity we are currently doing, or if we aren’t doing anything to create a new idle activity and do that instead.

We’ll also implement what it means to push a pet.


    public class MotiveBase
    {
        public float Fun { get; set; }
        public float Energy { get; set; }
    }





    public abstract class Pet : GameObject
    {
        public MotiveBase Motives { get; set; }

        public Activities.Activity Activity { get; set; }
        /// <summary>
        /// Initializes a new instance of the Pet class.
        /// </summary>
        public Pet(PlayingField owner)
            : base(owner)
        {
            Activity = new Activities.Idle(this, null);
            Motives = new MotiveBase();
        }

        /// <summary>
        /// Allow a pet to do something custom on their update
        /// </summary>
        /// <param name="seconds"></param>
        protected virtual void OnUpdate(float seconds) { return; }

        public override void Update(float seconds)
        {
            if (Activity == null)
            {
                Activity = new Activities.Idle(this, null);
            }
            Activity.Update(seconds);
        }

        public override void PushToward(System.Drawing.PointF destination, float seconds)
        {
            base.PushToward(destination, seconds);

            // TODO: Someday accumulate force and make a physics system.  Just bump it the correct direction.
            // TODO: Create a vector class someday
            float xDiff = destination.X - Location.X;
            float yDiff = destination.Y - Location.Y;
            float magnitude = (float)Math.Sqrt(xDiff * xDiff) + (float)Math.Sqrt(yDiff * yDiff);
            if (magnitude > (MaxSpeed() * seconds))
            {
                float scale = (MaxSpeed() * seconds) / magnitude;
                xDiff *= scale;
                yDiff *= scale;
            }
            Location = new PointF(xDiff + Location.X, yDiff + Location.Y);
        }
    }




Puppies!

So finally we’ll implement a type of pet.

A puppy has an image, and a puppy has a maximum speed. Note that we just pull these from the saved resources so a designer can adjust them later.
    class Puppy : Pet
    {
        /// <summary>
        /// Initializes a new instance of the GameObject class.
        /// </summary>
        public Puppy(PlayingField owner)
            : base(owner)
        {
            
        }

        public override System.Drawing.Image Image
        {
            get { return FSM_Puppies.Properties.Resources.Puppy; }
        }

        public override float MaxSpeed()
        {
            return FSM_Puppies.Properties.Settings.Default.Pet_Puppy_MaxSpeed;   
        }
    }


Toys

A toy is also a game object, so it can behave as a state machine and as a state node, as appropriate.

A toy has a default activity associated with it. When a pet attempts to use a toy they will get this default activity (aka behavior tree) and start running it.

A toy is also responsible for computing the interest level in the object. For now these will just be hard-coded formulas inside each toy object. Later on these could be a more complex series of interactions but for this system it is adequate.

Here’s the Toy interface
    public abstract class Toy : GameObject
    {
        /// <summary>
        /// Initializes a new instance of the Toy class.
        /// </summary>
        public Toy(PlayingField owner)
            : base(owner)
        {
            
        }

        public abstract Activities.Activity DefaultActivity(Pets.Pet actor, GameObject target);
        public abstract float Interest(Pets.Pet pet);

        public override void Update(float seconds)
        {
            // Note that most toys do nothing of themselves.  They are driven by their activities.
            return;
        }

    }


Two Toys

Now we’ll create two concrete classes for toys.

First, a sleeping mat. The interest of the sleeping mat is only based on energy. It has an image to draw. The default activity is to sleep on the mat.
    class SleepingMat : Toy
    {
        /// <summary>
        /// Initializes a new instance of the SleepingMat class.
        /// </summary>
        public SleepingMat(PlayingField owner)
            : base(owner)
        {
            
        }
        public override FSM_Puppies.Game.Activities.Activity DefaultActivity(Pets.Pet actor, GameObject target)
        {
            return new Activities.SleepOnMat(actor, this);
        }

        public override System.Drawing.Image Image
        {
            get { return FSM_Puppies.Properties.Resources.SleepingMat; }
        }

        public override float Interest(FSM_Puppies.Game.Pets.Pet pet)
        {
            return 100 - pet.Motives.Energy;
        }
    }



Second, a ball to kick around. The interest is only based on fun, although it probably should include an energy component. It has an image to draw, and the default activity is to chase the ball.

    class Ball : Toy
    {
        /// <summary>
        /// Initializes a new instance of the Ball class.
        /// </summary>
        public Ball(PlayingField owner)
            : base(owner)
        {
        }

        public override Image Image
        {
            get { return FSM_Puppies.Properties.Resources.Ball; }
        }

        public override Activities.Activity DefaultActivity(Pets.Pet actor, GameObject target)
        {
            return new Activities.ChaseBall(actor, target);
        }

        public override float Interest(FSM_Puppies.Game.Pets.Pet pet)
        {
            return 100 - pet.Motives.Fun;
        }
    }


Now we move on to the activities that drive the system.

Activities Are Glue and Oil

Activities serve as glue to the system. They are the interactions between actors and objects. Without them there wouldn’t be much of a connection between the two.

Activities also serve as the oil to the system. They are constantly moving. They change themselves, and they change the actors they work with, and they can change the objects they work with. A more complex example of a food bowl could change the actor by modifying hunger, and also change the target by reducing the amount of food in the bowl.

So here is our activity base class.

An activity has an Actor and a Target. I intentionally limited Actors to be pets. I could have allowed any object to interact with any object, but that doesn’t quite make sense in practice. We don’t really want a food bowl to interact with a chew toy, or a ball to interact with a sleeping mat. We DO want to allow a pet to be a target allowing default activities to play social events. For example, pets could dance together or sniff each other or do whatever groups of pets do together.

We allow an Update event on the activity base. This update is run by the pet earlier. We pass that on through the OnUpdate callback in each activity. If the activity returns true then we know it is complete and the pet needs to find something new to do.

Finally we have a magical function, FindBestActivity() that needs to live somewhere in the world.

This FindBestActivity is the magic that makes the AI do fun things. In this example it is only 35 lines. We loop over all the toys in the game world and see how interesting they are. Then we take the best interaction and return a new instance of it. If we fail we just return the idle activity.

For a game like The Sims there are potentially tens of thousands of objects to choose from, and each object can have many activities associated with it. Finding the best activity among them all is a complex job. The theory behind it is no different: Find the best activity, and create an instance of it.
        public abstract class Activity
    {
        public Pets.Pet Actor { get; set; }
        public GameObject Target { get; set; }

        /// <summary>
        /// Initializes a new instance of the Activity class.
        /// </summary>
        public Activity(Pets.Pet actor, GameObject target)
        {
            Actor = actor;
            Target = target;
        }

        /// <summary>
        /// Update this activity state
        /// </summary>
        /// <param name="seconds">elapsed time</param>
        /// <returns>true if the activity is complete</returns>
        public abstract bool OnUpdate( float seconds );
        
        /// <summary>
        /// Update this activity state
        /// </summary>
        /// <param name="seconds">elapsed time</param>
        public void Update(float seconds)
        {
            if(OnUpdate(seconds))
            {
                Actor.Activity = new Idle(Actor, null);
            }
        }

        /// <summary>
        /// Utility function to locate the best next activity for the actor.
        /// </summary>
        /// <returns></returns>
        public static Activity FindBestActivity(Pets.Pet actor)
        {
            // Look for a toy to play with...
            if (actor.Owner != null
                && actor.Owner.GameObjects != null)
            {
                List<Toys.Toy> candidates = new List<Toys.Toy>();
                foreach (GameObject obj in actor.Owner.GameObjects)
                {
                    Toys.Toy t = obj as Toys.Toy;
                    if (t != null)
                    {
                        candidates.Add(t);
                    }
                }
                if (candidates.Count > 0)
                {
                    float bestScore = float.MinValue;
                    Toys.Toy bestToy = null;

                    foreach (Toys.Toy t in candidates)
                    {
                        float myscore = t.Interest(actor);
                        if(myscore>bestScore)
                        {
                            bestScore = myscore;
                            bestToy = t;
                        }
                    }
                    return bestToy.DefaultActivity(actor, bestToy);
                }
            }

            return new Idle(actor, null);
        }

        public override string ToString()
        {
            string classname = base.ToString();

            int index = classname.LastIndexOf('.');
            string shortname = classname.Substring(index + 1);
            return shortname;
        }
    }



Idle Activity

We’ll start with the idle activity.

It has an idle time. After enough time has passed we look for something new to do. This new activity will replace our current idle activity.
If we don’t find anything interesting to do we can just sit there, slowly dropping our fun and our energy.

Since this is C# we don’t need to schedule cleanup and deletion of our own idle activity which simplifies our code quite a lot.
    class Idle : Activity
    {
        float mTimeInIdle = 0;

        public Idle(Pets.Pet actor, GameObject target)
            : base(actor, target)
        {
        }
        
        public override bool OnUpdate(float seconds)
        {
            mTimeInIdle += seconds;
            if (mTimeInIdle >= FSM_Puppies.Properties.Settings.Default.Activity_Idle_WaitingTime)
            {
                Actor.Activity = FindBestActivity(Actor);
            }

            // Sitting there idle isn't much fun and slowly decays energy.  This encourages us to pick up other activiites.
            Actor.Motives.Fun += FSM_Puppies.Properties.Settings.Default.Activity_Idle_Fun * seconds;
            Actor.Motives.Energy += FSM_Puppies.Properties.Settings.Default.Activity_Idle_Energy * seconds;

            // Always return false because idle is never finished.  It auto-replaces if it can find something.
            return false;
        }
    }


ChaseBall Activity

This is actually TWO activities. Chasing a ball has one component “RunToObject”, and then a second component where they actually kick the ball.

So every update we attempt to run to the ball object. If we succeeded to running to the object, wet kick the ball a random distance. We also bump fun a little bit whenever they kick the ball.

An activity’s return result indicates when it is complete. We’ll return true only when our fun is maxed out. We might want to have a second exit condition when energy runs low, but that is for later.
    class ChaseBall : Activity
    {
        RunToObject mRto;

        /// <summary>
        /// Initializes a new instance of the ChaseBall class.
        /// </summary>
        public ChaseBall(Pets.Pet actor, GameObject target)
            : base(actor, target)
        {
            mRto = new RunToObject(actor, target);
        }

        public override bool OnUpdate(float seconds)
        {
            // When they kick the ball, move it to a new location and continue our activity.
            if( mRto.OnUpdate(seconds))
            {
                float kickDistance = FSM_Puppies.Properties.Settings.Default.Activity_ChaseBall_KickDistance;
                // Get a random number with +/- kick distance
                float newX = Target.Location.X + (((float)Target.Owner.Rng.NextDouble()*(2*kickDistance))-kickDistance);
                float newY = Target.Location.Y + (((float)Target.Owner.Rng.NextDouble()*(2*kickDistance))-kickDistance);
                PointF randomLocation = new PointF(newX,newY);
                Target.Location = randomLocation;
                Actor.Motives.Fun += FSM_Puppies.Properties.Settings.Default.Toy_Ball_Fun;
                if(Actor.Motives.Fun > 100)
                    return true;
            }
            return false;
        }
    }


RunToObject Activity

Next we’ll look at how they run to an object.

It is pretty simple. If we are close enough (another designer-adjustable value) then they have made it to the object and we return true. If they are not there yet we push them toward the object, drop their energy, and return false (we aren’t done with the activity yet).
    class RunToObject : Activity
    {
        /// <summary>
        /// Initializes a new instance of the RunToObject class.
        /// </summary>
        public RunToObject(Pets.Pet actor, GameObject target)
            : base(actor, target)
        {
            
        }
        public override bool OnUpdate(float seconds)
        {
            // Are we there yet?
            // And why didn't PointF implement operator- ?
            PointF offset = new PointF( Target.Location.X - Actor.Location.X, Target.Location.Y - Actor.Location.Y);
            float distanceSquared = offset.X * offset.X + offset.Y * offset.Y;
            float closeEnough = FSM_Puppies.Properties.Settings.Default.Activity_RunToObject_CloseEnough;
            float closeEnoughSquared = closeEnough * closeEnough;
            if (distanceSquared < closeEnoughSquared)
                return true;

            Actor.PushToward(Target.Location, seconds);
            Actor.Motives.Energy += FSM_Puppies.Properties.Settings.Default.Activity_RunToObject_Energy * seconds;
            return false;
        }
    }


Sleeping On the Mat

Just like chasing a ball, we start out by running to the object. So first we call RunToObject.

If it succeeds (meaning we finally got there), then we start resting. We bump the motives, return true or false based on our energy status.
    class SleepOnMat : Activity
    {
        RunToObject mRto;

        /// <summary>
        /// Initializes a new instance of the SleepOnMat class.
        /// </summary>
        public SleepOnMat(Pets.Pet actor, GameObject target)
            : base(actor, target)
        {
            mRto = new RunToObject(actor, target);
        }
        public override bool OnUpdate(float seconds)
        {
            // Route to the sleeping mat
            if(mRto.OnUpdate(seconds))
            {
                // Now that we are on the mat, just sit here and increase our motives.
                Actor.Motives.Energy += FSM_Puppies.Properties.Settings.Default.Toy_SleepingMat_Energy;
                Actor.Motives.Fun += FSM_Puppies.Properties.Settings.Default.Toy_SleepingMat_Fun;

                if (Actor.Motives.Energy > 100)
                    return true;
            }
            return false;
        }
    }


All Done

Go ahead and play around with this. Drop multiple play mats and multiple balls and multiple puppies.

It is not much, but it again enough to see a game starting to grow.

Repeat this thirty times with new objects, add a few motives, and you can have your own self-running simulated pet world.

But I Wanted a First Person Shooter AI

That is solved very easily:

Rename “Pets” to “Monsters”, and “Puppy” to “Grunt”.

Rename “Toys” to “WayPoint”, “Ball” to “Flamethrower”, and “SleepingMat” to “SpawnPoint”.

Finally, rename activities as appropriate.

Always Leave Them Wanting More

Okay, I’m not going to add much of a preview in the source code. In this case it is difficult to reveal extensions without directly including them.

In this case my children are driving what we add in Part 6: The Conclusion. They have a laundry-list of toys and activities with the toys. The biggest modification will be allowing attributes of each pet to modify their interest levels. I can tell it has turned into a game because my three children are all begging to add game designs to it.

Also, I have been asked to turn this series of journal entries into a full GameDev.net Article. Or series of articles, I’m not sure quite yet. So I need to make some minor adjustments to accommodate that format.

Finally, the complete source code below.


Attached File  StateMachineTutorialsV5.zip (64.24KB)
downloads: 56


State Machines in Games – Part 4

Posted by , 13 March 2013 - - - - - - · 1,503 views

Back to the castle exploration game. We're still working with an single state machine in the AdaptableStateMachine engine. This will be our third concrete example of a state machine.

Recap:

In Part 1 we covered the definition of a state machine and implemented a simple machine with a case statement.

In Part 2 we created a common state machine interface, created a state machine runner for the interface, and a boring state machine concrete class.

In Part 3 we left the state machine runner unchanged, created new state machine concrete classes for a castle map. Finally it looks like a game. A single state machine game.

Now in Part 4 we will make the state machine game data driven.

What Does Data Driven Mean?

So far every state machine and every state has been written in C#. If I wanted to make changes I needed to modify the source code, recompile, and test the app. For a small map and a single developer this is not a bad thing.

But what happens when the app grows?

Let's imagine I hire a person to design my levels. That person is not a programmer. I don't want them mucking about in my C# files, and I don't want to teach them how to read and write C#. So what do I do?

Simple: I create a save file that contains all the information describing the level.

By loading the level at runtime I can allow non-programmers to develop the game. It means level designers can modify rooms, put different things in different locations, and otherwise improve the game without touching code. It means another programmer can implement game objects like ropes and bottles and such (should be in Part 7, I think) and the level designer can manipulate those in the world without modifying code. If I had artwork, artists could create new art and designers could put it in the world without touching code.

If this were a major game we might have ten or twenty people working on the game. Only a quarter of those people would be programmers. Everyone else would be modifying the game data rather than modifying the game code.

Data Driven means only the programmers need to touch the code, and everyone else gets to use data files to modify the game.

If we wanted to be fancy we could add a mechanism to reload data files while the game is running. That would let designers and artists and animators and modelers iterate on their work much faster, saving many hours of development time.

If we wanted to get REALLY fancy would could use operating system events to notify us when a change is made and automatically reload the resources at runtime.

On to the Code!

Once again I am using IState and IStateMachine interfaces for my machine.

And just like before, the entire game is managed by these same two files.

The States

States are only slightly modified from last time.

Last time I stored the state's name, description, and neighbors.

This time I add a unique name key and a set of flags.

The flags are: None, Enter, and Exit. This allows me to code in multiple exit nodes. I am still required to have only one entry node because that is just how state machines work; you need to start somewhere.

Next I added the functions ReadXml and WriteXml. These two functions save and load my five elements (unique name, flags, visible name, description, and neighbors) into an xml file. Because it is basically free I chose to implement them using the IXmlSerializable interface.

Since the state machine will need to create these objects from XML, I create a second constructor that takes an XmlReader and simply pass that on to the ReadXml function.

Finally I added some accessors and mutators (get and set functions) to help out the state machine.

The code:
    public class SavedMachineState : IState, IXmlSerializable
    {
        #region Members
        [Flags]
        public enum StateFlags
        {
            None = 0,
            Enter = 1,
            Exit = 2,
        }

        public string mKey;
        StateFlags mFlags;
        string mName;
        string mDescription;
        List<string> mNeighbors = new List<string>();
        public List<string> Neighbors { get { return mNeighbors; } }
        #endregion
        #region Constructors
        /// <summary>
        /// Manual constructor for default maze
        /// </summary>
        /// <param name="uniqueKey">unique name for the stateFlags</param>
        /// <param name="flags">flags to indicate enter nodes and exit nodes</param>
        /// <param name="name">name to show to the user</param>
        /// <param name="description">text to show for the description</param>
        /// <param name="neighbors">unique keys for neighboring rooms, seperated by commas and not spaces</param>
        public SavedMachineState(string uniqueKey, StateFlags flags, string name, string description, string neighbors)
        {
            mKey = uniqueKey;
            mFlags = flags;
            mName = name;
            mDescription = description;
            mNeighbors.AddRange(neighbors.Split(','));
        }
        /// <summary>
        /// Constructor to create an object from a save file
        /// </summary>
        /// <param name="reader">xml stream to read from</param>
        public SavedMachineState(XmlReader reader)
        {
            ReadXml(reader);
        }
        #endregion
        #region Helper Functions
        public bool IsStartState { get { return (mFlags & StateFlags.Enter) != StateFlags.None; } }
        public bool IsExitState { get { return (mFlags & StateFlags.Exit) != StateFlags.None; } }
        public string Key { get { return mKey; } }
        public bool IsMyName(string nameToTest)
        {
            //TODO: Add shortcuts to names.  For example, allow "Great Hall", "Hall", etc.
            if (nameToTest.ToLower() == mName.ToLower())
                return true;
            if (nameToTest.ToLower() == mKey.ToLower())
                return true;
            return false;
        }
        #endregion
        #region IState Overrides
        public override string GetName()
        {
            return mName;
        }

        public override void Run()
        {
            // We don't do any fancy stuff, just print out where we are
            Console.WriteLine();
            Console.WriteLine(mDescription);
        }
        #endregion
        #region IXmlSerializable Members

        public System.Xml.Schema.XmlSchema GetSchema()
        {
            return null;
        }

        public void ReadXml(System.Xml.XmlReader reader)
        {
            reader.ReadStartElement();
            mKey = reader.ReadElementContentAsString("UniqueName","");
            string flagString = reader.ReadElementContentAsString("Flags","");
            mFlags = (StateFlags)Enum.Parse(typeof(StateFlags), flagString);
            mName = reader.ReadElementContentAsString("VisibleName", "");
            mDescription = reader.ReadElementContentAsString("Description", "");
            string neighborsString = reader.ReadElementContentAsString("Neighbors", "");
            mNeighbors.AddRange(neighborsString.Split(','));
            reader.ReadEndElement();
        }

        public void WriteXml(System.Xml.XmlWriter writer)
        {
            writer.WriteElementString("UniqueName", mKey);
            writer.WriteElementString("Flags", mFlags.ToString());
            writer.WriteElementString("VisibleName", mName);
            writer.WriteElementString("Description", mDescription);
            string neighbors = String.Join(",",Neighbors.ToArray());
            writer.WriteElementString("Neighbors",neighbors);
        }

        #endregion
    }

It might be a little intimidating if this were the first exposure you had to the code, but if you have been following along and since it has been gradually expanding it should seem to be just an incremental change.

The State Machine

The changes to the state machine were a little more dramatic.

As promised last time I was able to remove the saved mExit state. Since this is now part of the data (and because there can now be more than one) we don't want to keep this around in code.

I moved all the map construction code from the constructor to its own function: GenerateDefaultMap(). No point in throwing away all that work, and it allows us to generate and save a map when bootstrapping the tool chain. Now the constructor calls ImportFromXml(). If that fails we generate the default map (which saves a copy with ExportToXML() ) and then reload our newly created map. It then searches for an entry node. If it cannot fine one, we abort.

The ExportToXML() function creates an XML writer, loops through the list of states, and writes each state out using a helper function. The ImportFromXML() function creates an XML reader, and reads it in using a helper function. Those helper functions are the same IXmlSerializable interface we used on the state. This allows us to someday take advantage of the C# serialization system. Perhaps around Part 8 or so.

The WriteXML() function creates an XML element <room> </room> and then calls the state's WriteXML to fill in the details. The ReadXML() function verifies that we have a room node, calls the state's constructor with the XML reader, and adds the newly created state to the list.

The Code:
    public class SavedMachine : IStateMachine, IXmlSerializable
    {
        #region Members
        List<SavedMachineState> mStates = new List<SavedMachineState>();
        SavedMachineState mCurrent;
        #endregion
        #region Constructor
        /// <summary>
        /// Initializes a new instance of the FunnerMachine class.
        /// </summary>
        public SavedMachine()
        {
            try
            {
                ImportFromXML();
            }
            catch (Exception ex)
            {
                mStates.Clear();
            }

            if (mStates.Count == 0)
            {
                GenerateDefaultMap();
                ImportFromXML();
            }

            // Find the entry state
            for (int i = 0; i < mStates.Count; i++)
            {
                if (mStates[i].IsStartState)
                {
                    mCurrent = mStates[i];
                    break;
                }
            }
            if (mCurrent == null)
            {
                Console.WriteLine("\n\nERROR! NO ENTRY STATE DEFINED.");
                throw new Exception("No entry state defined in this state machine.  Cannot continue.");
            }
        }
        #endregion
        #region Helper Functions
        private void GenerateDefaultMap()
        {
            mStates.Clear();

            // Create all the fun states in our mini-world
            mStates.Add(new SavedMachineState("entryHall", SavedMachineState.StateFlags.Enter, "Grand Entrance", "You are standing in a grand enterance of a castle.\nThere are tables and chairs, but nothing you can interact with.", "staircase,outside"));
            mStates.Add(new SavedMachineState("staircase", SavedMachineState.StateFlags.None, "Grand Staircase", "The staircase is made from beautiful granite.", "eastWing,westWing,entryHall"));
            mStates.Add(new SavedMachineState("eastWing", SavedMachineState.StateFlags.None, "East Wing", "This wing is devoted to bedrooms.", "bedroomA,bedroomB,bedroomC,staircase"));
            mStates.Add(new SavedMachineState("westWing", SavedMachineState.StateFlags.None, "West Wing", "This wing is devoted to business.", "workroomA,workroomB,workroomC"));
            mStates.Add(new SavedMachineState("bedroomA", SavedMachineState.StateFlags.None, "Master Suite", "This is the master suite.  What a fancy room.", "eastWing"));
            mStates.Add(new SavedMachineState("bedroomB", SavedMachineState.StateFlags.None, "Prince Bob's Room", "The prince has an extensive library on his wall.\nHe also has more clothes than most males know what to do with.", "eastWing"));
            mStates.Add(new SavedMachineState("bedroomC", SavedMachineState.StateFlags.None, "Princess Alice's Room", "The princess has filled her room with a small compur lab.\nShe spends her days playing games and writing code.", "eastWing"));
            mStates.Add(new SavedMachineState("workroomA", SavedMachineState.StateFlags.None, "Study", "This is the study.  It has many books.", "westWing"));
            mStates.Add(new SavedMachineState("workroomB", SavedMachineState.StateFlags.None, "Bathroom", "Every home needs one", "westWing"));
            mStates.Add(new SavedMachineState("workroomC", SavedMachineState.StateFlags.None, "Do Not Enter", "I warned you not to enter.\nYou are in a maze of twisty little passages, all alike.", "passage"));
            mStates.Add(new SavedMachineState("passage", SavedMachineState.StateFlags.None, "Twisty Passage", "You are in a maze of twisty little passages, all alike", "passage"));
            mStates.Add(new SavedMachineState("outside", SavedMachineState.StateFlags.Exit, "Outside", "You have successfully exited the castle.", ""));

            ExportToXML();
        }
        public void ExportToXML()
        {
            XmlWriterSettings settings = new XmlWriterSettings();
            settings.Indent = true;
            settings.OmitXmlDeclaration = true;
            settings.NewLineHandling = NewLineHandling.Entitize;

            using (XmlWriter writer = XmlWriter.Create("GameRooms.xml",settings))
            {
                writer.WriteStartDocument();
                writer.WriteStartElement("SavedMachine");
                WriteXml(writer);
                writer.WriteEndElement();
                writer.WriteEndDocument();
            }           
        }
        public void ImportFromXML()
        {
            XmlReaderSettings settings = new XmlReaderSettings();
            settings.IgnoreWhitespace = true;
            XmlReader reader = XmlReader.Create("GameRooms.xml", settings);
            ReadXml(reader);
        }
        #endregion
        #region IStateMachine Overrides
        public override IState CurrentState
        {
            get { return mCurrent; }
        }
        public override string[] PossibleTransitions()
        {
            List<string> result = new List<string>();
            foreach (string state in mCurrent.Neighbors)
            {
                result.Add(state);
            }
            return result.ToArray();
        }
        public override bool Advance(string nextState)
        {
            foreach (SavedMachineState state in mStates)
            {
                if(state.IsMyName(nextState)
                    && mCurrent.Neighbors.Contains(state.Key))
                {
                    mCurrent = state;
                    return true;
                }
            }
            System.Console.WriteLine("Cannot do that.");
            return false;
        }
        public override bool IsComplete()
        {
            return mCurrent.IsExitState;
        }
        #endregion
        #region IXmlSerializable Members

        public System.Xml.Schema.XmlSchema GetSchema()
        {
            return null;
        }


        public void ReadXml(XmlReader reader)
        {
            bool isEmpty = reader.IsEmptyElement;
            reader.ReadStartElement();
            if (isEmpty) return;
            while (reader.NodeType == XmlNodeType.Element)
            {
                if (reader.Name == "Room")
                {
                    mStates.Add(new SavedMachineState(reader));
                }
                else
                    throw new XmlException("Unexpected node: " + reader.Name);
            }
            reader.ReadEndElement();
        }

        public void WriteXml(XmlWriter writer)
        {
            foreach (SavedMachineState state in mStates)
            {
                writer.WriteStartElement("Room");
                state.WriteXml(writer);
                writer.WriteEndElement();
            }
        }

        #endregion
    }
Run the Game

Now when I run the game, I select option 3 for this new state machine. Note that I had already included that option in Part 2's source code.

When it runs it attempts to load the save file, cannot find one, and generates the new room xml file. Then it plays the state machine as normal.

I don't care to play right now, I just want to read the XML.

Looking at the GameRooms.xml

Let's jump strait to the generated file:
<SavedMachine>
  <Room>
    <UniqueName>entryHall</UniqueName>
    <Flags>Enter</Flags>
    <VisibleName>Grand Entrance</VisibleName>
    <Description>You are standing in a grand enterance of a castle.
There are tables and chairs, but nothing you can interact with.</Description>
    <Neighbors>staircase,outside</Neighbors>
  </Room>
  <Room>
    <UniqueName>staircase</UniqueName>
    <Flags>None</Flags>
    <VisibleName>Grand Staircase</VisibleName>
    <Description>The staircase is made from beautiful granite.</Description>
    <Neighbors>eastWing,westWing,entryHall</Neighbors>
  </Room>
  <Room>
    <UniqueName>eastWing</UniqueName>
    <Flags>None</Flags>
    <VisibleName>East Wing</VisibleName>
    <Description>This wing is devoted to bedrooms.</Description>
    <Neighbors>bedroomA,bedroomB,bedroomC,staircase</Neighbors>
  </Room>
  <Room>
    <UniqueName>westWing</UniqueName>
    <Flags>None</Flags>
    <VisibleName>West Wing</VisibleName>
    <Description>This wing is devoted to business.</Description>
    <Neighbors>workroomA,workroomB,workroomC</Neighbors>
  </Room>
  <Room>
    <UniqueName>bedroomA</UniqueName>
    <Flags>None</Flags>
    <VisibleName>Master Suite</VisibleName>
    <Description>This is the master suite.  What a fancy room.</Description>
    <Neighbors>eastWing</Neighbors>
  </Room>
  <Room>
    <UniqueName>bedroomB</UniqueName>
    <Flags>None</Flags>
    <VisibleName>Prince Bob's Room</VisibleName>
    <Description>The prince has an extensive library on his wall.
He also has more clothes than most males know what to do with.</Description>
    <Neighbors>eastWing</Neighbors>
  </Room>
  <Room>
    <UniqueName>bedroomC</UniqueName>
    <Flags>None</Flags>
    <VisibleName>Princess Alice's Room</VisibleName>
    <Description>The princess has filled her room with a small compur lab.
She spends her days playing games and writing code.</Description>
    <Neighbors>eastWing</Neighbors>
  </Room>
  <Room>
    <UniqueName>workroomA</UniqueName>
    <Flags>None</Flags>
    <VisibleName>Study</VisibleName>
    <Description>This is the study.  It has many books.</Description>
    <Neighbors>westWing</Neighbors>
  </Room>
  <Room>
    <UniqueName>workroomB</UniqueName>
    <Flags>None</Flags>
    <VisibleName>Bathroom</VisibleName>
    <Description>Every home needs one</Description>
    <Neighbors>westWing</Neighbors>
  </Room>
  <Room>
    <UniqueName>workroomC</UniqueName>
    <Flags>None</Flags>
    <VisibleName>Do Not Enter</VisibleName>
    <Description>I warned you not to enter.
You are in a maze of twisty little passages, all alike.</Description>
    <Neighbors>passage</Neighbors>
  </Room>
  <Room>
    <UniqueName>passage</UniqueName>
    <Flags>None</Flags>
    <VisibleName>Twisty Passage</VisibleName>
    <Description>You are in a maze of twisty little passages, all alike</Description>
    <Neighbors>passage</Neighbors>
  </Room>
  <Room>
    <UniqueName>outside</UniqueName>
    <Flags>Exit</Flags>
    <VisibleName>Outside</VisibleName>
    <Description>You have successfully exited the castle.</Description>
    <Neighbors />
  </Room>
</SavedMachine>
This looks suspiciously like our original map. In fact, it is our original map, just saved out to xml.


To prove that the save and load system actually works, let's make some minor modifications to the map.

Attached Image

This will require a tiny modification to the entrance hall (poininting the neighbor to 'courtyard') and making three new rooms.

Easy enough, a few seconds in a text editor, copy/paste, a bit of wordsmithing, and I get this addition:
  <Room>
    <UniqueName>courtyard</UniqueName>
    <Flags>None</Flags>
    <VisibleName>Courtyard</VisibleName>
    <Description>The courtyard is decorated with many large trees and several marble benches.</Description>
    <Neighbors>entryHall,townGate</Neighbors>
  </Room>
  <Room>
    <UniqueName>townGate</UniqueName>
    <Flags>None</Flags>
    <VisibleName>Town Gate</VisibleName>
    <Description>You arrive at the gate of the town.  Ahh, to be home again.\n\nNOTICE: The guards will not let you return to the castle if you leave.</Description>
    <Neighbors>courtyard,village</Neighbors>
  </Room>
  <Room>
    <UniqueName>village</UniqueName>
    <Flags>Exit</Flags>
    <VisibleName>Quaint Village</VisibleName>
    <Description>You return to your village.  You won't soon forget your experiences in the castle.</Description>
    <Neighbors />
  </Room>
Modify the save file, reload the app, and automatically I have three new rooms.

Since I haven't done this yet, I'll just say that a map editor and more complex maps are left as an exercise to the reader. You can do that if you want to. I'm busy writing code for parts 5, 6 and 7.

And speaking of Part 5...

Always Leave Them Wanting More

So now we have a game world that is data driven. Any programmer or level designer can modify the GameRooms.xml file to make the map as complex as you want. I recommend creating a simple tool if you plan on making more than a few dozen rooms, but that is really up to you.

Where are we going next?

I promised we'd get in to AI.

AI in a text adventure is nice, but it isn't very interactive.

So Part 5 will have an actual VIDEO game. With graphics. And an update loop. And individual actors that have an AI. But not so much for gameplay since all we are testing is AI. We'll also have a collection of state machines that interact with each other.

Naturally there is a preview of it in this part's source code, as an incentive. Like always.

Attached File  StateMachineTutorialsV4.zip (55.92KB)
downloads: 80


State Machines in Games – Part 3

Posted by , in Programming 11 March 2013 - - - - - - · 1,438 views

In Part 1 we learned what state machines are and we also hard-coded a simple state machine using a switch statement.

In Part 2 we made a simple state machine interface and a generic state machine runner. We also created one boring state machine instance.

Now in Part 3 we are going to actually make a game. It isn't much of a game, but it has all the elements of gameplay. It has risk. It has danger. It has a way to win.

Okay, it is just navigating a maze of rooms in a text adventure. There are no monsters, no inventory, and no plot (we'll add all those later). For now we can just navigate a castle full of rooms.

We won't modify the state machine interface or the state machine runner that we made in part 2. They work just fine for our purposes right now.

The States of the Game

Just like the boring state machine, each state implements the IState interface and also contains some useful information specific to that state machine implementation.

The 'useful information' is a name of the room, a description of the room, and links to the neighboring rooms. The name and description are set by the constructor and the neighbors are added by the state machine when it builds the graph.

The run function actually does something this time: It writes out the description of the room as written by the constructor.

On to the code!
class FunMachineState : IState
    {
        string mName;
        string mDescription;
        List<FunMachineState> mNeighbors = new List<FunMachineState>();
        public List<FunMachineState> Neighbors { get { return mNeighbors; } }


        /// <summary>
        /// Initializes a new instance of the FunnerState class.
        /// </summary>
        /// <param name="mName">Name to display for this state</param>
        /// <param name="mDescription">Text to display for this state</param>
        public FunMachineState(string mName, string mDescription)
        {
            this.mName = mName;
            this.mDescription = mDescription;
        }

        #region IState Overrides
        public override string GetName()
        {
            return mName;
        }

        public override void Run()
        {
            // We don't do any fancy stuff, just print out where we are
            Console.WriteLine();
            Console.WriteLine(mDescription);
        }
        #endregion
    }

That wasn't too bad, was it?

Most of the State Machine

Now comes the time to describe the state machine implementation.

This one gets to be a little more interesting.

I'm going to include everything in the short listing EXCEPT the state machine constructor. I'll go over that in detail in the next section.

It implements the generic IStateMachine interface and that is pretty simple.

It also adds three machine-specific values: A list of states, a link to the current state, and a link to the exit state (which we'll get rid of in the next lesson).

You might have realized this already, but each state represents a room in the map. Hopefully that was already clear, but if not I figured I should point that out.

Of special note is how it gets the list of possible transitions and how it advances. We look at the names of the current node's neighbors -- in this case is the name of a room on the map. We advance by making sure we can only travel to neighboring rooms. That avoids the exploit of typing in "move Exit" and winning the game in the first step.
    class FunMachine : IStateMachine
    {
        List<FunMachineState> mStates;
        FunMachineState mCurrent;
        FunMachineState mExit;

        /// CONSTRUCTOR NOT SHOWN

        #region IStateMachine Overrides
        public override IState CurrentState
        {
            get { return mCurrent; }
        }
        public override string[] PossibleTransitions()
        {
            List<string> result = new List<string>();
            foreach (FunMachineState state in mCurrent.Neighbors)
            {
                result.Add(state.GetName());
            }
            return result.ToArray();
        }
        public override bool Advance(string nextState)
        {
            foreach (FunMachineState state in mCurrent.Neighbors)
            {
                if (nextState == state.GetName())
                {
                    mCurrent = state;
                    return true;
                }
            }
            System.Console.WriteLine("Invalid state.");
            return false;
        }
        public override bool IsComplete()
        {
            return mCurrent == mExit;
        }
        #endregion
    }

Again, this is mostly straightforward. We've got our game state machine in just a few lines of code.

On to the Game's State Machine Constructor...

Here is where the fun is.

This state machine is also hard coded. In order to generate the game's maze we'll need to manually construct each room (aka FSM state, or vertex, or node). And we'll need to construct each transition (aka edge) of the state machine.
        public FunMachine()
        {
            // Create all the fun states in our mini-world
            FunMachineState entryHall = new FunMachineState("Grand Entrance", "You are standing in a grand entrance of a castle.\nThere are tables and chairs, but nothing you can interact with.");
            FunMachineState staircase = new FunMachineState("Grand Staircase", "The staircase is made from beautiful granite.");
            FunMachineState eastWing = new FunMachineState("East Wing", "This wing is devoted to bedrooms.");
            FunMachineState westWing = new FunMachineState("West Wing", "This wing is devoted to business.");
            FunMachineState bedroomA = new FunMachineState("Master Suite", "This is the master suite.  What a fancy room.");
            FunMachineState bedroomB = new FunMachineState("Prince Bob's Room", "The prince has an extensive library on his wall.\nHe also has more clothes than most males know what to do with.");
            FunMachineState bedroomC = new FunMachineState("Princess Alice's Room", "The princess has filled her room with a small compur lab.\nShe spends her days playing games and writing code.");
            FunMachineState workroomA = new FunMachineState("Study", "This is the study.  It has many books.");
            FunMachineState workroomB = new FunMachineState("Bathroom", "Every home needs one");
            FunMachineState workroomC = new FunMachineState("Do Not Enter", "I warned you not to enter.\nYou are in a maze of twisty little passages, all alike.");
            FunMachineState passage = new FunMachineState("Twisty Passage", "You are in a maze of twisty little passages, all alike");

            mExit = new FunMachineState("Outside", "You have successfully exited the castle.");

            // Hook up doors.
            entryHall.Neighbors.Add(staircase);
            entryHall.Neighbors.Add(mExit);

            staircase.Neighbors.Add(eastWing);
            staircase.Neighbors.Add(westWing);
            staircase.Neighbors.Add(entryHall);

            eastWing.Neighbors.Add(bedroomA);
            eastWing.Neighbors.Add(bedroomB);
            eastWing.Neighbors.Add(bedroomC);
            eastWing.Neighbors.Add(staircase);

            bedroomA.Neighbors.Add(eastWing);
            bedroomB.Neighbors.Add(eastWing);
            bedroomC.Neighbors.Add(eastWing);

            westWing.Neighbors.Add(workroomA);
            westWing.Neighbors.Add(workroomB);
            westWing.Neighbors.Add(workroomC);

            workroomA.Neighbors.Add(westWing);
            workroomB.Neighbors.Add(westWing);

            // Trap of doom.
            workroomC.Neighbors.Add(passage);
            passage.Neighbors.Add(passage);

            // Add them to the collection
            mStates = new List<FunMachineState>();
            mStates.Add(entryHall);
            mStates.Add(staircase);
            mStates.Add(eastWing);
            mStates.Add(westWing);
            mStates.Add(bedroomA);
            mStates.Add(bedroomB);
            mStates.Add(bedroomC);
            mStates.Add(workroomA);
            mStates.Add(workroomB);
            mStates.Add(workroomC);
            mStates.Add(passage);
            mStates.Add(mExit);

            // Finally set my starting point
            mCurrent = entryHall;
        }

This creates a fun little graph.

In case you're having trouble visualizing it, the picture version looks like this:

Attached Image

It has a few rooms with their descriptions. It has a death trap room, and it has a route to victory.

It looks like a game!

It isn't much of a maze but that's all I wanted to hard code right now.

Run the Sample Code

Now is the time to actually see the game start to come together. If you haven't downloaded the source code already from the first two samples, you can download it at the bottom of this article.

Just run the project.

When you run it you can hit "1" for the boring state machine we had in Part 2. It is boring but it works.

Much more interesting is to hit "2" for this funner state machine. Navigate the map. Get lost in the death trap (and manually close the window or Ctrl+C out of the app because there is no way out).

And you can hit "3" to see a preview of what is coming.

Always Leave Them Wanting More

So now we're done with Part 3. We have made a minimal game with state machines.

Sure the game has no items to manipulate, or plot, but that will all come later. Also to come later are other uses for state machines, such as for AI systems.

Now I'm getting ahead of myself.

If you hit "3" in the sample code (I know you did) you have had your preview to Part 4 - making your state machine data driven.

Why make it data driven? You weren't seriously thinking of hard coding a large complex dungeon into your game, were you?

Attached File  StateMachineTutorials.zip   13.42KB   99 downloads


State Machines in Games – Part 2

Posted by , in Programming 10 March 2013 - - - - - - · 1,370 views

We completed Part 1 with a simple boring hard-coded state machine, implemented by a switch statement.

It didn’t do much. In textbooks that would be called “an exercise for the reader.”

In this entry (Part 2) we're going to prepare the rest of the way for our game. Since I know in advance where I am leading you, I'll tell you that we need a bunch of state machines.

As this is going to become a collection of state machines, we’ll extend it out into a common interface.

Programming to an interface, also called the Dependency Inversion Principle, allows us to extend a simple state machine into many different types of machines.

The Interface

Here are the two interface classes used in all the examples.
public abstract class IStateMachine
{
    // Accessor to look at the current state.
    public abstract IState CurrentState { get; }

    // List of all possible transitions we can make from this current state.
    public abstract string[] PossibleTransitions();

    // Advance to a named state, returning true on success.
    public abstract bool Advance(string nextState);

    // Is this state a "completion" state. Are we there yet?
    public abstract bool IsComplete();
}
public abstract class IState
{
    // Utility function to help us display useful things
    public abstract string GetName();
    // Do something
    public abstract void Run();

    // This isn't really needed, but it helps in debugging and other tasks.
    // It allows hover-tips and debug info to show me the name of the state
    // rather than the default of the type of the object
    public override string ToString()
    {
        return GetName();
    }
}


That’s it for our interface.

The State Machine Runner

Now that we have the interface we can start using it.

One of the beauties of using interfaces ("Dependency Inversion Pattern") is that we can start writing our state machine without any other details.

So here is the app I wrote. Again I didn’t actually implement any of the state machines for this state machine runner. I just used the interfaces:
static void Main(string[] args)
{
    // First we need to create the state machine.
    // Note that I'm using the abstract IStateMachine instead of a concrete class.
    IStateMachine machine = GetMachine();

    // We have a machine, now run it.
    while(!machine.IsComplete())
    {
        // Print out our current state
        Console.WriteLine("Currently in " + machine.CurrentState);
        machine.CurrentState.Run();

        // Print out our possible transitions
        Console.WriteLine("\nAvailable choices are:");
        string[] transitions = machine.PossibleTransitions();
        foreach (string item in transitions)
        {
            Console.WriteLine(" " + item);
        }

        // Request a transition from the user
        Console.WriteLine("\nWhat do you want to do?");
        string nextState = Console.ReadLine();
        machine.Advance(nextState);
    }

    // And we're done!
    // Run our final node as a special case since the above loop won't do it.
    Console.WriteLine("Currently in " + machine.CurrentState);
    machine.CurrentState.Run();

    // Finish off.
    Console.WriteLine("\n\nPress any key to continue.");
    Console.ReadKey(true);
}
That’s it! 36 lines of code and our game runner is finished.

What?! The game runner is finished?

That little beauty will run any state machine that implements the interface. And our little game can fit into that interface. So let’s get on to filling in those pesky details.

Let’s try it out with a traditional textbook state machine. I’m talking about how textbooks use labels like 0, 1, 2, and variables like x, y, and z, that carry no real useful information.

Boring State Machine Implementation

There are two major options when it comes to state machines. You can make the machine know about traversing the machine, or you can make the state know about it. It's just like in graphics how you can either have objects move around the world or you can have the world move around objects. Either way, the result is the same.

In this boring example I made a single state and made the state modify itself as the machine moved around it.

Boring Machine Source
class BoringMachine : IStateMachine
{
    BoringMachineState mState = new BoringMachineState();

    public override IState CurrentState
    {
        get { return mState; }
    }
    public override string[] PossibleTransitions()
    {
        // For this simple example, forward it on to the state
        return mState.ListTransitions();
    }
    public override bool Advance(string nextState)
    {
        Console.WriteLine("I'm a boring state machine. I don't care what you entered. Advancing state.");
        return mState.Advance();
    }
    public override bool IsComplete()
    {
        // For the simple example, forward it on to the state
        return mState.IsComplete();
    }
}
Because it is a boring example I’ve simply ignored the name of the state transition. We don’t need it, we simply tell the state that it is time to move on.

Boring Machine State code

Here it is, in all it’s boring detail:
class BoringMachineState : IState
{
    #region Members
    internal enum SameOldStates
    {
        Enter,
        DoStuff,
        Exiting,
        Done
    }
    SameOldStates mState = SameOldStates.Enter;
    #endregion
    #region IState overrides
    public override string GetName()
    {
        return mState.ToString();
    }
    public override void Run()
    {
         // Do nothing. This is the game logic.
    }
    #endregion
    #region Helper functions
    public bool IsComplete()
    {
        return mState == SameOldStates.Done;
    }
    public string[] ListTransitions()
    {
        List<string> result = new List<string>();
        switch (mState)
        {
        case SameOldStates.Enter:
            result.Add("DoStuff");
            break;
        case SameOldStates.DoStuff:
            result.Add("Exiting");
            break;
        case SameOldStates.Exiting:
            result.Add("Done");
            break;
        case SameOldStates.Done:
            result.Add("Done");
            break;
        }
        return result.ToArray();
    }
    public bool Advance()
    {
        switch (mState)
        {
        case SameOldStates.Enter:
            mState = SameOldStates.DoStuff;
            break;
        case SameOldStates.DoStuff:
            mState = SameOldStates.Exiting;
            break;
        case SameOldStates.Exiting:
            mState = SameOldStates.Done;
            break;
        case SameOldStates.Done:
            // Do nothing.
            break;
        }
        return true;
    }
    #endregion
}
It is a little boring. Everything is hard coded. It doesn’t look like a game. It ignores user input.

But when I run it, IT WORKS!

And that’s what matters.

Always Leave Them Wanting More

Okay, my game really is close to being finished.

The game runner is complete. It works perfectly for the game we’re trying to make.

In part three, I’ll create a second implementation of IStateMachine and IState that make an actual interactive game.

(Okay, part 3 isn't an actual FUN game, but it is enough to technically be a text adventure. That is for a very loose definition of 'adventure'.)

You can download the code in the link below. Just like before it has a preview of what is to come.

Attached File  StateMachineTutorials.zip   13.42KB   99 downloads


State Machines in Games – Part 1

Posted by , in Programming 10 March 2013 - - - - - - · 2,259 views

I recently had a conversation with a beginner on GameDev.net about the importance of state machines.

Most of the beginners on the site are teenagers enrolled in secondary school. Often they pick up concepts here and there over the Internet when they think it will help, but don’t take the time to learn topics that don’t seem interesting.

State machines. Bah! Who needs them, right?

Games need them.

Let’s get the boring part over with…

The Computer Science Part

Finite State Machines, FSMs, or Finite State Automata, are an abstract machine. The machine is made up of multiple states, also called a node or vertex. The machine transitions to a new state by a trigger or event or condition. The transitions are also called edges. One node is designated as the start node. Notes may be marked as exit nodes. The machine enters at the start node, transitions to new states, and eventually ends on an exit node.

So what does that mean?

We can explain the same state machine in many different ways. It is usually easier to understand a state machine when it is put into a pretty picture. It is usually easier to modify, codify, and program state machines when they are in tables. So we’ll do both.

Let’s start with this very simple state machine:

Attached Image

Note that there are 2 entries for state 2. That is because it has two transitions.

Graphically it looks like this:

Attached Image

But we don’t have to use boring names like 0, 1, 2, and 3 for state names, just like we don’t have to stick with x and y for variable names.

Let’s redraw the graphs with better names:

Attached Image

Suddenly it looks much less like a boring theory topic and more like something to do with games.

In fact, it looks like an NPC’s game logic.

Implementing a simple state machine

Now we can start out simple implementation.

For the common quick-and-dirty simple state machine that will never change, we can simply hard code the machine into the code. In that case the programmer calls on his favorite little friend, the switch statement:
public class StateMachine
{
public enum State
{
Routing,
Sentrying,
Attacking,
Ending
}
State mState = State.Routing;
Random rng = new Random();

public string GetStateName()
{
return mState.ToString();
}
public string UpdateState()
{
return "Running state " + GetStateName() +". Your game logic goes here.";
}

public void NextState()
{
switch (mState)
{
case State.Routing:
mState = State.Sentrying;
break;
case State.Sentrying:
mState = State.Attacking;
break;
case State.Attacking:
// TODO: Make this based on game logic instead of random name generator
if (rng.NextDouble() < 0.75)
{
Console.WriteLine("Random generator says NPC has survived.");
mState = State.Routing;
}
else
{
Console.WriteLine("Random generator says NPC did not survive.");
mState = State.Ending;
}
break;
case State.Ending:
// Nothing to do.
break;
}
}
public bool IsDone()
{
return mState == State.Ending;
}
}


"But wait", you might exclaim, "That doesn’t look like a big ugly table or a fancy graph!" That’s right. I said above that you can have many different ways to represent a state machine, and this is one of them.

Always Leave Them Wanting More

This sums up the first lesson. You know what a state machine is in an abstract way. You saw that they might potentially have some uses in games. And you hopefully want to know more.

Stop by next time for the second lesson about extending the basic state machine.

Source code is attached. You can download it to get a sneak peak at what is coming up.

Attached File  StateMachineTutorials.zip (13.42KB)
downloads: 99


The New Theme -- following up.

Posted by , in Site Related 15 January 2011 - - - - - - · 461 views

I've gone through the rest of the items that I care about, and adjusted them.

I've also only tested this on Chrome. Why? Because I made it for me, not really for the public. I'm throwing it out there for the world in the hopes that somebody else might find it useful, too. And because a few people asked me about it.


Right now I'm satisfied with how this looks in chrome. As the site continues to evolve I'll probably make more changes to it as I see fit.

For easy installation and better distribution I've posted the script on userstyles.org. That way I won't need to provide further installation instructions. You can follow the link and use the "Install it" button.

Without this I was able to see just one post on a page. Now I can see about 3 posts on a page. There is more information, and it doesn't limit me to 1250 pixels when I go full screen. Full screen on my big monitor shows about 5 posts per page. That's enough to satisfy my craving for seeing nearby posts when composing a reply. And that's all I really wanted.


Posted Image

Posted Image


The New Theme

Posted by , in Site Related 13 January 2011 - - - - - - · 403 views

As I've mentioned in a few posts, I'm working on trimming down the site a little bit in my own user css.

I have trimmed down the top menus, assorted boxes and dialogs, and forum post tables. I toned down the background and adjusted a few colors. I moved the feedback image so it doesn't cover the text, and moved the "loading" tab to the side for the same reason.

I'm developing it in the Chrome browser because it is surprisingly easy to create style rules, edit them, and review changes on the fly. I have no idea if this works in any other browser. Let me know if you like it.

I've still got a lot of work left on it, but if you want some changes please let me know and I'll incorporate them.

Enjoy the file. I hope you find it useful.



EDIT:
Pictures!

Posted Image


Posted Image

The next item on my hist list is the user info boxes on the left side of forum posts. They take WAY too much vertical space.

Attached Files








September 2016 »

S M T W T F S
    123
45678910
11121314151617
18192021222324
25262728 29 30 

Recent Comments



PARTNERS