itch.io is community of indie game creators and players

Devlogs

When the Titanic Crashed to an Iceberg that was Close to the Destination, This is Truly How I Felt

An Approval to A Robot
A downloadable game for Windows and Linux

Day 2

Yep, I’m starting in day 2. I skipped day 1 because I was focusing on Go Godot Jam 3 and now here I am. Making another game for a game jam.

Anyway, the theme is “Love, Death, and Robots”. I immediately think of ideas on my book and I have three.

  • Robots fall in love, player can kill themselves and reboot in spawn area
  • Lover got kidnapped by evil robots, player must save and fight against deadly robots. Death gives power up, sorta
  • Player is a robot and needs to find love before death comes.
  • Couple fell in love and must escape from deadly robots. One die, all dies

All four ideas seem to be generic and I’m quite unsure which is the best idea. Although, the third idea stood out the most. Why?

First idea: Basically a puzzle platformer game. If executed well, game can be good.

Second idea: It’s just super Mario bros, but maybe with guns

Third idea: Story focused and can improve my writing dialogue skills, but questionable since this is only a week to make the game and I haven’t tried with this kind of genre before

Fourth idea: It’s just a two local player game, but with the second idea.

Now, I can choose the easy path and make another generic platformer game but with better controls and interesting mechanic, but I can try to make the visual novel focused game and improve my game development skills, which could be fun.

So, I decided to make a dating game on a Godot Engine. However, another idea pops up in mind which would improvise but make the development much difficult.

New idea: At the start of the game, player can chooses to be a human or a robot. The goal is quite simple; find love. If you are a human, find love or else be executed. If you are a robot, find love or else be terminated.

This new spark of idea might be good, but the difficult part is that I need to make the dialogue for the player to be in human or robot mode, and even the NPC the player will deal with.

As a start, I made the text box and the button for player to go auto mode or not

Just like that, I rested and moved on to the third day.

Day 3

On Day 3, I began working on creating the character sprites. Since I want this game to be involved in a speed dating thing, the player has only 60 seconds to communicate before time runs out. For now, I decided that the player is a robot and its goal is to find someone who truly loves it. I added a clock with a 60 second timer which would start counting down when it starts.

And then something hit me. If I were to make a speed dating game, there would no need for an auto text button. That means it’s going to be deleted and to the bin. I then created a human template with eyes, shirt, mouth, pants, and hair. I’m using a procedural generated human, with animated eyes and moving mouth, as well as 8 unique hairstyles and 8 different colors.

That is… until I realized that 8 is a bit too little for colors and colors can be more random.

As you can see, the skin, pants, and shirt aren’t animated sprites. They’re normal sprites. However, the eyes, mouth, and hair are animated sprite. Here’s something interesting about this. The hair will not be animated; therefore the animation speed is 0. It’s just to set the hairstyle at the start among the available eight. The eyes will blink at a random chance while the mouth only moves if the human is talking.

For the eyes to blink, just set the luck chance to happen every tick. Since every tick is normally around 60 FPS or even 120 FPS, I’ll set the possibility to only be… 0.1%. Very small chance, but that’s the thing. Eyes don’t blink all the time. The code is all easy it’s just these below.

func _ready():
    randomize()
    skin.modulate = Color(rand_range(0,1),rand_range(0,1),rand_range(0,1))
    pants.modulate = Color(rand_range(0,1),rand_range(0,1),rand_range(0,1))
    shirt.modulate = Color(rand_range(0,1),rand_range(0,1),rand_range(0,1))
    eyes.modulate = Color(rand_range(0,1),rand_range(0,1),rand_range(0,1))
    mouth.modulate = Color(rand_range(0,1),rand_range(0,1),rand_range(0,1))
    hair.frame = randi() % 8
    hair.modulate = Color(rand_range(0,1),rand_range(0,1),rand_range(0,1))
func _process(delta):
    randomize()
    var blink_luck = randi() % 1000
    if blink_luck < 10 and !eyes.playing:
        eyes.play()
