views:

105

answers:

3

Hello, I am currently developing a 3D Action/RPG game in C++, and I would like some advice in choosing a scripting language to program the AI of the game. My team comes from a modding background, and in fact we are still finishing work on a mod of the game Gothic. In that game (which we also got our inspiration from) the language DAEDALUS (created by Piranha Bytes, the makers of the game) is used. Here is a full description of said language.

The main thing to notice about this is that it uses instances moreso than classes. The game engine is closed, and so one can only guess about the internal implementation of this language, but the main thing I am looking for in a scripting language (which ideally would be quite similar but preferably also more powerful than DAEDALUS) is the fact that there are de facto 3 'separations' of classes - ie classes, instances and (instances of instances?).

I think it will be easier to understand what I want if I provide an example. Take a regular NPC. First of all you have a class defined which (I understand) mirrors the (class or structure) inside the engine:

CLASS C_NPC 
{
    VAR INT     id                              ;       //  absolute ID des NPCs
    VAR STRING  name            [5]             ;       //  Namen des NPC
    VAR STRING  slot                            ;           
    VAR INT     npcType                         ;           
    VAR INT     flags                           ;           
    VAR INT     attribute       [ATR_INDEX_MAX] ;           
    VAR INT     protection      [PROT_INDEX_MAX];           
    VAR INT     damage          [DAM_INDEX_MAX] ;           
    VAR INT     damagetype                      ;
    VAR INT     guild,level                     ;           

    VAR FUNC    mission         [MAX_MISSIONS]  ;           
    var INT     fight_tactic                    ;           
    VAR INT     weapon                          ;           

    VAR INT     voice                           ;           
    VAR INT     voicePitch                      ;           
    VAR INT     bodymass                        ;           

    VAR FUNC    daily_routine                   ;       //  Tagesablauf
    VAR FUNC    start_aistate                   ;       //  Zustandsgesteuert

    // **********************                   
    // Spawn                                    
    // **********************                   
    VAR STRING  spawnPoint                      ;       //  Beim Tod, wo respawnen ?
    VAR INT     spawnDelay                      ;       //  Mit Delay in (Echtzeit)-Sekunden

    // **********************                   
    // SENSES                                   
    // **********************                   
    VAR INT     senses                          ;       //  Sinne
    VAR INT     senses_range                    ;       //  Reichweite der Sinne in cm

    // **********************                   
    // Feel free to use                         
    // **********************                   
    VAR INT     aivar           [50]            ;                       
    VAR STRING  wp                              ;           

    // **********************                   
    // Experience dependant                     
    // **********************                   
    VAR INT     exp                             ;       // EXerience Points
    VAR INT     exp_next                        ;       // EXerience Points needed to advance to next level
    VAR INT     lp                              ;       // Learn Points     
};

Then, you can also define prototypes (which set some default values). But how you actually define an NPC is like this:

instance BAU_900_Ricelord (Npc_Default) //Inherit from prototype Npc_Default
{
    //-------- primary data --------

    name        =   "Ryzowy Ksiaze";
    npctype     =   NPCTYPE_GUARD;  
    guild       =   GIL_BAU;      
    level       =   10;
    voice       =   12;
    id          =   900; 

    //-------- abilities --------
    attribute[ATR_STRENGTH]     = 50;
    attribute[ATR_DEXTERITY]    = 10;
    attribute[ATR_MANA_MAX]     = 0;
    attribute[ATR_MANA]         = 0;
    attribute[ATR_HITPOINTS_MAX]= 170;
    attribute[ATR_HITPOINTS]    = 170;

    //-------- visuals --------
    //              animations
    Mdl_SetVisual       (self,"HUMANS.MDS");
    Mdl_ApplyOverlayMds (self,"Humans_Arrogance.mds");
    Mdl_ApplyOverlayMds (self,"HUMANS_DZIDA.MDS");
    //          body mesh     ,bdytex,skin,head mesh     ,headtex,teethtex,ruestung 
    Mdl_SetVisualBody (self,"Hum_Body_CookSmith",1,1,"Hum_Head_FatBald",91 ,  0,-1);

    B_Scale (self); 
    Mdl_SetModelFatness(self,2);

    fight_tactic    =   FAI_HUMAN_STRONG;

    //-------- Talente --------                                    
    Npc_SetTalentSkill  (self,NPC_TALENT_1H,1); 


    //-------- inventory --------                                    

        CreateInvItems (self, ItFoRice,10);
        CreateInvItem (self, ItFoWine);
        CreateInvItems(self, ItMiNugget,40);
        EquipItem  (self, Heerscherstab); 

        EquipItem  (self, MOD_AMULETTDESREISLORDS); 

        CreateInvItem (self, ItMi_Alchemy_Moleratlubric_01);
        //CreateInvItem (self,ItKey_RB_01);

        EquipItem (self, Ring_des_Lebens);

    //-------------Daily Routine-------------
    daily_routine = Rtn_start_900;

};

