2

I'm a JS beginner. I want to make a visual novel type game. Kinda like the game Ace Attorney when you are in court. In this game you meet people and instead of fighting, you talk to them. In encounters, at the top is an image. This image will change depending on your response. There is a text box below it that shows the current text. And 2 (or possibly more) options boxes below. You have a Patience meter (just like a Health meter) that has like 100 points. Depending on the option you choose, you will lose or gain patience. You run out you lose. The goal is to use the correct responses and reach the end of the dialog without losing all patience. Depending on the choice you make, there will be different dialogs. So I'm preparing a dialog tree.

enter image description here

Any ideas on how to code this? I'm having a really hard time figuring out where to store all the text and options. And how to access the text and options.

Here is a sample of the text and options. At first I tried putting the text in objects. sales1 is the intro text. If you selection opt1, sales2 text will show on the screen. If you select opt2, sales3 text will show on the screen.

const sales1 = { action: "Salesman Chad wants to talk!", opt1: "Tell him you're not interested", opt2: "Hear him out" };

const sales2 = { action: "He has a rebuttal for that! "Sir, a classy man like you needs this car!"", opt1: ""We're not even at a dealership!"", opt2: ""Ooo a new car"" };

const sales3 = { action: ""Ssssoooooooo, what kind of TV service do you have?"", opt1: "Tell him your landlord pays for it", opt2: "Tell him you don't watch much TV" };

@font-face {
    font-family: "Game Over Cre";
    src: url(fonts/gameovercre1.ttf);
    font-style: normal;
    font-weight: 300;
}

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    font-family: "Game Over Cre", sans-serif;
    font-size: 40px;
}

body {
    background: black;
    color: white;
}

#image-box {
    border: 4px solid white;
    height: 500px;
    width: 60%;
    margin: 10px auto;
    align-items: center;
    display: flex;
    justify-content: center;
}

#opponent {
    height: 400px;
}

#text-box {
    border: 4px solid white;
    height: 150px;
    width: 60%;
    margin: 10px auto;
    padding: 20px;
}

#options {
    display: flex;
    width: 60%;
    /* background: gray; */
    margin: auto;
    flex-wrap: wrap;
    justify-content: space-between;
}

#option-1, #option-2, #option-3, #option-4 {
    height: 100px;
    width: 49.5%;
    border: 4px solid white;
    margin-bottom: 10px;
    font-size: 35px;
    padding: 10px;
}

#option-1:hover, #option-2:hover, #option-3:hover, #option-4:hover {
    background: white;
    color: black;
}
<body>
    <section class="main-hub">
        <div id="image-box">
            <img id="opponent" src="img/salesman-chad.png">
        </div>
        <div id="text-box">
            <h1 id="action-text">Salesman Chad wants to talk!</h1>
        </div>
        <div id="options">
            <div id="option-1">Tell him you're not interested</div>
            <div id="option-2">Hear him out</div>
            <!-- <div id="option-3"></div>
            <div id="option-4"></div> -->
        </div>
    </section>

    <script src="main.js"></script>
</body>
tneilson08
  • 137
  • 2
  • 9
  • Sounds to me like you're coding a decision tree (a bit like Black Mirror - Bandersnatch, in JavaScript). – Matthew Layton Sep 19 '20 at 20:54
  • 1
    Q: [Where can I ] store all the text and options. And how to access the text and options? A: Why not save your data in a Json file, and read the Json directly into a JS object? Here's a good example: https://stackoverflow.com/a/24378510/421195 This link might also help: https://www.gamedesigning.org/learn/javascript/ – paulsm4 Sep 19 '20 at 20:56
  • 1
    Hey, tneilson08: Let me know if my answer helped! It seems useful, imho. I guess someone disliked it enough to go through all my answers and downvote each one. People, huh! Anyway, hoped my answer helped. – HoldOffHunger Sep 19 '20 at 23:59
  • Awesome it works! I do like your original answer better since with the array it will get confusing to know which number I'm at when there are like 50+ dialog sections! – tneilson08 Sep 20 '20 at 05:06

2 Answers2

2

You can expand your data structure from simply opt1:sometext, to opt1:{text:sometext, nextpoint:2}, and this way, you can display and also traverse your novel game in a relatively, simple manner. You'll only need to able to do JSON to continue from here.

With the code sample, try playing a mini-version of the game below!

const sales = [
    {
        action: "Salesman Chad wants to talk!",
        opt1: {
            text: "Tell him you're not interested",
            nextpoint: 1,
        },
        opt2:  {
            text: "Hear him out",
            nextpoint: 2,
        },
    },
    {
        action: "He has a rebuttal for that! 'Sir, a classy man like you needs this car!'",
        opt1: {
            text: "We're not even at a dealership!",
            nextpoint: 0,
        },
        opt2: {
            text: "Ooo a new car",
            nextpoint: 2,
        }
    },
    {
        action: "Ssssoooooooo, what kind of TV service do you have?",
        opt1: {
            text: "Tell him your landlord pays for it",
            nextpoint: 1,
        },
        opt2: {
            text: "Tell him you don't watch much TV",
            nextpoint: 0,
        }
    }
];

function applyPoint(next) {
    const point = sales[next];
    $('#action-text').text(point.action);
    $('#option-1').text(point.opt1.text);
    $('#option-2').text(point.opt2.text);
    $('#option-1').attr('data-nextpoint', point.opt1.nextpoint);
    $('#option-2').attr('data-nextpoint', point.opt2.nextpoint);
}

$('#option-1').click(function(e) {
    applyPoint($(this).attr('data-nextpoint'));
});