func _on_Eyes_animation_finished():
    eyes.stop()
    eyes.frame = 0

Now, here comes the difficult part: Dialogue system. I have ever tried doing this technique on my previous game, Slime Rancher’s Revenge, when creating the intro story. However, the story is very straightforward and clean. This time, there are options and different stories the player can select.

Oh, also, I added so that the human can move around, hovering like a living being. Although it’s weird, but it seems alive, right? This time, the percentage of it moving is 5%. Here’s the full code of it.

func _process(delta):
    var move_luck = randi() % 100
    if move_luck < 5 and !tween.is_active():
        random_movement()
func random_movement():
    tween.interpolate_property(self,"position",position,Vector2(rand_range(-32,32),rand_range(-32,32)),1,Tween.TRANS_CUBIC,Tween.EASE_OUT_IN)
    tween.start()

The way the code is implemented is quite similar to the eye blinking, but there’s a function called random_movement that would run if the condition above is true in the process function.

Anyway, back to the dialog box problem, we just have to make the dialog box ourselves. And that will be Day 4’s problem.

Day 4

I started to study how to use Dialogue Manager, which is an addon for Godot developers to create a dialog system into the game. Unlike Dialogic, another addon, this one is easier to manage. There is only one weakness: Mouse input. It’s quite uncertain on how to use mouse input to detect the options, but that’s not too much of a problem. As long as the game works for now, it’s more than enough.

FolIowing the tutorial provided by the creator, https://www.youtube.com/watch?v=08HHSQGXfgM, I created an auto spinner to make the pointer spin, because everyone loves something spinning. I also modified the textbox to follow the color palette I chose on Day 3. I double checked the addons code and found that you can skip the whole text, which is something that I’d like to avoid. Remove that from the code, badabing badaboom and it's gone! No skipping!

I was working on with the writing and the story telling and came up with an idea. Humans like to talk about several things, such as hobbies, jobs, dreams, memories, families, and politics. Wait, politics? Yes! Here, there are six categories the player can talk about. Every category has different ways of a human thinks. The robot will learn what the humans have, both his suffering and his happiness. And good luck solving it all in 1 minute :D

As I kept reading the documentation of the addons, I understood how to use it. And as time goes on, the dialogue for the first human has been set. Well, not fully set, but partially. I’m planning to finish the code part, then writing the whole story by tomorrow.

First, we need a way so a new potential enters and leaves the setting. And that’s quite simple. The old one goes from the center to the left, then get destroyed. The new one appears on the other side and slides from right to center. We can easily do this using tween. Here’s a whole function on how it works!

func leave_and_arrive():
    var obj = human.instance()
    obj.position = Vector2(640,0)
    $Opposing/Tween.interpolate_property(get_node("Opposing").get_node("Human"),position,Vector2(0,0),Vector2(-640,0),1,Tween.TRANS_LINEAR,Tween.EASE_OUT_IN)
    get_node("Opposing").get_node("Human").queue_free()
    $Opposing.add_child(obj)
    $Opposing/Tween.interpolate_property(get_node("Opposing").get_node("Human"),position,Vector2(640,0),Vector2(0,0),1,Tween.TRANS_LINEAR,Tween.EASE_OUT_IN)
    yield($Opposing/Tween,"tween_completed")
    new_lover_potential()

 Yep! It’s that easy! The new_love_potential function is a function to set the new lover type for the dialogue to run.

func new_lover_potential():
    randomize()
    var customer_type : int = arr_customer[randi() % arr_customer.size()] arr_customer.erase(customer_type)
    $Clock/Timer.start()
    customer_love = 0
    Interface.show_dialogue("CustomerType"+str(customer_type),resource)

Don’t forget to erase that type of customer if we had gotten one. We don’t want any unnecessary multiple personalities but different humans. Oh, also, if the timer runs out, all we need to do is to commence the dialog that the timer is up. Remember that this needs to happen only once!

    if time_left == 0 and !stop:
        $Clock/Timer.stop()
        stop = true
        get_node("ExampleBalloon").queue_free()
        yield(get_tree().create_timer(3),"timeout")
        Interface.show_dialogue("TimeIsUp",resource)

