Custom movement. Way to go?

Started by stdgregwar, January 29, 2017, 09:27:17 PM

Previous topic - Next topic
Hello,

I was experimenting with the engine and tried to make NPC "crowds". i.e. Many entities that moves together.

My question here is:

How do I take advantage of the existing collisions detections features while implementing a new kind of movement? Is there a way to do this cleanly?

I didn't see anything about this in the lua API.

What I tried:


  • Setting the positions of the entities manually. But without suprise the NPCs move in walls.
  • Using a path movement where I redefine the path every 200 frames. This works but is not very smoothly.

Here is code from the 'path' movement solution:

for i,ent in ipairs(ents) do
    --deduce entity direction from boids force
    local dir = utils.dir_from_xy(ent.f:unpack())
    --check deduced direction
    print(dir)
    --update movement path
    ent.mov:set_path{dir}
  end


Thanks.

Gregwar

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