The World’s Leading Microsoft .NET Magazine
   
 
The .NET Addict's Blog

My Top Tags

                                                           

My RSS Feeds








I heart FeedBurner

Latest Diggs - Programming

Computers Blogs - Blog Top Sites

Site Hits

Total: 4,904,749
since: 19 Jan 2005

Windows Workflow Foundation + Windows Presentation Foundation = Real-Time Strategy Game!?!

posted Tue 20 Jun 06
[The Best Rod Serling impression that I can muster]
There is a fifth dimension beyond those known to developers. It is a dimension vast as space and timeless as infinity. It is the middle ground between fun and productivity, between the pit of his fears and the summit of his knowledge. This is the dimension of imagination. It is an area called the WWF Fun Zone
[End Rod Serling]

So I was standing on the platform waiting for the train the other day when it hit  me - I can use WWF and WPF to provide the framework for tech/research trees in RTS games! I realize that some people might be hit with more interesting tidbits than this, but...you make the best out of what you've got. In case you aren't familiar with the genre, an RTS game is a Real-Time Strategy game. Typically, you are in control over a collection of units (be they an army, a horde of aliens, or a group of wizards and warriors). You tell these units what to do and they happily (most of the time) obey. They'll chop down wood, harvest resources from a mine, attack your enemy, build fortifications, or improve the landscape to make it more friendly to your army. What most of these games have in common is the notion of research. You can typically dedicate some resources (time, money, equipment, facilities, or even people/units) to research. There is often what is referred to as a 'tech tree': a hierarchical structure representing the technology that your people can acquire, and the order in which they can acquire it. For example, in a space-based RTS, one might research laser technology to gain access to improved ship-board weapons. Once laser technology has been acquired, you might then be able to branch off and either research more advanced ship-board lasers or invest time in developing land-based laser defense systems.

Let me describe it another way: you (the player) decide to invest resources. The result is you learn a new technology. Having learned that new technology, you now have a different list of available technologies that you can acquire. You repeat the process over and over again - until you have decided to stop learning, or have learned everything you can. This process absolutely screams workflow. There is an underlying workflow that is not in the foreground controlling all of your actions, but it does limit the actions you can take based on your current state within the workflow. This is an ideal candidate for a state machine workflow.

Typically, in a state machine workflow, each state receives a list of events. Each event can then transition the workflow from that state to another state. The difference here is that in an RTS "tech tree", the player can decide on any number of potential paths through the tech tree. We still want the state machine workflow to do the guiding, so what we can do is have every state respond to the same event (OnResearchCompleted). When a state with multiple potential states receives this event, a little Rule-Based Condition can be used to determine which state to progress into. For example, if the player has told the game that they are interested in progressing toward ship-based weapons, when the research cycle goes through an iteration, the workflow can move forward into the ship-based weapons state even though there are a number of potential states. I'm not sure if there is a term for this, but I'm going to call it selective state transition. By that, I mean that the state machine workflow can transition from state A to states B, C, or D based on a variable such as a user preference or affinity for a particular state.

For this particular blog entry, I am going to show you the workflow, the code that supports the workflow, and the Console application code that verifies that the workflow works properly. In my subsequent blog entries, I will develop the game a little further by supplying a WPF demo that pokes and prods the workflow in various ways.

First, the state machine workflow:
WWF RTS Tech-Tree

As you can see, there is an initial state where the workflow is waiting for research to begin. Every time the application / game completely finishes a research cycle, the workflow transitions from the current state to the next state. The next state is indicated either by a single possibility (a few of the states can only transition to 1 other state) or by a preference for the next state indicated by a custom event-args class passed in the OnResearchCompleted event called StrategyDataEventArgs using a property called NextResearchState.

In order to fire events into a state machine workflow, you need an External Data Exchange Service. Here is the interface I'm using (you must use an interface or the WWF designer will not be able to rig up event handlers) and the class implementation:
using System;
using System.Collections.Generic;
using System.Text;
using System.Workflow.Activities;

namespace Sample.StrategyGame.Workflow
{
    [System.Workflow.Activities.ExternalDataExchange]
    internal interface IStrategyExternalDataExchange
    {
        event EventHandler<StrategyDataEventArgs> OnResearchCompleted;
    }   
}

And here's the class implementation of IStrategyExternalDataExchange:
using System;
using System.Collections.Generic;
using System.Text;
using System.Workflow.Runtime;

namespace Sample.StrategyGame.Workflow
{
    [Serializable]
    public class StrategyExternalDataExchangeService : IStrategyExternalDataExchange
    {
        #region IStrategyExternalDataExchange Members