That’s the reason there’s a Boolean variable in there.

Since this is speed dating, I believe background is also important. So I made one!

It may look very blurry and bright, but you’ll understand if you play the game.

Before I ended my day, I remembered I haven’t implement the mouth moving when the human is talking. Luckily, I inspected the addons and found that there is a way to check if it is typing or not. I then referenced it to a variable in the main script and check the variable. If the variable is true, then mouth play. If not, mouth stop. If you’re confused on where it is, it’s in the dialogue_label.gd, connected inside the balloon.

    if has_node("ExampleBalloon"):
        typing = get_node("ExampleBalloon").get_node("Balloon").get_node("Margin").get_node("VBox").get_node("Dialogue").is_typing

This one whole condition and expression explains it all. First, check if the ExampleBalloon node exists in the scene (therefore it won’t crash). Then, set the Boolean variable called typing to be the is_typing variable from the dialogue_label.gd. Simple as that.

Before I finally rest, I began planning out the whole schedule for the rest of the days, making sure that I finished the game without even overscoping it.

Day 5

 On this day, I planned to finish 3 character dialogues as well as making an emotion like in Animal Crossing whenever one is happy, annoyed, sad, disgusted, etc. And so, I started writing the dialogues… until I realized that politics isn’t a good idea as a topic. Guess that idea is thrown into the bin :P

I made 4 different emote for each human: Happy, Sad, Annoyed, and Surprised. These four emotions will tell the player whether what they did is good or bad. The positive emotes are happy and surprised while the negative emotes are annoyed and sad. Here is what they all look like.

In my opinion, they all look great. I’m surprised that I’ve improved this much.

Anyway, now we just have to set it as an animated sprite, set the speed to 0, add them all as frames, and then create some function that would show this emotions for just 1 sec before turning the visibility off again.

func show_happy():
    $Emotions.frame = 0    
    $Emotions.visible = true
    yield(get_tree().create_timer(1),"timeout")
    $Emotions.visible = false
func show_sad():
    $Emotions.frame = 1
    $Emotions.visible = true
    yield(get_tree().create_timer(1),"timeout")
    $Emotions.visible = false
func show_annoyed():
    $Emotions.frame = 2
    $Emotions.visible = true
    yield(get_tree().create_timer(1),"timeout")
    $Emotions.visible = false
func show_surprised():
    $Emotions.frame = 3
    $Emotions.visible = true
    yield(get_tree().create_timer(1),"timeout")
    $Emotions.visible = false

The code should be like these. And here, I know some of you are saying, “Why not just create one function that requires a parameter of integer to determine if they’re happy, sad, annoyed, or surprised instead? Like, if it’s sad, set the parameter to be 1 and then play the function like this instead!”

func show_emote(x : int):
    $Emotions.frame = x
    $Emotions.visible = true
    yield(get_tree().create_timer(1),"timeout")
    $Emotions.visible = false

Now, this is a good idea. However, there’s just one drawback: I need to memorize that 0 means happy, 1 means sad, 2 means annoyed, and 3 means surprised. Yes, I agree. It is easier and simpler. However, I prefer the other option because it’s easier to read by everyone. It’s not the most efficient, but at least it’s way better than just using if else conditions. *cough**cough* Yandere Dev *cough**cough*

On the way of the development progress, there was a problem. There might be too many people to be created. I was thinking maybe a little less, maybe 5 or 7. I also didn’t forget that this is a game jam and people want to play one game with another. So, ten is a bit too much. I decided to reduce it to just 5, but the player has longer time to talk to them. I’m considering 90 seconds or 120 is more than enough.

The best thing is that “Death” from the theme is inevitable. Really. If you watch the video How Machines Work by CGP Grey, https://www.youtube.com/watch?v=R9OHn5ZF4Uo, I remembered that robots that took the test and failed would be recycled while the best were gone to one side. In other words, the goal of this game is to make all humans fall in love with the robot. Therefore there can only be two endings. One is where all humans love, while the other is at least one human doesn't.

