As a few of you know, I am currently working on an RPG for iOS. Since I have finally gotten past the point of getting a good tile engine set up, adding characters, collision detection, character dialog, touch events, etc, I thought it was finally time to start actually making the game (depressed sarcasm intended). Overall it looks pretty solid right now: runs on my iphone 4 relatively smoothly and astetically looks very nice (thanks to many of the LPC asset donators whom will be attributed in my credits).
My problem: What is the best practice for organizing the main story's logic? As in: Do I just make a variable to represent what part of the story they are on?
(I'm using lua)
local gameStoryVariable = 0
if character.x==5 and character.y==6 then
gameStoryVariable=1
end
(now I'll use psudo code to get my point across)
if gameStory Variable==1 and quest1 completed then
gameStoryVariable=2
end
if key was found and gameStoryVariable==2 then
gameStoryVariable=3
end
This seems very inefficient and leaves a bad taste in my mouth. Any better ideas? Btw, I'm pretty horrible at programming and am self taught, so please forgive.
-Curt
Hi, for advanced management of gamestates in Lua, there is (among others, probably):
https://github.com/kikito/stateful.lua
Depending on how your game is structured, you might face a major refactoring; it comes with good examples though, so you get a good idea (I hope).
If it isn't for you, gamestates (and how to implement them) is what you could search for instead.
For Flare we use "campaign statuses" to tell where the player is in the story. During triggered map events or while talking to NPCs, scripts can set a status or check a status. So if you're on a certain quest an NPC might have unique dialog. Or an NPC will thank you if you have the status that proves you returned their lost item.
The statuses are unique strings, so the current campaign state is a set of these strings. Example campaign statuses are "found_silver_key" or "hunt_the_goblins" for tracking items and quests.
A common expansion on this system is to have a string and number combination for quest chains. The string is the tag of the overall quest chain and the number is your current progress through the quest. I think World of Warcraft does it this way. So you might have a hunt_the_goblins quest chain that starts off at 0 and ends at 100 when the quest is complete (basically allowing one quest to have 100 possible steps/branches, but you can combine multiple long quests easily). This is good if you have many long quest chains. E.g. every time you have a quest to find an item you have three states: accepted the quest, found the item, returned the item. If you're only using strings you need three unique identifiers e.g. silver_key_accepted, silver_key_found, silver_key_returned. If you combine with numbers it might be silver_key:0, silver_key:50, silver_key:100 instead.
Hope this helps! We use the simpler string-only version and it works well enough for us.
Have a look at this example NPC file from Flare. https://github.com/clintbellanger/flare-game/blob/master/mods/alpha_demo/npcs/guill.txt
He says different things depending on whether a campaign status exists (request_status) or does not exist (requires_not) and also moves the story forward himself (set_status). Whenever it's time for this NPC to talk the game checks all the available dialog choices to find one that meets the current quest requirements.
Using a number to store the main quest progress will cause you trouble later if you decide to insert stages into your story line. If you do that you'll find yourself do stuff like: gameStoryVariable = 5.29
Clint's suggestion is good. Using something akin to it will result in your quest state/progress being stored in a table like this:
queststate = {
village_found_key = true,
village_talkedto_elder_count = 10,
village_talkedto_oldlady = true,
swamp_entered = true
}
Checking to see if certain events have happened becomes as simple as this:
if queststate["swamp_defeated_ratboss"] or queststate["swamp_found_ring"] do
-- do stuff here
end
An example with setting:
function village_search_crate() do
if queststate["village_found_key"] then
message("You find nothing else of interest hidden among the straw.")
else
queststate["village_found_key"] = true
message("The crate is full of straw, but at the bottom you find an old tarnished key.")
--add tarnished key to inventory
end
end
A slightly fancier example:
function village_talkto_elder() do
local talkcount = (queststate["village_talkedto_elder_count"] or 0) + 1
queststate["village_talkedto_elder_count"] = tc
if talkcount == 1 do
speak("Elder", "Welcome to the village stranger.")
end
if talkcount == 11 do
speak("Elder", "You're a chatty one aren't you.")
end
--add normal talking here
end
You'll want a naming convention for your states to keep them organised. For the examples I used: location_verb_object
I put most of my logic into XML files, but the variables behind the logic are held inside an array or database table. Sometimes, however, I just use plain ol' boolean variables and that's it. It really depends on what I'm making.
Syrsly
Twitch Streamer, Web/Game Developer, & Artist
syrsly.com - contact me for commissions, thanks!
Thanks guys, very good stuff! I now have a pretty good grasp of how I should be setting up the main story quest and side quests.
in lua, there are lua tables that have keys and values, so this should do exactly what you are saying. Only problem with lua (unlike c++) is that it doesn't really have a switch statement. The reason I say this is that using a comparison chain (If-then) will eventually be inneficient if a main character has a lot of dialog situations. This can be overcome by putting all the commands/actions in a different table.
**Sorry, the formatting wasn't working correctly in here, so I just quick took a screen shot in microsoft word.. ignore the red and green squiggles.
I'm not totally sure if the code is right.. but you get the idea.. and I'll have to think up a clean way to do it.I'll just have to write up a class structure like this for dialog, items, walkpatterns, spawnrates, level lighting... etc.
There are a number of solutions to the lack of switch statements in Lua. A web search for "lua switch statement" should turn up a few of varying complexity.
My initial suggestion is to go with the simple if-elseif chains at first and see what patterns emerge. You might find only a handful of NPCs require long conditional chains that justify switch like constructions, or handling everything might be more elegant with dialog text files akin to the example from Flare that Clint linked to.
That said, I do worry that your comments are alluding to plans for a giant monolithic function for all of the player's dialog interaction.
JevaEngine seperates the dialog files from the entity scripts - and quest 'states' from dialog. This allows the creation of long, complex and branching dialog without creating messy scripts and IMO makes the game much more maintainable. The scripts on worry about what they need to, and the dialog does the same. Further, quests are seperated from dialog as well, and instead are driven by the scripted entities which also drive the dialog. Here is how it works:
First, an array of DialogNodes are created and stored in a document. Some DialogNodes are not connected to the same tree - tree roots act as entry-points for Dialog (entry points can be selected based by the entity script after it has polled a the user's quest state.)
Each DialogNode consits of Answers, each Answer links to another DialogNode or terminates the branch(end of dialog.) Each answer can also issue a DialogEvent (but doesn't have to.) when the DialogNode raises a DialogEvent, the driving script (can be an NPC, or any other entity) is informed that this event was raied by the dialog and has a chance to reroute the dialog, let it continue on its path, or just change some internal state (such as quest progress)
I'll grab my script files in a momment.
Here (I'm not using XML until I establish an editor):http://pastebin.com/ZHxpsN1u
Keeping track of the indexes is a pain in the ass, but eventually you'll wirte an editor for it.
Using this structure you can implement things like one's morality level while they traverse through their dialog tree and make individual decisions in dialog choice. For example, you could have two answers to a dialognode, one could be "Goodbye Friend" and the other "Get away sicko!" - the two would link to the same next dialog node (though they don't have to...) but they could raise different commands, one to raise morality, the other to decrease it.
I have been using this model and its incredibly flexible for me, and my scripts are very portable & easy to maintain.