Breathe – Ma Interactive Process

Update – 22/10/22

After receiving feedback for Breathe, the following changes were made:

  • Replaced the BeepBox tones with water drop sound effects sourced on Zapsplat
  • HTML build: Changed music to play on start, rather than on awake

 


 

The Plan

Breathe is a small interactive based on Japanese concept Ma and inspired by water ripples. Ma is a traditional Japanese concept about the meaning and shape of empty space, or the interval between two things. To me, throwing a stone in water represents a feeling of calmness, acceptance and content. I imagine a quiet moment between the stone hitting the water and the ripples fading away. In other terms, Ma. This is what I want to portray in my interactive. It will be small, simple and have no colour, as I don't want to distract the viewer from the Ma between the interaction and motion of ripples.

 

The Process

I started by creating an animation of ripples in a pixel art program called Aseprite. It works frame by frame, so I used the grid as my guide and a canvas size of 256 pixels and drew each circle, reducing the opacity of the white colour as the ripples grew out. I exported the frames at 300% and implemented them into Unity's animator tool. In future I would like to experiment with vector graphics to compare how the interactive would feel using smooth lines verse jagged pixel art lines.

Creating the ripple animation in Aseprite.

I created a 2D sprite object in Unity to be the dot and added a script that will trigger the ripple animation when the object is clicked. This required getting the position of the dot and setting the ripple position to the dot's position before playing the animation. I hide the dot while the animation plays out. Once the animation ends, the dot will get a new random position within the camera view and reappear to be clicked on again.

Breathe first draft.

At this point I had no sound, and I felt it needed a pause before the dot reappeared, to force the player to take a moment. I figured out a way to add a delay before the dot changes position and reappears for the final version using a Coroutine.

I added a relaxing instrumental song to play on loop that sets a calm mood. For the sound effects, I used a music generator online called BeepBox. It lets you change the key scale, tempo, instrument, and many other settings, and then you click on the rows to create a song. It exports it as one long track, so I had to separate the tones myself in Adobe Audition before implementing them into Unity. When the dot is clicked it will play one of the tones chosen at random from an array.

public class GrowRipples : MonoBehaviour
{
    public Animator anim;
    public Transform ripplesPosition;

    Vector3 dotPosition;
    Renderer dotRenderer;
    BoxCollider2D dotCollider;

    public AudioClip[] tones;
    public AudioSource toneSource;

    void Start()
    {
        dotPosition = gameObject.transform.position;
        dotRenderer = gameObject.GetComponent<Renderer>();
        dotCollider = gameObject.GetComponent<BoxCollider2D>();

        dotRenderer.enabled = false;
        dotCollider.enabled = false;
        StartCoroutine(WaitToShow());
    }

    void OnMouseDown()
    {
        // On click, move ripple position to the dot position, hide dot and play animation

        Debug.Log("begin ripples");
        Debug.Log("current dot position: " + dotPosition);

        // Set ripples to dot position
        ripplesPosition.position = dotPosition;

        // Hide dot
        dotRenderer.enabled = false;
        dotCollider.enabled = false;

        // Play random tone
        toneSource.clip = tones[Random.Range(0, tones.Length)];
        toneSource.Play();

        // Play ripples animation from beginning
        anim.Play("ripples", -1, 0f);
    }

    public void ChangePosition()
    {
        // Once anaimation ends, move dot to new position then reveal dot

        Debug.Log("end ripples and set new position");

        // Get a random position in the camera view
        float newPositionY = Random.Range(Camera.main.ScreenToWorldPoint(new Vector2(0, 0)).y, Camera.main.ScreenToWorldPoint(new Vector2(0, Screen.height)).y);
        float newPositionX = Random.Range(Camera.main.ScreenToWorldPoint(new Vector2(0, 0)).x, Camera.main.ScreenToWorldPoint(new Vector2(Screen.width, 0)).x);
        dotPosition = new Vector3(newPositionX, newPositionY);

        // Set dot to new random position
        gameObject.transform.position = dotPosition;

        // Delay and show dot
        StartCoroutine(WaitToShow());
    }

    IEnumerator WaitToShow()
    {
        yield return new WaitForSeconds(3);

        // Show dot
        dotRenderer.enabled = true;
        dotCollider.enabled = true;
    }
}
GrowRipples script. Unity/C#.
Breathe gameplay.

 

The Outcome

The process to develop this interactive was enjoyable and relatively simple without any hiccups. It's almost exactly as I imagined when first conceptualising the idea. The only part I think needs changing is the sound effects when clicking on the dots. I think the chiptune-like sound effects compliment the visuals, but do not suit the music track or mood I have tried to inspire.

 

Go To Project