$('#option-2').click(function(e) {
    applyPoint($(this).attr('data-nextpoint'));
});

applyPoint(0);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<body>
    <section class="main-hub">
        <div id="image-box">
            <img id="opponent" src="img/salesman-chad.png">
        </div>
        <div id="text-box">
            <h1 id="action-text"></h1>
        </div>
        <div id="options">
            <div id="option-1"></div>
            <div id="option-2"></div>
        </div>
    </section>
</body>

As you can see, the novel itself is stored in const sales, as a simple array of JSON objects. In the function applyPoint(), we move from the current point of the story to a new point of the story. The buttons that control this for the two options is the jQuery syntax for $('#option-1').click(function(e) {...}) and likewise for #option-2.

With all of that defined, I start the game/novel with the initial, starting point, by means simply of: applyPoint(1);.

HoldOffHunger
  • 18,769
  • 10
  • 104
  • 133
  • Your data structure suggests an array rather than an object. – connexo Sep 19 '20 at 21:24
  • Good point, an array would have worked, as well, in this case. But an object with `nextpoint` could easily become datetime combinations or other storyline-based keys. – HoldOffHunger Sep 19 '20 at 21:27
  • This is using jQuery, this was not asked at all. – NVRM Sep 19 '20 at 21:48
  • Hi, NVRM: Thanks for pointing that out. jQuery is a JavaScript framework, and the above code was intended to solve the OP`s problem: *I'm having a really hard time figuring out where to store all the text and options. And how to access the text and options.* – HoldOffHunger Sep 19 '20 at 21:51
  • Strictly speaking that is not a JSON object as the keys are not strings. – lawrence-witt Sep 19 '20 at 21:58
  • Ah, you are right! Thanks for letting me know, just converted it to just an array and updated the comments. – HoldOffHunger Sep 19 '20 at 22:03
  • I still think it's misleading to call them JSON objects since they are not. A [JSON object](https://www.w3schools.com/js/js_json_objects.asp) is a specific data format with type limitations and keys enclosed with double quotes - not the same as a Javascript object. Otherwise good job, although I agree with NVRM that jQuery is an unnecessary abstraction. – lawrence-witt Sep 19 '20 at 22:12
1

This is solution expands on @HoldOfHugers answer by including several concepts (beyond data storage) that could make developing a more complex game a bit easier to reason about.

<body>
    <section class="main-hub">
        <div id="image-box">
            <img id="opponent" src="img/salesman-chad.png">
        </div>
        <div id="text-box">
            <h1 id="action-text"></h1>
        </div>
        <div id="options">
            <div id="option-1"></div>
            <div id="option-2"></div>
        </div>
    </section>
</body>
const sales = [
    {
        action: "Salesman Chad wants to talk!",
        opt1: {
            text: "Tell him you're not interested",
            nextpoint: 1,
        },
        opt2:  {
            text: "Hear him out",
            nextpoint: 2,
        },
    },
    {
        action: "He has a rebuttal for that! 'Sir, a classy man like you needs this car!'",
        opt1: {
            text: "We're not even at a dealership!",
            nextpoint: 0,
        },
        opt2: {
            text: "Ooo a new car",
            nextpoint: 2,
        }
    },
    {
        action: "Ssssoooooooo, what kind of TV service do you have?",
        opt1: {
            text: "Tell him your landlord pays for it",
            nextpoint: 1,
        },
        opt2: {
            text: "Tell him you don't watch much TV",
            nextpoint: 0,
        }
    }
];

const state = {
  index: 0,
  data: sales,
  get current() {
    return this.data[this.index]
  }
};

const ui = {
  action: document.querySelector('#action-text'),
  left: document.querySelector('#option-1'),
  right: document.querySelector('#option-2')
};

const update = (nextpoint) => {
  state.index = nextpoint;
  render();
};

const render = () => {
  ui.action.innerText = state.current.action;
  ui.left.innerText = state.current.opt1.text;
  ui.right.innerText = state.current.opt2.text;
};

ui.left.addEventListener('click', () => update(state.current.opt1.nextpoint));
ui.right.addEventListener('click', () => update(state.current.opt2.nextpoint));

render();

The first thing that I added was a game state object. This could really be anything, but its typically a good idea to keep the state of a game in an accessible area such as a variable or set of variables and not in the DOM. You can imagine adding patience, a score value, a timer, inventory, npcs, or whatever else that may represent the game. For our case, we probably only want the current index in the dialog, maybe all the dialog information, and then the current derived dialog value so look ups are easier. It can literally be whatever makes your life simpler.

const state = {
  index: 0,
  data: sales,
  get current() {
    return this.data[this.index]
  }
};

The next thing is the update method. This can be thought of as a step in our game. Its in charge of taking our current game state and some inputs and integrating or deriving the next state. Update functions typically are called at a fixed interval say 60 times a second, but for this game we really only need to respond to users. In a more complex game, the update function call processes many subsystems such as physics, animations, game logic, ect. For our purpose our game 'steps' when we receive a new decision from our user.

const update = (nextpoint) => {
  state.index = nextpoint;
  render();
};

The final part of the code is just rendering the current game state. The render function literally looks at the current state and make sure the UI reflect those values. The ui object simply allows me to keep ui elements organized in semantic way and is not required.

If you have done any web programming in the past you might see that this is pretty similar to MVC or reactive ui frameworks. Data flows unidirectionally.

Brenden
  • 1,947
  • 1
  • 12
  • 10
  • Thank you for expanding on it! Seeing these solutions makes me realize that I still need to learn more of the fundamentals of JS. – tneilson08 Sep 26 '20 at 16:43