Also, if you’re curious what the Dialogue Manager Addons looks like, here’s the script for the dialogue. A small glimpse of it. It’s actually this simple. It’s all coding and all that, but it’s almost as easy as Twine, another game engine to make text based games.

Oh yeah, I removed the option to talk about “Family” since I feel that topic is a bit personal.  Therefore, there can only be 5 topics to talk about.

I’ve successfully created five different characters, each having different personality. We have Jason, Maddie, Peter, Josh, and Lily. Jason is the normal kind of guy, Maddie is the so-called princess, Peter is energetic, Josh is weird, and Lily is quirky. Each of them will have different memories, hobbies, jobs, friends, and dreams.

Here, I could’ve continued working on with the game. But everything is ruined thanks to an assignment which was given on that day to replace the class’ midterm exam. And because I don’t want to waste any more time, I’d prefer to do it immediately.

It took the whole day to do the assignment and yet it’s still not done. However, I managed to make partial of the main menu before I finally rest.

Day 6

Assignment isn’t done, but it’s more than enough. Anyway, let’s work on the game again. I worked on the main menu and finished creating the design. Now, it’s time to make them all working properly.

…here, I hate my brain for telling me to create an options button. I’ve never done that in my life as I would always ignore that one thing: creating an options for the player. But, I’m gonna try to make it.

And finally, after a few hours, the whole main menu, both design and program, is done.

If you take a look at the screen, the main menu has an instructions now. The instructions will tell the player how to play, both for the menu and for the game. The code is quite simple, although it’s a lot. We need the player to go up and down, right? So if press “go up” then selection_value subtract by 1 and if press “go down”, selection_value add by 1. What if it goes out of bound? Clamp it! Godot has a feature to clamp any numerical values from what value to what value. The code is as such!

    selection_value = clamp(selection_value,0,2)
    if !active_options:
        if Input.is_action_just_pressed("ui_up"):
            selection_value -= 1
        if Input.is_action_just_pressed("ui_down"):
            selection_value += 1

By the way, there is a condition called !active_options, which means if the active_options is false. This is a Boolean variable that will be discussed later below.

Don’t forget that we need the player to see which one is the player choosing. Therefore:

    match selection_value:
        0:
            play.frame = 1
            options.frame = 0
            credits.frame = 0
        1:
            play.frame = 0
            options.frame = 1
            credits.frame = 0
        2:
            play.frame = 0
            options.frame = 0
            credits.frame = 1

Godot’s match function is similar to a switch case in normal programming. Here, we compare which one is the value of the selection_value. If it’s 0, then the play button is being selected. If it’s 1, options is being selected. If 2, credits is being selected. You can see that the value 1 in the frame means it is selected while 0 means it’s not.

Then, we need the acceptance switch. If the player accepts at a certain selection_value, do this.

    if Input.is_action_just_pressed("ui_accept"):
        if $Credits.visible:
            $Credits.visible = false
        else:
            match selection_value:
                0:
                    pass
#This is to change scene to the game. I forgot to add it in the latest code.
                1:
                    $Options.visible = true
                2:
                    $Credits.visible = true

Here’s something that you need to know. The credits function does nothing. It just shows the credits and nothing else. Here’s what it looks like.

Therefore, just press space and it’s gonna be invisible. For the options, however, it’s quite different.

Since the options are limited, the player can only set the volume, sound effects, and people talking sound in an interval of 25%. Here’s what the options looks like.

