Creating Co-routines for use in scripts (C#)

Started by
2 comments, last by Telastyn 10 years, 10 months ago

I am attempting to implement co-routines in C# in order to make my life easier when it comes to scripting enemy behavior in my game. The game is fixed framerate. What would be an ideal syntax for writing the scripts would be:


wait(60)
while(true)
{
  shootAtPlayer();
  wait(30);
}
 

Which means wait 60 frames, shoot at player, wait 30, shoot at player, wait 30... e.t.c.

I have sort of implemented this solution using yield return in C#:


public IEnumerable update()
{
  yield return 60;
  while(true)
  {
    shootAtPlayer();
    yield return 30;
  }
}

The caller keeps a stack of alive routines (0 on the IEnumerable counter) and sleeping routines ( > 0). Every frame the caller reduces the sleeping frame counter of each sleeping routine by 1 until one reaches 0. This routine is made alive again and continues execution until the next yield return. This works fine in most cases but it is annoying that the subroutine cannot be split up like below:


public IEnumerable update()
{
  yield return 60;
  while(true)
  {
    doSomeLogic()
    yield return 30;
  }
}

public IEnumerable doSomeLogic()
{
  somethingHappening();
  yield return 100;
  somethingElseHappens();
}

The above syntax is incorrect as yield return cannot be nested within another method meaning the execution state can only ever be maintained in a single method (the update() in this case). This is a limitation of the yield return statement and I couldn't really find a way to work around it.

I am now trying to implement the same thing using the C# async await keyword, but I couldn't get my head around how to implement a way to cause a wait for x frames before continuing. I saw ways to get the tasks to sleep for x frames but not a way to easily control specific waiting for frames from the caller. Can anyone give me some pointers on how to implement functionally like what I want above using await?

Advertisement

It is much more effort, but game scripts are best when they run independent from the main game.

Calls made by scripts can then be handled differently if they need to yield. For example, you can have a script say Actor.PlayAnimation(...) or Simulator.Sleep(...), and from the scriptwriter view it happens correctly.

On the game engine side, you need to recognize that the calls need to suspend the script until the conditions are met.

If you are writing your scripts directly into the game, you'll instead probably be better off having the scripts implement themselves in terms of a simple state machine. They run a tiny bit of work and save their state.

The plan was to be able to write the scripts independently from the main game and have them be dynamically compiled + loaded from the running game editor so I can modify them easily without having the entire game recompile. This to me seems advantageous as I have the help of the IDE and debugging through the scripts is easier.

The await keyword seems to fit my needs, it is supposed to be able to hold the state of a method until a condition is satisfied, I can't seem to figure out how to work it in though. I have tried something like this, but I think I am using this incorrectly:


        public static void Main(String[] args)
        {
            Task task = tick();
            while (true)
            {
                Thread.Sleep(16);
                task.Start();
            }
        }

        public static async Task tick()
        {
            while (true)
            {
                await wait(600);
                Console.WriteLine("tick");
            }
        }
        protected static async Task wait(int frames)
        {
            for (int i = 0; i < frames; i++)
            {
                await TaskEx.Yield();
            }
        }
Instead of just calling the sub function, you will likely need to iterate the sub function and yield its results in turn. How to line that up depends on the mechanics of what you expect.

This topic is closed to new replies.

Advertisement