0
\$\begingroup\$

I've been struggling conceptualizing the best way to make a level flow with dialog and help hints changing as you play etc.

Imagine your goal is to make this one screen 'adventure' game, where you leave your house for work, but along the way, you realize you cannot find your keys to leave the house (and drive to work), also the door handle has fallen out of your door again!

Here is the structure of the story as I see it and how I'd break it down for a game. But what data types would you use, and how would you send messages to the dialog classes telling them to display the lines of text in the correct timing and order?

items:
metal bar - floor
door handle - floor
keys - down sofa

bools:
hasFoundKeys
hasFoundDoorHandle
hasFoundBar

dialog flow:
1. "Well I better get to work now."
2. "Oh damn, I can't find my keys." [when trying door]
3. "Ooh whats this" [said when keys found / other items found]
4. "Oh great, the door handle fell out again" [when trying door, but now have keys]
5. "You need that little metal bar thing before the handle works" [trying door before using metal bar on door]
6. "I cant turn that with my bare hands" [trying door without handle but with metal bar in door]

The text at the bottom would be the exact string I'd want to pop up, the timing for each one is described in the [] brackets.

\$\endgroup\$
  • \$\begingroup\$ Try and break this down into further tasks (otherwise this question is too broad) and ask how to do the smallest thing you don't know how to do. Do you know how you want to display the dialogue? Once you have that, you need to know how to detect clicking on things so you can call the code that displays the dialogue. and so on \$\endgroup\$ – Ruzihm Dec 21 '18 at 21:53
  • \$\begingroup\$ ok i feek you there. but quite honestly I have already asked several questions about it (and had lots of helpful answers) but I still can't quite feel I am doing it correctly. When I implement a system for something like the above, it feels like so much code for such a basic story that its not scaleable to a proper sized story \$\endgroup\$ – Big T Larrity Dec 21 '18 at 21:54
  • \$\begingroup\$ My question the smallest possible way is, what should a 'LineData' data type consist of, how can it send a message to set the value of the bools in code, and how exaclty wouldd a professionaly lay out the Json (ie. would you have the json for each object, or each scene, or the whole game just one big json) \$\endgroup\$ – Big T Larrity Dec 21 '18 at 21:57
  • 1
    \$\begingroup\$ Quick hint since I'm short on time: First create a standard list of Actions that can be done to Things, this would be like an Interface. Second create each Thing with a way to handle Actions being done to it, either custom code for this Thing when it implements the Actions Interface or some kind of scripting attached to the Thing. Now all you have to do for the entire rest of your game is have a way for the Player to do Actions to Things, and then add scripts/code to all Things to do whatever it is they do when some Action is done to them. \$\endgroup\$ – Patrick Hughes Dec 21 '18 at 21:58
  • \$\begingroup\$ Patrick, you're a hero! that actually makes a lot of sense to me, thanks I'll give it a try and if I run into a specific problem there I can post another question . Legend ! \$\endgroup\$ – Big T Larrity Dec 21 '18 at 21:59
1
\$\begingroup\$

The usual approach to this is to define the specific requirements / sequencing / outcomes of a particular puzzle piece in your game's data. That way you don't need custom code for every different permutation, and (especially if you have a large team), your level designers don't need to check out & modify files in the game's codebase when they just want to make a small change to one level.

Typically this will involve defining a "building block" data type for the kinds of actions your puzzles require, then chaining together several of these building blocks to express more complex behaviour.

