Hello Again,
I managed to find that the 'target' movement was perfectly suited for this.
I still have a problem with the animations of the npcs. They don't switch well from 'stopped' to 'walking'.
Here is the full entity script, it is already usable and could easily be modified to create enemy crowds.
-- Lua script of custom entity npc_crowd.
-- This script is executed every time a custom entity with this model is created.
--Emulate a crowd of npc following a random path
--Avoiding themselves and the hero
local entity = ...
local Vector = require("scripts/Vector")
local random = math.random
local game = entity:get_game()
local map = entity:get_map()
local hero = map:get_hero()
--physics constants
local dt = 1.0/60
local rep = 9000
local coh = 3
local mrep = 16000
local hrep = 128000
--all npcs of the crowd
local ents = {}
-- Event called when the custom entity is initialized.
function entity:on_created()
-- Initialize the properties of your custom entity here,
-- like the sprite, the size, and whether it can traverse other
-- entities and be traversed by them.
local sx,sy = entity:get_size()
--entity count based on custom entity size if not provided
local n = entity.mob_count or (sx*sy)/64
--take default sprites if not given
local sprites = entity.sprites or {"npc/villager1","npc/villager2","npc/villager3"}
--set phantom mode to crowd 'core'
self:set_can_traverse(true)
self:set_traversable_by(true)
self:set_size(8,8)
--params of the generated npcs
local w, h = 8,8
local x, y,layer = entity:get_position()
local sq = math.ceil(math.sqrt(n))
for i=1,n do
local ex = x+(i%sq)*w
local ey = y+(i/sq)*h
local ent = map:create_npc({
name = "crowd" .. i,
layer = layer,
direction=3,
subtype=1,
x = ex,
y = ey,
width=w,
height=h,
sprite= sprites[random(1,#sprites)],
model = "extended_npc"
})
local pos = Vector(ex,ey)
local mov = sol.movement.create("target")
mov:set_target(entity)
mov:start(ent)
ents[i] = { --crowd character table
ent = ent,
pos = pos,
speed=Vector(0,0),
mov = mov,
f = Vector.new(0,0)
}
end
--set random movement to crowd core
local mov = sol.movement.create("random_path")
mov:set_speed(48)
mov:start(entity)
end
function entity:on_pre_draw()
local epos = Vector(entity:get_position())
local hpos = Vector(hero:get_position())
--compute all forces
for i,ent in ipairs(ents) do
ent.f = Vector(0,0)
--reset position to effective position
ent.pos = Vector(ent.ent:get_position())
--compute pairs repulsive forces
for j,ont in ipairs(ents) do
local r = ent.pos - ont.pos
if r:lenSq() > 0.00001 then --if force not huge
ent.f = ent.f + rep * (1/r:lenSq()) * r:normalized()
end
end
local re = ent.pos - epos
local rh = ent.pos - hpos
--cohesive force (to the center of crowd)
ent.f = ent.f - coh * re
--repulsion from crowd center
ent.f = ent.f + mrep * (1/re:lenSq()) * re:normalized()
--repulsion from the hero
ent.f = ent.f + hrep * (1/rh:lenSq()) * rh:normalized()
--speed damping
ent.f = ent.f - 1*ent.speed
end
for i,ent in ipairs(ents) do
--integrate forces
ent.speed = ent.speed + dt*ent.f
ent.speed:truncate(64)
ent.pos = ent.pos + dt*ent.speed
--update target movement
ent.mov:set_target(ent.pos:unpack())
ent.mov:set_speed(ent.speed:len())
end
end
The simulation could be optimized by updating the pairs forces at a smaller rate with a timer. But this already works for small crowds of like 40 npcs.
Greg