Mastodon Icon GitHub Icon LinkedIn Icon RSS Icon

Bacon Game Jam 09: Back in Business

Hi everyone! I’m here to tell you the story of my last game jam: the Bacon Game Jam 09. It was a long time since my last game jam (and my game-dev stuff in general). I was a bit distracted by other part of my life and, in general, I wasn’t in the best mood. Because of this, I’m very happy to partecipate to  this jam, also if the final result is not completely satisfying.

Anyway, let’s start!

The Jam

The Bacon Game Jam (formally Reddit Game Jam) is a game jam organized by the Reddit gamedev community. It is one of the oldest (before they’re starting to appear anywhere), not at Ludum Dare level, but it is definitely a good one. In the 9th edition it was a **48-hours random theme **jam, starting from Friday 12th June 22:00 UTC to Sunday 14th June 22:00 UTC. It is not a really good time intervall, as I’ll explain later, and 48 hours jame are the most challenging. Ah, Depth, was the theme of the Jam.

Day 1 - Just Thinking

As I said before, the jam started in a calm and hot Friday night at 22:00 UTC. In Italy it is exactly midnight. After a stressful (or at least mentally challenging) day of work. It is not a good start. However, me and my usual fellow (@mauriliodc) was ready. He bought the usual _“Game Jam Starting Kit” _consisting in various unhealthy snacks and caffeinated drinks; everything was set up.  This time there was another friend in the team: @FabroNencio that was finally available to work with us. I am very glad of this!

We waited for midnight together, watching stuff and games on the internet. Then the theme comes out! It was Depth. Mmm.  Not a very inspiring one I have to say, but it’s ok. There was several obvious direction to follow:

  • Depth, as focal depth or layers.
  • Depth as underground.
  • Depth as underwater.
  • Depth as “deep space”.

In fact, most of the jam entry are developed around these themes. I tried to find a smarter interpretation but I failed. Moreover, this time I really wanted to do something extremely simple, but more refined. I partially achieved this.

We spent about two hours in brainstorming. After this, we had a bunch of ideas:

  • A game involving drills underground (there was several partial ideas moving in several directions).
  • A game involving “water diviners” finding water in a desert. I still have to understand where was the game part of this idea. :D
  • A game involving submarines war.
  • A single-input reverse-jumping game involving a dwarf jumping down into a well/mine.

However none of this was successfully acclaimed that night. Then I remembered of an idea Maurilio had after buying this poster at IKEA:

The inspiring Magic Carp-et IKEA poster.
Figure 1. The inspiring Magic Carp-et IKEA poster.

Knights riding fishes fighting in an arena? We can do it. The result was not what I initially thought but it was better than my expectations. At 3:00 AM we decided to go to sleep.

Day 2 - The Hard Part

The morning of day 2 we will wake up full of energy. I have to say that we had the biggest start since I do game jams. Three programmers, finally able to work at the same time without dead times.

We started setting up the environment: Unity3d, as usual. Also if I started exploring several other platforms (such as Phaser for full-HTML games, and the Unreal Engine) I still find more intuitive to do game jams with Unity for a single but huge feature: it requires zero-start time. Then we started assigning the tasks:

  • Maurilio, as usual, started with the graphical details. I always repeat that that is not the right thing to do with a 3 programmers team but I think that is impossible to change his mind. Anyway, it did an amazing job. For a non-artist I think the result is pretty amazing: the arena is quite vibrant, a bit crispy, but I think it is a great success.
  • Me. My goal for this jam is to do something that I’ve never done before. You know, it is my usual jam approach. This time, I decided to do de dialogues. Tell stories, is the reason for which I’d like to do games, unfortunately, this is something that is always put apart during a game jam. This time I wanted to tell a story, a bad and simple one, but I wanna to. I did music too.
  • Fabrizio, was the jolly man. Due to his lack of experience in jam, he decided to put this work to fill all the other gap. So he went straight for the hard programming stuff. At the end, he did more then 50% of all the code in the game.

The Dialogue System

My first goal was to build a piece of code to handle an intro story (and a final story). The idea is simple: two main elements, the narrator, who wrote the story in the middle of the screen, and the dialogue box, an UI element with an avatar and a text field to describe what the characters say. Plus: the annoying sound when the chars are typed.

On top of these two elements there is the “narrator” who is in charge to tell the actual story, triggering the right events, and so on. The narrator is, in practice, a simple Final State Machine in which each state represent a step in the narration and the only actions available are “next”, who proceed to the next step, or “skip” who skip the full story segment. The story is stored in a simple dictionary with the text of the narration step, a flag indicating if the text is a global narration or a character dialogue and, in the latter, the avatar of the speaking character. At this point the narrator just have to handle the FSM and trigger the right effects for each state. Quite simple and effective.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class AutoType : MonoBehaviour {

    public float letterPause = 0.2f;
    public AudioClip sound;

    new Text guiText;

    private IEnumerator runningType;

    // Use this for initialization
    void Awake() {
        this.guiText = GetComponent<Text>();  
    }

    public void TypeText(string message) {
        this.guiText.text = "";
        this.runningType = TypeTextIteration(message);
        StartCoroutine(this.runningType);
    }

    public void StopType() {
        if (runningType != null) {
            StopCoroutine(runningType);
            this.runningType = null;
        }
    }

    IEnumerator TypeTextIteration(string message) {
        foreach (char letter in message.ToCharArray()) {
            this.guiText.text += letter;
            if (sound)
                AudioSource.PlayClipAtPoint(sound, Vector3.zero, 0.6f);
            yield return new WaitForSeconds(letterPause);
        }
    }
}

