|
If you have read enough of my blog entries, you know that when I encounter a new technology, the first thing I ask is, "That's great, but can I game with it?". The answer to WF is of course, yes.
The basic idea is that NPCs will be instances of workflows managed by a central workflow runtime within the Universe Server. The Universe Server is the server-side portion of UA that drives all of the central state management as well as manages all NPCs and player-persisted data. Each of these NPC workflows will be a state machine. I would have had figures, but my blog software made the "upload" button dissappear on my file space. So, if I had a picture, imagine that in this picture, you saw an initial state. From this initial state, the NPC immediately drops into a state called Patrolling. There's also an end state and possibly a couple other states like Combat and Corpse. The Corpse state is awesome because what I can do is have the hull wreckage still visible (still broadcasting radar pings), but then after a set delay like 30 seconds, I can go to the workflow termination state - at which point the instance dissappears, the memory goes away, and the NPC stops broadcasting radar pings because the workflow isn't running. All of this without having to clog up the main server itself.
I have a state initialization activity on the Patrolling state. As soon as the NPC enters this state, I have an activity that I coded called LaunchWFActivity (should probably call it LaunchPatrolWorkflowActivity now that I think about it... that's what I get for starting with an unrelated proof of concept) that is invoked. This activity launches a sequential workflow called Patrol (stored in Patrol.xoml). Here's the code for the launching activity (again, the naming is poor):
using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Collections;
using System.Drawing;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Design;
using System.Workflow.ComponentModel.Compiler;
using System.Workflow.ComponentModel.Serialization;
using System.Workflow.Runtime;
using System.Workflow.Activities;
using System.Workflow.Activities.Rules;
namespace NPCWorkflowTest
{
public partial class LaunchWFActivity: SequenceActivity
{
public LaunchWFActivity()
{
InitializeComponent();
}
public static DependencyProperty LaunchedWFGuidProperty =
System.Workflow.ComponentModel.DependencyProperty.Register(
"LaunchedWFGuid", typeof(Guid), typeof(LaunchWFActivity));
[Description("")]
[Category("")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public Guid LaunchedWFGuid
{
get
{
return ((Guid)(base.GetValue(LaunchWFActivity.LaunchedWFGuidProperty)));
}
set
{
base.SetValue(LaunchWFActivity.LaunchedWFGuidProperty, value);
}
}
protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
IStartWorkflow startWorkflow =
executionContext.GetService(typeof(IStartWorkflow)) as IStartWorkflow;
LaunchedWFGuid = startWorkflow.StartWorkflow(typeof(Patrol), null);
Console.WriteLine("Launched patrol WF with Guid {0}", LaunchedWFGuid.ToString());
return ActivityExecutionStatus.Closed;
}
}
}
Something that will prove to be crucial is that I hit the "Promote Bindable Properties" hotlink in the quick tasks panel that shows up below the activity properties when looking at the LaunchWFActivity item in the designer. This makes it so that I can bind the accompanying ShutDownWFActivity (which, of course, should also be renamed to ShutdownPatrolWorkflowActivity) so that it knows what GUID to shut down.
I also need a service that performs the actual shutdown. The model here is that the shutdown activity will grab the (not a) instance of the shutdown service from the execution context and then use the shutdown service to kill the (data-bound) appropriate GUID. Here's the code for the shutdown service (I cut out the using directives, they're the same as the previous code sample):
namespace NPCWorkflowTest
{
public class WFShutDownService : WorkflowRuntimeService
{
public void ShutDownWorkflow(Guid workflowGuidtoShutdown)
{
WorkflowInstance wi = this.Runtime.GetWorkflow(workflowGuidtoShutdown);
wi.Terminate("normal shutdown");
}
}
}
And finally, here's the code from the shutdown activity (which should be called ShutdownPatrolWorkflowActivity):
namespace NPCWorkflowTest
{
public partial class ShutdownWFActivity : Activity
{
public static DependencyProperty WFToShutDownGuidProperty =
System.Workflow.ComponentModel.DependencyProperty.Register("WFToShutDownGuid", typeof(Guid),
typeof(ShutdownWFActivity));
[Description("")]
[Category("")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public Guid WFToShutDownGuid
{
get
{
return ((Guid)base.GetValue(ShutdownWFActivity.WFToShutDownGuidProperty));
}
set
{
base.SetValue(ShutdownWFActivity.WFToShutDownGuidProperty, value);
}
}
protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
WFShutDownService shutdownService = executionContext.GetService<WFShutDownService>();
shutdownService.ShutDownWorkflow(this.WFToShutDownGuid);
return ActivityExecutionStatus.Closed;
}
}
}
In order for this to work, you have to make absolutely sure that you bind the WFToShutDownGuid property to the promoted property fed by the LaunchedWFGuid property from the launch activity. You can have as many child workflows running as you want, that can be interrupted by external events. For example, my implementation uses the ShutdownWFActivity activity to shut down the patrol activity when the state machine workflow as a whole (the NPC) is attacked.
So let's review: I've illustrated by example of NPC behavorial simulation that you can create state machine workflows that spawn sequential workflows when certain states are entered or even when certain events are signalled into the workflow by outside stimuli. You can also dynamically shut down these workflows in response to additional stimuli.
Where am I going with this?:
I'd love to hear comments about this system from WF enthusiasts or even WF critics.
p.s. I also want to thank Serge for a lot of help with the syntax. Now that I look at how its implemented, it seems only natural. But staring at a blank workflow surface trying to figure this out with no samples (that I could find) on the web was daunting.
Great ideas! Bennage and I have implemented similar concepts for
controlling our NPCs in Empyrean. We have rehosted the designer to allow
an Admin to create NPCs out of "personality components". Some of the
things you are doing are slightly different than us, but I like the
direction you are going. I think you are on your way to having a great
game. Count me in when you are ready to beta test.