And here’s how the code looks like.

    match selection_value:
        0:
            $Music/AnimatedSprite.modulate = Color(0,0,0)
            $"Sound FX/AnimatedSprite".modulate = Color(1,1,1)
            $"Human Talking/AnimatedSprite".modulate = Color(1,1,1)
            $Back.modulate = Color(1,1,1)
        1:
            $Music/AnimatedSprite.modulate = Color(1,1,1)
            $"Sound FX/AnimatedSprite".modulate = Color(0,0,0)
            $"Human Talking/AnimatedSprite".modulate = Color(1,1,1)
            $Back.modulate = Color(1,1,1)
        2:
            $Music/AnimatedSprite.modulate = Color(1,1,1)
            $"Human Talking/AnimatedSprite".modulate = Color(0,0,0)
            $"Sound FX/AnimatedSprite".modulate = Color(1,1,1)
            $Back.modulate = Color(1,1,1)
        3:
            $Music/AnimatedSprite.modulate = Color(1,1,1)
            $"Sound FX/AnimatedSprite".modulate = Color(1,1,1)
            $Back.modulate = Color(0,0,0)
            $"Human Talking/AnimatedSprite".modulate = Color(1,1,1)
    selection_value = clamp(selection_value,0,3)
    $Music/AnimatedSprite.frame = 4 + int(Interface.music_volume/25)
    $"Sound FX/AnimatedSprite".frame = 4 + int(Interface.sfx_volume/25)
    $"Human Talking/AnimatedSprite".frame = 4 + int(Interface.people_talking_volume/25)
    if visible:
        if Input.is_action_just_pressed("ui_up"):
            selection_value -= 1
        if Input.is_action_just_pressed("ui_down"):
            selection_value += 1
        if Input.is_action_just_pressed("ui_accept"):
            match selection_value:
                0:
                    if Interface.music_volume == -100:
                        Interface.music_volume = 0
                    else:
                        Interface.music_volume -= 25
                1:
                    if Interface.sfx_volume == -100:
                        Interface.sfx_volume = 0
                    else:
                        Interface.sfx_volume -= 25
                2:
                    if Interface.people_talking_volume == -100:
                        Interface.people_talking_volume = 0
                    else:
                        Interface.people_talking_volume -= 25
                3:
                    visible = false

If you take a look, the code is quite similar to the main menu, just some difference. I’m not gonna tell too much of this whole thing. It’s just that the difference is located that instead of frames changing, the colors changing. And if the player accepts at a selection where the selection_value is not 3, then it’ll subtract the volume value by 25. Also, the “Interface.” here means that the Interface is an autoload, which means it will exist the moment we start the game. And if the value is more than -100, go back to 0. Now you may be wondering, “Why do you subtract but not add instead?” Because that’s not how sounds work. The smaller the decibels, the quieter the sound is. However, -10dB means the sound cuts by half while -100dB is a quarter of the original. However, -100dB is so quiet we can set it as 0%.

Listen I’m tired of writing this devlog while making the game so please cut me some slack.

Anyway, with all of that is done, it’s time to continue making the dialogue. That is… until I add some sound effects for the menu. Yep, I added sound effects and the sound effects for people to talk. No issues at all, just simple a b c.

Soon, it was time to make the whole dialogue. 5 hours of progress, thinking of what Jason should reply with, and Jason is done. That’s all I did for the day. It was time consuming, tiring, and realized of how much I overscoped, I think.

Before I ended the day, I finished making the intro cutscene of the game. The way the cutscene works is the same as the way my previous game works. The code is also the same. Therefore, I don’t think it would be too much problem to make it.

var typing : bool = true
var scene : int = 0
var lapsed : float
const text_label = ["One day, there is a laboratory called AI Lab, working on a robot that could help with humans communicating with one another.",                                                                         "The robot is called COM-69420. He can talk with certain topics with other humans, greet them, and have positive feedback afterwards.", "This robot, however, is still under prototype process. The goal for this robot is to get all customers to be fully satisfied and loved the robot.", "If there is at least one customer not satisfied or dislike the robot, then he will be returned to be manufactured until he is fully ready to be published.", "The test is about to begin. Good luck, COM-69420"]
func _process(delta):
    var total_letters = len(text_label[scene])
    lapsed += 1
    if lapsed >= total_letters:
        typing = false
    $Label.visible_characters = lapsed
    $Label.text = text_label[scene]
    $AnimatedSprite.frame = scene
    if Input.is_action_just_pressed("ui_accept"):
        if !typing:
            if scene == 4:
                get_tree().change_scene("res://GameTest.tscn")
            next_scene()
        else:
            lapsed = total_letters
