
Último Amanecer en Unity
Este proyecto consiste en el desarrollo de un juego en Unity donde el jugador deberá recolectar la mayor cantidad posible de monedas mientras es perseguido por hordas de zombis. La mecánica central combina acción, evasión y estrategia, ya que cada encuentro con los enemigos reducirá la vida del personaje, poniendo en riesgo su supervivencia.
Una de las innovaciones clave del juego es la integración del sistema FaceBuilder, que permite al jugador personalizar el rostro del protagonista usando su propia cara. Esto añade un nivel extra de inmersión, haciendo que la experiencia sea más personal y envolvente.
Los zombis están controlados por un sistema de inteligencia artificial que detecta y sigue al jugador de forma dinámica.Su comportamiento adaptativo y su constante acecho convierten cada partida en un desafío diferente, aumentando la tensión y la re jugabilidad del juego.
Aqui presentamos el paso a paso.
Paso a Paso
Proceso para dar textura a personaje
Paso 1
Cargamos modelo de facebuilder creado

Paso 2
Importamos un modelo con esqueleto desde Mixamo; luego, le quitamos la cabeza y colocamos la nuestra, tal como se muestra.

Paso 3
Una vez creado el modelo con el que jugaremos en Unity, lo exportamos en formato FBX junto con una copia de sus texturas.


Paso 4
Vamos a Unity y creamos una scene

Paso 5 terreno en unity
Añadimos el terreno en Unity


Paso 6
Vamos a la Unity Asset Store y descargaremos todos los modelos gratuitos que necesitemos. Como están vinculados a una misma cuenta, podremos acceder a ellos directamente desde Unity.

Paso 7 importar en unity
Importamos los recursos de la siguiente manera:

En el apartado MyAssets de Unity veremos todos los agregados, los descargaremos e importaremos.

Aca veremos todo lo importado a Unity

Paso 8
Importamos un paquete de texturas para el terreno

Paso 9
Vamos al menú de edición del terreno

Paso 10
Una vez en el menú, arrastramos el material seleccionado al terreno elegido, quedando de la siguiente manera; así, nuestros personajes no caerán al vacío.


Paso 11
Una vez aquí, importaremos nuestro modelo a Unity.


Paso 12
Arrastramos el modelo y arrastramos la textura de la cabeza hacia el terreno

Paso 13
De la misma forma haremos uso de los assets importados a Unity para hacer la funcionalidad en tercera persona

Paso 14
Lo llevamos a nuestra scene y lo acomodamos como procede.

Paso 15
Una vez lo tenemos bien ubicado llevamos nuestro modelo al player Armature.

Paso 16
Escondemos el modelo rosado en el inspector, desactivamos el modelo para que solo nos quede nuestro modelo

Paso 17
Ahora pondremos las animaciones a nuestro modelo en Unity

Paso 18
Usaremos el siguiente paquete que trae modelos de un templo.

Paso 19
En los prefabs vamos a ver todos los objetos disponibles


Paso 20
Agregaremos algunos arboles con el siguiente paquete

Paso 21
Una vez importado tendremos estos modelos

Paso 22
Una vez elegimos los que deseemos tendremos el siguiente resultado

Paso 23
Para nuestro juego queremos que los enemigos se muevan solos, por lo que usaremos el NavMeshSurface, de esta manera el reconoce el terreno por el cual se pueden mover los enemigos y de esta manera evitar bugs.

Paso 24
Pondremos las monedas que el Player va recolectar,

Paso 25
Las pondremos en la scene y revisamos que el collaider quede bien ajustado

Paso 26
Diseñaremos un contador de las monedas que el player atrape y de esta manera podamos ver las que hacen falta.
En el menú superior vamos a : GameObject > UI > Text – TextMeshPro.
De esta manera podemos ver en pantalla el contador de las monedas


Paso 27
Le ponemos la etiqueta Coin y es importante que tenga el Sphere Collider y el Is Triggr activado


