Diving into Scripting....

Hey guys! Long time no chat.

I’m building up my game engine and beginning to design my scripting system. This is more a theory question than anything else, as this will be the first major scripting system I’ve ever written (using Lua with libgdx, for the curious). I basically am going to (tentatively, pending suggestions) have a system that operates as follows for character class behavior:

  1. Classes are defined in individual .lua files loaded at runtime (i.e., bandit.lua, warrior.lua, necromancer.lua, etc.). These define things like what weapons are compatible, the class description, what abilities it learns, etc.
  2. Abilities are defined in their own individual .lua files also loaded at runtime and referenced from the aforementioned class files (i.e., steal.lua will define what actually takes place when a character utilizing a thief class steals, and so forth).

I look at that model and think hm, it seems like it would be a good idea to blend the two together and instead just have one .lua file for each class that encompasses the abilities as well. Though clean, there’s also the part of me that wouldn’t know how to encapsulate those moves properly, as the end goal is to have Lua call something like new Class() with the class script file and new Ability() with each ability so that the classes are generic enough to fully encapsulate themselves and their behavior, and having the class file also contain the logic for each ability would presumably make this difficult unless I incorporate some meta-based object-oriented programming to hold the information for each ability… this comes with the trade-off of readability and simplicity for someone like my partner to be helping with the scripting, who is more design-based than code-based. Also, having the moves separate allows other classes to reuse those same moves.

Typically, I wouldn’t necessarily approach scripting for something like this were my game to have on the order of 10-15 classes, but there are going to be (and don’t laugh) roughly 100 classes at this point, all of which my partner and I have outlined already. Being able to script these rather than hardcode them just makes sense, especially because each class will also have 5-10 abilities that are unique to it (though some may have shared moves as mentioned before, lending utility to the scripting approach).

There are other areas of the engine I intend to script (entity generation, behaviors, questing, weapon and spell effects), but I figured this would be a good place to begin in terms of learning how to do things. I would love hearing suggestions as to how to fine-tune or change this approach to be more optimized or cleaner, and if anyone has insight on how to factor or reapproach the aforementioned issue with separate script types, that would be great as well. Scripting is looking like a very exciting frontier for a dynamic engine, but there are many things to consider, it would seem. Thanks in advance!

Colton

I hate to say it, but scripting a Java game is usually overkill. Unless maybe you are developing the next World of Warcraft and you need scriptable plugins for your user-base that is in the hundreds. Java code compiles pretty darn fast. If you need data, e.g. for a game’s item database, it would be better to “keep it simple, stupid” and use JSON.

But maybe it would be better to invest your time in a GUI tool that allows you to write new classes. It would export to JSON or another format. Does each ability really need to be uniquely programmed? (Impressive that you even came up with that many unique abilities…)

You can use Java and reflection if you have hundreds of unique abilities. If you have a fixed number of moves (that have different properties, visuals, etc) then you could use another pattern, like Enums, to avoid the need for reflection.

public enum AttackCommands implements Command {
    SwordStab {
        CommandResult performAction(CommandEvent ev) {
            ... do some crazy logic that can't be expressed through JSON ...
        }
    },

   etc...
}

With that out of the way, scripting in Lua can still be pretty cool and fun to work with, even though it will just end up slowing your development down, introducing more bugs, worse performance, and making your game engine less attractive to other devs. I can’t really comment on best practices in Lua – just do whatever feels right for your team and leads to acceptable performance.

Thanks for the reply, davedes!

So, I love the idea of being able to use something like a GUI or whatnot to create any game asset really and have that exported as a script; I’ve run this through my head a few times, but JSON (or any pure-data file format) just is limited by the lack of logic, I feel. I love JSON as a tool for writing things that are simple, like items (though even items with functionality, like spawning things or turning you into a different creature, can get funky), and I use it all the time in my day-to-day non-game programming because of its utility and ubiquitousness, but things like class abilities that perform something that interacts with the game world and NPC scripting and whatnot feel impossible with it. My ideal goal would be to have all of this contained within one nice little pretty script file that, though not necessarily short for some cases, would at least be self-contained, dynamically loaded, and editable by others, not just me, so my engine doesn’t have to be recompiled every time I’d like to add some new functionality or the community wants to create their own complete spin-off using my engine. Does this seem like a plausible goal to you? Not only are these ~100 classes desirable, but also community-driven ones down the road, and not just classes but many things would be great, like user scripted random world and level generation, weapon generation, etc (all things I’ve worked on already and am incorporating into the game engine), though we can save that for another time; for now, just focused on classes and abilities! Thank you very much for your time; sorry for the verbosity!

