Seasons – Wabi-sabi Interactive Process

Update – 23/10/22

After receiving feedback for Seasons, I made the following changes:

  • Fixed colour transparency issue by changing the material rendering mode to Fade instead of Transparent
  • Added UI to indicate when the season is locked and can't be changed
  • Changed click delay (season locked time) to 6 seconds

 


 

The Plan

Seasons is inspired by Wabi-sabi, an ancient Japanese aesthetic concept. The interpretation of Wabi-sabi is subjective. I understand it as the impermanence and imperfection of things, designed by nature to be ever changing. From the jagged and slow degradation of ocean-side cliffs, to the fading ink on paper receipts. Although our society is fixated on making things permanent and indestructible, nature has a way of persisting with change. I want my interactive to focus on this, in particular how seasons are an uncontrollable force that grows and fades and constantly changes.

Seaons interactive concept sketch
Seasons concept sketch.

The sketch above visualises my concept idea for this project. My plan is to create a simple 2D interactive that is contained within a small window frame. At the top will be a spawner, which spawns the particle objects into the scene. The particles will fall to the bottom and as they hit the ground, they will start to fade until they are completely gone. However, the player will be able to sweep the particles along the ground and push them into the inactive area where they will stop fading. This is meant to represent the way humans attempt to preserve things.

The player will be able to change the season with a button which will change what type of object the spawner will start spawning. The seasons can only be changed in order, for example, winter, spring, summer, and autumn. I want the visuals to be simple, using basic shapes and solid colours to represent the season. For example, white circle for snow, green triangle for leaves, etc.

 

The Process

I began with setting up a prefab for the snow, then created a script called ObjectSpawner which spawns the prefab at a random position above the camera view every 0.1 seconds. The next step was to get the snow objects to fade in transparency when they hit the ground, then destroy the object once the material alpha reached 0. This was a long and arduous process trying to piece together multiple YouTube tutorials and forum posts. I first tried using Color.Lerp() to change the alpha but it wasn’t working for me, so I switched to a standard Mathf.Lerp() function and changed the alpha property directly over time at the speed I declared with the variable fadeSpeed.

I set up a tag for the ground object so that the fade function only gets triggered when the snow object collides with the ground and not other snow objects. This then sets a boolean variable to true and triggers the fade to begin in the Update() function. See FadeObject script below.

public class FadeObject : MonoBehaviour
{
    Renderer obj;

    Color objColor;
    float newAlpha;

    bool hitGround = false;

    float startTime;
    public float fadeSpeed;

    void Start()
    {
        obj = gameObject.GetComponent<Renderer>();
        objColor = obj.material.color;
    }

    void Update()
    {
        if (hitGround)
        {
            FadeAlpha();
        }
    }

    private void OnCollisionEnter2D(Collision2D collision)
    {
        // detect collision with ground object using a Tag
        if (collision.gameObject.CompareTag("Ground"))
        {
            // set startTime to current time when collision happens
            startTime = Time.time;

            hitGround = true;
        }
    }

    void FadeAlpha()
    {
        // begin timer for lerp
        float t = (Time.time - startTime) * fadeSpeed;
        
        // set the new alpha value to lerp between 1 (opaque) and 0 (transparent)
        newAlpha = Mathf.Lerp(1, 0, t);

        // set the object material color using newAlpha
        objColor.a = newAlpha;
        obj.material.color = objColor;

        // destroy once object is transparent
        if (newAlpha == 0)
            Destroy(gameObject);
            
    }

    private void OnDestroy()
    {
        // destroy the instanced material created in FadeAlpha() once game ends
        Destroy(obj.material); // ?? might not be necessary if the gameObject has already been destroyed ??
    }
}
FadeObject script. Unity/C#.

I added a third script called RandomiseObject to control various properties of the prefab such as drag, which is calculated at a random number between a minimum and maximum value. This is what makes the snow fall at different speeds.

Seasons first draft.

At this point I decided against creating the functionality to push around the particle objects where they would stop and start fading depending on where they were on the ground. The main reason was because I needed to reduce the scope, and judging by how long it took me to figure out the first part of it, I thought it was necessary for my sanity. This left just the one interaction, the ability to cycle through the seasons by clicking a button.

First, I needed to create the objects and spawners for the other three seasons. I started testing the leaf object using a 3D triangle that I quickly modelled in Maya. I couldn’t figure out how to create a 2D collider in the shape of a triangle, so I added a circle collider instead. This worked well enough, so I created a new spawner for it but then realised the model wasn’t facing the right direction originally. From the camera’s orthographic perspective, they all looked like squares when spawned into the scene. I was unsure how to code the rotation that I needed with the Instantiate() function, so I edited the rotation of the original model in Maya. While I was in there, I played around with modelling a more realistic shape of a leaf, but soon realised I was straying from my original plan to only use basic shapes. This is when I decided to go super simple and use a cube directly in Unity instead.

I ended up only using the basic sphere and cube objects for the season spawners. Winter and summer use spheres. Spring and autumn use cubes. I set up custom properties in the ObjectSpawner and RandomiseObject scripts to differentiate the objects in each season. The properties include, colour, size, drag, spawn frequency, and fade speed.

Seasons second draft.

