Niixervideo juegos serios

Paso a paso videojuego cámara 0

Introducción

Este videojuego serio en 2D, desarrollado con Unity, está diseñado como una herramienta educativa para enseñar de manera práctica y entretenida los conceptos básicos sobre el funcionamiento de la cámara. A través de una experiencia interactiva, el jugador aprenderá desde las partes fundamentales de una cámara hasta principios clave como la exposición, el enfoque, la apertura del diafragma y la velocidad de obturación.

Con un estilo visual accesible y dinámicas diseñadas específicamente para facilitar la comprensión, el juego combina elementos pedagógicos con mecánicas simples para fomentar el aprendizaje autónomo. Su diseño en 2D y desarrollo en Unity permiten una ejecución fluida y multiplataforma, adecuada tanto para entornos educativos como para uso personal.

A continuación, se presenta el proceso detallado de creación de este videojuego.

Pantalla principal

Primeramente se va a configurar una pantalla para poder iniciar el juego, para esto se crea una nueva escena.

En este caso se llama menú inicial ya que será la pantalla con los botones para iniciar el juego

menú inicial


Se abre dicha escena para que se pueda ver en la jerarquía (Hierachy) en la parte izquierda del editor

jerarquía (Hierachy)

Para crear el canvas inicial, dentro de la jerarquía se presiona clic derecho, UI y en la opción donde dice canvas

Ahora dentro del canvas creado anteriormente se va a crear un panel que será la imagen de fondo que se va a tener para la pantalla inicial

En este caso se le dio el nombre de inicio

El plano creado se debe ver de la siguiente manera

Ahora en la parte derecha, es decir en el inspector se busca el apartado donde dice background

Para la imagen que se va a poner se arrastra la imagen deseada en la parte de abajo en los assets

Posteriormente se selecciona la imagen y se arrastra al apartado background mostrado anteriormente

Después de esto el plano quedará automáticamente con la imagen asignada 

Botones

En la jerarquía se vuelve a presionar clic derecho para crear un botón en las siguientes opciones

Se creará un botón y al desplegar saldrá un apartado llamado text TMP

Se selecciona para poder editar el texto que llevará el botón desde el inspector

Después de esto volviendo a seleccionar el botón se podrá cambiar el color nuevamente en el inspector

Ahora se repite el mismo procedimiento para crear el botón de salir 

Una vez realizado esto se tendrán los dos botones que irán en la pantalla principal

Ya que se tienen acomodados los botones se les debe agregar la funcionalidad mediante scripts usando en este caso Visual Studio Code, para esto nos dirigimos al canvas 

Y en la parte derecha, en el inspector seleccionamos la opción Add component para añadir un nuevo script 

Se le da un nombre y se selecciona new script de la siguiente manera

Se abre el script en Visual y por defecto se va a ver así:

using UnityEngine;

public class MenuInicial : MonoBehaviour
{
    
}

Para poder cambiar entre escenas en la parte superior del código se debe importar la siguiente librería

using UnityEngine.SceneManagment;

Se crea la función jugar y dentro de este se escribe scene manger.load scene para cargar una escena y dentro del paréntesis se le escribe get active scene para abrir la escena en la que nos encontramos y se le suma 1 al final para que cargue la siguiente 

 public void Jugar(string Selección)
    {
        SceneManager.LoadScene(Selección);
    }

Ahora se crea la función para salir de la aplicación

Adentro se escribe Application.Quit(); que será lo que va a cerrar el juego cuando este creado

    public void salir()
    {
        Debug.Log("Salir...");
        Application.Quit();
    }
}

Para tener el orden de las escenas posteriores y que se pueda hacer el cambio de escenas correctamente se deben ordenar, para esto nos dirigimos a el apartado file y luego en build profiles en la parte superior

Aca se deben ordenar correctamente las escenas que se van a tener, en caso de agregar una una nueva se presiona Add open scenes y para ordenarlas se selecciona la escena y se arrastra al orden que se quiera, en este caso ya se ordenaron correctamente

Por último se le agrega la funcionalidad que se establece en los canvas a los botones creados, para esto abrimos el botón jugar primero y en el inspector en la parte derecha nos vamos al componente llamado button

En la parte de abajo de este componente donde dice On click se presiona el simbolo + de la parte inferior derecha 

Cuando se le da al + saldra lo siguente dentro de esa interfaz

Ahora se debe arrastrar el canvas en donde dice None(Object)

Al lado donde dice no function se despliega ese menú y se le da en donde dice menú Inicial que corresponde al script creado anteriormente

Y dentro de menú inicial se busca la funcion jugar

Se repite la misma operación para el botón de salir a diferencia de que en el último paso en la función se selecciona salir

Para probar la funcionalidad de los botones se le da play en la parte superior del editor 