FUNC VOID Rtn_start_900 ()
{
    TA_Boss         (07,00,20,00,"NC_RICELORD");
    TA_SitAround    (20,00,24,00,"NC_RICELORD_SIT");
    TA_Sleep        (24,00,07,00,"NC_RICEBUNKER_10");
};

As you can see, the instance declaration is more like a constructor function, setting values and calling functions from within. This still wouldn't pose THAT much of a problem, if not for one more thing: multiple copies of this instance. For example, you can spawn multiple BAU_900_Ricelord's, and each of them keeps track of its own AI state, hitpoints etc.

Now I think the instances are represented as ints (maybe even as the id of the NPC) inside the engine, as whenever (inside the script) you use the expression BAU_900_Ricelord it can be only assigned to an int variable, and most functions that operate on NPCs take that int value. However to directly modify its hitpoints etc you have to do something like var C_NPC npc = GetNPC(Bau_900_Ricelord); npc.attribute[ATR_HITPOINTS] = 10; ie get the actual C_NPC object that represents it.

To finally recap - is it possible to get this kind of behaviour in any scripting languages you know of, or am I stuck with having to make my own? Or maybe there is an even better way of representing NPC's and their behaviours that way. The IDEAL language for scripting for me would be C#, as I simply adore that language, but somehow I doubt it is possible or indeed feasible to try and implement a similar kind of behaviour in C#.

Many thanks

+3  A: 

C# can be used as a scripting language.

Apart from c#, lua is very popular as a game scripting language

Your npc example could be solved as following:

  • Create a baseclass npc
  • inherit the npc class to create a custom npc with specific characteristics/behaviours
  • create instances of the inherited class
