Cameron Albert

Ramblings of software development, games and technology.

Scripting for Games

Scripting in games is not a new concept, most games do this on some level. Scripting allows game objects to execute code that is not compiled as part of the object. In the realm of roe playing games scripting gives objects the ability to react to the world around them. For instance, an object could execute script whenever a player gets near it, allowing a vender to hawk their wares, creatures to attack, etc. There are a variety of scripting languages currently being implement in games such as Lua and Python. Recently I have been researching these two for possible addition to Perenthia to provide me the ability to add custom behavior to objects and NPCs.

Edit: Thanks to some helpful insight and information from Michael Foord I was able to get an IronPython sample working. The sample adds scripting support to an object; the code is very basic but accomplishes the task. Thanks Michael!

Here is a snippet of a console app using the latest IronPython Beta4:

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Scripting;

using IronPython;
using IronPython.Hosting;
using IronPython.Runtime;
using IronPython.Runtime.Exceptions;

using Microsoft.Scripting;
using Microsoft.Scripting.Hosting;

namespace IronPythonGameScripting
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                Actor player = new Actor("Cam");
                Actor mob = new Actor("Mob");
                mob.Script = @"class Mob:
    def onEncounter(self, *value):
        value[0].Name = ""Mr Groovy""";

                Console.WriteLine("Player name was '{0}'", player.Name);

                mob.Execute(Actor.EVENT_ON_ENCOUNTER, player);

                Console.WriteLine("Player name is now '{0}'", player.Name);

                mob.Execute("Test", player);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
            finally
            {
                Console.WriteLine("Press any key to exit...");
                Console.ReadKey();
            }
        }
    }

    public static class Engine
    {
        private static ScriptEngine _engine;
        private static ScriptScope _scope;

        static Engine()
        {
            _engine = PythonEngine.CurrentEngine;
            _scope = _engine.CreateScope();
        }

        public static void Initialize()
        {
        }

        public static void Execute(string script)
        {
            ScriptSource source = _engine.CreateScriptSourceFromString(script, SourceCodeKind.Statements);
            source.Execute(_scope);
        }

        public static void Execute(string script, string className, string methodName, params object[] args)
        {
            ObjectOperations ops = _engine.Operations;
            ScriptSource source = _engine.CreateScriptSourceFromString(script, SourceCodeKind.Statements);
            source.Execute(_scope);

            if (_scope.ContainsVariable(className))
            {
                object @class = _scope.GetVariable(className);
                object instance = ops.Call(@class);
                if (instance != null)
                {
                    if (ops.ContainsMember(instance, methodName))
                    {
                        object method = ops.GetMember(instance, methodName);
                        ops.Call(method, args);
                    }
                }
            }
        }
    }

    public class Actor
    {
        public const string EVENT_ON_ENCOUNTER = "onEncounter";

        public string Name { get; set; }
        public string Script { get; set; }

        public Actor(string name)
        {
            this.Name = name;
        }

        public void Execute(string methodName, params object[] args)
        {
            Engine.Execute(this.Script, "Mob", methodName, args);
        }
    }
}

 

As it turns out I was attempting to use the IronPython libraries compiled against the Silverlight framework rather than the libraries compiled against the normal .NET framework. I was also using an older beta 1 version of the IronPython library which may have attributed to the exceptions I was seeing. Got to make sure you reference the correct assemblies when working in both .NET and Silverlight. Surprised this is the first time I’ve done this…

I reviewed IronPython because it has Silverlight support with the DLR but it is really designed to be used instead of C# not as a scripting language executed from within C#. With Lua though I may be able to get it working although it still seems like everything was designed to simply execute the game in lua scripts using C# as the core instead of augmenting C# classes.

I really want to just be able to raise events on the mobile objects and have instances handle those events in a custom way. I certainly don’t want to compile C# on the fly either as that has its own set of issues. Compiling C# on the fly is like running an exe for each script that is executed and would be way too costly in both execution time to compile and memory consumption.

Also, since both the client and server will need to execute scripts the scripting language needs to work in both Silverlight and ASP.NET.

Perenthia currently handles commands using a Dictionary<int, Command> where the int is command value, from a list of constants, and the Command is a method pointer (delegate). When the game loads up the first time the list of commands is constructed and the methods pointers are set to methods that related to the command. An example would be a chat command. When the command comes in and is validated the method pointer is found using this dictionary and then executed in the context of the connected player. I was thinking of doing the same thing with mobiles and the actions they can take and respond to based on the world around them. It is a bit more complex with mobiles than commands since commands do a specific thing whereas every mobile could execute a different block of code for a given event. I still have some work to do on it but this will probably be what I use in place of scripting. I loose the flexibility of writing quick scripts, changing them on the fly, etc. but gain the ability to have it work within my code framework, rely on only .NET and work in both Silverlight and ASP.NET.

About these ads

Written by Cameron

August 26, 2008 at 1:02 am

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: