Introducción

1

Game Design del juego

2

La estructura y assets de un proyecto en Unity

3

Convirtiendo nuestros assets en Tiles

4

Tilemaps y creación del escenario

5

Bonus: FastForward de la creación del escenario, sorting layers y creación de colliders

6

Acá encontrarás los archivos de este curso

Personaje principal: Movimiento y Animaciones

7

El jugador y el movimiento

8

Creando nuestra primera animación

9

Creando el grafo de animaciones

10

Usando un árbol de animaciones

11

Mostrando la solución del desafío e implementando transición entre blended trees

12

Programando una cámara que siga al jugador

13

Corrección del bug gráfico

14

límites del escenario, rigid bodies

15

Ejercicio: diseño de interiores

Escenarios Avanzados

16

Transiciones entre escenas

17

Mantener Player y Camera entre escenas

18

Spawning Points: programando que el jugador y la cámara aparezcan en el lugar correcto al cambiar de escena

19

Agregando Identificadores a nuestros Spawning Points para controlar mejor el flujo de nuestro juego

Enemigos Avanzados

20

Creando nuestro enemigo

21

Programando las físicas y el patrullaje del enemigo

22

Generando movimiento completamente aleatorio

23

Programando el ataque del enemigo

24

Crear Manager de Health del Player

25

Crear armas

26

Programando el ataque del Player con arma

27

Mover la espada al caminar

28

Creando el ataque con espada

29

Ejecutando el ataque con un botón

30

Movimiento en diagonal

31

Optimizando nuestro player controller

32

Ataque mejorado

33

Uso de partículas

34

Añadir el daño del enemigo en batalla

35

Programando los contadores de vida del enemigo

36

Colocando más info de UI en pantalla

37

Script de la vida

Personaje principal avanzado

38

Añadir el daño del personaje (ejercicio)

39

Sistema de puntos de experiencia para nuestro personaje principal

40

Level Up!

41

Level Up! Defensa y Reto Final del Módulo: Stats de los enemigos

42

Creando un NPC

43

Limitar el movimiento de nuestro NPC

44

Creando una pantalla de diálogos para nuestro RPG

45

El diálogo del NPC

46

Múltiples líneas de diálogo

47

Parar el NPC durante el diálogo

48

Parar el personaje durante el diálogo

Quests

49

La estructura de una quest

50

Quest 1: Ir a un lugar

51

Quest 2: Encontrar un objeto

52

Quest 3: Matar enemigos

53

Revisión de bugs

54

Mantener la cámara dentro del escenario

55

El problema al cambiar de escena

Audio

56

Agregando SFX a nuestro videojuego

57

Agregando música a nuestro videojuego

58

Ajustar volúmen del audio de cada uno de los efectos de sonido

59

Creando un VolumeManager

60

Agregando economía a nuestro juego y cierre

No tienes acceso a esta clase

¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera

Programando las físicas y el patrullaje del enemigo

21/60
Recursos

¿Cómo programar movimiento aleatorio en un enemigo de videojuego?

¡Crear un enemigo que se mueva inteligentemente por el escenario puede elevar el nivel de tu videojuego a nuevas alturas! Este ejercicio te guiará en la programación de un script en C# que dota a tu enemigo de movimiento aleatorio en un juego tipo rol. Sin embargo, este script es lo suficientemente genérico para que se aplique tanto a distintos tipos de enemigos como a múltiples mecánicas dentro del juego.

La clave aquí será crear una pequeña inteligencia artificial que simule el desplazamiento aleatorio de los enemigos. ¡Manos a la obra!

¿Cómo estructurar el script del controlador del enemigo?

Para comenzar, necesitamos un nuevo script en C# que sea llamado EnemyController. Asegúrate de asignarlo correctamente al objeto enemigo en Unity. Este controlador será el cerebro que manejará el movimiento aleatorio del enemigo. Una vez preparado, abriremos el script en Visual Studio para definir las variables necesarias.

¿Cuáles son las variables esenciales para el movimiento del enemigo?

Dentro de nuestro script, introduciremos variables que contribuirán directamente al manejo del movimiento. Estas incluyen:

  • public float enemySpeed = 1;: define la velocidad inicial del enemigo.
  • private Rigidbody2D enemyRigidbody;: necesario para manejar las físicas de su movimiento.
  • private bool isMoving;: identifica cuándo el enemigo está en movimiento.
  • public float timeBetweenSteps;: tiempo entre cada movimiento.
  • private float timeBetweenStepsCounter;: control de tiempo entre movimientos.
  • public float timeToMakeStep;: tiempo que dura un movimiento.
  • private float timeToMakeStepCounter;: contador para calcular el tiempo de un movimiento.
  • public Vector2 directionToMakeStep;: dirección del próximo movimiento.
  • private Animator enemyAnimator;: para actualizar la dirección del movimiento en las animaciones.
  • Constantes para animaciones:
  • private const string horizontal = "Horizontal";
  • private const string vertical = "Vertical";

¿Cuál es la lógica detrás de su movimiento?

El flujo del movimiento se diseñará dentro del método Update. Se verificará constantemente si el enemigo está en movimiento o detenido y actuará en consecuencia:

  1. Movimiento activo (isMoving):
  • Resta Time.deltaTime a timeToMakeStepCounter.
  • Mueve al enemigo aplicando enemyRigidBody.velocity = directionToMakeStep * enemySpeed;.
  • Si el contador llega a cero, se detiene el movimiento asignando enemyRigidbody.velocity = Vector2.zero;, marcando isMoving como falso.
  1. Movimiento inactivo:
  • Decrementa timeBetweenStepsCounter utilizando Time.deltaTime.
  • Al agotarse el contador, se activa nuevamente isMoving e inicia un nuevo periodo de movimiento asignando valores aleatorios a directionToMakeStep con Random.Range.

Al final del Update, se deben enviar las direcciones al Animator para reflejar los cambios visuales en el personaje.

enemyAnimator.SetFloat(horizontal, directionToMakeStep.x);
enemyAnimator.SetFloat(vertical, directionToMakeStep.y);

¿Por qué es importante ajustar los parámetros?

Los parámetros definen aspectos importantes como la velocidad y tiempo entre movimientos que influyen en el dinamismo y fluidez del juego. Un ejemplo de configuración podría ser:

  • Velocidad (enemySpeed) de 1 unidad.
  • Tiempo entre pasos (timeBetweenSteps): 2 segundos.
  • Tiempo para un paso (timeToMakeStep): 1.5 segundos.

Estos valores deben ser configurados cuidadosamente para garantizar un comportamiento realista y desafío para el jugador. Con estos ajustes, podrás observar cómo el enemigo cambia de dirección y se desplaza aleatoriamente por el nivel, haciendo tu juego más interesante e impredecible. ¡No olvides guardar el script y dale al play para ver tu progreso en acción!

Aportes 10

Preguntas 0

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