Paso 28
Creamos el script que tendrá el contador de las monedas y lo asignamos al player armature
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using UnityEngine.SceneManagement;
public class CoinCollector : MonoBehaviour
{
public int totalCoins = 8;
private int collectedCoins = 0;
public TMP_Text coinText;
public string nextLevelName;
public GameObject panelCompletado; // <– Panel de nivel completado
void Start()
{
UpdateUI();
if (panelCompletado != null)
{
panelCompletado.SetActive(false); // Ocultar al inicio
}
}
void OnTriggerEnter(Collider other)
{
if (other.CompareTag(“Coin”))
{
collectedCoins++;
Destroy(other.gameObject);
UpdateUI();
if (collectedCoins >= totalCoins)
{
Debug.Log(“¡Nivel completado!”);
if (panelCompletado != null)
{
panelCompletado.SetActive(true); // Mostrar panel
Time.timeScale = 0f; // Pausar juego
}
}
}
}
void UpdateUI()
{
if (coinText != null)
{
coinText.text = “Monedas: ” + collectedCoins + “/” + totalCoins;
}
}
// Llama esto desde el botón “Continuar”
public void ContinuarNivel()
{
Time.timeScale = 1f; // Reanudar tiempo
if (!string.IsNullOrEmpty(nextLevelName))
{
SceneManager.LoadScene(nextLevelName);
}
}
}
Paso 29
Ahora vamos asignarle el panel y el contador de monedas.

Paso 30
Ahora vamos a crear la barra de salud del player, con este script.
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
public class Salud : MonoBehaviour
{
public float saludInicial;
public float saludActual;
public UnityEvent eventoMorir;
public GameObject Derrota;
void Start()
{
saludActual = saludInicial;
Derrota.SetActive(false);
}
public void CausarDaño(float valor)
{
saludActual -= valor;
if (saludActual <= 0)
{
print(“Muerto !!! -> ” + gameObject.name);
eventoMorir.Invoke();
Derrota.SetActive(true);
Time.timeScale = 0f;
}
}
public void Reintentar()
{
print(“Intentando reiniciar…”);
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
Time.timeScale = 1f;
}
}
Paso 29. Creamos un objeto vacio y asignamos el script, pondremos el PlayerArmature y el canvas de la barra de vida que tambien tiene este script
using UnityEngine;
using UnityEngine.UI;
public class BarraDeVida : MonoBehaviour
{
public Salud saludJugador;
public Image barraPorcentaje;
void Update()
{
if (saludJugador != null && barraPorcentaje != null)
{
float porcentaje = saludJugador.saludActual / saludJugador.saludInicial;
barraPorcentaje.fillAmount = porcentaje;
// Cambiar color según el porcentaje
if (porcentaje > 0.6f)
barraPorcentaje.color = Color.green;
else if (porcentaje > 0.3f)
barraPorcentaje.color = Color.yellow;
else
barraPorcentaje.color = Color.red;
}
}
}

Paso 31
Ahora crearemos una función que permitirá reintentar cuando el jugador pierda.

Paso 32
Después de crear el panel, generamos el texto que se mostrará.

Paso 33
Ahora en el inspector

Paso 34
Además, creamos un script que nos permitirá reiniciar y avanzar al siguiente nivel.
using UnityEngine;
using UnityEngine.SceneManagement;
using Cinemachine;
public class GameManagerScript : MonoBehaviour
{
public GameObject[] personajes;
public Transform puntoSpawn;
public CinemachineVirtualCamera camaraVirtual;
public GameOverUI gameOverUI;
void Start()
{
int indexSeleccionado = PlayerPrefs.GetInt(“PersonajeSeleccionado”, 0);
if (indexSeleccionado >= 0 && indexSeleccionado < personajes.Length)
{
GameObject personajeInstanciado = Instantiate(personajes[indexSeleccionado], puntoSpawn.position, Quaternion.identity);
if (camaraVirtual != null)
{
camaraVirtual.Follow = personajeInstanciado.transform;
camaraVirtual.LookAt = personajeInstanciado.transform;
}
else
{
Debug.LogWarning(“No se ha asignado la cámara virtual en el inspector.”);
}
}
else
{
Debug.LogError(“Índice de personaje fuera de rango.”);
}
}
public void Reintentar()
{
Time.timeScale = 1f;
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
}
public void SiguienteNivel()
{
Debug.Log(“Cargando siguiente nivel…”);
Time.timeScale = 1f;
int siguienteIndex = SceneManager.GetActiveScene().buildIndex + 1;
if (siguienteIndex < SceneManager.sceneCountInBuildSettings)
{
SceneManager.LoadScene(siguienteIndex);
}
else
{
Debug.Log(“No hay más niveles. Fin del juego.”);
if (gameOverUI != null)
{
gameOverUI.MostrarPantallaFinal();
}
}
}
}
Paso 35
Ahora vamos a configurar los botones

Paso 36
Ahora para delimitar y que nuestro personaje no caiga al vacío, creamos 3 cubos modificando sus dimensiones y los ubicamos de esta manera
Paso 37
Después de esto, desactivamos el objeto para que deje de ser visible.

