Reto 01 (07 / 04 / 2024)
Enunciado
El primer reto del mes consiste en crear un reproductor de música (con 10 canciones, por ejemplo) que permita reproducir y pausar una canción. También, que permita pasar a la siguiente canción o volver a la anterior.
Solución
En un primer script al que llamaremos MusicManager, necesitamos tres variables: una que almacene las canciones (de tipo AudioClip), otra que guarde referencia al AudioSource que reproducirá esas canciones y una variable que guarde el índice de la canción actual.
Finalmente, tendremos que declarar las funciones que se encargarán de reproducir, pausar, pasar a la siguiente canción o volver a la canción anterior.
using UnityEngine;
public class MusicManager : MonoBehaviour
{
public static MusicManager Instance { get; private set; }
[SerializeField] private AudioClip[] songs;
[SerializeField] private AudioSource audioSource;
private int currentSong;
private void Awake()
{
if (Instance != null)
{
Debug.LogError("There's more than one instance");
}
Instance = this;
}
public void PlaySong()
{
audioSource.clip = songs[currentSong];
audioSource.Play();
}
public void PauseSong()
{
audioSource.Pause();
}
public void PlayNextSong()
{
currentSong++;
if (currentSong >= songs.Length)
{
currentSong = 0;
}
PlaySong();
}
public void PlayPreviousSong()
{
currentSong--;
if (currentSong < 0)
{
currentSong = songs.Length - 1;
}
PlaySong();
}
}
Hemos utilizado el patrón de programación Singleton, que nos asegura que solo habrá una única instancia de MusicManager.
Ahora, en un segundo script, UIManager, por ejemplo, asignamos a cada botón de la UI su función correspondiente.
using UnityEngine;
using UnityEngine.UI;
public class UIManager : MonoBehaviour
{
[SerializeField] private Button playButton;
[SerializeField] private Button pauseButton;
[SerializeField] private Button previousSongButton;
[SerializeField] private Button nextSongButton;
private void Awake()
{
playButton.onClick.AddListener(MusicManager.Instance.PlaySong);
pauseButton.onClick.AddListener(MusicManager.Instance.PauseSong);
previousSongButton.onClick.AddListener(MusicManager.Instance.PlayPreviousSong);
nextSongButton.onClick.AddListener(MusicManager.Instance.PlayNextSong);
}
}
Finalmente, hay que configurar el inspector:
- A la componente Audio Source le hemos desactivado la opción de Play On Awake
- A la componente UIManager, le hemos asignado a cada variable Button, el botón correspondiente
- La componente MusicManager ha quedado como se muestra en la imagen
Y la UI nos ha quedado del siguiente modo:
Reto 02 (14 / 04 / 2024)
Enunciado
Añade al reproductor de música del reto de la semana pasada la posibilidad de elegir una canción aleatoria (y que la siguiente canción también se elija de forma aleatoria).
Solución
Necesitamos añadir nuevas funciones a MusicManager:
public bool PlayRandomSong()
{
isRandomModeActive = !isRandomModeActive;
if (isRandomModeActive)
{
GenerateRandomSong();
PlaySong();
}
return isRandomModeActive;
}
private void GenerateRandomSong()
{
currentSong = Random.Range(0, songs.Length);
}
Donde isRandomModeActive es una variable privada de tipo bool que se encargará de determinar si tenemos activado o no el modo random. Por defecto no lo tenemos activado. Pero cada vez que llamemos a la función PlayRandomSong(), el modo random si está activo, se desactivará y, si está desactivado, se activará.
Luego, si tenemos el modo random activado, generamos una canción aleatoria y la reproducimos. Si no, simplemente devolvemos el valor de isRandomModeActive.
En el Update de MusicManager comprobaremos cuándo acaba la canción (cuando el time del Audio Source supera la longitud del Audio Clip). Si estamos con el modo random activado, reproducimos una canción aleatoria. Si estamos con el modo random desactivado, reproducimos la siguiente canción.
private void Update()
{
if (audioSource.time >= songs[currentSong].length)
{
if (isRandomModeActive)
{
PlayRandomSong();
}
else
{
PlayNextSong();
}
}
}
Por último, hay que configurar un nuevo botón. Además, para tener un feedback visual sobre si el modo random está activado o no, configuramos el script UIManager del siguiente modo:
using UnityEngine;
using UnityEngine.UI;
public class UIManager : MonoBehaviour
{
[SerializeField] private Button playButton;
[SerializeField] private Button pauseButton;
[SerializeField] private Button previousSongButton;
[SerializeField] private Button nextSongButton;
[SerializeField] private Button randomSongButton;
[SerializeField] private Image randomSongImage;
[SerializeField] private Color selectedButtonColor;
private void Awake()
{
playButton.onClick.AddListener(MusicManager.Instance.PlaySong);
pauseButton.onClick.AddListener(MusicManager.Instance.PauseSong);
previousSongButton.onClick.AddListener(MusicManager.Instance.PlayPreviousSong);
nextSongButton.onClick.AddListener(MusicManager.Instance.PlayNextSong);
randomSongButton.onClick.AddListener(() =>
{
bool isRandomModeActive = MusicManager.Instance.PlayRandomSong();
randomSongImage.color = isRandomModeActive ? selectedButtonColor : Color.white;
});
}
}
Ya solo queda un pequeño detalle que es que, si tenemos el modo random activado, la siguiente canción debería ser aleatoria. Por lo que la función PlayNextSong() quedará del siguiente modo:
public void PlayNextSong()
{
if (isRandomModeActive)
{
GenerateRandomSong();
}
else
{
currentSong++;
if (currentSong >= songs.Length)
{
currentSong = 0;
}
}
PlaySong();
}
Si queréis hacer lo mismo para PlayPreviousSong() os lo dejamos como ejercicio. Nosotros hemos preferido dejar la función tal cuál para ir a la canción anterior de la que está sonando, independientemente de si el modo aleatorio está activado o no.
Reto 03 (21 / 04 / 2024)
Enunciado
Haz que el reproductor de música del Reto 01 muestre la información del título, el autor y una imagen para cada canción.
Solución
Vamos a empezar creando un Scriptable Object que guarde un AudioClip, un string (songName), otro string (author) y un Sprite (sprite).
using UnityEngine;
[CreateAssetMenu(menuName = "Music Player/Song")]
public class SongSO : ScriptableObject
{
public string songName;
public string author;
public AudioClip song;
public Sprite sprite;
}
A continuación, el antiguo array de AudioClip de MusicManager, lo vamos a convertir en un array de SongSO. Una vez hecha esta modificación, hay que corregir un error en el Update y otro en PlaySong().
private void Update()
{
if (audioSource.time >= songs[currentSong].song.length)
{
if (isRandomModeActive)
{
PlayRandomSong();
}
else
{
PlayNextSong();
}
}
}
public void PlaySong()
{
audioSource.clip = songs[currentSong].song;
audioSource.Play();
}
Ahora, vamos a crear un evento en MusicManager que se va a llamar cada vez que cambiemos de canción. Este evento va a recibir un parámetro de tipo SongSO. ¿Y cuándo sabremos cuando cambiamos de canción? Pues eso ocurrirá siempre que llamemos a PlaySong(). Por tanto, esta función la modificamos del siguiente modo:
public void PlaySong()
{
OnSongChange?.Invoke(songs[currentSong]);
audioSource.clip = songs[currentSong].song;
audioSource.Play();
}
donde el evento OnSongChange ha sido declarado del siguiente modo:
public static event Action<SongSO> OnSongChange;
Ahora ya solo nos queda suscribir una función al evento y lo haremos desde UIManager. Será la encargada de cambiar el nombre, el autor y la imagen.
private void MusicManager_OnSongChange(SongSO songSO)
{
songNameText.text = songSO.songName;
songAuthorText.text = songSO.author;
songImage.sprite = songSO.sprite;
}
Nos falta configurar todo en el editor. Primero habrá que crear los 10 Scriptable Objects de las 10 canciones, configurando para cada una un nombre, un autor, un Sprite y un AudioClip. Luego, esos 10 Scriptable Objects los añadimos a la lista de SongSO de MusicManager.
En la UI, habrá que crear un texto para mostrar el nombre, otro para el autor y una imagen para mostrar el Sprite de cada canción. Y no hay que olvidar asignar las referencias en UIManager.
Una vez todo está configurado, el proyecto se ve así:
Reto 04 (28 / 04 / 2024)
Enunciado
Añade al reproductor de música del Reto 01 la opción de reproducir una canción en bucle.
Solución
Necesitamos añadir una nueva función a MusicManager:
public bool PlaySongOnLoop()
{
audioSource.loop = !audioSource.loop;
return audioSource.loop;
}
Esta función la va a llamar un nuevo botón cada vez que sea pulsado. Lo que hace la función es activar el modo bucle si este está desactivado. Si por el contrario, el modo bucle ya estaba activado, lo desactiva. Además, devolvemos el valor una vez modificado para así poder modificar el estado del botón tal cual hacemos con el modo random.
private void Awake()
{
playButton.onClick.AddListener(MusicManager.Instance.PlaySong);
pauseButton.onClick.AddListener(MusicManager.Instance.PauseSong);
previousSongButton.onClick.AddListener(MusicManager.Instance.PlayPreviousSong);
nextSongButton.onClick.AddListener(MusicManager.Instance.PlayNextSong);
randomSongButton.onClick.AddListener(() =>
{
bool isRandomModeActive = MusicManager.Instance.PlayRandomSong();
randomSongButtonImage.color = isRandomModeActive ? selectedButtonColor : Color.white;
});
loopButton.onClick.AddListener(() =>
{
bool isLoopActive = MusicManager.Instance.PlaySongOnLoop();
loopButtonImage.color = isLoopActive ? selectedButtonColor : Color.white;
});
}
Y ya solo queda configurar todo por inspector.
Comparte tus Soluciones
¿Has conseguido resolver todos los retos? ¿Has encontrado una solución diferente a las propuestas? Pues no dudes en compartir esas soluciones con nosotros. Puedes hacerlo comentando este mismo post o respondiendo a cualquier publicación de nuestras redes sociales.
¡Anímate!
Aprende Unity con Frogames
¿Se te ha complicado algún reto? ¿No sabes exactamente cómo enfocar la solución? ¿No acabas de entender alguna línea de código de las soluciones propuestas?
Si quieres ser capaz de resolver todos estos retos, quieres iniciarte en el mundo del desarrollo de videojuegos con Unity o bien quieres mejorar tus habilidades en este mundillo, entonces visita la Ruta de Desarrollo de Videojuegos donde tenemos toda una parte dedicada al desarrollo de videojuegos con Unity.
En cada curso aprenderás a desarrollar nuevas mecánicas y diferentes tipos de videojuegos. Hay un poco de todo: videojuegos 2D, videojuegos 3D, juegos en primera persona, juegos en tercera persona, shooters, RPGs, clásicos retro...
Además, al inscribirte a alguno de nuestros cursos tendrás acceso a la comunidad de videojuegos para hacernos cualquier duda que te surja durante tu aprendizaje y, sobre todo, para compartir con nosotros y el resto de estudiantes tus avances y tus proyectos.
¿Te lo vas a perder? ¡Nos vemos en clase!