Skip to main content

Bots

How can I make a bot that shoots projectiles at the nearest player?

Before you start, make sure you understand the difference between a bot and a mob:

  • A bot is a hero controlled by a computer, created using the SpawnBot function. The hero could have been controlled by a human player, but the computer is controlling it instead. The bot normally controls its hero by simulating inputs (button clicks, mouse movement, etc.).

  • A mob is an enemy controlled by the game. It can never be controlled by a human player. In fact, it is not a player at all, it is just an entity in the game world.

It is much more complex to implement a bot than a mob, so make sure that a bot is really what you want to implement, and not a mob. See Mobs for a simpler way to make mobs that shoot at players.

Step 1: Spawn some bots

First, use SpawnBot to spawn some bots in your game world. You can do this in your Main function.

pub game fn World.Main() {
// ...

repeat 5 {
SpawnBot
}
}

In a real game you might want to wait until the player clicks a Play vs AI button before spawning the bots, but for this example we will just spawn them immediately.

Step 2: Give a BotSystem to your hero

When a Hero is under the control of a bot, it needs to have a BotSystem to simulate inputs for it. We will define what a BotSystem does in the next step.

pub fn unit.Hero([owner]) {
// ...

if !IsHuman {
BotSystem
}
}

Step 3: Define the BotSystem

A simple BotSystem simply runs every few ticks, finds the nearest player, and simulates a mouse click to shoot at them.

fn unit.BotSystem([owner]) {
use body=unit

on Tick(0.5s) {
let enemy = QueryNearest(filter=Category:Hero, against=Alliance:Enemy)
if !enemy { continue }
Pointer = enemy.Pos
ButtonDown(Click)
ButtonUp(Click)
}

// ...
}
  • QueryNearest is used to find the nearest enemy player. As the nearest Hero is always going to be itself, notice how we use against=Alliance:Enemy to only find other heroes.
  • Pointer is used to aim at the enemy player's position.
  • ButtonDown(Click) and ButtonUp(Click) simulate a mouse click to shoot at the enemy. Both the ButtonDown and ButtonUp are needed to simulate a full click, without ButtonUp, the bot would just hold down the mouse button and it would never shoot again.

Full Code Listing

pub tangible category Category:Hero
pub tangible category Category:Projectile

pub game fn World.Main() {
SpawnEachPlayer owner {
Spawn unit {
Hero
}
}

repeat 5 {
SpawnBot
}
}

pub fn unit.Hero([owner]) {
use body=this, radius=1, shape=Circle
Body(pos=20*RandomVector)
PolygonSprite(ownerColor=true)
PolygonCollider(category=Category:Hero)
DecaySpeed(1)

on BeforePhysics {
Move(0.25 * Joystick)
}

on ButtonDown(Click) {
Spawn projectile {
Bullet(heading=Angle(Pointer - Pos), parent=unit)
}
}

if !IsHuman {
BotSystem
}
}

fn unit.BotSystem([owner]) {
use body=unit
on Tick(0.5s) {
let enemy = QueryNearest(filter=Category:Hero, against=Alliance:Enemy)
if !enemy { continue }

Pointer = enemy.Pos
ButtonDown(Click)
ButtonUp(Click)
}

on Tick(0.25s) {
Joystick = RandomVector
}
}

pub fn projectile.Bullet(heading, parent, [owner]) {
use body=this, radius=0.25, shape=Circle
Body(pos=parent.Pos, velocity=30*heading.Direction)
PolygonSprite(ownerColor=true)
PolygonCollider(category=Category:Projectile, parent=)

on BeforeCollide that {
Expire
}
once Tick(1.5s) { Expire }
}
Open in Editor