Colton

I agree with @davedes

It sounds like you’re trying to program the engine in Java, and then the entire game in some scripting interface. You should delegate only as much as you need (for flexibility in terms of your game’s construct) to scripting. Going data-driven is the much better idea for game character classes. Maintaining 100 different class files (in scripts or in Java) will be a pain to manage and I can’t imagine a scenario where the code won’t be very redundant. So I would say it would be safe to do both, but with a much greater tendency to data-driven then script driven (avoid scripts all together if you can.)

Use scripts for things like NPC AI, Maps, Triggers etc (again, as much of the code inside of the engine exposing only what you need to keep it flexible in your games construct.) So for example, you might have npc1.moveTo(x,y); npc2.initiateDialogue(…); npc3.attack(…) etc, they should work at a fairly abstract level. Your game shouldn’t be written in scripts, however guiding the game with scripts is a good idea.

The Oracle JRE (6+) distributes with a jsr-223 JavaScript ScriptEngine, and you can plugin luaJ if you’re looking to use one under the same interface. OpenJRE doesn’t distribute with a JavaScript ScriptEngine, but can be compiled with one with extra configuration.

JevaEngine allows scripts to optionally be associated with entities to drive Entity logic, dialogue, and construct the UI on the server & client side.
jMonkeyEngine uses a scripting and XML data format for their UI and throughout their engine. So maybe take a look there for some ideas.

Thanks for the reply, Jeremy! I would love to have a strictly data-driven interface for loading classes, abilities, and so forth, which would make development in that part of the game like butter, but I just have a hard time mainly with the abilities. Here is a sample of the latest design of my class JSON file:


{
    "className" : "Bandit",
    "classId" : "bandit",
    "classDescription" : "A sly picker of pockets, gifted with the feet of a cat.",
    "classIcon" : "bandit.png",
    "effectiveWeapons" : ["daggers", "shortswords", "bows"],
    "ineffectiveWeapons" : ["axes", "maces", "hammers"],
    "effectiveArmor" : ["light"],
    "ineffectiveArmor" : ["heavy"],
    "abilities" : [
        {
            "name" : "Pickpocket",
            "id" : "pickpocket",
            "description" : "Deftly acquire that which belongs to someone else's pockets!",
            "type" : "move",
            "manaCost" : 0,
            "healthCost" : 0,
            "cooldown" : 3,
            "successChance" : [50, 55, 60]
        },
        {
            "name" : "Sneak",
            "id" : "sneak",
            "description" : "Render those around you oblivious of your subtle presence...",
            "type" : "move",
            "manaCost" : 0,
            "healthCost" : 0,
            "cooldown" : 5,
            "successChance" : 100
        }, 
        {
            "name" : "Deft Strike",
            "id" : "deftStrike",
            "description" : "Utilize your dexterity and cunning to cleverly and critically strike your opponent!",
            "type" : "move",
            "manaCost" : 10,
            "healthCost" : 0,
            "cooldown" : 5
            "successChance" : 100
        },
        {
            "name" : "Flee",
            "id" : "flee",
            "description" : "Gain an increase in running speed to dodge tricky situations!",
            "type" : "move",
            "manaCost" : 15,
            "healthCost" : 0,
            "cooldown" : 25,
            "successChance" : 100
        },
        {
            "name" : "Steal",
            "id" : "steal",
            "description" : "Strike your enemy with the chance to steal something of theirs!",
            "type" : "move",
            "manaCost" : 10,
            "healthCost" : 0,
            "cooldown" : 15,
            "successChance" : [50, 55, 60]
        },
        {
            "name" : "Dagger Mastery",
            "id" : "daggerMastery",
            "description" : "Improve your overall success with daggers!",
            "type" : "passive",
            "manaCost" : 0,
            "healthCost" : 0,
            "cooldown" : 0,
            "successChance" : 100
        },
        {
            "name" : "Quick Strike",
            "id" : "quickStrike",
            "description" : "Nimbly strike your opponent twice in rapid succession!",
            "type" : "move",
            "manaCost" : 20,
            "healthCost" : 0,
            "cooldown" : 10,
            "successChance" : 100
        },
        {
            "name" : "Leap",
            "id" : "leap",
            "description" : "Add extra juice to your jump to escape, aid dungeon crawling, or show off!",
            "type" : "move",
            "manaCost" : 10,
            "healthCost" : 0,
            "cooldown" : 5,
            "successChance" : 100
        }
    ],
}