        public event EventHandler<StrategyDataEventArgs> OnResearchCompleted;

        #endregion

        public void CompleteResearch(Guid instanceId, string nextState)
        {
            if (OnResearchCompleted != null)
            {
                OnResearchCompleted(this, new StrategyDataEventArgs(instanceId, nextState));
            }
        }
    }
}


The important things to keep in mind here are:
  • The Workflow runtime will subscribe to any events indicated by your workflow when the exchange service is associated with the runtime.
  • Your data exchange service, and all data types exposed by it, must be marked as [Serializable]
With the workflow defined, and the exchange service rigged up, the only thing left to do is use a simple Console application to verify that the workflow does as it should. Next blog entry will have a far more appealing UI than the console app, but, the task of manually exercising a workflow through stub code is very valuable as it gives you the chance to debug your code, debug your workflow, and see if the results are what you intended.

Here is the code to the Console application harness:
#region Using directives

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Workflow.Runtime;
using System.Workflow.Runtime.Hosting;
using System.Workflow.Activities;

using Sample.StrategyGame.Workflow;

#endregion

namespace ConsoleHarness
{
    class Program
    {
        static void Main(string[] args)
        {
            using(WorkflowRuntime workflowRuntime = new WorkflowRuntime())
            {
                AutoResetEvent waitHandle = new AutoResetEvent(false);
                workflowRuntime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e) {waitHandle.Set();};
                workflowRuntime.WorkflowTerminated += delegate(object sender, WorkflowTerminatedEventArgs e)
                {
                    Console.WriteLine(e.Exception.Message);
                    waitHandle.Set();
                };

                workflowRuntime.Started += delegate(object sender, WorkflowRuntimeEventArgs e)
                {
                    Console.WriteLine("Workflow is started: " + e.IsStarted.ToString());
                };

                WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(Sample.StrategyGame.Workflow.ResearchWorkflow));
                ExternalDataExchangeService eds = new ExternalDataExchangeService();
                workflowRuntime.AddService(eds);

                StrategyExternalDataExchangeService stratService = new StrategyExternalDataExchangeService();
                eds.AddService(stratService);
              
                StateMachineWorkflowInstance smwi = new StateMachineWorkflowInstance(workflowRuntime, instance.InstanceId);               
               
                instance.Start();
               

                // sample progression : AwaitingResearch -> BasicWeaponry -> SideArms -> AssaultRifles
                smwi.SetState("AwaitingResearch");
                Thread.Sleep(300);
                Console.WriteLine("Research Progress: " + smwi.CurrentStateName);
                Console.WriteLine("Researching Weapons... Press enter to move on.");
                stratService.CompleteResearch(smwi.InstanceId, "BasicWeaponry");
                Console.ReadLine();

                Console.WriteLine("Research Progress: " + smwi.CurrentStateName);
                Console.WriteLine("Researching SideArms... Press enter to move on.");
                stratService.CompleteResearch(smwi.InstanceId, "SideArms");
                Console.ReadLine();

                Console.WriteLine("Research Progress: " + smwi.CurrentStateName);
                Console.WriteLine("Researching forward with no preference.... Press enter to move on.");
                stratService.CompleteResearch(smwi.InstanceId, string.Empty);
                Console.ReadLine();

                Console.WriteLine("Research Progress: " + smwi.CurrentStateName);
                Console.WriteLine("Press enter to finish the research workflow.");
                Console.ReadLine();
               
                smwi.SetState("ResearchComplete");               
                waitHandle.WaitOne();
            }
            Console.WriteLine("all done... press enter.");
            Console.ReadLine();
        }
       
    }
}


If you want, you can draw out your own state machine workflow and give this a whirl. Otherwise, once I put a somewhat useful GUI on the top of this workflow, I will probably make it available as a downloadable file. Stay tuned, because the next blog entry on this topic will turn this console harness into a WPF application as I move it in the ultimate direction of being something playable.

Another caveat: When testing your workflow - make sure you don't do so quickly. By that I mean it can be quite easy to signal an event to the workflow while it is still in transition from one state to another. Rather than waiting for the workflow to finish its current state transition, it attempts to handle the event immediately. This has the end result of firing an event that is not appropriate or not handled by the current state - and you get all kinds of weird error messages about message queues being disabled. The console harness uses Console.ReadLine() to allow the states to transition smoothly in the background so that when you send the event to the workflow, its being sent in a consistent state.

tags:                

links: digg this    del.icio.us    technorati    reddit