Al presionar salir nos aparecerá el mensaje en la parte inferior ya que no se tiene el juego terminado por ahora solo se mostrará dicho mensaje

Y al presionar jugar nos lleva a la siguiente escena.

Historia

Antes de empezar con el juego, se da una breve introducción donde se le da al jugador su misión además de empezar con el aprendizaje que se quiere impartir

Se crea una nueva escena llamada introducción

Se repite el mismo procedimiento que se hizo para agregar una imagen en la portada para poder darle un fondo a esta nueva escena, posteriormente se añade un texto 

Por último se le agrega un botón con el mismo script usado en la escena anterior para cambiar a la siguiente

Cinemática inicial

Para la cinemática inicial realizada con CapCut y se eañade a los assets de unity el video en formato mp4

Se creo una nueva escena llamada cinemática y siguiendo la misma lógica de poner los botones para la siguiente escena se creó un canvas donde se añadió dicho botón y se crea un nuevo objeto llamado videoPlayer

Seleccionando el video player nos dirigimos al inspector y en la opción llamada videoplayer donde dice videoClip se arrastra el video anterior

Al reproducir la escena se reproduce el video con el botón previamente establecido

Video de la cinemática

Ventana de instrucciones

Se sigue el mismo procedimiento anterior con la imagen requerida para darle las instrucciones al jugador

Para añadir las escenas se debe tener en cuenta el orden de las mismas, esto se hace yendo nuevamente a la sección filoe en la parte superior izquierda y en build profiles, más adelante si se siguen incluyendo escenas se debe tener en cuenta la realización de este paso

Construcción nivel 1

Para esto se importa un mapa previamente buscado en los assets de unity y se arrastra a la escena

Para poder poner los fondos se crea un nuevo objeto llamado Quad

Este objeto se verá como un rectángulo en la escena y se le pueden arrastrar imágenes previamente cargadas para generar el efecto del fondo

Y para llenar el mapa se pueden simplemente copiar y pegar para posteriormente distribuirlos a lo largo del mapa

Después de completar el primer nivel va a aparecer una pantalla con la información de cada una de las partes encontradas y la cámara armada

Para esto se hace una nueva escena y se le pone un plano siguiendo los mismos pasos anteriores

Construcción nivel 2

Animación de enemigos

Es necesario colocar el sprint en complementos del muñeco y coloque el avatar  para que el enemigo  siga al jugador 

Script Enemy controller

using UnityEngine;
using System.Collections;

[RequireComponent(typeof(Animator))]
[RequireComponent(typeof(Rigidbody2D))]
[RequireComponent(typeof(Collider2D))] // Añadido para asegurar colisionador
public class EnemyControler : MonoBehaviour
{
    [Header("Configuración de Objetivo")]
    [SerializeField] private Transform playerTarget;
    [SerializeField] private float detectionRange = 5f;
    [SerializeField] private float stoppingDistance = 1f;

    [Header("Configuración de Movimiento")]
    [SerializeField] private float moveSpeed = 3f;
    [SerializeField] private float acceleration = 5f;
    [SerializeField] private float deceleration = 7f;

    [Header("Configuración de Ataque")]
    [SerializeField] private float attackRange = 0.8f;
    [SerializeField] private int attackDamage = 10;
    [SerializeField] private float attackCooldown = 2f;
    [SerializeField] private float attackDuration = 1f;
    [SerializeField] private LayerMask playerLayer;
    public int damageOnCollision = 1;
    public float knockbackForce = 10f;
    public float stompForce = 15f;

    [Header("Componentes")]
    private Animator animator;
    private Rigidbody2D rb;
    private Collider2D enemyCollider; // Añadido referencia al collider
    private float lastAttackTime;
    private bool isAttacking;
    private Vector2 currentVelocity;
    private Coroutine attackCoroutine;

    private void Awake()
    {
        animator = GetComponent<Animator>();
        rb = GetComponent<Rigidbody2D>();
        enemyCollider = GetComponent<Collider2D>(); // Inicializar collider
        
        // Configuración física mejorada
        rb.constraints = RigidbodyConstraints2D.FreezeRotation;
        rb.gravityScale = 3f;
        rb.collisionDetectionMode = CollisionDetectionMode2D.Continuous; // Mejor detección de colisiones
        
        // Asegurar que el tag es correcto
        gameObject.tag = "Enemy";
    }

    private void Start()
    {
        // Buscar al jugador si no está asignado
        if (playerTarget == null)
        {
            GameObject player = GameObject.FindGameObjectWithTag("Player");
            if (player != null) playerTarget = player.transform;
        }
    }

