12
\$\begingroup\$

I'm planning out an adventure game, and can't figure out what's the right way to implement the behaviour of a level depending on state of story progression.

My single-player game features a huge world where the player has to interact with people in a town at various points in the game. However, depending on story progression, different things would be presented to the player, for e.g. the Guild Leader will change locations from the town square to various locations around the city; Doors would only unlock at certain times of the day after finishing a particular routine; Different cut-screen/trigger events happen only after a particular milestone has been reached.

I naively thought of using a switch{} statement initially to decide what the NPC should say or which he could be found at, and making quest objectives interact-able only after checking a global game_state variable's condition. But I realised I would quickly run into a lot of different game states and switch-cases in order to change the behaviour of an object. That switch statement would also be massively hard to debug, and I guess it might also be hard to use in a level editor.

So I thought, instead of having a single object with multiple states, maybe I should have multiple instances of the same object, with a single state. That way, if I use something like a level editor, I can put an instance of the NPC at all the different locations he could ever appear at, and also an instance for each conversation state he has. But that means there'll be a lot of inactive, invisible game objects floating around the level, which might be trouble for memory, or simply hard to see in a level editor, i don't know.

Or simply, make an identical, but separate level for each game state. This feels the cleanest and bug-free way to do things, but it feels like massive manual work making sure each version of the level is really identical to each other.

All my methods feel so inefficient, so to recap my question, is there a better or standardised way to implement behaviour of a level depending on state of story progression?

PS: I don't have a level editor yet - thinking of using something like JME SDK or making my own.

\$\endgroup\$
9
\$\begingroup\$

I think what you need in this case is the State Design Pattern. Instead of having multiple instances of each game object, create a single instance, but encapsulate its behavior in a separate class. Create multiple classes, one for each possible behavior, and give all classes the same interface. Associate one to your game object (the initial state) and, when conditions change (a milestone is reached, the time of the day passes, etc) you switch that object's state (i.e. associate it with a different object depending on your game logic) and update its properties if applicable.

One example of how a state interface would look like (completely made up - just to illustrate the level of control this scheme gives you):

interface NPCState {
    Scene whereAmI(NPC o);
    String saySomething(NPC o);
}

And two implementing classes:

class Busy implements NPCState {
    Scene whereAmI(NPC o) {
        return o.getWorkScene();
    }
    String saySomething(NPC o) {
        return "Can't talk now, I'm busy!";
    }
}

class Available implements NPCState {
    Scene whereAmI(NPC o) {
        return TAVERN;
    }
    String saySomething(NPC o) {
        String[] choices = o.getRandomChat();
        return choices[RANDOM.getInt(choices.length)];
    }
}

And switching states:

