The project spanned for 3 months and was released by our start-up studio now known as ThunderBlossom. I worked in the assets programming department using Unity engine with C# .NET. I was responsible for implementing vital features.
Role: Co-lead Asset Programmer
Duration: 3 Months (September 2022 - January 2023)
Methodology: Agile Scrum (Worked within iterative development cycles, contributing to regular sprints and standups)
Overview:
This was our first publicly launched project at ThunderBlossom Studio, designed for the Itch.io platform, available on both Windows and WebGL. It’s a platformer adventure game that showcases our studio’s skills and served as our introduction to the indie development scene. The project is currently being remastered for mobile platforms.
Responsibilities:
- Audio SFX and background music.
- Dynamic objects such as destrucible and hazardous stage props.
- UI and menu functionality.
- Level/scene changing and loading functions.
Technologies Used:
- Unity Engine
- C# .NET
- Git & GitHub
- HacknPlan (Kanban)
Sample Code:
This is an example of a class I created for the project. Its role is to control all SFXs globally in the worldspace.
/// Deals with Audio instances and their corresponding AudioSource. Hooks up with AudioMixer.
public class AudioManager : MonoBehaviour
{
[Serializable]
protected class Audio
{
[Range(0f, 1f)]
[SerializeField] private float _volume = 1f;
[Range(.1f, 3f)]
[SerializeField] private float _pitch = 1f;
[field: SerializeField] public string Name { get; private set; }
[field: SerializeField] public AudioClip Clip { get; private set; }
[field: SerializeField] public bool IsLoop { get; private set; }
[field: SerializeField] public bool HasCooldown { get; private set; }
[field: SerializeField] public AudioSource Source { get; set; }
[field: SerializeField] public AudioMixerGroup MixerGroup { get; private set; }
public float Volume
{
get { return _volume; }
set { _volume = value; }
}
public float Pitch
{
get { return _pitch; }
set { _pitch = value; }
}
}
private const string _volumeControl = "VolumeControl";
private static Dictionary<string, float> AudioTimerDictionary;
[SerializeField] private Audio[] _audioClips;
[field: SerializeField] public AudioMixer Mixer { get; private set; }
private void Awake()
{
AudioTimerDictionary = new Dictionary<string, float>();
Initialize();
}
private void Initialize()
{
foreach (Audio audio in _audioClips)
{
audio.Source = gameObject.AddComponent<AudioSource>();
audio.Source.clip = audio.Clip;
audio.Source.outputAudioMixerGroup = audio.MixerGroup;
audio.Source.volume = audio.Volume;
audio.Source.pitch = audio.Pitch;
audio.Source.loop = audio.IsLoop;
if (audio.HasCooldown)
{
AudioTimerDictionary[audio.Name] = 0f;
}
}
}
private Audio FindClip(string name)
{
Audio audio = Array.Find(_audioClips, clip => clip.Name == name);
if (audio == null)
{
Debug.Log("Audio not found: " + name);
return null;
}
return audio;
}
public void RandomizePitch(string name, Vector2 pitchRange)
{
Audio audio = FindClip(name);
audio.Pitch = UnityEngine.Random.Range(pitchRange.x, pitchRange.y);
}
private void UpdateClip(Audio audio)
{
audio.Source.volume = audio.Volume;
audio.Source.pitch = audio.Pitch;
audio.Source.loop = audio.IsLoop;
}
#region Playback
public void Play(string name)
{
Audio audio = FindClip(name);
UpdateClip(audio);
if (!CanPlayClip(audio)) return;
audio.Source.Play();
}
public void Play(string name, float newVolume)
{
Audio audio = FindClip(name);
audio.Volume= newVolume;
UpdateClip(audio);
if (!CanPlayClip(audio)) return;
audio.Source.Play();
}
public void Play(string name, float newVolume, float newPitch)
{
Audio audio = FindClip(name);
audio.Volume = newVolume;
audio.Pitch = newPitch;
UpdateClip(audio);
if (!CanPlayClip(audio)) return;
audio.Source.Play();
}
public void Stop(string name)
{
Audio audio = FindClip(name);
audio.Source.Stop();
}
private static bool CanPlayClip(Audio clip)
{
if (AudioTimerDictionary.ContainsKey(clip.Name))
{
float lastTimePlayed = AudioTimerDictionary[clip.Name];
if (lastTimePlayed + clip.Clip.length < Time.time)
{
AudioTimerDictionary[clip.Name] = Time.time;
return true;
}
return false;
}
return true;
}
#endregion
#region Mixer Control
public void SetMixerVolume(float sliderValue)
{
Mixer.SetFloat(_volumeControl, Mathf.Log10(sliderValue) * 20);
}
public void IsMixerVolumeOn(bool isOn)
{
if (!isOn) Mixer.SetFloat(_volumeControl, -80);
}
#endregion
}
}