    private void Update()
    {
        if (playerTarget == null) return;

        float distanceToPlayer = Vector2.Distance(transform.position, playerTarget.position);
        bool playerInRange = distanceToPlayer <= detectionRange;
        bool shouldChase = playerInRange && distanceToPlayer > stoppingDistance && !isAttacking;
        bool shouldAttack = distanceToPlayer <= attackRange && !isAttacking && Time.time >= lastAttackTime + attackCooldown;

        HandleMovement(shouldChase);
        HandleOrientation();

        animator.SetBool("IsAttacking", isAttacking);

        if (shouldAttack)
        {
            attackCoroutine = StartCoroutine(PerformAttack());
        }

        if (isAttacking && distanceToPlayer > attackRange * 1.5f)
        {
            InterruptAttack();
        }
    }

    private void HandleMovement(bool shouldChase)
    {
        if (isAttacking)
        {
            currentVelocity = Vector2.zero;
            rb.linearVelocity = new Vector2(0, rb.linearVelocity.y); // Mantener velocidad Y
            return;
        }

        if (shouldChase)
        {
            Vector2 direction = (playerTarget.position - transform.position).normalized;
            direction.y = 0;
            
            currentVelocity = Vector2.MoveTowards(
                currentVelocity, 
                direction * moveSpeed, 
                acceleration * Time.deltaTime);
        }
        else
        {
            currentVelocity = Vector2.MoveTowards(
                currentVelocity, 
                Vector2.zero, 
                deceleration * Time.deltaTime);
        }

        rb.linearVelocity = new Vector2(currentVelocity.x, rb.linearVelocity.y);
        animator.SetBool("running", shouldChase);
    }

    private void HandleOrientation()
    {
        if (playerTarget == null) return;

        float directionToPlayer = playerTarget.position.x - transform.position.x;
        if (Mathf.Abs(directionToPlayer) > 0.1f)
        {
            transform.localScale = new Vector3(
                Mathf.Sign(directionToPlayer) * Mathf.Abs(transform.localScale.x), 
                transform.localScale.y, 
                transform.localScale.z);
        }
    }

    private IEnumerator PerformAttack()
    {
        isAttacking = true;
        lastAttackTime = Time.time;

        // Detener movimiento pero mantener gravedad
        currentVelocity = Vector2.zero;
        rb.linearVelocity = new Vector2(0, rb.linearVelocity.y);

        animator.SetTrigger("Attack");
        animator.SetBool("IsAttacking", true);

        yield return new WaitForSeconds(0.3f);

        if (Vector2.Distance(transform.position, playerTarget.position) <= attackRange * 1.2f)
        {
            ApplyDamage();
        }

        yield return new WaitForSeconds(attackDuration - 0.3f);

        isAttacking = false;
        animator.SetBool("IsAttacking", false);
        attackCoroutine = null;
    }

    private void InterruptAttack()
    {
        if (attackCoroutine != null)
        {
            StopCoroutine(attackCoroutine);
            attackCoroutine = null;
        }

        isAttacking = false;
        animator.SetBool("IsAttacking", false);
        animator.SetTrigger("CancelAttack");
    }

    private void ApplyDamage()
    {
        Collider2D[] hits = Physics2D.OverlapCircleAll(transform.position, attackRange, playerLayer);
        foreach (var hit in hits)
        {
            if (hit.CompareTag("Player"))
            {
                WarriorMovimiento warrior = hit.GetComponent<WarriorMovimiento>();
                if(warrior != null)
                {
                    Vector2 direction = (hit.transform.position - transform.position).normalized;
                    if(Mathf.Abs(direction.x) < 0.3f) direction.x = Mathf.Sign(direction.x) * 0.3f;
                    direction.y = Mathf.Clamp(direction.y, 0.3f, 0.7f);
                    warrior.RecibirDanio(direction, attackDamage);
                }
                break;
            }
        }
    }

    private void OnCollisionEnter2D(Collision2D collision)
    {
        if(collision.gameObject.CompareTag("Player"))
        {
            WarriorMovimiento warrior = collision.gameObject.GetComponent<WarriorMovimiento>();
            if(warrior != null)
            {
                if(IsPlayerStomping(collision))
                {
                    warrior.RecibirDanio(Vector2.up, damageOnCollision);
                    warrior.GetComponent<Rigidbody2D>().AddForce(Vector2.up * stompForce, ForceMode2D.Impulse);
                    
                    // Aplicar fuerza hacia abajo al enemigo
                    rb.AddForce(Vector2.down * stompForce * 0.5f, ForceMode2D.Impulse);
                }
                else
                {
                    Vector2 direction = (collision.transform.position - transform.position).normalized;
                    if(Mathf.Abs(direction.x) < 0.3f) direction.x = Mathf.Sign(direction.x) * 0.3f;
                    direction.y = Mathf.Clamp(direction.y, 0.3f, 0.7f);
                    warrior.RecibirDanio(direction, damageOnCollision);
                }
            }
        }
    }

