[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 |