It contains most of the foreseeably necessary data, but in something like Pickpocket, for example (were I to want to keep the abilities and the class collected together rather than splitting up the moves into separate files), I find it difficult to describe the flow of how the ability should proceed. I would want it to go something like:


if player.distanceToTarget(3):
   player.playAnimation("touch")
   player.stealItem("opponent", successChance)

It’s simplified in this example but still a reasonable estimate of what an ability may require. Were I to want a strictly data-driven approach (which again, I would love), how could this be accomplished utilizing such, ideally incorporating into the JSON file shown above? Thanks for your time!

In what exactly lies the problem ? I think Your json looks okay.

Only things I see are “distanceToTarget” is missing from the json. Animation can be described in a data-driven format also or already included in the package. Success chances are already described in the json and the opponent comes from the engine.

When you go totally data driven, you have one generic type which gets configured with data. This doesn’t work for your abilities, because they have different logic at the highest level.

My tip for you would be to implement all the different abilities on their own, but let them share some common interface. Each ability only has the parameter it needs, so all abilities are unique. JSON for those abilities could look like this:


"abilities" : [
        {
            "name" : "Pickpocket",
            "cooldown" : 3,
            "successChance" : [50, 55, 60]
        },
        {
            "name" : "Flee",
            "manaCost" : 15,
            "cooldown" : 25,
        },
}

Now the only thing you would need is some parsing utility. I would do it like this, extract all key/value pairs of the JSON and get some Builder by name. Give that builder the key/values and ask if he can build an ability from that data.

something like:


interface Ability{
  String getDescription();
}
class Pickpocket implements Ability{
  int cooldown;
  int[] successChance;
}
interface Builder{
  Ability build(Map<String, JsonValue> values) throws DataException;
}
class PickpocketBuilder implements Builder
{
    Ability build(Map<String, JsonValue> values) {
       Integer cd = values.get("cooldown").toInt();
       int[] ch = values.get("successChance").toArray();
       
       if(cd == null || ch == null)
         throw new DataException();
       else
         return new Pickpocket(cd, ch);
   }
}



Hi Danny,

Thanks for the reply. That’s not a bad idea, but it introduces the need to create a class still for every single ability I want to make, defeating my goal of being able to completely define these things as data files. The end goal is something a lot more akin to having a generic Ability class that can take in a list of Events that comprise it, which maybe could be tied with a list of Fields that are dynamic and basically object wrappers around names and values. I’m having a hard time actually writing out a concrete implementation base for this at the moment; any thoughts on how this could be accomplished? Thanks!

Colton

You need to think a little more abstractly. For example, Pickpocket is really just a “Steal Something From Target” action. Flee, Dagger Mastery and Leap are all “Change Player/Target Stats” action.

I would break it down like so. You have the following types, triggers and actions with the logic defined in Java:

Types
    Melee    //close-combat attack, needs a melee weapon in hand
    Range    //range attack, needs bow + arrows
    Spell    //a magical spell of some kind
    Generic  //could be used for Pickpocket, a generic Taunt, or a fast movement
    Passive  //does not need to be activated

Triggers
    Use     //on action use/click, for spells like Spawn, Pickpocket, etc.
    Attack  //on successful melee/range strike
    Block   //on successful block
    Evade   //on successful evade
    Death   //on player death
    etc...

Actions
    Effect   //generic effect to increase/decrease stats/health/etc
    Steal    //steals an item or attribute from the enemy target
    Hide     //invisibility effect
    Spawn    //spawns some minions
    etc...

Now you can describe almost all of your skills/spells/etc in a pure data format. Here are two examples to give you an idea:
http://pastie.org/8370747

The above uses YAML, but it could just as easily be JSON exported from a visual tool.

I vaguely started to babble about these kinds of systems here: http://www.java-gaming.org/topics/archtypes-composition-and-code-is-data/26554/view.html

davedes, that was a great example and fits the model I was trying to capture in my mind fantastically. Thank you very much! I will eagerly pursue fleshing it out.

Roquen, I will take a look at your article as well; from the first few paragraphs, it looks interesting! Thank you!

Colton

@davedes,

I went ahead and modified my entire list of abilities from before with this new model; does this look like it’s following yours well enough? Thank you very much for all your time and help!