Paso 38
Ahora vamos a crear los enemigos, por lo que importaremos desde Mixamo las animaciones y el modelo con el que vamos a trabajar.

Crearemos 3 Scripts para los distintos enemigos que tendremos.
Paso 39
Script para Enemigo base.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
public class Enemigo : MonoBehaviour
{
public Estados estado;
public float distanciaseguir;
public float distanciaatacar;
public float distanciaEscapar;
public bool autoseleccionarTarget = true;
public Transform target;
public float distancia;
public bool vivo = true;
public void Awake()
{
StartCoroutine(CalcularDistancia());
}
private void Start()
{
if(autoseleccionarTarget)
target = Personaje.singleton.transform;
}
private void LateUpdate()
{
checkestado();
}
private void checkestado()
{
switch (estado)
{
case Estados.idle:
Estadoidle();
break;
case Estados.Seguir:
transform.LookAt(target, Vector3.up);
Estadoseguir();
break;
case Estados.atacar:
Estadoatacar();
break;
case Estados.muerto:
Estadomuerto();
break;
default:
break;
}
}
public void cambiarestado(Estados e)
{
switch (e)
{
case Estados.idle:
break;
case Estados.Seguir:
break;
case Estados.atacar:
break;
case Estados.muerto:
vivo = false;
break;
default:
break;
}
estado = e;
}
public virtual void Estadoidle()
{
if(distancia < distanciaseguir)
{
cambiarestado(Estados.Seguir);
}
}
public virtual void Estadoseguir()
{
if(distancia < distanciaatacar)
{
cambiarestado(Estados.atacar);
}
else if (distancia > distanciaEscapar)
{
cambiarestado(Estados.idle);
}
}
public virtual void Estadoatacar()
{
if (distancia > distanciaatacar + 0.4f)
{
cambiarestado(Estados.Seguir);
}
}
public virtual void Estadomuerto()
{
}
IEnumerator CalcularDistancia()
{
while (vivo)
{
yield return new WaitForSeconds(0.2f);
if (target != null)
{
distancia = Vector3.Distance(transform.position, target.position);
}
}
}
public enum Estados
{
idle = 0,
Seguir = 1,
atacar = 2,
muerto = 3
}
}
Y tambien necesitamos de este
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
[RequireComponent(typeof(NavMeshAgent))]
public class Enemigo0orco : Enemigo
{
private UnityEngine.AI.NavMeshAgent agente;
public Animator animaciones;
public float daño = 3;
private void Awake()
{
base.Awake();
agente = GetComponent<UnityEngine.AI.NavMeshAgent>();
}
public override void Estadoidle()
{
base.Estadoidle();
if(animaciones!=null) animaciones.SetFloat(“velocidad”,0);
if(animaciones!=null) animaciones.SetBool(“atacando”,false);
agente.SetDestination(transform.position);
}
public override void Estadoseguir()
{
base.Estadoseguir();
if(animaciones!=null) animaciones.SetFloat(“velocidad”,1);
if(animaciones!=null) animaciones.SetBool(“atacando”,false);
agente.SetDestination(target.position);
}
public override void Estadoatacar()
{
base.Estadoatacar();
if(animaciones!=null) animaciones.SetFloat(“velocidad”,0);
if(animaciones!=null) animaciones.SetBool(“atacando”,true);
agente.SetDestination(transform.position);
transform.LookAt(target, Vector3.up);
}
public override void Estadomuerto()
{
base.Estadomuerto();
if(animaciones!=null) animaciones.SetBool(“vivo”,false);
agente.enabled = false;
}
[ContextMenu(“Matar”)]
public void Matar()
{
cambiarestado(Estados.muerto);
}
public void Atacar()
{
Personaje.singleton.salud.CausarDaño(daño);
}
}
Paso 40
Modificamos los datos seleccionados con la flecha