aquí les dejo código ya comentado


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EnemyController : MonoBehaviour
{
    public float enemySpeed = 1;//velocidad movimiento
    private Rigidbody2D enemyRigidbody;//rigidbody enemigo

    private bool isMoving;//saber si se esta moviendo o no

    public float timeBetweenSteps;//tiempo entre movimientos
    private float timeBetweenStepsCounter;//contador cuanto tiempo a pasado desde el ultimo movimiento

    public float timeToMakeStep;//el tiempo que pasa en hacer el paso de una celda a la siguiente
    private float timeToMakeStepCounter;//el condador de cuanto tiempo a pasado en hacer el paso

    public Vector2 directionToMakeStep;//una direccion de movimiento

    private Animator enemyAnimator;//para transmitir los parametros horizontal y vertical 
    private const string horizontal = "Horizontal";//nombre de los parametros que estan en unity
    private const string vertical = "Vertical";//nombre de los parametros que estan en unity



    // Start is called before the first frame update
    void Start()
    {
        enemyRigidbody = GetComponent<Rigidbody2D>();//inicilisamos las variabes
        enemyAnimator = GetComponent<Animator>();////inicilisamos las variabes

        timeBetweenStepsCounter = timeBetweenSteps;//se inicialice con la informacion que le ponemos en unity
        timeToMakeStepCounter = timeToMakeStep;//se inicialice con la informacion que le ponemos en unity
    }

    // Update is called once per frame
    void Update()
    {
        if (isMoving)
        {
            timeToMakeStepCounter -= Time.deltaTime;//descuenta el tiempo del ultimo renderisado
            enemyRigidbody.velocity = directionToMakeStep;//movemos al enemigo a la direccion

            if (timeToMakeStepCounter < 0)//si se acaba el tiempo de movimiento
            {
                isMoving = false;//pone en falso el movimiento
                timeBetweenStepsCounter = timeBetweenSteps;//reinicia el contador
                enemyRigidbody.velocity = Vector2.zero;//para el movimiento
            }
        }
        else//si no se esta moviendo
        {
            timeBetweenStepsCounter -= Time.deltaTime;//resta tiempo al contador
            if (timeBetweenStepsCounter < 0)//si se acaba el tiempo de espera para el siguiente
            {
                isMoving = true;//ponemos en true para empesar a movernos
                timeToMakeStepCounter = timeToMakeStep;//re iniciamos el contador
                directionToMakeStep = new Vector2(Random.Range(-1, 2), Random.Range(-1, 2)) * enemySpeed;//nos movemos
            }
        }
        
        enemyAnimator.SetFloat(horizontal, directionToMakeStep.x);//lo movemos ya con los valores dados
        enemyAnimator.SetFloat(vertical, directionToMakeStep.y);//lo movemos ya con los valores dados
    }
}



Hay algo que no se explica con el Random.Range y es que cuando se usan valores --int-- el segundo valor es exclusivo, esa es la razon por la que el personaje siempre baja en la demostracion, entonces deberia ser Random.Range(-1,2) o Random.Range(-1f,1f)

Siempre es importante establecer y definirle una capa al enemigo.

Hice el codigo del enemigo similar al del jugador que cuando no se esta moviendo se queda parado viendo en la ultima dirrecion que este se movio

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EnemyController : MonoBehaviour
{
    public float enemySpeed = 1;
    private Rigidbody2D enemyRigidbody;

    private bool isMoving = false;

    public float timeBetweenSteps;

    private float timeBetweenStepsCounter;

    public float timeToMakeStep;
    private float timeToMakeStepCounter;

    public Vector2 directionToMakeStep;
    private Vector2 lastMovement = Vector2.zero;

    private Animator enemyAnimator;

    private const string horizontal = "Horizontal";

    private const string vertical = "Vertical";
    private const string moving = "isMoving";

    private const string lastHorizontal = "LastHorizontal";

    private const string lastVertical = "LastVertical";

    // Start is called before the first frame update
    void Start()
    {
        enemyRigidbody = GetComponent<Rigidbody2D>();
        enemyAnimator = GetComponent<Animator>();

        timeBetweenStepsCounter = timeBetweenSteps * Random.Range(0.5f, 1.5f);
        timeToMakeStepCounter = timeToMakeStep * Random.Range(0.5f, 1.5f);
    }

    // Update is called once per frame
    void Update()
    {
        if (isMoving)
        {
            timeToMakeStepCounter -= Time.deltaTime;
            enemyRigidbody.velocity = directionToMakeStep;

            if (timeToMakeStepCounter < 0)
            {
                isMoving = false;
                timeBetweenStepsCounter = timeBetweenSteps;
                enemyRigidbody.velocity = Vector2.zero;
            }
        }
        else
        {
            timeBetweenStepsCounter -= Time.deltaTime;
            if (timeBetweenStepsCounter < 0)
            {
                isMoving = true;
                timeToMakeStepCounter = timeToMakeStep;

                directionToMakeStep = new Vector2(
                    Random.Range(-1, 2), // de esta forma porque el Random.Range no llega al ultimo
                    Random.Range(-1, 2) // de esta forma porque el Random.Range no llega al ultimo
                ) * enemySpeed;

                lastMovement = directionToMakeStep;
            }
        }

        enemyAnimator.SetFloat(horizontal, directionToMakeStep.x);
        enemyAnimator.SetFloat(vertical, directionToMakeStep.y);

        if (lastMovement != Vector2.zero)
        {
            enemyAnimator.SetBool(moving, isMoving);
        }

        enemyAnimator.SetFloat(lastHorizontal, lastMovement.x);

        enemyAnimator.SetFloat(lastVertical, lastMovement.y);
    }
}

