I cookies sono quelle brutte cose che tanto hanno portato dolore a noi sviluppatori; servono principalmente per capire chi sei, cosa fai, e come vendere i tuoi dati, in ogni caso niente di grave. Chiudendo questo banner o continuando la navigazione sul sito acconsenti all'utilizzo dei cookies e la tua privacy sarà per sempre compromessa, ma tanto lo è già, quindi tanto vale. Leggi tuttoChiudi

Postmortem

BLACKSUN

The result of Ludum Dare 50, a famous (and tiring) game jam

BLACKSUN on itch.io
Link to the Github repository

Introduction

Some time ago, exactly the first weekend of April, I had the foolish idea to participate to a Ludum Dare, right on its 50th edition. It's not the first time I participate to this event, but I never delivered before. But I knew that was the right time, and I waited until 3AM on Friday night for the theme, full of hopes and determination. Oh boy, was I wrong.

Ludum Dare has two different competition: the "Compo" is the OG format, you have to work alone, you have to create everything on your own and you have to deliver in 48. The "Jam" instead has no limit of members per team, it's a little more loose on what you can use and is 72h long. Of course I chose the "Compo", because I love quiet and relaxing weekends.

The theme actually was not that bad: "Delay the inevitable" is evocative and broad enough to fit a bunch a different ideas. I went to bed after a couple of minutes with only a seed of an idea.

I woke up fairly early next morning and I started to write down ideas. After a couple of dozens of minutes I have a good laid plan in front of me. The game theme and lore will orbit aroud the Aztec myth of the Five Suns: the player has to manage different resources to satisfy the demands of the gods and delay the fifth sun, the black one, and the consequent end of humanity.

Inspiration

I don't have enough creativity to birth a fresh new idea in half an hour, so i "borrowed" concept from different games to build mine. Please, don't start on stealing ideas, "Imitation is the sincerest form of flattery" told a weird guy called Wilde and yada yada yada, eventually ideas have no value and so on. I can continue on this topic for hours and be extremely boring too, so let's move on.

The main two games I analyzed to create BLACKSUN were "A Dark Room" (wiki page for more in-depth explanation) and "Neverending Legacy", a very obscure game from the creator of the infamous "Cookie Cliker".

If you play to them you can find a string connecting those two games and BLACKSUN: people management, incremental progress and a lot of different resources. In BLACKSUN there aren't so many building to unlock, I had not so much time to create them and every new building means more time on balancing.

Now I have a plan and a somewhat clear vision on what I have to do and how to do that. I already published this postmorted in the Ludum Dare blog and I will copy it here, correcting some things and expading others, but it will focused mainly on the actual programming of the game using C# through Unity Engine. It will not be a strict explanation of all the code, but some highlights I found to be helpful on the future.

Singleton Pattern

The singleton pattern is a very diffuse software pattern that let you instantiate a specific class (MonoBehaviour in our case) once, like it is a global variable. This is often seen as anti-pattern (something to avoid because prone to problems later in the project), but I find it very useful in Unity.

In practical usage let you access a MonoBehaviour object almost as static, so you don't have to link the object around the scene. Usually I have a block of important objects I use frequently called managers, like in my case the GameManager, the ResourceManager and the AltarManager.

There are different ways to create a singleton in Unity, each one with its bonus and malus, personally I use this one:

using UnityEngine;

public class GameManager : MonoBehaviour
{
    static GameManager _instance;
    public static GameManager Instance
    {
        get
        {
            if (_instance == null) _instance = FindObjectOfType();
            return _instance;
        }
    }

    // Public variables
    public bool IsPlaying;
}

The script declare a static property of the GameManager object returning the *non-static* _instance variable. If _instance does not exists yet, then search for it. That means if also you put two GameObject in your scene, both with the GameManager component attached, GameManager.Instance will refer to only one of these two. Then, if I want to get the IsPlaying variable from anywhere in the project, I just have to write GameManager.Instance.IsPlaying.

The theory behind a generic singleton pattern is not really intuitive to get in the beginning, but trying the pattern on Unity really shows how it's used and why.

After the jam I kept exploring the idea of using a faster way to create singletons. The first solution I had is stuffing the code above in a VS Code snippet. The solution was working great, but I really wanted to keep the code clean and understandable, without repetitions (DRY, you know that?).

In the end I created an abstract class with a bunch of C# generics and seems it's working fine for my needs:

using UnityEngine;

namespace PlusLib
{
    public abstract class Singleton : MonoBehaviour where T : class
    {
        private static T _instance = null;