Paso 41
Enemigo patrullero Script
using UnityEngine;
using UnityEngine.AI;
[RequireComponent(typeof(NavMeshAgent))]
public class Patrullaje : Enemigo
{
private UnityEngine.AI.NavMeshAgent agente;
public Animator animaciones;
public Transform[] CheckPoints;
private int indice;
public float distanciaCheckpoints;
private float distanciaCheckpoints2;
public float daño = 3;
private void Awake()
{
base.Awake();
agente = GetComponent<UnityEngine.AI.NavMeshAgent>();
distanciaCheckpoints2 = distanciaCheckpoints * distanciaCheckpoints;
}
public override void Estadoidle()
{
base.Estadoidle();
if(animaciones!=null) animaciones.SetFloat(“velocidad”,1);
if(animaciones!=null) animaciones.SetBool(“atacando”,false);
agente.SetDestination(CheckPoints[indice].position);
if((CheckPoints[indice].position – transform.position).sqrMagnitude < distanciaCheckpoints2)
{
indice = (indice + 1) % CheckPoints.Length;
}
}
public override void Estadoseguir()
{
base.Estadoseguir();
if(animaciones!=null) animaciones.SetFloat(“velocidad”,1);
if(animaciones!=null) animaciones.SetBool(“atacando”,false);
agente.SetDestination(target.position);
}
public override void Estadoatacar()
{
base.Estadoatacar();
if(animaciones!=null) animaciones.SetFloat(“velocidad”,0);
if(animaciones!=null) animaciones.SetBool(“atacando”,true);
agente.SetDestination(transform.position);
transform.LookAt(target, Vector3.up);
}
public override void Estadomuerto()
{
base.Estadomuerto();
if(animaciones!=null) animaciones.SetBool(“vivo”,false);
agente.enabled = false;
}
[ContextMenu(“Matar”)]
public void Matar()
{
cambiarestado(Estados.muerto);
}
public void Atacar()
{
Personaje.singleton.salud.CausarDaño(daño);
}
}
Paso 42
Modificaremos los puntos por los cuales se moverá el enemigo. Los “checkpoints” serán objetos vacíos que ubicaremos en las posiciones por donde queremos que pase.

Paso 43
Crear enemigo perseguidor
Creamos el script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class Perseguidor : MonoBehaviour
{
public Transform objetivo;
public NavMeshAgent agente;
public Animator animador;
public float distanciaAtaque = 2.0f;
public float daño = 3f;
private float tiempoEntreAtaques = 1.5f;
private float tiempoProximoAtaque = 0f;
void Start()
{
if (objetivo == null)
{
GameObject jugador = GameObject.FindGameObjectWithTag(“Player”);
if (jugador != null)
{
objetivo = jugador.transform;
}
}
}
void Update()
{
if (objetivo == null) return;
float distancia = Vector3.Distance(transform.position, objetivo.position);
if (distancia > distanciaAtaque)
{
agente.SetDestination(objetivo.position);
if (animador != null)
{
animador.SetFloat(“velocidad”, agente.velocity.magnitude);
animador.SetBool(“atacando”, false);
}
}
else
{
agente.SetDestination(transform.position); // se detiene
transform.LookAt(objetivo);
if (animador != null)
{
animador.SetFloat(“velocidad”, 0);
animador.SetBool(“atacando”, true);
}
if (Time.time >= tiempoProximoAtaque)
{
Atacar();
tiempoProximoAtaque = Time.time + tiempoEntreAtaques;
}
}
}
void Atacar()
{
// Aquí puedes acceder a la salud del jugador si tienes un sistema
if (objetivo.TryGetComponent<Salud>(out Salud saludJugador))
{
saludJugador.CausarDaño(daño);
}
Debug.Log(“¡Atacando al jugador!”);
}
}
Paso 44
Una vez creado, asignamos el script al enemigo y configuramos los objetos correspondientes en el inspector.

Es importante mencionar que se debe agregar el componente Nav Mesh Agent para que todo lo que se implemente funcione correctamente.
Paso 45
Se agregará música o sonidos tanto para los enemigos como para el juego en general.
Para el entorno del juego, creamos un objeto vacío al que le asignamos el sonido que hayamos descargado.


Para crear la selección de personajes
Creamos el script
using UnityEngine;
using UnityEngine.SceneManagement;
public class SelectorPersonajes : MonoBehaviour
{
public GameObject[] personajesDisponibles;
public Transform puntoSpawn;
private int indiceSeleccionado = 0;
void Start()
{
MostrarPersonaje(indiceSeleccionado);
}
void MostrarPersonaje(int indice)
{
// Destruye el personaje anterior si existe
foreach (Transform hijo in puntoSpawn)
{
Destroy(hijo.gameObject);
}
// Instancia el nuevo personaje
GameObject personaje = Instantiate(personajesDisponibles[indice], puntoSpawn.position, Quaternion.identity);
personaje.transform.parent = puntoSpawn;
}
public void SiguientePersonaje()
{
indiceSeleccionado = (indiceSeleccionado + 1) % personajesDisponibles.Length;
MostrarPersonaje(indiceSeleccionado);
}
public void PersonajeAnterior()
{
indiceSeleccionado = (indiceSeleccionado – 1 + personajesDisponibles.Length) % personajesDisponibles.Length;
MostrarPersonaje(indiceSeleccionado);
}
public void ConfirmarSeleccion()
{
PlayerPrefs.SetInt(“personajeSeleccionado”, indiceSeleccionado);
SceneManager.LoadScene(“Juego”);
// Cambia por el nombre de tu escena de juego
}
}
Paso 46
Sonido para enemigos, agregamos el componente del sonido preferido al enemigo y ajustamos volumen

