Objetivo del capítulo
En este capítulo vas a construir enemigos con IA básica usando solo eventos: patrullaje, persecución por distancia, disparo por temporizador y un sistema de estados (idle/alert/attack). Además, implementarás spawn/despawn, oleadas y dificultad progresiva con variables, y definirás hitboxes y reglas de daño equilibradas. La práctica final incluye dos tipos de enemigo con comportamientos distintos y un sistema de oleadas que ajusta velocidad, vida y frecuencia de ataque.
IA sin programar: pensar en “reglas” y “estados”
En Construct, la IA básica se resuelve combinando: condiciones (distancia, línea de visión, colisiones, temporizadores) + acciones (mover, girar, disparar, cambiar variables) + variables (estado, vida, cooldown). El truco para que no se vuelva caótico es usar un estado por enemigo y permitir transiciones claras.
Estados recomendados (idle / alert / attack)
- idle: patrulla o se queda quieto.
- alert: detectó al jugador, se orienta y se acerca o busca.
- attack: ejecuta el ataque (melee o disparo) con cooldown.
Representa el estado con una variable de instancia en el enemigo, por ejemplo state (texto) o state (número: 0/1/2). Para este capítulo usaremos texto por claridad: "idle", "alert", "attack".
Preparación: variables y objetos mínimos
Objetos sugeridos
- Player (ya existe en tu proyecto).
- Enemy_Chaser (enemigo que persigue y golpea).
- Enemy_Shooter (enemigo que mantiene distancia y dispara).
- Bullet_Enemy (proyectil enemigo).
- Hitbox_Player (opcional, para separar colisión visual de daño).
- Hitbox_Enemy (opcional).
- Spawner (puede ser un Sprite invisible o un objeto punto).
Variables globales para oleadas y dificultad
Crea variables globales (Project):
Wave(número, inicia en 1)EnemiesAlive(número, inicia en 0)EnemiesToSpawn(número, inicia en 0)SpawnedThisWave(número, inicia en 0)Difficulty(número, inicia en 1)SpawnInterval(número, por ejemplo 1.2)
Variables de instancia por enemigo
En Enemy_Chaser:
- Escuche el audio con la pantalla apagada.
- Obtenga un certificado al finalizar.
- ¡Más de 5000 cursos para que explores!
Descargar la aplicación
hp(número, ej. 30)state(texto, inicia"idle")speedBase(número, ej. 120)damage(número, ej. 10)attackCooldown(número, ej. 0.8)canAttack(booleano, inicia true)detectRange(número, ej. 320)attackRange(número, ej. 48)patrolMinX,patrolMaxX(número, para patrulla horizontal)dir(número, inicia 1; 1 derecha, -1 izquierda)
En Enemy_Shooter:
hp(número, ej. 20)state(texto, inicia"idle")speedBase(número, ej. 90)detectRange(número, ej. 420)keepDistance(número, ej. 220)fireCooldown(número, ej. 1.1)canFire(booleano, inicia true)bulletSpeed(número, ej. 420)damage(número, ej. 8)
Patrullaje sin programar (Enemy_Chaser)
El patrullaje más estable sin comportamientos extra es mover al enemigo entre dos límites y cambiar dirección al llegar.
Paso a paso: definir límites de patrulla al crear el enemigo
- Cuando instancies un
Enemy_Chaser, asigna sus límites:patrolMinX = X - 120ypatrolMaxX = X + 120(ajusta el ancho según tu nivel). - Inicializa
dir = 1ystate = "idle".
Eventos de patrulla
Enemy_Chaser | state = "idle" -> Set X to X + dir * speedBase * dt * DifficultyEnemy_Chaser | state = "idle" AND X >= patrolMaxX -> Set dir to -1Enemy_Chaser | state = "idle" AND X <= patrolMinX -> Set dir to 1Nota didáctica: usar dt (delta time) hace que la velocidad sea consistente a distintos FPS. Si ya estás usando un comportamiento de movimiento en tu proyecto, mantén el enfoque de este capítulo en la lógica de IA (estado y condiciones) y aplica la velocidad con el método que ya uses.
Persecución por distancia (detección simple)
La detección por distancia es el “sensor” más común. Se basa en comparar la distancia entre enemigo y jugador con un rango.
Transición idle → alert
Enemy_Chaser | state = "idle" AND distance(Enemy_Chaser.X, Enemy_Chaser.Y, Player.X, Player.Y) <= detectRange -> Set state to "alert"Movimiento en alert (perseguir)
Enemy_Chaser | state = "alert" -> Move toward position (Player.X, Player.Y) at (speedBase * Difficulty)Si no quieres usar “Move toward position”, puedes simularlo con ángulo hacia el jugador y avance. Lo importante es que el enemigo cambie de comportamiento al entrar en alert.
Transición alert → idle (si el jugador se aleja)
Enemy_Chaser | state = "alert" AND distance(...) > detectRange * 1.2 -> Set state to "idle"El multiplicador 1.2 crea histeresis: evita que el estado cambie en bucle cuando el jugador está justo en el borde del rango.
Ataque melee con cooldown (Enemy_Chaser)
Para un melee equilibrado necesitas: rango de ataque, ventana de daño (hitbox) y cooldown.
Transición alert → attack
Enemy_Chaser | state = "alert" AND distance(...) <= attackRange -> Set state to "attack"Ejecutar ataque con “canAttack”
Enemy_Chaser | state = "attack" AND canAttack = true -> Set canAttack to false; (Aplicar daño si colisiona); Wait attackCooldown seconds; Set canAttack to trueAplicar daño: lo más limpio es que el daño ocurra solo si el enemigo está en rango y hay solape con la hitbox del jugador.
Enemy_Chaser | state = "attack" AND canAttack = true AND Enemy_Chaser is overlapping Hitbox_Player -> Subtract Enemy_Chaser.damage from Player.hpSalir de attack
Enemy_Chaser | state = "attack" AND distance(...) > attackRange * 1.2 -> Set state to "alert"Disparo por temporizador (Enemy_Shooter)
El disparo por temporizador se basa en un cooldown que habilita el disparo. El enemigo “shooter” suele ser más interesante si intenta mantener distancia.
Transición idle → alert
Enemy_Shooter | state = "idle" AND distance(...) <= detectRange -> Set state to "alert"Movimiento en alert: mantener distancia
Regla simple:
- Si el jugador está muy cerca, el shooter se aleja.
- Si está muy lejos pero dentro de detección, se acerca.
- Si está cerca de la distancia ideal, se queda y dispara.
Enemy_Shooter | state = "alert" AND distance(...) < keepDistance * 0.8 -> Move away from Player at (speedBase * Difficulty)Enemy_Shooter | state = "alert" AND distance(...) > keepDistance * 1.2 -> Move toward Player at (speedBase * Difficulty)Enemy_Shooter | state = "alert" AND distance(...) between keepDistance*0.8 and keepDistance*1.2 -> Stop movement (o velocidad 0)Transición alert → attack (cuando está en rango de disparo)
Enemy_Shooter | state = "alert" AND distance(...) <= detectRange -> Set state to "attack"En este caso, attack significa “modo disparo”, no necesariamente quedarse quieto.
Disparo con cooldown
Enemy_Shooter | state = "attack" AND canFire = true -> Set canFire to false; Spawn Bullet_Enemy; Set Bullet_Enemy angle toward Player; Set Bullet_Enemy speed to bulletSpeed; Wait fireCooldown seconds; Set canFire to trueConsejo: si el jugador puede esquivar, añade una pequeña imprecisión para que no sea injusto:
Set Bullet_Enemy angle to angle(Enemy_Shooter.X, Enemy_Shooter.Y, Player.X, Player.Y) + random(-6, 6)Spawn / Despawn: controlar la población de enemigos
El spawn crea presión y ritmo; el despawn evita que el nivel se llene de objetos fuera de cámara o irrelevantes.
Spawn con un objeto Spawner
Coloca varios Spawner en el layout (por ejemplo, en bordes o puertas). Cada spawner puede tener una variable de instancia type (texto: "chaser" o "shooter").
Reglas de despawn recomendadas
- Si el enemigo está demasiado lejos del jugador durante cierto tiempo.
- Si sale de los límites del layout.
- Si la oleada terminó y quieres limpiar rezagados (opcional).
Enemy_* | distance(...) > 1200 -> DestroySi destruyes enemigos por despawn, recuerda ajustar EnemiesAlive para no romper la lógica de oleadas.
Oleadas: estructura simple y robusta
Una oleada necesita: cuántos enemigos se generan, con qué intervalo, y cómo detectas que terminó.
Definir el tamaño de oleada y el intervalo
Ejemplo de fórmula (ajústala a tu juego):
EnemiesToSpawn = 4 + Wave * 2SpawnInterval = max(0.35, 1.2 - Wave * 0.08)Difficulty = 1 + Wave * 0.12
Inicio de oleada
System | On start of layout -> Set Wave=1; Call StartWaveFunction StartWave -> Set SpawnedThisWave=0; Set EnemiesAlive=0; Set EnemiesToSpawn = 4 + Wave*2; Set Difficulty = 1 + Wave*0.12; Set SpawnInterval = max(0.35, 1.2 - Wave*0.08)Spawning por temporizador (sin bucles complejos)
Usa un temporizador repetitivo mientras falten enemigos por generar.
System | Every SpawnInterval seconds AND SpawnedThisWave < EnemiesToSpawn -> Pick a random Spawner; Spawn enemy based on Spawner.type; Add 1 to SpawnedThisWave; Add 1 to EnemiesAlivePara elegir tipo sin spawners tipados, puedes hacerlo por probabilidad:
System | (al spawnear) -> If random(0,100) < 60 spawn Chaser else spawn ShooterFin de oleada
System | SpawnedThisWave = EnemiesToSpawn AND EnemiesAlive = 0 -> Add 1 to Wave; Call StartWaveActualizar EnemiesAlive al morir
Enemy_* | hp <= 0 -> Destroy; Subtract 1 from EnemiesAliveImportante: asegúrate de que EnemiesAlive solo se incrementa cuando el enemigo realmente aparece y solo se decrementa una vez (al morir o despawnear).
Dificultad progresiva: qué ajustar y cómo evitar picos injustos
La dificultad progresiva funciona mejor cuando ajustas pocas variables pero de forma consistente:
- Velocidad: aumenta presión y reduce margen de error.
- Vida: alarga combates, pero puede volverse tedioso.
- Frecuencia de ataque: sube intensidad; cuidado con “spam”.
Aplicar escalado al instanciar enemigos
En el momento del spawn, ajusta stats con Difficulty para que cada oleada “nazca” con valores coherentes.
On spawn Enemy_Chaser -> Set hp to round(30 * (1 + (Difficulty-1)*0.8)); Set speedBase to 120 * (1 + (Difficulty-1)*0.35); Set attackCooldown to max(0.35, 0.8 - (Difficulty-1)*0.08)On spawn Enemy_Shooter -> Set hp to round(20 * (1 + (Difficulty-1)*0.7)); Set bulletSpeed to 420 * (1 + (Difficulty-1)*0.25); Set fireCooldown to max(0.4, 1.1 - (Difficulty-1)*0.10)Regla práctica: limita con max() los cooldowns para que nunca lleguen a valores imposibles de esquivar.
Hitboxes y reglas de daño equilibradas
Separar “cuerpo” de “daño”
Si tu sprite tiene formas irregulares, una hitbox dedicada hace el combate más justo. Dos enfoques comunes:
- Hitbox como objeto hijo: un Sprite invisible anclado al jugador/enemigo (con Pin).
- Polígono de colisión ajustado en el propio Sprite (más simple, menos flexible).
Invulnerabilidad breve (i-frames) para evitar daño múltiple
Si el jugador puede recibir daño por solape continuo, añade una ventana de invulnerabilidad. Variable sugerida en Player: invuln (boolean) y/o invulnTime.
Player | On damaged AND invuln = false -> Set invuln=true; Wait 0.6 seconds; Set invuln=falseLuego, cualquier evento de daño debe comprobar invuln = false.
Tabla de balance inicial (ejemplo)
| Elemento | Valor recomendado | Motivo |
|---|---|---|
| Chaser daño | 8–12 | Castiga cercanía, pero permite reaccionar |
| Chaser cooldown | 0.7–1.0 s | Evita “triturar” al jugador |
| Shooter daño bala | 6–10 | Menor que melee por ser a distancia |
| Shooter cooldown | 0.9–1.3 s | Da tiempo a esquivar |
| I-frames jugador | 0.4–0.8 s | Evita daño múltiple por solape |
Práctica guiada: 2 enemigos + oleadas + dificultad
Parte A: Enemy_Chaser completo (patrulla → persecución → melee)
- Crea Enemy_Chaser con variables:
hp,state,speedBase,damage,attackCooldown,canAttack,detectRange,attackRange,patrolMinX,patrolMaxX,dir. - Al spawnear: asigna límites de patrulla alrededor de su X inicial y escala stats con
Difficulty. - Eventos de idle: mover en X con
diry cambiar dirección en límites. - Detección: si distancia ≤
detectRange, cambia aalert. - Persecución: en
alert, moverse hacia el jugador. - Entrar en attack: si distancia ≤
attackRange,state="attack". - Daño con cooldown: si solapa hitbox del jugador y
canAttack=truey jugador no invulnerable, resta vida y activa cooldown. - Salir de attack: si se aleja, vuelve a
alert; si se aleja mucho, vuelve aidle.
Parte B: Enemy_Shooter completo (alert → mantener distancia → disparo)
- Crea Enemy_Shooter con variables:
hp,state,speedBase,detectRange,keepDistance,fireCooldown,canFire,bulletSpeed,damage. - Al spawnear: escala
hp,fireCooldownybulletSpeedconDifficulty. - Detección: distancia ≤
detectRange→alert. - Movimiento: si está muy cerca, alejarse; si está muy lejos, acercarse; si está en rango ideal, frenar.
- Disparo: en
attack(o enalertsi prefieres simplificar), sicanFireentonces crearBullet_Enemy, apuntar al jugador con ligera desviación y aplicar velocidad. - Daño de bala: al colisionar con hitbox del jugador, restar vida (si no invulnerable) y destruir bala.
- Despawn de balas: destruir si salen de pantalla o tras X segundos para evitar acumulación.
Parte C: Sistema de oleadas con spawners
- Coloca varios Spawner en el layout (mínimo 3). Opcional: variable
typepara forzar qué enemigo sale de cada punto. - Crea función StartWave que calcule
EnemiesToSpawn,SpawnIntervalyDifficultysegúnWave, y reinicie contadores. - Evento de spawn periódico: cada
SpawnIntervalsegundos, siSpawnedThisWave < EnemiesToSpawn, elige un spawner y genera un enemigo. IncrementaSpawnedThisWaveyEnemiesAlive. - Control de fin de oleada: cuando
SpawnedThisWave = EnemiesToSpawnyEnemiesAlive = 0, incrementaWavey llama StartWave. - Despawn seguro: si destruyes enemigos por distancia/límites, decrementa
EnemiesAliveigual que si murieran.
Parte D: Ajuste de dificultad que modifique velocidad, vida y frecuencia
Implementa el escalado en el evento de spawn (no en un “Every tick”), para que sea predecible y fácil de balancear. Recomendación:
- Chaser: sube velocidad moderada + baja un poco cooldown + sube vida.
- Shooter: sube velocidad poco + sube velocidad de bala + baja cooldown con límite + sube vida.
Verifica en juego que:
- En oleadas tempranas, el jugador aprende patrones (tiempo para reaccionar).
- En oleadas medias, el reto viene de la combinación (chaser presiona, shooter castiga).
- En oleadas altas, el límite de cooldown evita que sea imposible.