"abilities" : {
        "pickpocket" : {
            "name" : "Pickpocket",
            "desc" : "Deftly acquire that which belongs to someone else's pockets!",
            "type" : "generic",
            "cooldown" : 3,
            "OnUse" : [
                {
                    "Steal" : {
                        "chance" : [25, 45],
                        "steals" : "randomItem"
                    }
                }
            ]
        },
        "sneak" : {
            "name" : "Sneak",
            "desc" : "Render those around you oblivious of your subtle presence...",
            "type" : "generic",
            "mana" : 5,
            "cooldown" : 5,
            "duration" : 30,
            "OnUse" : [
                {
                    "Effect" : {
                        "statIncrease" : {
                            "stat" : "sneak",
                            "amount" : ["25%", "30%", "35%"]
                        },
                        "target" : "self",
                        "duration" : "parent",
                    }
                }
            ]
        }, 
        "deftStrike" : {
            "name" : "Deft Strike",
            "desc" : "Utilize your dexterity and cunning to cleverly and critically strike your opponent!",
            "type" : "melee",
            "mana" : 10,
            "cooldown" : 5,
            "OnUse" : [
                {
                    "MeleeAttack" : {
                        "animation" : "fastSingleAttack",
                        "vfx" : [],
                        "bonusDamage" : ["20%", "25%", "30%"]
                    }
                }
            ]
        },
        "flee" : {
            "name" : "Flee",
            "desc" : "Gain an increase in running speed to dodge tricky situations!",
            "type" : "generic",
            "mana" : 15,
            "cooldown" : 25,
            "duration" : 15,
            "OnUse" : [
                {
                    "Effect" : {
                        "StatIncrease" : {
                            "stat" : "speed",
                            "amount" : ["30%", "35%", "40%"]
                        }
                    }
                }
            ]
        },
        "mug" : {
            "name" : "Mug",
            "desc" : "Strike your enemy with the chance to steal something of theirs!",
            "type" : "generic",
            "mana" : 10,
            "duration" : 60,
            "cooldown" : 15,
            "OnAttack" : [
                {
                    "Steal" : {
                        "steals" : "random_item",
                        "chance" : ["40%", "45%", "50%"]
                    }
                }
            ]
        },
        "daggerMastery" : {
            "name" : "Dagger Mastery",
            "desc" : "Improve your overall success with daggers!",
            "type" : "passive",
            "OnUnlock" : [
                {
                    "SkillIncrease" : {
                        "skill" : "daggers",
                        "amount" : [5, 10, 15]
                    }
                }
            ]
        },
        "quickStrike" : {
            "name" : "Quick Strike",
            "desc" : "Nimbly strike your opponent twice in rapid succession!",
            "type" : "melee",
            "mana" : 20,
            "cooldown" : 10,
            "OnUse" : [
                {
                    "MeleeAttack" : {
                        "animation" : "fastDoubleAttack",
                        "vfx" : ["blurPlayer"],
                        "hits" : {
                            "frames" : [2, 4]
                        }
                    }
                }
            ]
        },
        "leap" : {
            "name" : "Leap",
            "desc" : "Add extra juice to your jump to escape, aid dungeon crawling, or show off!",
            "type" : "generic",
            "mana" : 10,
            "cooldown" : 5,
            "duration" : 15,
            "OnUse" : [
                {
                    "Effect" : {
                        "statIncrease" : "jumpHeight",
                        "amount" : [3, 4, 5],
                    }
                }
            ]
        }
    }

Are you hand-writing the JSON? JSON is not really that easy to hand-write, and doesn’t include comments. I think YAML is a better fit if you plan to write it all by hand; it leads to better readability and ultimately less human error.

I think your new JSON looks more reasonable, and doesn’t need any scripting languages. :wink:

Keep in mind you will probably want to use something very similar for items and weapons, to define its attributes and events (like 5% mana steal on hit, or 10% chance of going invisible on block).

davedes,

Yes, I do plan on hand-writing the JSON, and I also really love the look of YAML (I program in Python often for my actual programming job). Do you have a preferred YAML parser? Also, thanks again for your help in figuring out a viable data-based alternative to scripting; this is what I’ve truly wanted from the get go rather than having any scripting, but it didn’t seem viable until put in perspective.

Also, yes, I would love to keep the format as uniform as possible amongst the different scripts throughout my engine, as I intend to use this as a means of user-generated content in any domain I can find good reason for.