First off, I played with lua some more and finally was able to get 'exactly' what I wanted in an elegant way, yay!
So with the scripting system finally settled it was time to finalize the new object system.
A problem that plagued the S3Engine was that only 'entities' were 'addressable objects', an entity was an element of a scene, like a character, or a tree. Now the reason addressable objects were so handy is that they had a flexible key-value dictionary bound to them and could have properties defined through scripting:
setKeyString(entity,"key","value");
value=getKeyString(entity,"key");
this is a very powerful feature for changing the properties of an object. problem was there were lots of other objects that could have used this feature, but due to the lack of foresight in design these things could not be 'entities'
things such as:
- The Game (the game world)
- Maps
- Animated Planes
- Etc.
this meant that specialzed api functions had to be written to manipulate properties of these objects.
such as setAmbientLight(1,1,1,1);
where setKeyVector(game,"lightColor",1,1,1,1); would have been much nicer.
Also maps were referenced by their 'key name' which is a string alias:
createMap("portPlacid","data/portPlacid.script");
selectMap("portPlacid");
this worked fine, however it wasn't in keeping with 'object referencing' parameters.
So this was the main feature I wanted to change in the core, and the problem was that most of the functionality that I wanted was implemented in Entity, which was exclusively a display object that existed inside a map; I needed to implement this functionality at a lower level.
And so, StateObject was born, the state object is considred the base element of all elements that happen to be in a game state. The state object knows what script it sends it's events to, has a reference to the main lua state, can raise script events, knows it's logical 'object id' used for referencing in-script, contains a key/value dictionary and accessors for it, contains a list of Lock objects, which are events waiting to happen that will resume execution of a script, contain virtual update and render methods, contain virtual save and load serialization methods, contain a mutex lock (for synchronizing access, to an object (not for multithreading though)) and lots of other stuff. It basically embodies the base of any object that can cause script events and be manipulated through script api functions.
The Engine object (which is not stated) maintains a vector of these objects, which is essentially 'the game state', element 0 of this vector is always the World object, which is a subclass of StateObject, and is in charge of setting up the root lua_state and registering itself as the global variable 'world' with id 0; from there the World object contains methods for creating various other objects such as Rooms (maps) and Actors, both which inherit from StateObject.
Another improvement is storing the objects in a vector, the placement in the vector corresponds to the object's internal id, this id number is used as the object's reference in script; using the vector placement index means there is no need for a 'nextID' registration system and it means an 'instant' random access lookup from script object ID to internal engine object pointer.
Through a bit of type inference magic I was also able to shave off some typing work:
Instead of:
setKeyString(this,"key","val");
getKeyString(this,"key");
setKeyNumber(this,"key",100);
getKeyNumber(this,"key");
setKeyBool(this,"key",true);
getKeyBool(this,"key");
setKeyVector(this,"key",1,1,1,1);
getKeyVector(this,"key");
It's now:
setKey(this,"key","val");
getKey(this,"key");
setKey(this,"key",100);
getKey(this,"key");
setKey(this,"key",true);
getKey(this,"key");
setKey(this,"key",1,1,1,1);
getKey(this,"key");
Yay, less typing, of course I could have called it just get and set, but I figure getKey is short enough :)
Another scripting improvement is a change to how the syncable script api functions work; in the S3Engine a typical move command would look like this:
w0=move(rebecca,"node2");
...
...
wait(rebecca,w0);
The move function would start the entity 'rebecca' moving to "node2", this function returned an "Syncronization Object" though it is considered transparent it was really just an integer 'eventID';
Later this action could be waited on if we wanted to, via the wait function;
the inconvience is the redundance of data, having to pass in rebecca again.
The solution to this was to have the move function reutrn a lua table instead, with two keys objectID and eventID, this is then passed to wait, and it extracts the two needed pieces of information, resulting in:
w0=move(rebecca,"node2");
...
...
wait(w0);
much nicer, and it removes the chance of error, such as in:
w0=move(rebecca,"node2");
...
...
wait(ivy,w0);
which I have done before, yay copy and paste :)
Another improvement is that certain functions, such as Talk and Pause, are now automatically sychronized.
Before writing dialogue was 50% more tedious:
w0=talk(rebecca,"this is line one");
wait(rebecca,w0);
w0=talk(rebecca,"this is line two");
wait(rebecca,w0);
it is extremely rare, and always avoidable to have a talk command which isn't to be waited upon, so making the programmer put in the wait is just a waste of effort.
Instead now we have:
talk(rebecca,"this is line one");
talk(rebecca,"this is line two");
wait is called automatically via the talk api in C, same with pause.
currently the only commands which support a waitable object are move and act, since it is quite common to want to move several objects and do other actions then later create a common wait point.
w0=move(rebecca,"node2");
w1=move(ivy,"node3);
wait(w0);
wait(w1);
engage(rebecca,ivy);
act(rebecca,"wave",true,true);
The result of all this work is a very well-designed object base and improved scripting api.
In addition i've taken the time to re-introduce a few systems that got pruned during the modification.
The first is the 'Topic' system, which includes the relationship of Topics and Actors and the TopicMenu, which is the Visual UI for doing conversations in the game, this is a straight port from the S3Engine, it was well written then and still good now :)
Then I reintroduced the Inventory object, along with the script functions takeItem and dropItem, which support input and output to the inventory; soon I will be reintroducing the ItemMenu, which is the inventory UI
Soon, I'll be at a point where I can dedicate 100% of the time twoards the new 3D graphics components and the Physics system.
Comments? Questions?
Quick question though: I know you thought that the previous S3 engine wasn't graphically capable of competing with modern commercial quality games, but what exactly are you shooting for with this graphics engine? Lots of shaders and top end systems? Mostly just curious as to your target demographic in that regard.
Keep the progress flowing!