Skip to main content

Mobs

Open this Example

Mobs example

How can I make a mob of enemies that shoot projectiles at their nearest player?

To make mobs of enemies, Spawn an entity to represent each enemy, and give it a behavior that finds the nearest player and shoots every tick.

Step 1: Spawn some mobs

In this example, our mobs of enemies are defined by a function called Mob. We will define what Mob does in the next step.

repeat 20 {
Spawn unit {
Mob
}
}

Step 2: Make mobs attack

To make our Mob attack, we have simply given them a behavior that runs every few ticks and finds the nearest player to shoot at.

pub fn unit.Mob() {
use body=this, radius=TileRadius, shape=Circle
let image=PickRandom(@enemy-*.png)
Body(pos=@(SignedRandom*BoundaryRadius, SignedRandom*BoundaryRadius), noRotation=true)
ImageSprite(image=)
PolygonCollider(category=Category:Mob)
DecaySpeed(1)

// Shoot towards nearest hero every so often
on Tick(10s * Random) {
let enemy = QueryNearest(filter=Category:Hero)
if !enemy { continue }
Spawn projectile {
Bullet(heading=Angle(enemy.Pos - Pos), parent=unit)
}
}

// ...
}
  • QueryNearest finds the nearest player.
  • The on Tick(10s * Random) means it will rerun this block somewhere between every 0s and 10s, chosen randomly

Step 3: Make mobs move

In a similar way, we can make our mobs move towards the nearest player by finding the nearest player and moving a small step in that direction every tick.

// Take 50 steps towards the hero every so often
on Tick(0.5s * Random) {
let target = QueryNearest(filter=Category:Hero).Pos
if !target { continue }

let step = 0.025 * (target - Pos).Direction
repeat 50 {
Move(step)
await Tick
}
}

Full Code Listing

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

pub const TileRadius = 0.5
pub const Tile = 1

pub const BoundaryRadius = 12

pub game fn World.Main() {
TopContent {
InsetPanel { "Arrow keys to move, click to fire" }
}
SolidBackground(#edceb0)
Camera(@(0, 0), radius=BoundaryRadius)

SpawnEachPlayer owner {
Spawn unit {
Hero
}
}

with Tick(1s) {
if QueryAny(filter=Category:Mob) { continue }
repeat 40 {
Spawn unit {
Mob
}
}
}
}

pub fn unit.Hero([owner]) {
use body=this, radius=TileRadius, shape=Circle
Body(pos=@(0, 0), noRotation=true)
ImageSprite(@hero-*.png, radius=1.5*radius)
PolygonCollider(category=Category:Hero)
DecaySpeed(1)

on BeforePhysics {
Move(0.05 * Joystick)
}

// Shoot towards pointer on click
on ButtonDown(Click) {
Spawn projectile {
Bullet(heading=Angle(Pointer - Pos), parent=unit)
}
}
}

pub fn unit.Mob() {
use body=this, radius=TileRadius, shape=Circle
let image=PickRandom(@enemy-*.png)
Body(pos=@(SignedRandom*BoundaryRadius, SignedRandom*BoundaryRadius), noRotation=true)
ImageSprite(image=, radius=1.5*radius)
PolygonCollider(category=Category:Mob)
DecaySpeed(1)

// Shoot towards nearest hero every so often
on Tick(10s * Random) {
let enemy = QueryNearest(filter=Category:Hero)
if !enemy { continue }
Spawn projectile {
Bullet(heading=Angle(enemy.Pos - Pos), parent=unit, owner=null)
}
}

// Take 50 steps towards the hero every so often
on Tick(0.5s * Random) {
let target = QueryNearest(filter=Category:Hero).Pos
if !target { continue }

let step = 0.025 * (target - Pos).Direction
repeat 50 {
Move(step)
await Tick
}
}

on BeforeCollide that {
if that.Category.Overlaps(Category:Projectile) && that.Owner {
Expire
}
}
}

// Bullet flies towards a target with a speed of 30
pub fn projectile.Bullet(heading, parent, [owner]) {
use body=this, radius=0.1, shape=Circle
Body(pos=parent.Pos, velocity=5*heading.Direction, heading=velocity.Angle)
ImageSprite(@bullet.png, radius=1.5*radius, angleOffset=0.25rev)
PolygonCollider(
category=Category:Projectile, parent=,
collideWith=(Category:Tangible ^ Category:Projectile),
)

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