func next_scene():
    typing = true
    lapsed = 0
    scene += 1

It’s something like this for the code. It’s more of to auto typewriter on the label. Then, the animated sprite, just like usual, have the speed to be 0 and the only trigger is the space button. Here’s how the design looks.

If you played my previous game, Slime Rancher's Revenge, it's similar to that mechanic. It's the laziest thing I can do, but if it works, it works. 

Day 7

With only 2 days remaining, the only things that aren’t done are the ending cutscene and the dialogue. I won’t be telling much about the dialogue because there would be spoilers. I will, however, explain how to implement the subtheme from the game jam. (This is tiring for just a solo developer…)

The subtheme is The Price of Freedom. And the price of freedom here is responsibility. You can do whatever you want in this world, such as build a house, buy stuff, getting married, getting into college, and others. You are free to do anything, but the cost of that freedom is responsibility. You gain responsibility to take care of the house once you owned it. You gain responsibility to make good use of the stuff when you buy them. When you get something, that thing becomes your responsibility.

Every customer in the game have some kind of freedom they want, but they need to take the price. They will talk about this stuff to the robot, if they reach to the right topic and the right responds. Of course, I won’t tell you where it is. This would be spoilers and the only way to find it is through conversation. You can interpret it all you like, so good luck.

I’ll come up with the others later in the next day. Right now, I need sleep. Oh, also, Lily’s character is fully done.  Jason and Lily are done, so now only 3 left. Day 8 is gonna be a hassle…

Oh, Easter Eggs? Yeah, that plan is scrapped. There will be no easter eggs. I changed my mind at the end of the day. It’s too difficult to think.

Day 8

Same goes for Day 7, I finished every dialogue up.  And then, it was time for music, sound effects, and a little bit of post processing. Everything was going great. I even added some music here and there and everything should be just fine. Of course, my boyfriend helped me with this stuff as well.  There was nothing interesting on this day. Just adding music, sound effects, edits, etc.

Oh, and I also made the ending for the game it's quite similar to the intro, but a little bit different. In the ending, it's either a success or a failure. We will check an array that shows the results of the customer's love. If one of them is below 10, then the game counts as a failure. But will the player knows if the love is below 10? Never. This is where the strategy comes in. So good luck smucko.

Day 9

On this day, I submitted the game. I had an exam on Monday, so I focused on studying that instead. I added some sound effects and other stuff that would polish the game, export to HTML5, and finally study for the rest for the day.

However, something happened on this day. My computer got blue screen when I was working on the game. Fortunately, not a single file was corrupted. So, everything's going to be fine. I continued exporting, submitted, and then study. (This is foreshadowing)

Aftermath If you read the title, then you know something bad happened. All because of that bluescreen, the HTML5 doesn't work. I tried replacing the project, it didn't work. I tried googling, no solutions. Honestly, it was tiring and stressful as I couldn't find a way to make the game work. I was about to give up... until I had to accept that the game will not be playable on HTML5, but rather on executable file. I can't even upload it to the website just because the game jam is still under voting session. Not to mention that the exam was harder than I thought I had a feeling I'd fail the exam.

Well... is this it? Is it all over? Do I just simply give up? No. I created a separate website that connects to the official website, making sure that everyone can play the game in the executable files, both for Windows and Linux. And here, I'm just hoping people would play and rate my game. But... will people actually play the game? Will they actually like it? Is it still count as a submission? Is it o-

Oh, the CEO of GoedWare DM'ed me.

Well, change my mind. It's still a valid submission!

Files

  • GWGJ#5 Linux.zip 19 MB
    Jun 13, 2022
  • GWGJ#5 Exe.zip 18 MB
    Jun 13, 2022
Download An Approval to A Robot
Leave a comment