Paso 47 creación de escena en unity
Inicio de juego, creamos una escena nueva
Para crear la selección de personajes
Creamos el script
using UnityEngine;
using UnityEngine.SceneManagement;
public class SelectorPersonajes : MonoBehaviour
{
public GameObject[] personajesDisponibles;
public Transform puntoSpawn;
private int indiceSeleccionado = 0;
void Start()
{
MostrarPersonaje(indiceSeleccionado);
}
void MostrarPersonaje(int indice)
{
// Destruye el personaje anterior si existe
foreach (Transform hijo in puntoSpawn)
{
Destroy(hijo.gameObject);
}
// Instancia el nuevo personaje
GameObject personaje = Instantiate(personajesDisponibles[indice], puntoSpawn.position, Quaternion.identity);
personaje.transform.parent = puntoSpawn;
}
public void SiguientePersonaje()
{
indiceSeleccionado = (indiceSeleccionado + 1) % personajesDisponibles.Length;
MostrarPersonaje(indiceSeleccionado);
}
public void PersonajeAnterior()
{
indiceSeleccionado = (indiceSeleccionado – 1 + personajesDisponibles.Length) % personajesDisponibles.Length;
MostrarPersonaje(indiceSeleccionado);
}
public void ConfirmarSeleccion()
{
PlayerPrefs.SetInt(“personajeSeleccionado”, indiceSeleccionado);
SceneManager.LoadScene(“Juego”); // Cambia por el nombre de tu escena de juego
}
}
Paso 48
Una vez creado el script, creamos el canvas, importamos los players y asignamos el script, configurando así los botones.




El desarrollo de este juego en Unity representa una experiencia completa de diseño interactivo, combinando mecánicas de supervivencia, recolección de objetos y personalización avanzada del personaje mediante FaceBuilder. La implementación de inteligencia artificial para los enemigos agrega un nivel de realismo y dificultad que mantiene al jugador constantemente alerta.
Este proyecto no solo demuestra habilidades técnicas en programación y diseño 3D, sino que también destaca la importancia de la inmersión y la personalización en los videojuegos modernos. Al permitir que los jugadores vean su propio rostro en el protagonista, se refuerza el vínculo emocional con el juego y se potencia la experiencia interactiva.
Resultado Final juego en unity:
Créditos
Autor : Daniela Caterine Quintero Aguilera y Juan Camilo Uribe Franco
Editor: Magister e ingeniero Carlos Iván Pinzón Romero
Código: UCCG1-9
Universidad: Universidad Central
Referecias
Adobe Systems Incorporated. (s.f.). Mixamo. https://www.mixamo.com/
IRulosso.2024. ¡Cómo Crear un Personaje en Unity 3D! (Fácil y Rápido). [Video]. YouTube. https://www.youtube.com/watch?v=FURegpjm9sY
LuisCanary. 2020, abril 25. Generar ejecutable de tu Videojuego con Unity/Sacar .exe o .apk para PC o Android/Compilar. [Video]. YouTube. https://www.youtube.com/watch
Morion Tutoriales (Morion VO) 2022, mayo 1. Personaje en tercera persona Unity [Video]. YouTube. https://www.youtube.com/watch?v=dltY_3nMZPo
Morion Tutoriales (Morion VO) 2023, septiembre 25. IA para enemigos en Unity - Cap 1 [Video]. YouTube. https://www.youtube.com/watch?v=pKMp_MKIamc&list=PLjCKKt9GhZuK7mSzeecIeANnjrt7YEqi8&index=1
Maty Dev. 2024, marzo 24. El MEJOR TUTORIAL de selector de personajes de UNITY [Video]. YouTube. https://www.youtube.com/watch?v=R9ywZcMdMoU
Pixabay. (s.f.). Efectos de sonido de bosque horror - búsqueda. https://pixabay.com/es/sound-effects/search/bosque-horror/
Unity Technologies. (s.f.). Unity Asset Store. https://assetstore.unity.com/