There is also another small trick. I did not want that the text appear all at once, so I’ve implemented a small Unity component to allow to “type” the text char by char. It exposes a Type function that can be used to overwrite the current UnityUI Text component’s text with a new one, char by char. You can see an example in the following video while the code is just in the box above.

I think it is quite easy and nice. :)

The Basic Enemy AI

Another task I’ve done that day is to write the backbone of the enemy AI. For this task I used the traditional “agent-steering” architecture. If you are not familiar with this, I’ll try to explain a bit more. The main element is the steering data structure. This is just a container who store the desired linear and angular velocity of the agent. Then, the agent AI behavior is divided into two separate components: the first one is the agent component, who contain a steering variable and a setter for it, while the second one is the agent-behavior component whose goal is to provide to the agent component the right steering data. We can now provide several behaviors extending the base agent-behavior component. For instance, we can define a seeking-behavior computing at each steering object who point to the target position.

However, seeking is not enough. I also want that the enemy fishes do not start to pile up in disorder around the player. To avoid this, we need to add a “separation steering” element to the previous steering. Luckily, computing the separation steering is quite easy: we just need to identify all the fishes in a certain radius around the controlled fish, sum all the vectors who connect the fish to each neighbour, normalize and then add this value to the seeking steering (with a certain weight).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class FishSeeking : FishBehavior {

    public float seekingWeight = 0.7f;
    public float separationWeight = 0.5f;

    public override Steering GetSteering() {
        Steering steering = new Steering();
        Vector3 targetDirection = target.transform.position - transform.position;
        Vector3 separationVector = ComputeSeparation();
        steering.linear = targetDirection * seekingWeight + separationVector * separationWeight;
        steering.linear.Normalize();
        steering.linear = steering.linear * fish.maxAccel;
        float angle = AngleSigned(transform.right, targetDirection, new Vector3(0.0f,0.0f,1.0f));
        return steering;
    }

    public static float AngleSigned(Vector3 v1, Vector3 v2, Vector3 n) {
        return Mathf.Atan2(
            Vector3.Dot(n, Vector3.Cross(v1, v2)),
            Vector3.Dot(v1, v2)) * Mathf.Rad2Deg;
    }

    private Vector3 ComputeSeparation() {
        Vector3 fishAvoidance = new Vector3();
        var fishes = GetFishesInRadius(1);
        foreach (Fish f in fishes) {
            fishAvoidance += (f.transform.position - transform.position);
        }
        fishAvoidance = - (fishAvoidance / fishes.Count);
        fishAvoidance.Normalize();
        return fishAvoidance;
    }

    private List<Fish> GetFishesInRadius(float radius) {
        Collider[] colliders = Physics.OverlapSphere(transform.position, radius);
        List<Fish> fishes = new List<Fish>();
        for (var i=0;i<colliders.Length;i++) {
            if (colliders[i].gameObject.tag == "Fish") {
                fishes.Add(colliders[i].gameObject.GetComponent<Fish>());
            }
        }
        return fishes;
    }
}

The result can be seen in the following video. :D

Day 3 - The End

The next day, I started to feel a bit tired. Since the start of the jam, only a bit more than 30 hours are passed but, for me, this feel like the third day of the jam. And, in fact, two night was passed from the start. Never mind if the first day was only night! Moreover, we knew that we could not finish this work after, let’s say, 5 pm, because at that time we would be too tired for sure! In the third day I had two tasks: complete all the transaction between the scenes and start adding the music. Unfortunately, this time, we had no musician with us (or, let explain this better, we didn’t get the music in time for the deadline). So I started to write some placeholder music by myself. I’m quite happy for this! In the past, I’ve used to write music but then I spent ten years without touching any electronic composition software. I feel happy that I can still do something. Not something really good, of course, but something! You can find the Last Fish Swimming soundtrack just here! :)

Be kind!

And Now?

You can download the game **here. **Unfortunately we have only windows binaries because of the Unity Web

Player problem (and WebGL refused to work). At the end, this is my takeaway:

  • We really need to stop try do to graphic in game jams. This time was quite good, but it is not our skill for now and we lose too much time on it!
  • I  did music again! Whooo.
  • I finally put some story in the game! Whooo bis!
  • I still know that I love writing games. That’s a relief.
  • Fabrizio is a really good jam crew member.

I think I already talked too much for this space. I hope you enjoy this very small diary of the jam.

Cheers.

comments powered by Disqus