// The time of day passed from "afternoon" to "evening"
NPCState available = new Available();
for ( NPC o : list ) {
    Scene oldScene = o.state.whereAmI(o);
    o.state = available;
    Scene newScene = o.state.whereAmI(o);
    moveGameObject(o, oldScene, newScene);
    ...

Important NPCs may have their custom states, the state choosing logic may be more customizable, and you can have different states for different facets of the game (in this example, I used a single class to tell both location and chat, but you could separate them and do many combinations).

This works well with level editors too: you can have a simple combo box to switch the "global" state of a level, then add and reposition the game objects as you want them to appear in that state. The game engine would be responsible for only actually "adding" that object to the scene when it has the correct state - but its parameters would still be editable in an user-friendly way.

(Disclaimer: I have little real-world experience with game editors, so I can's say with confidence about how professional editors work; but my point about the State Pattern still holds, organizing your code this way should be clean, maintainable and not waste system resources.)

\$\endgroup\$
  • \$\begingroup\$ y'know, you could combine this State design pattern with the associative array I described. You could code the state objects as described here, and then choose between different state objects using an associative array as I suggested. \$\endgroup\$ – jhocking Jun 1 '12 at 11:49
  • \$\begingroup\$ I agree, it's also good to separate the game from its engine, and hardcoding game logic strengthens the coupling between them (reducing the possibility of reuse). There's a tradeoff, though, since depending on the complexity of your intended behavior trying to "soft code" everything may result in unnecessary clutter. In this case, a mixed approach might be desirable (i.e. have a "generic" state transition logic, but allowing for custom code to be incorporated as well) \$\endgroup\$ – mgibsonbr Jun 1 '12 at 12:21
  • \$\begingroup\$ So as I understand it, there is one-to-one mapping btwn NPCState and GameState. Then I wld put NPCs in an array and iterate thru it, assigning the new NPCState when a game state change is observed. The NPCState has to be capable of knowing how to handle every diff NPC that is sent to it, so essentially the NPCState contains the behaviour of all NPCs for a given state? I like that all the behaviours store cleanly in a single NPCState, which maps cleanly to game editor implementation, but it kinda makes the NPCState pretty huge. \$\endgroup\$ – Cardin Jun 2 '12 at 3:05
  • \$\begingroup\$ Oh, I think I misunderstood ur ans. I changed it a bit to include observers. So it's one diff NPCState for every diff NPC, except the super generic ones like Crowd NPC which can share state. For each game state, the NPC will register itself and its NPCState with an Observer. Hence the Observer will know exactly which NPC is registered to change behavior at which game state, and simply iterate through them. And on the game editor side, the game editor just has to pass a signal to the Observer to change the state of the whole level. \$\endgroup\$ – Cardin Jun 2 '12 at 3:29
  • 1
    \$\begingroup\$ Yes, that's the idea! Important NPCs will have many states, and the state transition will depend mostly on the completed milestones. Generic NPCs might also react to milestones sometimes, and even have their chosen state dependent on an internal property (let's say all NPCs have a default initial state, and when you talk to one of them for the first time he introduces himself, then enter the normal state-switching cycle). \$\endgroup\$ – mgibsonbr Jun 2 '12 at 3:44
2
\$\begingroup\$

The choices I would consider are either making the individual objects respond to different gamestates, or serve up different levels in different gamestates. The choice between those two would depend on what exactly I am trying to do in the game (what are the different states? how will the game transition between states? etc.)

Either way however I wouldn't do it by hard-coding the states into the game code. Rather than a massive switch statement in NPC objects, I would instead of the NPC behaviors loaded into an associative array from a data file and then use that associative array to run different behavior for their associated states, something like this:

if (state in behaviors) {
  behaviors[state]();
}
\$\endgroup\$
  • \$\begingroup\$ Would that data file be a kind of scripting language? I think a plain text data file might not be enough to describe behaviour. At any rate, you're right that it should be dynamically loaded. I can't quite think of using a game editor to generate valid Java code, it definitely have to be parsed somewhat. \$\endgroup\$ – Cardin Jun 2 '12 at 2:23
  • 1
    \$\begingroup\$ Well that was my initial thought, but after seeing mgibsonbr's answer I realized you could code the various bevaviors as separate classes and then in the data file just say which behavior classes go with which state. Load that data into an associative array at runtime. \$\endgroup\$ – jhocking Jun 2 '12 at 15:51
  • \$\begingroup\$ Oh.. Yea that's definitely simpler! :D Compared to the scenario of embedding something like Lua haha.. \$\endgroup\$ – Cardin Jun 3 '12 at 2:15
2
\$\begingroup\$

What about using a observer pattern to look for milestone-changes? If a change happens, some class would recognize this and handle for example a change that has to be done to a npc.

Instead of the mentioned state design pattern I would use a strategy-pattern.

If a npc has n ways to interact with the character and m positions where he could be, there is a maximum of (m*n)+1 classes you have to design. Using the strategy-pattern you would end up with n+m+1 classes but these strategies could also be used by other npcs.

So there could be a class handling the milestones, and classes who observe this class and handle either npc or enemies or whatever should be changed. If the observers get updated they would decide if they have to change something to the instances they rule. The NPC class for example would, in the constructor, inform the NPC-Manager when he has to be updated and what has to be updated...

\$\endgroup\$
  • \$\begingroup\$ The Observer pattern seems interesting. I think it could cleanly leave all responsibilities with the NPC to register itself with the state observer. The Strategy pattern feels a lot like Unity Engine's Trigger and AI behaviours, which is used to share behavior btwn diff game objects (i think). It sounds feasible. I'm nt sure what's the pros/cons right now, but that Unity uses the same method too is somewhat reassuring haha.. \$\endgroup\$ – Cardin Jun 2 '12 at 2:36
  • \$\begingroup\$ I just used these two patterns a few times so I'm not able to tell you about the cons :-/ But I think it is nice in case of single responsibility and the availability to test every strategy :) It might get confusing if your using a strategy in many different classes and you want to find every class which uses it. \$\endgroup\$ – TOAOGG Jun 3 '12 at 14:03
0
\$\begingroup\$

All of the approaches given are valid. It depends on the situation you are in at any given moment. Many adventures or MMOs use a combination of these.

E.g. if a pivotal event changes a large part of the level (e.g. a debt collector cleans out your apartment and everyone in it gets arrested), it is usually easier to replace the entire room with a second room that just looks similar.

OTOH, if characters walk around the map and do different things in different places, you often have a single actor that rotates through different behaviour objects (E.g. walk straight ahead/no conversations vs. stand here/conversation about Mitch's death), which might include "hidden" if their purpose has been fulfilled.

That said, usually having duplicates of an object that you create manually should not cause any problems. How many objects can you create? If you can create more objects than your game can loop over, look at their "hidden" property and skip, your engine is too slow. So I wouldn't worry about that too much. Many online games actually do this. Certain characters or items are always there, but are not displayed to characters who do not have the corresponding mission.

You can even combine the approaches: Have two doors in your apartment building. One leads to the "before the debt collector" apartment, one to the apartment after. When you enter the corridor, only the one that applies to your progression in the story is actually shown. That way, you can just have a generic mechanism for "item is visible at current point in story" and a door with a single destination. Alternately, you could make more complicated doors that can have behaviours that can be swapped out, and one of them is "go to full apartment", the other "go to empty apartment". This may seem nonsensical if really just the door's destination changes, but if its appearance changes as well (e.g. a big lock in front of the door that you first have to crack), this may be a better approach than replacing the entire corridor as well.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.