MusicMaker is a C# script that creates randomly-generated background music for the game. If I elaborate it more, it selects the AudioClips you provide randomly, and plays the selected one via its AudioSource.
If you ask yourself, what is the advantage or need of having such thing, well, if you are an indie developer trying to create some background music, you don’t have much options. When I was checking Asset Store to find some free background music that I could use, I came across with some free “loops”. They were averagely 25 seconds long, so, they were not very effective to use right away, but I came up with this MusicMaker idea and solved a problem for my project.
So, the advantage (or need) of having such thing is that you can have randomly-generated background music for your game. And it’s literally random, so it means that you will hear a different background music whenever you play the game. And I’m sure with some tweak you can get really cool results.
So, in this post, I will talk about making this script happen. For this time, I am not going to provide the best version right away, so the absolute beginners can also benefit from it.
Very Basic and Wrong Version
Here, we will see the very straight forward and wrong approach to make MusicMaker happen. What we want and what we need are very straightforward:
We need bunch of AudioClips, an AudioSource, a C# script file including a class name MusicMaker that is inherited from MonoBehaviour (so we can attach it to a Unity GameObject), a method to assign and play the AudioClips randomly, a method that will keep track of the AudioClip playing and change it when the time is right.
So, here is your straight forward MovieMaker script:
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 |
using System.Collections; using System.Collections.Generic; using UnityEngine; public class MusicMaker : MonoBehaviour { public List AudioClips; private AudioSource _audioSource; private float _clipTime = 0f; // Use this for initialization void Start () { _audioSource = GetComponent < AudioSource > (); changeSong (); } private void changeSong() { int songNo = Random.Range (0, AudioClips.Capacity - 1); _audioSource.clip = AudioClips[ songNo ]; _audioSource.Play (); _clipTime = 0f; } // Update is called once per frame void Update () { _clipTime += Time.deltaTime; if(_clipTime > _audioSource.clip.length) { changeSong (); } } } |
The key problem in here is using MonoBehaviour.Update(). If you only have one or two objects in your game scene that have and call MonoBehaviour.Update(), you won’t have problems, your game will survive. But, imagine, you have thousands of GameObjects having MonoBehaviour.Update()… Then, you will have some hard time.
You can consider MonoBehaviour.Update() like the game loop. So, it is better to have it just one in your GameManager, or to keep the number of MonoBehaviour.Update() as less as possible.
So, what we can do in here is to introduce a base class (like BaseGameObject) that have its own update(), refresh(), callOnFrame(), or whatever you call it method, and call this method via GameManager’s MonoBehaviour.Update() method, whenever we need.
So, here is the base class I am talking about:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
using UnityEngine; // Base class for our game objects public abstract class BaseGameObject : MonoBehaviour { // ABSTRACT METHODS // Called every frame public abstract void GameObjectUpdate(); // Override when need it public virtual void Init() { } } |
And this is the new and clean version of our MusicMaker class:
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 |
using System.Collections; using System.Collections.Generic; using UnityEngine; public class MusicMaker : BaseGameObject { public List<AudioClip> AudioClips; private AudioSource _audioSource; private float _clipTime = 0f; // Use this for initialization public override void Init () { _audioSource = GetComponent<AudioSource> (); changeSong (); } private void changeSong() { _audioSource.clip = AudioClips[ Random.Range (0, AudioClips.Capacity - 1) ]; _audioSource.Play (); _clipTime = 0f; } // Update is called once per frame public override void GameObjectUpdate() { _clipTime += Time.deltaTime; if(_clipTime > _audioSource.clip.length) { changeSong (); } } } |
Now we need to control MusicMaker.Init() and MusicMaker.Update() methods manually from the GameManager, but the performance of the game won’t suffer even if we have thousands of game objects in the scene:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; public class GameManager : MonoBehaviour { public MusicMaker MusicMaker; void Start () { // Init the MusicMaker MusicMaker.Init (); } void Update () { // Call MusicMaker's update MusicMaker.GameObjectUpdate (); } } |