Controla con precisión el movimiento aleatorio de tu NPC en Unity: evita que se pierda por el mapa, salga del escenario o entre en zonas ilógicas. Aquí verás cómo definir una frontera de movimiento y cómo hacer que el script la respete, sin romper la naturalidad del paseo.
¿Cómo evitar que un NPC se pierda por el mapa?
Cuando un NPC elige una dirección cada pocos segundos y camina de forma aleatoria, puede alejarse demasiado o incluso salir del escenario. La solución es definir una zona de movimiento con una caja que actúe como límite.
Crea un GameObject vacío llamado "Villager Zone".
Añade un BoxCollider2D y deja el tamaño base en 1x1.
Escálalo en horizontal y vertical para marcar la zona permitida.
No es obligatorio centrar la caja en el NPC: puede estar en una esquina si te conviene.
Activa el parámetro isTrigger para evitar colisiones no deseadas con el jugador u otros objetos.
Usa un nombre genérico si vas a crear un prefab, pero cada NPC puede tener su propia zona.
Idea clave: la caja define los límites exactos por donde el NPC puede moverse; así no cruzará el camino si no quieres, o no entrará en la armería por accidente.
¿Qué ventajas tiene esta frontera?
Mantiene al NPC en un área coherente con la escena.
Evita misiones imposibles por NPCs inubicables.
Permite personalizar el comportamiento de varios NPCs con zonas distintas.
¿Qué cambios aplicar en el script para respetar la zona?
Necesitas exponer la zona al script y detener el movimiento al alcanzar cualquier borde de la caja. Así conservas el paseo aleatorio, pero dentro de los límites.
Declara en tu script (por ejemplo, en npcMovement): una variable pública del tipo BoxCollider2D llamada villagerZone.
En el editor, arrastra la "Villager Zone" al campo villagerZone del NPC.
En Update, si villagerZone no es null, comprueba los cuatro bordes con villagerZone.bounds.min y villagerZone.bounds.max.
Si se supera izquierda, derecha, abajo o arriba, llama al método stopWalking() para frenar en seco, poner velocidad en 0 y reiniciar el contador de movimiento.
Atención a los ejes: x con x e y con y; no los cruces.
publicBoxCollider2D villagerZone;// zona permitidavoidUpdate(){// ... lógica previa, por ejemplo: si estamos caminandoif(villagerZone !=null){var pos = transform.position;var bounds = villagerZone.bounds;bool fueraIzquierda = pos.x < bounds.min.x;bool fueraDerecha = pos.x > bounds.max.x;bool fueraAbajo = pos.y < bounds.min.y;bool fueraArriba = pos.y > bounds.max.y;if(fueraIzquierda || fueraDerecha || fueraAbajo || fueraArriba){stopWalking();}}// ... resto de la lógica de movimiento}
Idea clave:villagerZone != null te permite tener dos comportamientos: NPCs libres por el pueblo o NPCs limitados a su área.
¿Cómo vincular bien las referencias?
Arrastra la "Villager Zone" al campo villagerZone del NPC en el editor.
Asegúrate de que isTrigger esté activo en la zona para no bloquear entradas.
¿Cómo probar el sistema y gestionar empujes de enemigos?
Para verificar rápido, reduce el wait time del paseo aleatorio a un valor pequeño: así verás al NPC moverse y parar al tocar los bordes de la caja.
Baja el wait time para observar cambios inmediatos.
Verás que los enemigos pueden empujar al NPC si colisionan.
Si te interesa evitarlo: crea capas separadas para enemigos y NPCs y haz que no interactúen en el motor de físicas.
Idea clave: limitar el área no impide que las físicas empujen; separa capas si necesitas evitar interacciones.
¿Te gustaría que agreguemos un ejemplo con capas de físicas o ajustar el paseo aleatorio a reglas más específicas? Cuéntalo en los comentarios y seguimos construyendo juntos.
Hola Juan/Comunidad, ami esta lógica no me sirvió, tengo al NPC solo (sin que lo muevan enemigos) y aun así se va de la zona, lo único que hace es que cada vez que esta por salir de la zona lo para, pero luego sigue caminando aleatoriamente y en algún momento termina saliendo. Se que explicarlo con palabras o imágenes puede ser difícil, acá les dejo un video donde muestro el código y el error. Video
a mi me pasa igual
:(
Despues de la comprobación de los limites ejecutas stopwalking para parar estableciendo su velocity en 0 , sin embargo lineas mas abajo le añades el movimiento al velocity nuevamente haciendo que ignore el parón, para corregir esto, basta con añadir un else después de realizar la comprobación de los limites y allí colocar el movimiento que se ejecutara solo cuando este dentro de los limites
Para que el enemigo no choque con el NPC, lo que se hace es marcar el NPC con un layer:
Y hacer lo mismo con el enemigo, marcarlo con un layer Enemy:
Despues en project settings se deselecciona la interaccion NPC - Enemy:
Buenas a todos!
Me gustaria compartir el codigo que me sirvio para que hacer que el npc se dirija a un punto aleatorio dentro de los limites de la zona del npc cuando llega a uno de los limites de la zona:
<if(NPC_zone!=null){if(this.transform.position.x<NPC_zone.bounds.min.x||this.transform.position.x>NPC_zone.bounds.max.x||this.transform.position.y<NPC_zone.bounds.min.y||this.transform.position.y>NPC_zone.bounds.max.y){//StopWalking();// Obtiene un vector que apunta desde la posición del npc a un punto aleatorio dentro de los limites de la zona del NPC. directionToMakeStep =(newVector3(Random.Range(NPC_zone.bounds.min.x,NPC_zone.bounds.max.x),Random.Range(NPC_zone.bounds.min.y,NPC_zone.bounds.max.y),NPC_zone.bounds.min.z)-this.transform.position).normalized* enemySpeed;}}>
Les comparto mi solucion, le agregue una variable llamada latestDirection, esto con el fin que cuando el NPC toque un boundary, el currentDirection no vuelva a ser el que llevaba cuando toco el boundary. Adicional cuando el NPC toca el boundary, lo que hago es que lo hago devolver a la direccion contraria.
Esta fue mi solucion utilizando el metodo clamp, simplemente hay que añadir este script al presonaje que se quiere dentro del perimetro, y hacemos referencia al boxcollider2D arrastrando. Esto funciona ya que Unity al hacer Update suma el resultado de todos los vectores.
using System.Collections;using System.Collections.Generic;using UnityEngine;publicclassBoundaries:MonoBehaviour{publicBoxCollider2D npcDelimitedZone;privatevoidUpdate(){this.transform.position=newVector3(Mathf.Clamp(this.transform.position.x, npcDelimitedZone.bounds.min.x, npcDelimitedZone.bounds.max.x),Mathf.Clamp(this.transform.position.y, npcDelimitedZone.bounds.min.y, npcDelimitedZone.bounds.max.y),this.transform.position.z);}}
Para hacer que el NPC vuelva a su zona lo resolvi con dos lineas. Simplemente, luego de detener al NPC lo muevo en direccion contraria, lo cual logro al hacer que la **speed ** sea negativa, y por ultimo **return **para salir y comenzar el ciclo de nuevo.
if(villagerZone !=null){//Si se sale de los boundariesif(this.transform.position.x< villagerZone.bounds.min.x||this.transform.position.x> villagerZone.bounds.max.x||this.transform.position.y< villagerZone.bounds.min.y||this.transform.position.y> villagerZone.bounds.max.y){StopWalking(); npcRigidBody.velocity= walkingDirection[currentDirection]*-speed;//muevo al npc al contrario al multipicarlo por speed negativoreturn;//para que salga del ciclo y no agarre la direccion aleatoria}}
El método planteado por el profe no funciona. Al collisionar con la villagerZone el personaje queda siempre en StopWalking() ya que siempre está collisionando.
Yo implemente un punto de referencia con un objeto vacio. Si el NPC se mueve más alla de la distancia máxima permitida, cambia la dirección hacia el objeto de referencia.
Luego en el FixedUpdate(ya que es mejor moverlo ahí que en el update) calculo la distancia entre mi NPC y el objeto de referencia y asigno la dirección
voidFixedUpdate(){//Calculo la distnacia entre el NPC y objeto sobre el cual no puede alejarse mucho _distanceToBoundary =Vector2.Distance(transform.position, _boundaryObject.position);if(isWalking){ walkCounter -=Time.deltaTime;if(_distanceToBoundary > _maxDistanceToBoyndary)//Si me aleje del rango, dirigo el movimiento hacie el objeto hijo sino que se mueva random.{ directionToMakeStep = _boundaryObject.position- transform.position; _boxCollider.enabled=false;}else{ _boxCollider.enabled=true;} _rigidbody.velocity= directionToMakeStep;
Con eso debería funcionar (obviamente el FixedUpdate tiene más cosas abajo pero para que no se haga super largo)
Aquí parte del código:
if(villagerZone !=null){//Comprobamos por cual lado esta saliendo el npc de su box.if(this.transform.position.x> villagerZone.bounds.max.x||this.transform.position.x< villagerZone.bounds.min.x||this.transform.position.y> villagerZone.bounds.max.y||this.transform.position.y< villagerZone.bounds.min.y){//Lo detenemos de caminar.StopWalking();}}```
Comparto como hice esta parte
Dentro de StopMovement la primer linea hago isMoving = false asi no tiene un comportamiento raro.