    private bool IsPlayerStomping(Collision2D collision)
    {
        foreach(ContactPoint2D contact in collision.contacts)
        {
            if(contact.normal.y < -0.5f && collision.rigidbody.linearVelocity.y < 0)
            {
                return true;
            }
        }
        return false;
    }

    // Para visualizar rangos en el editor
    private void OnDrawGizmosSelected()
    {
        // Rango de detección (amarillo)
        Gizmos.color = Color.yellow;
        Gizmos.DrawWireSphere(transform.position, detectionRange);
        
        // Rango de ataque (rojo)
        Gizmos.color = Color.red;
        Gizmos.DrawWireSphere(transform.position, attackRange);
        
        // Rango de parada (verde)
        Gizmos.color = Color.green;
        Gizmos.DrawWireSphere(transform.position, stoppingDistance);
    }
}

Construcción nivel 3 Trivia

Se crea una nueva escena y se le añade un panel, en este caso se llama Nivel_1

Después se le añade una imagen a este panel

Posteriormente se añade un texto siguiendo el mismo paso anterior

Se añaden cuatro botones abajo que serán las opciones de respuesta

Se escriben los textos correspondientes a cada respuesta en los textos de los botones

Se arrastra una imagen en la en el apartado donde dice image en el inspector de la imagen que se creó al principio

Para realizar más preguntas copiamos el canvas creado para nivel 1 y se deben cambiar acorde a las preguntas que se vayan a realizar

Ya con las preguntas terminadas, se crea un gameobject en la jerarquía

En este caso se nombra GameManager

Y creamos un nuevo script llamado Manager

Volvemos al inspector del GameManager

Y dónde niveles se escribe la cantidad de preguntas realizadas, en este caso 4, al agregar esto se despliegan los elementos, en cada uno se deben arrastrar las preguntas

Ahora para las respuestas correctas nos vamos al inspector en cada una de las preguntas correctas y le damos la siguiente acción en el apartado onClick

Conclusión

Así fue como se creó Cámara 0, un videojuego 2D que mezcla lo educativo con lo narrativo para enseñar los fundamentos de la fotografía desde una experiencia interactiva y entretenida. A lo largo del desarrollo, se buscó no solo diseñar mecánicas que facilitaran el aprendizaje, sino también construir un universo visual y sonoro que envolviera al jugador en una historia cargada de nostalgia, exploración y descubrimiento.

Este documento detalla paso a paso cómo se construyó el juego en Unity: desde el diseño de los personajes y escenarios en pixel art, la planificación del guion visual, hasta la programación de mecánicas que permiten recolectar piezas y avanzar en la historia. Cada parte fue pensada no solo como una herramienta lúdica, sino como una forma de reforzar conceptos clave sobre el arte de la fotografía, su lenguaje, y su valor expresivo.

El objetivo principal siempre fue claro: demostrar que aprender también puede ser una aventura. Y Cámara 0 se convirtió en eso —una puerta a un mundo olvidado donde ver con intención es el primer paso para crear. Este proyecto no solo propone una forma alternativa de enseñar, sino que también rinde homenaje a la imagen como forma de memoria, arte y lenguaje universal.

Créditos

Autor: Catalina Rojas, Alexander Forero, Alejandro Rodriguez

Editor: Magister ingeniero Carlos Iván Pinzón Romero

Código: UCVS-9

Universidad: Universidad Central

Fuentes

KanarianDev. (2024, noviembre 3). ¡ANIMA a TU ENEMIGO en UNITY! [Video]. YouTube. https://www.youtube.com/watch?v=yWvUwpjUXkg&list=PL9v36qYVbM9hXN4qovHah718G_gfAkMJl&index=13
KanarianDev. (2024, mayo 3). ¡ANIMA a TU PERSONAJE en UNITY! [Video]. YouTube. https://www.youtube.com/watch?v=kM-jcFYVY3s
KanarianDev. (2024, agosto). ¡CÓMO hacer un ENEMIGO en UNITY! [Video]. YouTube. https://www.youtube.com/watch?v=ZvIB-u-jgEA
KanarianDev. (2024, agosto 21). ¡CÓMO hacer un ENEMIGO en UNITY! [Video]. YouTube. https://www.youtube.com/watch?v=ZvIB-u-jgEA&list=PL9v36qYVbM9hXN4qovHah718G_gfAkMJl&index=11
KanarianDev. (2024, mayo 15). ¡COMO usar el ANIMATOR en UNITY! [Video]. YouTube. https://www.youtube.com/watch?v=lu-w7A8weVI
KanarianDev. (2024, diciembre 19). Domina el Tilemap en Unity y crea juegos increíbles [Video]. YouTube. https://www.youtube.com/watch?v=ENOtmXIbA14