Dejo mi código mucho mas entendible. Creo que el profesor le erra al poner esos nombres a las variables. La traducción al español no es lo que está queriendo indicar.
Dejo mi codigo con nombres más amenos, optimización usando el FixedUpdate para el movimiento y el LateUpdate para las animaciones con un OnTriggerEnter2D para evitar que el personaje colisione con objetos y se quede caminando en el aire.
Ademas también sucede que si la velocidad del rigidbody es 0 pero el bool _isMoving = true, el tipo hace el moon walk a lo Michael Jackson. Con este código se soluciona en el IF que puse.

public class EnemyController : MonoBehaviour
{
    [Header("Configuration")]
    public float enemySpeed = 1.0f;
    public float timeBetweenSteps;
    public float timeToMove;

    private Rigidbody2D _rigidbody;
    private bool _isMoving;
    private float _timeWaiting; //Counter that indicates how much time enemy is in Idle until he moves again.
    private float _movingTime; //Counter that indicates how much time enemy moves in the same direction

    public Vector2 directionToMakeStep;
    private Animator _animator;

    private const string _horizontal = "Horizontal";
    private const string _vertical = "Vertical";

    private void Awake()
    {
        _rigidbody= GetComponent<Rigidbody2D>();
        _animator= GetComponent<Animator>();
    }
    // Start is called before the first frame update
    void Start()
    {
        _timeWaiting = timeBetweenSteps;
        _movingTime = timeToMove;
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    private void FixedUpdate()
    {
        if (_isMoving)
        {
            _movingTime -= Time.deltaTime; //Counter starts decreasing
            _rigidbody.velocity = directionToMakeStep; //Enemy start moving in any direction

            if (_movingTime <= 0) //Enemy stop moving and restarts counter to go in another direction
            {
                _isMoving = false;
                _timeWaiting = timeBetweenSteps;
                _rigidbody.velocity = Vector2.zero;

               
            }
        }
        else
        {
            _timeWaiting -= Time.deltaTime;
            if (_timeWaiting < 0)
            {
                //Stop waiting. Time to move
                _isMoving = true;
                _movingTime = timeToMove;

                directionToMakeStep = new Vector2(Random.Range(-1, 2), Random.Range(-1, 2)) * enemySpeed;

                
            }
        }

     }

    private void LateUpdate()
    {
        //If Enemy is moving and velocity is not 0 then execute movement animations
        if (_isMoving && _rigidbody.velocity != Vector2.zero)
        {
            _animator.SetBool("IsMoving", true);
            _animator.SetFloat(_horizontal, directionToMakeStep.x);
            _animator.SetFloat(_vertical, directionToMakeStep.y);
        }
        else 
        {
            //If enemy is not moving. Then Idle animations
            _animator.SetBool("IsMoving", false);
            _animator.SetFloat("LastHorizontal", directionToMakeStep.x);
            _animator.SetFloat("LastVertical", directionToMakeStep.y);
        }
    }

    private void OnCollisionEnter2D(Collision2D collision)
    {
        //If Enemy collision to an object. Then change _isMoving to false. That will make another move to other direction or stops.
        if (!collision.gameObject.CompareTag("Player"))
        {
            _isMoving = false;
        }
    }
}

Optimice el update para que no sea tan mareante:

//Update
void Update()
    {
        Booleans();

        Move();

        Attack();
        
        CheckVariables();
    }

//Otras funciones
    void Booleans()
    {
        attacking = false;
        walking = false;
    }

