[GD-General] Scripting
Brought to you by:
vexxed72
|
From: Brian H. <bri...@py...> - 2002-12-06 22:33:47
|
> I've been wrestling with the idea of scripting for years.
I think that's one of my problems with scripting, in general. It comes
off as a solution looking for a problem. I'm not saying that scripting
is intrinsically crappy or bad or wrong, but in many cases I've found
that it's implemented because the developers feel an obligation to have
some kind of scripting, even if they don't know why.
If you enumerate the legitimate reasons that scripting is implemented, I
would bet it's something like this:
- End user modifications that are safe (sandboxed VM), portable and
free to develop
- Ease of extensibility for artists/designers
- Faster prototyping/development because the language is more dynamic
and doesn't require rebuild
- Rich data definition language
So looking at the above list, if you don't need end user mods, then the
first group is just out. Note that you can also implement sandboxed,
portable and safe mods using a regular off the shelf language --
Quake3's VM system with the retargeted lcc works just like that.
The "ease of extensability" I think is overblown. There's a common
misconception that script languages are somehow more approachable to
non-programmers than regular compiled languages, which I find to be
hogwash. Sure, C can be pretty arcane syntactically, but I dare someone
to tell me with a straight face that Scheme or Python or Perl are
intrinsically easier to use than traditional compiled languages. Not
only that, but C can be made fairly readable by using appropriate
typedefs, macros and presenting a small, constrained API for the
designer/artist -- if they have to do pointer arithmetic, sure, there
are problems, but if they do everything like this:
MAKEMODEL( myGunModel, "gun.mdl" );
MAKEENTITY( myGunEntity, myGunModel );
placeEntity( myGunEntity, 10, 100, 20 );
instead of like this:
model_t *gun = malloc( sizeof( model_t ) );
entity_t *ent = malloc( sizeof( entity_t ) );
if ( !loadModel( gun, "gun.mdl" ) )
{
free( gun ); free( ent );
return 0;
}
if ( !createEntity( ent, gun ) )
{
free( gun ); free( ent );
return 0;
}
placeEntity( ent, 10, 100, 20 );
They're both "C", but one is obviously going to be a little easier to
grasp than the other for a non-programmer.
The faster prototyping comes at a cost, which is usually a lack of type
checking and significant run-time penalties. The faster you can develop
new code by just creating new types and messages on the fly, the less
robust your run-time environment will be. Discipline fixes this
somewhat, but if you're not going to require type checking of arguments
to functions (for examples), you're asking for trouble. Languages that
have traditionally done this are definitely less stable. If you expect
'x' to respond to the 'doSomething' message and it has no clue what that
is, ka-boom.
Now, is this really what you want in a shipped game? Is this what you
want in a persistent universe? I don't think so, personally.
Of course, if your current project has 16 programmers and requires 1+
hour to do a full rebuild, then maybe scripting simply is required,
period, to make development even feasible. But then it's a case of
scripting not being inherently useful, only that scripting is a band aid
to another broken part of the development process.
The rich data description I think is a good use of scripting, since you
can have conditional data. But that's typically using just a small
subset of a script language, and I tend to think of it more as data
definition than as scripting. And in many cases, you can avoid this by
simply making it more data driven (which, granted, incurs its own set of
headaches, but is eminently doable).
For example, if a certain door can only be unlocked by a red key, then
you can either drive this with pure data:
finalDoor = {
type = door,
key = redKey
};
or you can do some scripting thing, which feels "niftier", but I'm not
sure if it's actually that much more powerful. It has the potential to
be far more expressive, but whether that's actually leveraged is often
up in the air. The ability to do something doesn't always translate to
doing something:
finalDoor inherits door
{
bool canUnlock( key k )
{
if ( key == redKey )
return true;
return door::canUnlock( k );
}
}
> - First I really, trully, think it's the best way to put the
> power in the artist's and designers hands without involving
> the programmers.
If the scripting language is robust and powerful, then the programmers
will be involved. With power comes complexity, I don't think there's
much you can do to avoid that. If the scripting language isn't that
robust and powerful, then the programmers are still going to be involved
to patch the binaries to add new features.
> - Then I start thinking that a programmer should be involved
> anyway, but it's still a great way to decouple game behavior
> that changes all the time from engine code.
Why is decoupling good? I'm quite serious -- this is a common
assumption that somehow the "engine code" is expected to be recompiled,
but that "game code" should be isolated from that. Engine, game,
whatever, it's still code.
Not only that, but in order to decouple, you have to "recouple". When
you integrate a scripting language, you now have to go back to your
engine and your scripts and figure out how the two components are going
to communicate. This sometimes means making wrappers for all your data
and entry points so they can be communicated back and forth. Don't
underestimate the amount of work that's required if you want the
scripting language to do non-trivial work.
In most situations with scripting, you have an engine that calls out to
script; a script that calls into the engine; or a mix of the two. No
matter what direction it goes, you still have to spend a lot of time
glueing the components together. If you change a data structure or
interface, the scripts are going to be broken again, so the decoupling
never really lasts.
If the engine is frozen solid, then you may not be spending a lot of
time repatching the scripts, but if that's the case, often you can just
separate the game code into a DLL and reap most of the benefits right
there.
> - Then I realize that if we're going to have a programmer
> involved, the pain and suffering we go through for a
> scripting language might be totally moot. Debugging goes from
> non-existent to sucky (compared to the
> C++ debugger), performance needs a lot of tweaking and has to be done
> carefully... so we might as well write all that code in C++.
Exactly.
So you get this keen scripting language, and now you have to deal with
the following:
- memory management or lack thereof
- compatibility with threading
- adhering to the language's model for objects, threads/coroutines,
memory
- learning a new language and set of programming idioms
- creating glue layers to go back and forth
- lack of a debugger
- dealing with bugs in the scripting environment
- performance problems due to GC, interpretation, lack of native types,
etc.
> All in all, when it comes down to it, I still haven't made up
> my mind. It's a potentially very complex and time-consuming
> system that could make things worse for everybody if done wrong.
The big realization I had is that I was looking at scripting as a
solution to a problem that I hadn't encountered yet. There's a lot of
conventional wisdom that scripting is required, necessary and just a
part of life, but I haven't actually witnessed a situation where the
actual scripting was beneficial WITHOUT being just a crutch to make up
for massive weaknesses somewhere else.
Scripting is all too often used as a patch for problems that, IMO,
should be addressed at the core. If your build times take hours, then
fix that. If your artists/designers are spending lots of time trying to
make new behaviours, then figure out a set of tools that enhance this
process -- do you really want artists/designers hand editing text files
with scripts and then running the game hoping it works?
Once again, to be very clear, I'm not saying that scripting is pointless
or a waste of time, but before someone sits down and takes the
considerable time to make an engine scriptable, they really need to make
sure that there's an actual problem being solved.
Sandboxing end user mods is a VERY good reason to script. Using scripts
for NPC interaction or possibly for more elaborate scripted sequences
and triggers might be good things as well (but even so, many games have
done just fine using data to describe triggers, NPC actions, levels,
missions, quests, end game conditions, etc.). Scripts as a way to make
portable, inexpensive mods (a la UnrealScript and QuakeC) is also a
valid reason.
Brian
|