Truckin' along

posted in A Keyboard and the Truth for project 96 Mill
Published April 26, 2006
Advertisement
I really like the approach I've taken in the development of this engine. Things are very organized (from both a design and code standpoint), I've used plenty of third party libraries to ease my pain:

-TinyXML
-Lua 5.1
-Ogg/Vorbis
-Boost

I've used the Standard Template Library, which eased a TON of pain =)

All systems have been developed from a 'needs' aspect instead of coding useless features.

So with that, it's only taken about 5 months to finish the engine (it will be done within the month), which is a huge time leap over Flare 3.0 which took about two years.

I think all will agree the new engine is far more asthetic, and will soon agree that it feels more responsive.

And the crown jewel of it all? The engine is Data-Driven, this means very inexperienced programmers (or even other ambitious folk) can effectively write games with it.

As Donny was saying in his journal the system I've built around Lua's fine language is quite nice, I'll give you an example of the simplicity here:


Scripting for the S3Engine

When the engine 'boots up' the first thing it tries to run is the init.script, which might look somthing like this:

function onInit()	createMap("map1","data/map1.map","data/map1.script");	selectMap("map1");	rebecca=createEntity("data/rebecca.entity","data/rebecca.script");	ivy=createEntity("data/ivy.entity","data/ivy.script");	mainEntity(rebecca);	watchEntity(rebecca);end


as seen above, first we create a map, using a .map file (xml), and telling it to send it's events to map1.script (lua) .

we then select the map, many functions depend on the currently selected map.

then we create an entity, passing a .entity (xml) file and a .script (lua) file.

we then set 'rebecca' as the main entity, this means her entity will recive all the commands from the player.

watchEntity is then called which makes the camera keep a focus on this entity.

when 'ivy' is created her script get's an event which we can use to prepare her:

function onCreate(this)	setKey(this,"name","Ivy");	setKey(this,"verbEye","look at");	setKey(this,"verbHand","push");	setKey(this,"verbMouth","talk to");	createTopic(this,"onWhatNow","What do we do now?",true);	createTopic(this,"onBoring","This is kind of boring eh?",false);	createTopic(this,"onBye","Nevermind.",true);endfunction onMouth(this)	lockScene();	engage(rebecca,this);	w0=talk(rebecca,"Ivy?");	wait(rebecca,w0);	w0=talk(this,"What's up?");	wait(this,w0);	beginDialogue(this);	unlockScene();endfunction onWhatNow(this,state)	lockScene();	engage(rebecca,this);	w0=talk(rebecca,"What do we do now?");	wait(rebecca,w0);	w0=talk(this,"How should I know, you're the leader.");	wait(this,w0);	showTopic(this,"onBoring");	hideTopic(this,"onWhatNow");	beginDialogue(this);	unlockScene();endfunction onBoring(this,state)	lockScene();	engage(rebecca,this);	w0=talk(rebecca,"This is kind of boring eh?");	wait(rebecca,w0);	if state == 0 then		w0=talk(this,"What did you expect, a party?");		wait(this,w0);		incTopic(this,"onBoring");	elseif state == 1 then		w0=talk(this,"Well then you should think of somthing to do.");		wait(this,w0);		hideTopic(this,"onBoring");	end	beginDialogue(this);	unlockScene();endfunction onBye(this,state)	lockScene();	engage(rebecca,this);	w0=talk(rebecca,"Nevermind.");	wait(rebecca,w0);	w0=talk(this,"Okay.");	wait(this,w0);	endDialogue();	unlockScene();end


That's a bit of code eh? Let's explain this sucker.

First onCreate is called, note how we pass in the 'this' argument, 'this' points to the entity that is using this script, so in this case it would be ivy's entity ID, very handy, especially if two entities are using the same script (common in some circumstances, a door for instance.)

so in onCreate we do some initialization of Ivy's state,
some entity properties are set via functions, and some are stored as 'keys' in an entities property dictionary.

using keys make for good, entity local storage of arbitrary data.

so first we set her name:
setKey(this,"name","Ivy");

then we set the contextual verbs for the three actions:
setKey(this,"verbEye","look at");
setKey(this,"verbHand","push");
setKey(this,"verbMouth","talk to");

then we create some dialogue topics that you can speak about if a beginDialogue command is issued on ivy.
createTopic(this,"onWhatNow","What do we do now?",true);
createTopic(this,"onBoring","This is kind of boring eh?",false);
createTopic(this,"onBye","Nevermind.",true);

the arguments go as (entity,name,text,visible), note how the middle topic is set to invisible by default.

next comes our first action response event, in-game when you click-hold on ivy, roll over the mouth icon (which says talk to, due to our setting the contextual verb) and release, the onMouth event will be raised on ivy's script.