    void Move()
    {
        if(Mathf.Abs(Input.GetAxisRaw(hor)) > 0.2f && Mathf.Abs(Input.GetAxisRaw(ver)) > 0.2f)
        {
            prb.velocity = new Vector2(Input.GetAxisRaw(hor) * speed, 
                                       Input.GetAxisRaw(ver) * speed);
            walking = true;
        }
        else if(Mathf.Abs(Input.GetAxisRaw(hor)) > 0.2f)
        {
            //this.transform.Translate(new Vector3(Input.GetAxisRaw(hor) * speed * Time.deltaTime,0,0));
            prb.velocity = new Vector2(Input.GetAxisRaw(hor) * speed,0f);
            walking = true;
            lastMovement = new Vector2(Input.GetAxisRaw(hor), 0);
        }
        else if(Mathf.Abs(Input.GetAxisRaw(ver)) > 0.2f)
        {
            //this.transform.Translate(new Vector3(0,Input.GetAxisRaw(ver) * speed * Time.deltaTime,0));
            prb.velocity = new Vector2(0f, Input.GetAxisRaw(ver) * speed);
            walking = true;
            lastMovement = new Vector2(0, Input.GetAxisRaw(ver));
        }
    }

    void Attack()
    {
        if(Input.GetMouseButtonDown(0))
        {
            attacking = true;
        }
    }

    void CheckVariables()
    {
        if(!walking)
        {
            prb.velocity = Vector2.zero;
        }

        anim.SetFloat(hor, Input.GetAxisRaw(hor));
        anim.SetFloat(ver, Input.GetAxisRaw(ver));
        anim.SetBool(walk, walking);
        anim.SetBool(attack, attacking);
        anim.SetFloat(lastHor, lastMovement.x);
        anim.SetFloat(lastVer, lastMovement.y);
    }

Genial

He aqui mi codigo, no me funciona y no se porque, ya lo he revisado varias veces, pido su apoyo Profesor.
gracias de antemano.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EnemyMovement : MonoBehaviour
{
    public float enemySpeed = 1;
    private Rigidbody2D enemyRigidbody;

    private bool isMoving;

    public float timeBetweenSteps;
    private float timeBetweenStepsCounter;

    public float timeToMakeStep;
    private float timeToMakeStepCounter;

    public Vector2 directionToMakeStep;

    private Animator enemyAnimator;
    private const string horizontal = "Horizontal";
    private const string vertical = "Vertical";

    // Start is called before the first frame update
    void Start()
    {
        enemyRigidbody = GetComponent<Rigidbody2D>();
        enemyAnimator = GetComponent<Animator>();

        timeBetweenStepsCounter = timeBetweenSteps;
        timeToMakeStepCounter = timeToMakeStep;
    }

    // Update is called once per frame
    void Update()
    {

        if (isMoving)
        {
            timeToMakeStepCounter -= Time.deltaTime;
            enemyRigidbody.velocity = directionToMakeStep;

            if(timeToMakeStepCounter < 0)
            {
                isMoving = false;
                timeBetweenStepsCounter = timeBetweenSteps;
                enemyRigidbody.velocity = Vector2.zero;
            }
            else
            {
                timeBetweenStepsCounter -= Time.deltaTime;
                if(timeBetweenStepsCounter < 0)
                {
                    isMoving = true;
                    timeToMakeStepCounter = timeToMakeStep;
                    directionToMakeStep = new Vector2(Random.Range(-1, 2), Random.Range(-1, 2)) * enemySpeed;
                }
            }
        }
        enemyAnimator.SetFloat(horizontal, directionToMakeStep.x);
        enemyAnimator.SetFloat(vertical, directionToMakeStep.y);
    }
}```

Podriamos realizar una variante de lo visto en esta clase con corrutinas para el movimiento?