Toad
+1 to LUA. It is easy and efficient, I'd go for it.
Ekin Koc
Yes, but see I don't want to inherit the NPC class, as every NPC is a copy of the same NPC class, it only has different values. What I think I would have to do is say: (pseudo-pseudo-code) `NPC npc = new NPC(); npc.name = "Random Guy"; NpcMgr.AddNpc(npc, "RndGuy");`and then spawning him in multiple instances? Hmm, maybe it is just a case of good management code, but I still feel it is redundant to do it that way (with all the `npc.` and storing it in a variable before copying it to the manager...) `NPC RndGuy{ name = "Random Guy" }`etc just seems so easier...
Radius
You could create static functions in your npc class which return a prefilled npc. E.g. Static npc CreateBadGuy() { npc baddy = new npc(); baddy.name = "bad guy"; baddy.score = 100; etc etc. Return baddy; }
Toad
I guess, but see the thing is, there is going to be hundreds of these NPCs, and also I do need a way of identifying them unambiguously for the dialogue scripts, eg `Dialogue RndGuy_Hello { name = "Hi, what's up?; npc_id = 1; conidtion = RndGuy_Hello_Cond; talk = RndGuy_Hello_Talk;} bool RndGuy_Hello_Cond() {return canWeTalkToRndGuy;} void RndGuy_Hello_Talk() {self.Say("Hi, what's up?"); other.Say("Get lost!");}`
Radius
I guess what I really need is a way to "override" the constructor of each object, wouldn't that be possible with say reflection? Is it possible to achieve that with lua?
Radius
What you are describing is classic class inheritance. The base class npc has a virtual method called talk(). Class rndguy inherits npc and implements the method talk. In your mainloop you just have a bunch of npc's. And you can potentially call the hello method on all npc's. It's only when you happen to have a rndguy instance that the hello method actually does something.
Toad
No, maybe I'm not clear enough, but I don't want to create a separate class for any separate npcs, as they are all instances of the same class. All their methods and properties are the same. The only thing that differs is the values of the properties. So what I want to be able to do is to assign a name (and later access) to a specific configuration of an object (ie values of all properties). This could be easily done of course by simply creating instances of the npc class and setting values, but I need to be able to separate the fact that maybe two different npcs have the same values (ctd.)
Radius
(name, hitpoints) but yet are two different npcs, while I could spawn two instances of the same NPC, and they could have different hitpoints etc (say one got damaged) and yet they know they are THAT npc, so say when I call `GetNPCatLocation(npc1location) == GetNPCatLocation(npc2location)` I get true, even though the objects are different (but are both of type NPC, but say have different values for fields). Maybe I could implement some id field that would be compared?
Radius
okay. If all the properties are shared, but you do want to differentiate, add an enumerated type: enum ObjectType { RndGuy=1, StrongGuy=2,SmartGuy=3, etc}; and add this to your npc class: class npc { ObjectType type; ..... } In any of you code you can now switch on the actual objecttype of an npc: switch(somenpc.type) { case RndGuy: DoSomethingSpecial(); break; etc etc In this scenario I would still recommend static functions which initialise the NPC's you have (Including the ObjectType)
Toad
If you find yourself, adding lot's of code like: switch(somenpc.type) { case RndGuy: SayHello(somenpc); break; ......}You could say that RndGuy is an NPC which implements the Hello functionality, while others don't. Again, this is classic inheritance. So even though you might still think in terms of your old script language, it might be worthwhile to keep an eye open for implementing things how other (more common) languages it might do.
Toad
The whole point of having this as a script is to not hardcode any npcs or anything like that into the engine. Also this is not a case of having say 70 differenct types of enemies that differ in AI. This is a case of having in the vicinity of 500, maybe 600 NPC's who differ in name, dialogue, looks etc, and have basically similar AI.
Radius
I think I will end up having to create my own language and parser :/ thanks anyway though, and I might look into Lua.
Radius
@radius instead of writing a full fledged parser and vm for some new language which is both difficult and laborious, you could perhaps design a small 'dsl'(domain specific language) just for the initialization of the npc's. The parser you can make in either c# or lua. This way only a small part (init and declaration of your 600 npc's) is done in your specific manner, while all other language features (the hard part) is already done
Toad
A snice cript like language for .net is called Boo. http://boo.codehaus.org/. It happens to very suitable for allowing very small dsl's from within the language. So basically it is like extending the compiler to allow for your own syntactic additions. A book (from a really bright author) just came out on the very topic: http://www.manning.com/rahien/. But if you search for dsl boo in google you'll find blog posts on it as well
Toad
thanks, i'll give that a try
Radius
+1  A: 

I'd go with Javascript. I'm working on a game engine myself that implements the V8 javascript engine used in Chrome. It's super easy to implement and Javascript is a very powerful language for that sort of thing, being entirely prototype-based.

Stephen Belanger
thanks! that idea would never occur to me! I'll check that out
Radius
Feel free to have a look at the code for my engine here;http://github.com/qard/jsgameAt the moment, it's basically just directly wrapping OpenGL, GLU, GLUT and OpenAL. It stores each library in separate global objects, so it doesn't pollute Global too much.
Stephen Belanger
+1  A: 

I think that whereas you are hoping for 3 levels of class/instance supported by the language, it helps if you actually have just 1, and build everything from there.

Static languages typically have 2 levels - the 'class', defined at compile-time, and the 'instance', created from the class at run-time. This makes a lot of sense given that it's practical to know exactly how many classes you want up-front, and usually impractical to know exactly how many instances. Classes become the cookie-cutter and instances are the cookies.

But ultimately, set aside the notion of classes and instances being totally different and consider that a class is a Thing that (a) allows you to create new Things and (b) serves as a central reference for how those new Things should act. Those Things in turn could also create new Things, and so on.

In languages like C++ and Java, we normally perform (a) by allocating a block of memory with a set of properties defined by the class constructor, and (b) by maintaining some reference back to the class's methods (eg. via a v-table). But you could just copy an object, including all the method references within it, one of which would be the constructor for that object's 'instances'. This is prototype-based programming and provides a very simple inheritance model in that your 'classes' are just the same as any other object, except that they provide a way to create new objects that count as instances.

It just so happens that the world's most popular scripting language - possibly the world's most popular language - gives you prototype inheritance. Javascript is what I am referring to and it's a pretty good choice for embedding in a game, with the benefit of being widely known by developers and being very actively worked on today. There are many tutorials online, but one covering various inheritance approaches is here. It can take a while to get used to, especially if you're coming from a traditional OO background with a clear class/instance division, but it's well worth learning the differences and evaluating the pros and cons for yourself.

Kylotan
Great! this is just what I've been looking for! Thanks!
Radius