        public static T Instance
        {
            get
            {
                if (_instance == null)
                {
                    _instance = GameObject.FindObjectOfType(typeof(T)) as T;
                    if (_instance == null) Debug.LogError("[Singleton] Could not found GameObject of type " + typeof(T).Name);
                }
                return _instance;
            }
        }
    }

}

Defining the ticks per second

In the beginning there was nothing, then Unity created Start and Update. But Update has a problem! It runs every frame, not caring if the game is running at 5 or 500 FPS, and I cannot calculate the duration of a job based on Update!

Yes, I could have used FixedUpdate, running on a fixed physics engine timeframe, but it's not really meant for that and I wanted more granular control. So I went for writing a tick clock by myself!

The system is actually pretty easy, it consists of three components: the interface, the registration and the propagator (not real computer science name, I like to be a little dramatic).

The interface is barely a couple of lines: its job is define a method that a class must have when it's inherited. The benefits of this method is that I can inherit the interface on multiple objects, and still I can fit them all into a single List. This is the code for the interface:

public interface IOnTickHandler
{
    public abstract void OnTick();
}

Pretty simple, huh? The interface just defines the signature of the method that must be implemented later in the class. Every class can implement the method as needed, like this:

using UnityEngine;

public class JobView : MonoBehaviour, IOnTickHandler
{
    private void Start()
    {
        GameManager.Instance.Register(this);
    }

    public void OnTick()
    {
        // Do stuff here
    }
}

This script do three things: inheriting the interface, implementing the method and registering the object. But why the registration is needed? You can find the solution on the GameManager code:

using System.Collections.Generic;
using UnityEngine;

public class GameManager : MonoBehaviour
{
    // Singleton implementation, see above

    public float TickPerSecond = 20f;

    float _time;
    long _ticks = 0;

    List _jobs = new List();

    private void Update()
    {
        if (_playing)
        {
            _time += Time.deltaTime;
            if (_time > 1f / TickPerSecond)
            {
                _ticks++;
                _time -= 1f/ TickPerSecond;

                foreach (IOnTickHandler jv in _jobs.ToArray()) jv.OnTick();
            }
        }
    }
}

The object with the interface inherited is added to a List, then in the Update function I sum Time.deltaTime (the time past from the last time Update is called) and check for a new tick. If the new tick has happened, cycle every object that inherit the interface and basically propagate the information that there is a new tick.

And that's conclude the whole lap. It's a little convoluted and probably overengineered for a game jam, but in this way I can control from the GameManager component the speed of the game during the game itself, and it could be extremely useful!

Difficulty ramp

I am not a game designer, and resource management games needs a lot of balancing, because there is the risk that a skilled player can manage to gather enough resources to keep playing the game indefinitely.

To solve this problem I've implemented an incrementing difficulty ramp. The variable is defined in the GameManager script:

using UnityEngine;

public class GameManager : MonoBehaviour
{
    public float Difficulty = 1f;

    private void Update()
    {
        // ...

        if (_time > 1f / TickPerSecond)
        {
            // ...

            Difficulty += .0001f;

            // ...
        }
    }
}

For each tick, increment the Difficulty variable a little. Then this variable is used in various places around the game, like in the AltarManager script to pick the amount of resources to demand:

using UnityEngine;

public class AltarManager : MonoBehaviour
{
    public void SpawnHead()
    {
        // ...

        int count = Mathf.RoundToInt(Random.Range(1, 3) * GameManager.Instance.Difficulty * (Random.value < .5f ? 3 : 5));

        // ...
    }
}

In this way the amount of resources is not only dependent from a random value, but it's also biased by the difficulty of the game. In the end some players managed to survive basically forever, but this method shoud have worked for most of the games.

Thought after the jam: in the end it worked property for a while. I had reports of players reaching 1000+ villagers, and by my calculations they played for almost two hours straight (the game has no save system at all), so with the right combination you can keep it going forever. But still, the majority of the games were covered, not bad for balancing the game for whole 15 minutes.

The names of the gods

This is a last moment addition, only for giggles: on the tooltip for the god head you can found the god's name and his/her domain. This elements are generated randomly, you can find the code in the GodHead script:

using System.Collections.Generic;
using UnityEngine;

