Making a game with Javascript, kinda like a visual novel

Viewed 653

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>

2 Answers

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);.

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.

Related