Objetivo do capítulo
Neste capítulo você vai criar inimigos “inteligentes” sem programar, combinando behaviors e eventos para formar padrões de movimento e um sistema simples de estados: Patrulhando, Alertado e Atacando. Você também vai usar waypoints (marcadores), timers e variáveis para controlar cadência de ataque, dano e invulnerabilidade temporária.
Conceitos essenciais (sem repetir fundamentos)
Padrões de movimento com behaviors + eventos
- Patrulha (vai e volta): o inimigo alterna entre dois pontos (waypoints) e inverte direção ao chegar.
- Perseguição: o inimigo passa a seguir o jogador quando uma condição é atendida (distância e/ou linha de visão).
- Estados: em vez de tentar controlar tudo com “se… então…” espalhado, você centraliza a lógica em um estado atual do inimigo. Isso organiza eventos e evita conflitos (por exemplo, patrulha e perseguição ao mesmo tempo).
Waypoints (marcadores) como alvos
Waypoints são sprites invisíveis (ou com opacidade baixa no editor) usados como “pontos de referência”. O inimigo não precisa saber coordenadas fixas: ele só precisa saber qual waypoint é o alvo atual.
Timers e variáveis para cadência
Para ataques e efeitos temporários, evite “esperar” com lógica confusa. Use:
- Timer (por instância): para controlar intervalos de ataque, tempo de alerta, duração de invulnerabilidade.
- Variáveis de instância: para guardar estado, alvo atual, vida, se está invulnerável, etc.
Preparação dos objetos do capítulo
Objetos necessários
- Player (já existente no projeto).
- Enemy_A: inimigo patrulheiro que vira perseguidor ao ver o jogador.
- Enemy_B: inimigo sentinela que “acorda” por distância e ataca em cadência (ex.: investida ou tiro).
- Waypoint: marcador genérico (pode ser um sprite simples).
- Hitbox_Player (opcional): caso você use uma área separada para dano.
- Projectile_B (opcional): se o Enemy_B atacar à distância.
Behaviors recomendados
- Enemy_A:
Pathfinding(para perseguir contornando obstáculos) ouMoveTo(mais simples, sem contorno). Para patrulha, você pode usar o mesmo behavior (MoveTo/Pathfinding) apontando para waypoints. - Enemy_B:
MoveTo(para investida) ou nenhum (se for estacionário e só atirar). - Waypoint: sem behaviors.
- Projectile_B (se usar):
Bullet.
Variáveis de instância sugeridas
Crie estas variáveis em Enemy_A e Enemy_B (ajuste conforme seu jogo):
| Variável | Tipo | Exemplo | Uso |
|---|---|---|---|
| state | Texto | "patrol" | Estado atual (patrol/alert/attack) |
| hp | Número | 3 | Vida do inimigo |
| invuln | Booleano | false | Invulnerabilidade temporária |
| targetUID | Número | -1 | UID do waypoint alvo (patrulha) |
| speedPatrol | Número | 80 | Velocidade na patrulha |
| speedChase | Número | 140 | Velocidade na perseguição |
| attackCooldown | Número | 0.8 | Intervalo entre ataques |
No Player, garanta que existam (ou crie) variáveis como hp e invuln para o exercício de dano.
- Ouça o áudio com a tela desligada
- Ganhe Certificado após a conclusão
- + de 5000 cursos para você explorar!
Baixar o aplicativo
Waypoints na prática (patrulha vai e volta)
1) Criar waypoints e vincular ao inimigo
Crie dois waypoints para cada inimigo patrulheiro: WP_A1 e WP_A2 (pode ser o mesmo objeto Waypoint com uma variável tag).
Opção organizada: use um único objeto Waypoint com variável de instância tag (Texto). Exemplos: "A1", "A2", "B1", "B2".
2) Definir o primeiro alvo ao iniciar
Crie um grupo de eventos chamado ENEMY_A | Setup:
On created (Enemy_A) -> Set state = "patrol" -> Set hp = 3 -> Set invuln = false -> Set speedPatrol = 80 -> Set speedChase = 140 -> Set attackCooldown = 0.8 -> Set targetUID = UID do waypoint inicial (ex.: o mais próximo com tag "A1")Como pegar o waypoint inicial sem “hardcode”:
- Selecione Waypoint com
tag = "A1"e use ação “Pick nearest” em relação ao Enemy_A (ou posicione apenas um A1 por inimigo). - Depois, faça
Enemy_A.targetUID = Waypoint.UID.
3) Movimento até o waypoint atual
Grupo ENEMY_A | Patrol (ativado quando state = "patrol"):
Enemy_A: state = "patrol" -> MoveTo: Set speed = speedPatrol -> MoveTo: Move to (Waypoint picked by UID = targetUID)Para “pegar por UID”, use um evento que selecione o waypoint correto:
Enemy_A: state = "patrol" -> Waypoint: Pick by UID (Enemy_A.targetUID) -> Enemy_A: MoveTo move to Waypoint.X, Waypoint.Y4) Trocar de alvo ao chegar (vai e volta)
Quando o inimigo estiver perto do waypoint, troque o alvo para o outro ponto:
Enemy_A: state = "patrol" AND Distance(Enemy_A, Waypoint(targetUID)) < 8 -> If Waypoint.tag = "A1" then set targetUID = UID do waypoint "A2" -> Else set targetUID = UID do waypoint "A1"Dica: em vez de comparar tags, você pode guardar targetTag no inimigo e alternar entre "A1" e "A2".
Perseguição por distância e linha de visão
Detecção por distância (rápida e eficiente)
Crie no Enemy_A variáveis:
detectRange(ex.: 220)loseRange(ex.: 280) para evitar “piscar” entre estados
Grupo ENEMY_A | Detect:
Enemy_A: state = "patrol" AND Distance(Enemy_A, Player) < detectRange -> Set state = "alert" -> Start timer "alert" for 0.4s (Enemy_A)O estado alert serve como transição (um “meio termo”) para dar tempo de animação/feedback e evitar mudanças bruscas.
Linha de visão (com Raycast)
Para linha de visão, você precisa de um objeto sólido/obstáculo (ex.: paredes) e usar um teste de “raio” entre inimigo e jogador. Em Construct, isso costuma ser feito com uma checagem de colisão ao longo de uma linha (Raycast) ou com um objeto auxiliar. A ideia é: se houver parede entre eles, não vê.
Estratégia simples e prática:
- Use uma condição de “Line of sight” se disponível (alguns plugins/behaviors) ou simule com Raycast em eventos.
- Se não tiver Raycast, use um “sensor” (um sprite fino) que aponta para o jogador e verifica colisão com paredes antes de confirmar.
Exemplo conceitual de evento (Raycast):
Enemy_A: state = "patrol" AND Distance(Enemy_A, Player) < detectRange AND Raycast(Enemy_A -> Player) hits Player before Wall -> Set state = "alert"Do alert para chase/attack
Grupo ENEMY_A | Alert:
Enemy_A: state = "alert" AND On timer "alert" -> Set state = "attack"Você pode renomear attack para chase se preferir separar “perseguir” de “atacar”. Aqui vamos usar attack como estado agressivo (persegue e tenta causar dano).
Estados do inimigo (organização por grupos)
Estrutura recomendada de grupos
- ENEMY_A | Setup
- ENEMY_A | Detect
- ENEMY_A | Patrol
- ENEMY_A | Attack
- ENEMY_A | Damage & Invuln
- ENEMY_B | Setup
- ENEMY_B | Detect
- ENEMY_B | Attack Pattern
- GLOBAL | Player Damage & Invuln
Use a condição Enemy.state = "..." no topo de cada grupo para manter tudo previsível.
Enemy_A (Patrulha + perseguição + contato com dano)
1) Estado Attack: perseguir o jogador
Grupo ENEMY_A | Attack:
Enemy_A: state = "attack" -> MoveTo: Set speed = speedChase -> MoveTo: Move to Player.X, Player.YPara evitar perseguição infinita, volte para patrulha quando o jogador escapar:
Enemy_A: state = "attack" AND Distance(Enemy_A, Player) > loseRange -> Set state = "patrol"2) Dano por contato com cadência
Sem cadência, o jogador pode perder toda a vida em um único segundo. Use cooldown no inimigo ou invulnerabilidade no jogador (ou ambos).
Opção A (recomendada): invulnerabilidade no jogador após levar dano.
Enemy_A collides with Player AND Player.invuln = false -> Subtract 1 from Player.hp -> Set Player.invuln = true -> Start timer "playerInvuln" for 0.8s (Player)E o fim da invulnerabilidade:
Player: On timer "playerInvuln" -> Set Player.invuln = falseFeedback visual rápido (piscar):
Player.invuln = true -> Set Player opacity to 50 (ou alternar com a função Sine/Timer) Player.invuln = false -> Set Player opacity to 100Enemy_B (Sentinela com padrão diferente: alerta por distância + ataque em cadência)
Ideia do padrão
O Enemy_B não patrulha. Ele fica “guardando” e, quando o jogador entra no raio, ele entra em alert e começa a atacar com cadência. Você pode escolher:
- Ataque à distância (projétil).
- Investida (dash curto em direção ao jogador).
A seguir, um passo a passo para o modelo de projétil (mais fácil de controlar e visualizar).
1) Setup do Enemy_B
On created (Enemy_B) -> Set state = "patrol" (ou "idle") -> Set hp = 2 -> Set invuln = false -> Set detectRange = 260 -> Set loseRange = 320 -> Set attackCooldown = 1.2Se preferir, use state = "idle" em vez de patrol para o Enemy_B.
2) Detectar e entrar em ataque
Enemy_B: state = "idle" AND Distance(Enemy_B, Player) < detectRange -> Set state = "attack" -> Start timer "shoot" for 0.1s (Enemy_B)O timer inicial curto serve para o primeiro ataque acontecer logo após detectar.
3) Atirar com timer (cadência)
Grupo ENEMY_B | Attack Pattern:
Enemy_B: state = "attack" AND On timer "shoot" -> Spawn Projectile_B at Enemy_B (image point "muzzle" ou centro) -> Projectile_B: Set angle toward Player -> Projectile_B: Set speed (ex.: 420) -> Enemy_B: Start timer "shoot" for attackCooldownSe o projétil usar Bullet behavior, você pode definir o ângulo e a velocidade diretamente no behavior.
4) Parar de atacar quando o jogador sair
Enemy_B: state = "attack" AND Distance(Enemy_B, Player) > loseRange -> Set state = "idle"5) Dano do projétil + invulnerabilidade do jogador
Projectile_B collides with Player AND Player.invuln = false -> Destroy Projectile_B -> Subtract 1 from Player.hp -> Set Player.invuln = true -> Start timer "playerInvuln" for 0.8s (Player)Também destrua o projétil ao bater em paredes/solids:
Projectile_B collides with Wall -> Destroy Projectile_BDano no inimigo e invulnerabilidade temporária (quando o jogador ataca)
Fonte de dano do jogador
Você pode ter um objeto de ataque (ex.: SwordHitbox) que só aparece durante a animação de ataque, ou usar colisão com um projétil do jogador. O padrão é o mesmo: ao colidir, reduzir HP e ativar invulnerabilidade do inimigo por um curto período.
Eventos de dano (para Enemy_A e Enemy_B)
Grupo ENEMIES | Damage & Invuln (pode separar por inimigo se preferir):
SwordHitbox overlaps Enemy_A AND Enemy_A.invuln = false -> Subtract 1 from Enemy_A.hp -> Set Enemy_A.invuln = true -> Start timer "invuln" for 0.25s (Enemy_A) -> Start timer "hitFlash" for 0.25s (Enemy_A)Enemy_A: On timer "invuln" -> Set invuln = falseFeedback visual no inimigo (piscar/vermelho):
Enemy_A.invuln = true -> Set Enemy_A blend mode/additive OR set color tint OR set opacity 60 Enemy_A.invuln = false -> Restore visualMorte do inimigo:
Enemy_A.hp <= 0 -> Spawn effect (opcional) -> Destroy Enemy_ARepita a mesma lógica para Enemy_B.
Exercício guiado: dois inimigos diferentes com eventos organizados
Regras do exercício
- Você deve implementar Enemy_A (patrulha vai-e-volta + persegue por distância/linha de visão) e Enemy_B (sentinela que atira com cadência).
- Os eventos devem estar organizados em grupos por inimigo e por responsabilidade (setup, detecção, estados, dano).
- Deve existir lógica de dano no jogador e nos inimigos.
- Deve existir invulnerabilidade temporária após receber dano (no jogador e nos inimigos).
Checklist de implementação (passo a passo)
Crie os waypoints do Enemy_A (A1 e A2) e posicione no layout.
Adicione variáveis em Enemy_A e Enemy_B:
state,hp,invuln,attackCooldown,detectRange,loseRange. No Enemy_A adicionetargetUIDe velocidades.Enemy_A | Setup: ao criar, definir estado patrulha e escolher waypoint inicial.
Enemy_A | Patrol: mover até o waypoint atual e alternar ao chegar (distância < 8).
Enemy_A | Detect: se jogador entrar no raio e (se aplicável) tiver linha de visão, mudar para
alerte depoisattackvia timer.Enemy_A | Attack: perseguir o jogador e voltar para patrulha se distância > loseRange.
Enemy_B | Setup: estado idle, ranges e cooldown.
Enemy_B | Detect: entrar em
attackao detectar o jogador e iniciar timershoot.Enemy_B | Attack Pattern: no timer
shoot, criar projétil mirando no jogador e reiniciar timer comattackCooldown.GLOBAL | Player Damage & Invuln: ao colidir com Enemy_A ou Projectile_B, reduzir HP e ativar
Player.invulnpor timer.ENEMIES | Damage & Invuln: ao receber golpe do jogador, reduzir HP, ativar invulnerabilidade curta e destruir ao chegar em 0.
Organização sugerida dos eventos (modelo)
| Grupo | Conteúdo |
|---|---|
| ENEMY_A | Setup | Variáveis iniciais, escolher waypoint inicial |
| ENEMY_A | Detect | Distância + linha de visão, transição para alert |
| ENEMY_A | Patrol | MoveTo para waypoint, alternância A1/A2 |
| ENEMY_A | Alert | Timer curto para entrar em attack |
| ENEMY_A | Attack | Perseguir, perder alvo e voltar |
| ENEMY_B | Setup | Variáveis iniciais |
| ENEMY_B | Detect | Entrar em attack e iniciar timer de tiro |
| ENEMY_B | Attack Pattern | Timer shoot, spawn projétil, cooldown |
| GLOBAL | Player Damage & Invuln | Contato/projétil, invulnerabilidade, feedback |
| ENEMIES | Damage & Invuln | Receber golpe, invuln curta, morte |
Ajustes finos (para ficar “com cara de jogo”)
Evitar tremedeira ao chegar no waypoint
- Aumente o raio de chegada (ex.: 10–16).
- Ao trocar de waypoint, espere 0.05s antes de mandar mover novamente (timer curto) se notar oscilação.
Evitar alternância rápida entre patrulha e ataque
- Use
detectRangemenor queloseRange(histerese). - Use o estado
alertcom timer curto antes de atacar.
Cadência consistente
- Centralize ataques em timers (por instância) e reinicie o timer ao atacar.
- Não use “Every X seconds” global se você quer controle por inimigo (isso pode sincronizar todos).