In a case like yours, we can handle this with....

  • an InteractionStep building block that represents one thing we want to do. That could include...

    • Checking for a required set of items (and aborting the action if they're not all available)

    • Giving the player a set of items (or taking them away)

    • Displaying a message

    • Triggering some other script

  • an InteractionChain collection that represents a sequence of steps to perform in order (like checking if certain items are held before we take any subsequent actions)

  • an Interactable component you can attach to your interactive objects. This can hold several Interaction Chains. When you trigger its interaction, it will try each chain in sequence until it finds one that it can successfully complete. We can use this to display a different message depending on which items you've collected.

Here's an example of what that can look like:

public class Interactable : MonoBehaviour { 

    [System.Serializable]
    public enum Interaction {
        None,
        RequireItems,
        GiveItems,
        TakeItems
    }

    [System.Serializable]
    struct InteractionStep {
        public Interaction interaction;
        public string message;
        public Items[] items;
        public UnityEvent effect;

        public bool Attempt(Actor actor) {
            switch(interaction) {
            case Interaction.RequireItems:
                foreach(var item in items)
                    if(!actor.inventory.Contains(item)) return false;
                break;
            case Interaction.GiveItems:
                foreach(var item in items)
                    actor.inventory.Add(item);
                break;
            case Interaction.TakeItems:
                foreach(var item in items)
                    actor.inventory.Remove(item);
                break;
            }

            // If we got here, then this outcome is valid for this inventory state.
            // Show our message and perform our action, if any.
            if(!string.IsNullOrEmpty(message))
                actor.messageChannel.Display(message);
            if(effect != null)
                effect.Invoke();

            return true;
        }
    }

    [System.Serializable]
    public struct InteractionChain {
        public string name; // This just helps us keep track of what's what in the Inspector.
        public InteractionStep[] steps;
    }

    public InteractionChain[] interactions;

    public void Interact(Actor actor) {
        // Try each of our possible chains of interaction in order.
        foreach(var actionChain in interactions) {
            // Abort a chain of actions if its success criteria aren't met.
            var successful = true;
            foreach(var action in actionChain.steps) {
                successful = action.Attempt(actor);
                if(!successful)
                    break;
            }
            // If we've completed a successful action chain, stop.
            if(successful)
               break;
        }
    }
}

So, the scenario you showed could be set up in data a bit like this:

  • on the Sofa Cushion Interactable:

    • name "Already Collected Keys"
      • Interaction: Require Items
      • Items: [Keys]
      • Message "Nothing more under here but lint"
    • name "Collect Keys"
      • Interaction: Give Items
      • Items: [Keys]
      • Message "Aha! My keys!"
  • on the Door Interactable:

    • name "Solved"
      • Interaction: RequireItems
      • Items: [Keys, MetalBar, DoorHandle]
      • Message: "Now I can finally get to work!"
      • Effect: Door.Teleporter.Activate()
    • name "Need Handle"
      • Interaction: RequireItems
      • Items: [Keys, MetalBar]
      • Message: "I can't turn the bar with my bare hands."
    • name "Need Bar"
      • Interaction: RequireItems
      • Items: [Keys, MetalBar]
      • Message: "The handle won't go on without the little metal bar thing."
    • name "Have Keys"
      • Interaction: RequireItems
      • Items: [Keys]
      • Message: "Great, the handle fell off again. I'll have to find the pieces."
    • name "No Keys"
      • Message: "Oh damn, I can't find my keys!"

More sophisticated versions of this could include:

  • additional state checks, where a step could check for the state of the door or other object too, rather than just work with the player's inventory

  • state changes, where a step could modify the state of an object in the scene

  • a "Use" verb, where the object can choose one of several different interaction chains based on what object was used on it.

  • branching action chains, where you can trigger one sequence of events or another based on a condition. This can skip some redundant checks if you have several outcomes that share several pre-conditions (like all of the various messages above that trigger only once you've found your keys)

...but all of these follow the same basic structure of defining a building block for your actions, writing some code that walks over those building blocks checking/executing them, and wiring-up your objects with the appropriate sequences of building blocks

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Thank you for this. I'll give it a whirl in the morning. This is the first I've seen of "Actor" and "UnityEvent", so I am hopeful it will help me fix then problem. I can copy your code and hopefully learn exactly how it works and get it running, then try add the extra features you mentioned. Really appreciate the help as always. \$\endgroup\$ – Big T Larrity Dec 23 '18 at 2:53
  • \$\begingroup\$ UnityEvent is just an event handler you can wire up in the inspector. Actor is a placeholder for whatever class you use to manage the agents that can interact with things (eg. player characters, or NPCs if you want them to be able to.trigger stuff too) \$\endgroup\$ – DMGregory Dec 23 '18 at 2:57
  • \$\begingroup\$ Just managed to get this running. Thanks again. No need for me to even use Json files or anything with this code. ..Now to try an get it working with branching actions and dozens/hundreds of game items.. I think i can go full steam ahead now with this , yet again you help was priceless and saved me lots of hours of mind-melting frustration :D all the very best! \$\endgroup\$ – Big T Larrity Dec 24 '18 at 17:16
  • \$\begingroup\$ Sorry to be a pain. im 90% there, I have your code compiling without error. But I can;t call the Attempt(actor) method. It seems hidden somehow ( i added my code to the original question, and see the OnMouseDown() at the bottom, that is where I thought I could call it from) \$\endgroup\$ – Big T Larrity Dec 24 '18 at 18:07
  • \$\begingroup\$ Did you mean to call Interact? \$\endgroup\$ – DMGregory Dec 24 '18 at 18:20

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.