I’ve set up winter to portray snow, spring to portray fresh leaves, summer to portray pollen and autumn to portray dead leaves. Although pollen might make more sense for spring, I want the type of object to change each season – sphere, cube, sphere, cube. I tried to keep in mind how slow dead leaves would fall compared to fresh leaves (dead leaves weigh less), how much snow there would be in a storm compared to pollen in the air during summer, and so on. This is what guided me when setting custom values for each season’s object. To complete the seasonal colours, I created colour gradients in Photoshop to use for the backgrounds. The colours were inspired by various palettes I found on Adobe Color search.

public class RandomiseObject : MonoBehaviour
{
    Rigidbody2D rb;

    [Header("Physics Settings")]
    public float dragMin;
    public float dragMax;

    [Header("Color Settings")]
    Renderer rend;
    public bool canChangeColor;
    public Color[] objectColors;

    void Start()
    {
        rb = gameObject.GetComponent<Rigidbody2D>();
        rend = gameObject.GetComponent<Renderer>();

        float dragAmount = Random.Range(dragMin, dragMax);
        rb.drag = dragAmount;

        if (canChangeColor)
        {
            rend.material.color = objectColors[Random.Range(0, objectColors.Length)];
        }
    }
}
RandomiseObject script. Unity/C#.

The interaction control to change between seasons is a simple click of the left mouse button. It cycles through each season spawner in order, using an array and for loop. I also added a click delay so the season will only change if it has been at least 10 seconds since the last change.

public class ChangeSpawner : MonoBehaviour
{
    public GameObject[] spawners;
    int currentSpawner = 0;

    public float clickDelay;
    float lastClicked;

    void Awake()
    {
        // set all spawners to inactive
        foreach (GameObject o in spawners)
        {
            o.SetActive(false);
        }
    }

    void Start()
    {
        // set first spawner to active
        spawners[currentSpawner].SetActive(true);

        lastClicked = 0f;
    }

    void Update()
    {
        if ((Time.time - lastClicked) > clickDelay && Input.GetMouseButtonDown(0))
        {
            lastClicked = Time.time;
            
            // set current spawner inactive
            spawners[currentSpawner].SetActive(false);

            currentSpawner++;

            // reset counter to beginning if the number reaches more than spawners available
            if (currentSpawner >= spawners.Length)
            {
                currentSpawner = 0;
            }

            spawners[currentSpawner].SetActive(true);
        }
    }
}
ChangeSpawner script. Unity/C#.

The final piece of the puzzle was sound. I searched online for ambient sounds that related to the seasons, such as, snowstorm winds, birds, rustling leaves, etc. Each season is using 2-3 sounds with different volume levels to create a fuller soundscape. A few years ago, I learnt that layering multiple sounds is the trick to creating great sound effects. This advice was from a YouTube video about sound design in games, of which I can’t find at the moment. But it’s certainly worked for the ambiance I’ve tried to make in my interactive. I also duplicated one sound to play in the following season to reduce it sounding awkward when the season changes. This has made it feel a little more seamless when clicking through the seasons.

public class ObjectSpawner : MonoBehaviour
{
    [Header("Spawn Properties")]
    public GameObject objectToSpawn;
    public float spawnTime;

    [Header("Object Scale")]
    public float sizeMin;
    public float sizeMax;
    public bool isAsymmetrical;

    [Header("Object Position")]
    public float positionMin;
    public float positionMax;

    [Header("Background Properties")]
    public SpriteRenderer backgroundObject;
    public Sprite backgroundGradient;

    [Header("Sound")]
    public AudioSource[] audioSources;

    void OnEnable()
    {
        backgroundObject.sprite = backgroundGradient;

        // unmute all audio sources
        foreach (AudioSource aS in audioSources)
        {
            aS.mute = false;
        }

        StartCoroutine(SpawnObject());
    }

    void OnDisable()
    {
        // mute all audio sources
        foreach (AudioSource aS in audioSources)
        {
            aS.mute = true;
        }
    }

    public IEnumerator SpawnObject()
    {
        while (true)
        {
            // Spawn object in a random (x) position above the sceen (y = 10)
            GameObject spawnedObject;
            spawnedObject = Instantiate(objectToSpawn, new Vector3(Random.Range(positionMin, positionMax), 10, Random.Range(-0.5f, 0.5f)), Quaternion.identity);

            // Set the object scale to a random size
            float randomScaleOne = Random.Range(sizeMin, sizeMax);
            float randomScaleTwo = Random.Range(sizeMin, sizeMax);
            if (isAsymmetrical)
                spawnedObject.transform.localScale = new Vector3(randomScaleOne, randomScaleTwo, randomScaleOne);
            else
                spawnedObject.transform.localScale = new Vector3(randomScaleOne, randomScaleOne, randomScaleOne);

            yield return new WaitForSeconds(spawnTime);
        }
    }
}
ObjectSpawner script. Unity/C#.
Seasons development process.

 

The Outcome

The final interaction turned out simpler than I had planned. If I had more time, I would like to add the extra functionality to allow the player to move around the particles on the ground and stop them fading when outside the active area. Alternatively, they could still fade outside the active area but a lot slower. Either way, I think this would be easy to code as it might just require changing the fadeSpeed variable on collision with the areas outside the active area. The part I’m unsure about it how to code the movement interaction to be able to sweep the particles from side to side.

Other improvements could be made to the objects, such as, modelling different shapes for each season, and adding the ability for the leaves to sway from side to side as they’re falling. Overall, I think this small interaction mostly aligns with the wabi-sabi concept, or at least my subjective interpretation of it.

 

Go To Project