public class GodHead : MonoBehaviour
{
    public static List Names = new List() {
        "Macuilcozcacuauhtli", "Macuilcuetzpalin", "Macuilmalinalli", "Macuiltochtli", "Macuilxochitl", "Cuahuitlicac", "Patecatl", "Ixtlilton", "Ometochtli", "Tezcatzoncatl", "Tlilhua",
        "Toltecatl", "Tepoztecatl", "Texcatzonatl", "Colhuatzincatl", "Macuiltochtli", "Iztacuhca", "Tlatlauhca", "Cozauhca", "Yayauhca", "Cipactonal", "Huehuecoyotl", "Huehueteotl",
        "Mictlanpachecatl", "Cihuatecayotl", "Tlalocayotl", "Huitztlampaehecatl", "Quetzalcoatl", "Xiuhtecuhtli", "Mictlantecuhtli", "Acolmiztli", "Techlotl", "Nextepeua", "Iixpuzteque", "Tzontemoc",
        "Xolotl", "Cuaxolotl", "Tloque-Nahuaque", "Ometeotl", "Ometecuhtli", "Tonacatecuhtli", "Piltzintecuhtli", "Citlalatonac", "Tonatiuh", "Nanauatzin", "Tecciztecatl", "Tlahuizcalpantecuhtli",
        "Xolotl", "Xocotl", "Tezcatlipoca", "Quetzalcoatl", "Xipe-Totec", "Huitzilopochtli", "Painal", "Tlacahuepan", "Tepeyollotl", "Itzcaque",
        "Chalchiutotolin", "Ixquitecatl", "Itztlacoliuhqui", "Macuiltotec", "Itztli", "Amapan", "Uappatzin", "Itzpapalotltotec", "Miquiztlitecuhtli", "Tlaloc", "Tlaloque", "Chalchiuhtlatonal",
        "Atlaua", "Opochtli", "Teoyaomiqui", "Tlaltecayoa", "Cipactli", "Itztapaltotec", "Cinteotl", "Ppillimtec", "Omacatl", "Chicomexochtli",
        "Chiconahuiehecatl", "Coyotlinahual", "Xoaltecuhtli", "Xippilli", "Xochipilli"
    };

    public static List Domains = new List() {
        "stars", "medicine", "fertility", "underworld", "ballgame", "sacrifice", "earth", "art", "excess", "pleasure", "gluttony", "music", "gambling",
        "healing", "peyote", "maize", "astrology", "old-age", "deception", "wisdom", "winds", "light", "fire"
    };

    public string Name;
    public string Domain;

    public void Initialize()
    {
        Name = GodHead.Names.PickRandom();
        Domain = Random.value < .5f ? "Goddess of " : "God of ";
        Domain += GodHead.Domains.PickRandom();
    }
}

It's not really remarkable, if not for the PickRandom() method: this method is not native of the List object, instead is added by the developer using what's called an extension method. You can find them on the EnumerableExtension script:

using System;
using System.Collections.Generic;
using System.Linq;

public static class EnumerableExtension
{
    public static T PickRandom(this IEnumerable source)
    {
        return source.PickRandom(1).Single();
    }

    public static IEnumerable PickRandom(this IEnumerable source, int count)
    {
        return source.Shuffle().Take(count);
    }

    public static IEnumerable Shuffle(this IEnumerable source)
    {
        return source.OrderBy(x => Guid.NewGuid());
    }
}

Those methods are using generics and LINQ queries, but the gist of it is that returns the first element of a shuffled list.

Pretty neat, huh? You can achieve the same result using a static method asking the list as parameter, but I'm a sucker for syntactic sugar, hence the expansion. Don't hate me please.

Clicking on the elements

I have doing UI stuff. I really despise it. And of course my games are heavily UI based, guess I hate myself a little too. Making the player interact with UI, but still keeping a GameObject is boring: you can create a canvas for each GameObject that needs UI elements, or you can set your UI in world space and move GameObject and UI element together, and both methods seems a little hacky.

I need that the player can click on a GameObject, but usually require casting a ray from the camera to the mouse position, but converted in world space and not in screen space. Then the method that shoots the ray should propagate the event to the correct object and make sure that the object handles it. A lot of work, isn't it?

Luckily, Unity warlords developers provide us a couple of useful interfaces: IPointerClickHandler, IPointerEnterHandler and IPointerExitHandler. An explanation about the interface is provided a little above.

This those three interfaces I can handle all mouse events without caring to shooting rays, setting up colliders and propagating things. Thank you Unity devs!

I used them mainly in GodHead and JobView scripts to handle the assignations and the tooltip. The methods provides also an object with a lot of useful informations, like the mouse position and the button clicked. This is the JobView script:

using UnityEngine;
using UnityEngine.EventSystems;