for us in this instance, it means we want to talk to ivy, so here is how we do it:

first, whenever you enter a scripted sequence it is important to lockScene();, this user input to be ignored so you cant interfear with the sequence.

next we call engage(rebecca,this);, this causes both characters to make eye contact.

then we make rebecca (your main character) say somthing
w0=talk(rebecca,"Ivy?");
note how we save the result of the talk function, this is a mutexID, which can be used to wait on this function's completion.

next, we do just that we wait(rebecca,w0);, which will cause the script to halt and wait until rebecca is done talking.

then we make Ivy respond, using the same system
w0=talk(this,"What's up?");
wait(this,w0);

now that our 'introduction' is over, we can go right into topic selection, beginDialogue(this); causes the system to bring up the topic selection system.

and finally, unlockScene(); gives the user back their input.

now say you select a topic, say 'What do we do now?', this will cause the system to try and raise an event on ivy's script, the same as the name of the topic, in this case onWhatNow:

so like before we lock the scene and engage the characters:
lockScene();
engage(rebecca,this);

ivy and rebecca talk a bit:
w0=talk(rebecca,"What do we do now?");
wait(rebecca,w0);
w0=talk(this,"How should I know, you're the leader.");
wait(this,w0);

and now here comes somthing new:

we instruct that the 'onBoring' topic, which was created as invisible, should now be shown:
showTopic(this,"onBoring");

and that we should hide the 'onWhatNow' topic which we just chose:
hideTopic(this,"onWhatNow");

finally, to reenter the choice selection we call beginDialogue again, and once again unlock the scene:
beginDialogue(this);
unlockScene();


if we then choose the 'onBoring' topic, it will again call a function this time called onBoring(this,state);

notice the state argument, each topic has a state (an int) which can be used for progression on a specific topic, by default all topics are set at 0, and once visited (chosen) they are greyed, but by incrementing or decrementing a topic state you can make it fresh again, and base the result of choosing it off the state.

so like before, lock, engage, have rebecca say somthing:
lockScene();
engage(rebecca,this);
w0=talk(rebecca,"This is kind of boring eh?");
wait(rebecca,w0);

then we do a conditional based on state:
if state == 0 then
w0=talk(this,"What did you expect, a party?");
wait(this,w0);

at the end of the 0 state, we indicate that this topic should increment it's state by one, keeping the topic fresh, and changing the state setting, the next time around:
incTopic(this,"onBoring");

like before we end the sequence resuming the dialogue and unlocking the scene:
beginDialogue(this);
unlockScene();

we'll note that 'onBoring', is not greyed, which indicates there is more to be said on the topic so if we choose it again:

state is now equal to one so the second conditional is executed,
ivy now says somthing different, and hides the topic:
elseif state == 1 then
w0=talk(this,"Well then you should think of somthing to do.");
wait(this,w0);
hideTopic(this,"onBoring");
end

like before we end the sequence resuming the dialogue and unlocking the scene:
beginDialogue(this);
unlockScene();

now you have only one dialogue choice, which you could have chosen at any time, 'onBye':

like before we lock the scene and they have a little talk:
lockScene();
engage(rebecca,this);
w0=talk(rebecca,"Nevermind.");
wait(rebecca,w0);
w0=talk(this,"Okay.");
wait(this,w0);

we then call endDialogue();, this causes the topic menu to hide itself and return us back to the normal game mode.

finally we unlock the scene and all it well:
unlockScene();


I hope you enjoyed the explanation of our scripting system, overall I think it's quite easy and well thought-out, and provides a good effort,flexibility,result ratio.

Previous Entry W00T
Next Entry Busy, Busy, Busy =D
0 likes 7 comments

Comments

gwihlidal
Very good ideas here Raymond. Designing a scripting system like this will promote ease of maintainability, and make your engine even more reusable for future projects!

On top of that, you can easily support third party expansions without giving access to any source code; just the scripting interface =)

Well done!

~Graham
April 26, 2006 09:44 AM
Twisol
Very cool! Just one question: what's Flare 3.0?
April 26, 2006 10:52 AM
EDI
Flare 3.0 is the engine that Morning's Wrath was built on.

I built it as well, however it was built to be far more generic, and as a result it was made overly complex, and it suffered from that.
April 26, 2006 12:14 PM
Twisol
Ah, I see.

What happened to 1.0 and 2.0? :D
April 26, 2006 12:45 PM
EDI
they were iterations =)
never made full games with them =D
April 26, 2006 02:59 PM
ildave1
Zomg! EDI rocks! :D Such pretty planning and implementing. Keep up the dang good work! =)

*hiccup*
April 27, 2006 12:17 AM
EDI
right on buddy!

*hic* =D
April 27, 2006 08:13 AM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Profile
Author
Advertisement
Advertisement