AddThis Social Bookmark Button




1. Charles left...
Tue 20 Jun 06 3:06 pm

Hi, Could you please describe your custom rule. I'm trying to dfo something similar in a different context and your approch seams very elegant. If you don't mind I would be interrested in playing with your code before your next post :) Charles


2. Christian Weyer left...
Wed 21 Jun 06 3:25 am :: http://blogs.thinktecture.com/cweyer

Great post! Do you have a code download?


3. Kevin Hoffman left...
Wed 21 Jun 06 8:48 am

Basically, for each situation where I need to test the value of the user's "research preference" (e.g. the preferred "next state" in the workflow) I have a Declarative Rule definition.

For example, I have a rule called "BasicWeaponry" that contains the following text:

this.StrategyEventArgs.NextState == "BasicWeaponry"

This gives me a nice boolean rule that I can use in a bunch of nested IfElse activities.


4. Rob Henry left...
Wed 18 Oct 06 3:38 pm

ROTFLMAO!!! Between Stirling and the gunporn this is funny!


5. Marc left...
Sat 21 Oct 06 1:54 pm

Have you actually got this working from WPF? I'm doing something very similar, but if I try to update a WPF UI following an external method call from WF I get an obscure error - "A first chance exception of type 'System.InvalidOperationException' occurred in WindowsBase.dll". This doesn't happen if I don't try and update UI so I'm assuming there's some threading issue between WF and WPF. Strangely, it works OK if instead of a UI call, I call a message box or something.


6. Hector left...
Wed 27 Jun 07 12:14 pm

I am trying to do the same thing but I keep getting a EventDeliveryFailedException error. For testing I only have 2 states. if I disable one of the event activitys then it works. But if I have 2 event activities listening on the same event then I get this error. EventDeliveryFailedException.


Tag Related Posts

WPF Control Development Unleashed

Wed 25 Mar 09 2:26 P GMT-05

My first day using Windows 7 Beta 1

Wed 25 Feb 09 1:58 P GMT-05

iPhone Underrated as a Gaming Device?

Fri 14 Mar 08 1:50 P GMT-05
tags:        

CLINQ v1 Demo - Network Message Filtering

Wed 09 Jan 08 7:47 P GMT-05
tags:        

Building a Ledger Style for WPF Grids

Tue 21 Aug 07 3:30 P GMT-05
tags:    

Continuous LINQ - Can I write games with it?

Mon 13 Aug 07 3:09 P GMT-05
tags:        

My 2008 Wishlist : A Transformers MMORPG

Tue 26 Jun 07 12:04 P GMT-05
tags:        

My first "Acropolis" Application

Mon 04 Jun 07 1:40 P GMT-05
tags:      

Exploring the Delegate Design Pattern

Mon 14 May 07 6:30 P GMT-05

Will Silverlight be DOA?

Mon 16 Apr 07 8:02 P GMT-05

Exploring the MVC Pattern in WPF

Tue 10 Apr 07 12:51 P GMT-05
tags:                      

WPF Bindings == WTF Bindings?

Mon 12 Mar 07 6:31 P GMT-05

On MUDs

Thu 08 Mar 07 5:00 A GMT-05
tags:                    

Cocoa Programming vs. WPF : NIB vs XAML

Tue 20 Feb 07 2:09 P GMT-05

Cocoa Bindings vs. WPF Binding

Thu 15 Feb 07 5:41 P GMT-05
tags:                

WPF Bumper Stickers

Tue 12 Dec 06 7:32 P GMT-05

Ulysses Agenda Makes Redmond Developer News

Wed 29 Nov 06 7:10 P GMT-05
tags:                

Is the continuous beta the new model for Vista?

Tue 21 Nov 06 8:51 P GMT-05
tags:              

A visit to Nintendo World on "Wii Day"

Mon 20 Nov 06 12:37 P GMT-05
tags:          

Ulysses Agenda - Network Engine Test 1

Mon 09 Oct 06 2:26 A GMT-05
tags:              

Ulysses Agenda : First Cut Networking Design

Thu 14 Sep 06 12:46 A GMT-05
tags:                  

First Impressions of Windows Vista RC1

Thu 07 Sep 06 1:30 P GMT-05
tags:                      

Localizing a WPF Application

Tue 22 Aug 06 11:39 A GMT-05
tags:            

WPF Slide Show and Photo Album

Fri 18 Aug 06 6:48 P GMT-05

Is Windows Workflow Foundation Too Complex?

Fri 18 Aug 06 12:15 P GMT-05