public class JobView : MonoBehaviour, IPointerClickHandler, IPointerEnterHandler, IPointerExitHandler
{
    public void OnPointerClick(PointerEventData e)
    {
        if (e.button == PointerEventData.InputButton.Left)
        {
            // Assign a villager to this job
        }

        if (e.button == PointerEventData.InputButton.Right)
        {
            // Remove a village from this job
        }
    }

    public void OnPointerEnter(PointerEventData eventData)
    {
        Tooltip.Instance.ShowJob(Job);
    }

    public void OnPointerExit(PointerEventData eventData)
    {
        Tooltip.Instance.Hide();
    }
}

I found this method straightforward and easy to use. The only thing you have to care about is setting up a Collider2D component (otherwise the events won't trigger) and be careful to not overlapping them with other UI elements blocking the raycasting.

A little take on shading

I didn't used so much shaders for this project: the only one is a shader assigned to the god heads, changing the color of them while the time is running out. The shader itself is pretty basic, plus a couple of lines to make it work with the alpha.

The shader is pretty hacked up together, time was running out, the shader is called GodHead_Shader (here's only the interesting part):

sampler2D _MainTex;

float _Timer;
float _Darken;

fixed4 frag (v2f i) : SV_Target
{
    fixed4 col = tex2D(_MainTex, i.uv) * i.color;

    float4 alt = col * _Darken;
    float blend = step(_Timer, i.uv.y);

    clip(col.a - 0.5f);

    return lerp(col, alt, blend);
}

In the fragment shader I use a step between the UV on the Y axis and an external property to change the color to a darker one. I can control the external property via script, like in the GodHead script:

using UnityEngine;

public class GodHead : MonoBehaviour
{
    public SpriteRenderer Head;
    public float TotalTicks;

    float _pastTicks = 0f;

    public void OnTick()
    {
        _pastTicks++;
        Head.material.SetFloat("_Timer", Mathf.Clamp01(1f - (_pastTicks / TotalTicks)));
    }
}

The clip function on the shader basically skip rendering the pixels when its argument is below zero. Writing the fragment shader this way make a cutout shader, where a pixel can be fully opaque or fully transparent, with to actual transparency.

Building and delivering

That's the part I was not really so expert. And to be honest I'm still not really that expert. I wanted to build the game for Windows and WebGL, so first thing I did is installing the "WebGL Build Support" module through Unity Hub. It took a dozen of minutes, pretty easy.

Second step was changing the Project Settings, specifically the Player section. For the Windows build I just changed a couple of settings, mostly the Fullscreen mode and the Default Screen Width/Height .

Third step: open Build Settings and add all required scenes to the Scenes in Build box, roughly ordered by the order of loading. I left all the other settings as they are and press Build: after a dozen of seconds it was done, zip the folder and upload it to my Drive. Done!

To build for WebGL the first thing you have to do is to switch platform from Build Settings: grab a snack, it will take a while. With WebGL had a little more problems: the first build I did has the resolution too high and for some users went out of screen, not good. So I limited the resolution and make the UI Canvas adapt the size for good measure.

Then I had a couple of problems withItch.io: with the default settings the browser cannot load the game, complaining about decompression issues. I solved disabling the compression from the Publishing Settings: the build is a little more heavier, but still manageable by any modern device.

Done! Finally I can rest my old and worn body!

Retrospect

That is the whole bunch of interesting things in the code. If you want more of it, feel free to browse the filter on the GitHub repository. I didn't pushed the art file source, but I don't think they are really notewhorty, I'm not really a great artist. At all. Actually I suck at drawing.

The results were published a couple of weeks after the end of the jam: the ratings are entirely from the player, there is no jury of experts and other bullshits. The system that Ludum Dare is using stimulate you to try, rate and comment other games to make yours to be rated. I like it in the beginning, but after a while I had the sensation that it was too much a rat race, like an Instagram of only influencers, liking each others only to be liked back. But maybe I have to still get used to the sense of community, I reserve my evaluation on the system for the next time.

The results were not really so bad:

My objective was top 100 in at least one category and I was really close. Well, it will be better next time. In the meanwhile I took some time to analyze what I had wrong:

In the very end I was satified: I was a little bitter for not reaching my objective, but from this "failure" I had a lot of insight on how to create a game. The experience was really positive after all, there were hard moments of anger and frustration, and the week after that I was basically a zombie, but the satisfaction of delivering a game is incomparable.

That was everything folks! The next Ludum Dare is at the end of September and I'm planning to participate to this one too. Cheers!

IADRTorna alla Home