How to make good dodging AI

General discussion about LÖVE, Lua, game development, puns, and unicorns.
Post Reply
User avatar
Gunroar:Cannon()
Party member
Posts: 1091
Joined: Thu Dec 10, 2020 1:57 am

How to make good dodging AI

Post by Gunroar:Cannon() »

This is the main boid code I use

Code: Select all

--[[
  Copyright (c) 2012 Roland Yonaba

  Permission is hereby granted, free of charge, to any person obtaining a
  copy of this software and associated documentation files (the
  "Software"), to deal in the Software without restriction, including
  without limitation the rights to use, copy, modify, merge, publish,
  distribute, sublicense, and/or sell copies of the Software, and to
  permit persons to whom the Software is furnished to do so, subject to
  the following conditions:

  The above copyright notice and this permission notice shall be included
  in all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
  CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
  TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--]]


local require = old_require or require

if (...) then
  local _PATH = (...):match('%w+%.')
  require  ('toybox.libs.steering.src.core.math')
  local Vec = require ('toybox.libs.steering.src.core.vector')
  
  local Zero = Vec()
  
  local min, random, cos, sin, tau = math.min, math.random, math.cos, math.sin, math.tau
  local l2Abs, rSign = math.localToAbsoluteReference, math.randomSign
  
  local function getTarget(target)
    if target.x then
      return target
    elseif target.pos then
      return target.pos
    elseif target.agent then
      return target.agent.pos or error("Agent incomplete!")
    end
    error("Invalid target")
  end
  
  local function _getHunter(hunter)
    if hunter.pos then
      return hunter
    elseif hunter.x then
      return {pos=hunter, vx=hunter.vx, vy = hunter.vy}
    elseif hunter.agent then
      return hunner.agent
    end
    error("Invalid hunter")
  end
  
  local function getHunter(h)
    h = _getHunter(h)
    h.vel = h.vel or Vec(h.vx or 0, h.vy or 0)
    return h
  end
    
  
  local SteeringBehaviour = {}

  function SteeringBehaviour.seek(agent,targetPos)
    local targetPos = getTarget(targetPos)
    
    local requiredVel = (targetPos - agent.pos):normalize() * agent.maxVel
    return requiredVel - agent.vel
  end

  function SteeringBehaviour.flee(agent,targetPos, panicDistance)
    local targetPos = getTarget(targetPos)
    if panicDistance then
      if agent.pos:distSqTo(targetPos) < panicDistance * panicDistance then
        local requiredVel = (agent.pos - targetPos):normalize() * agent.maxVel
        return requiredVel - agent.vel     
      end
    end
    return Zero
  end
  
  local DecelerationType = {slow = 3, normal = 2, fast = 1}  
  function SteeringBehaviour.arrive(agent, targetPos, typeDec)
local targetPos = getTarget(targetPos)
    local vTarget = targetPos - agent.pos
    local distToTarget = vTarget:mag()
    if distToTarget > 0 then
      typeDec = typeDec or 'normal'
      local vel = distToTarget/DecelerationType[typeDec]
      vel = min(vel, agent.maxVel:mag())
      local requiredVel = vTarget * (vel/distToTarget)
      return requiredVel - agent.vel
    end
    return Zero
  end
  
  function SteeringBehaviour.pursuit(agent, runaway)
    runaway = getHunter(runaway)
    local vRunaway = runaway.pos - agent.pos
    local relativeHeading = agent.velHeading:dot(runaway.velHeading or Vec(0,0))
    if vRunaway:dot(agent.velHeading) > 0 and relativeHeading < -0.95 then
      return SteeringBehaviour.seek(agent,runaway.pos)
    end
    local predictTime = vRunaway:mag()/(agent.maxVel:mag()+runaway.vel:mag())
    return SteeringBehaviour.seek(agent,runaway.pos + runaway.vel * predictTime)
  end
  
  function SteeringBehaviour.evade(agent,hunter)
    hunter = getHunter(hunter)
    local vHunter = hunter.pos - agent.pos
    local predictTime = vHunter:mag()/(agent.maxVel:mag() + hunter.vel:mag())    
    return SteeringBehaviour.flee(agent, hunter.pos + hunter.vel * predictTime,100)
  end
  
  function SteeringBehaviour.wander(agent, radius, distance, jitter)
    jitter = jitter or 10
    radius = radius or 100
    distance = distance or 100
    local jitterThisFrame = jitter * agent.dt
    local theta = random() * tau
    local targetPos = Vec(radius * cos(theta), radius * sin(theta))
    targetPos = targetPos + Vec((random()*rSign() * jitterThisFrame),
                                (random()*rSign() * jitterThisFrame))    
    targetPos:normalize()
    targetPos = targetPos * radius
    local targetAtDist = targetPos + Vec(distance,0)
       
    local newTargetPos = l2Abs(targetAtDist, agent.velHeading, agent.velPerp, agent.pos)
    --[[
    print('agentVelH', agent.velHeading)
    print('agentVelPerp', agent.velPerp)
    print('agentpos', agent.pos)
    print('newTargetPos', newTargetPos)
    --io.read()
    --]]
    return newTargetPos - agent.pos
    
  
  end
  
  return SteeringBehaviour
end  
It works okay for simple AI that follows the player with seek and pursuit, but when it tries to dodge bullets, using evade, it works poorly by kind of going in one direction.

Any ideas to improve it?
The risk I took was calculated,
but man, am I bad at math.

-How to be saved and born again :huh:
User avatar
darkfrei
Party member
Posts: 1184
Joined: Sat Feb 08, 2020 11:09 pm

Re: How to make good dodging AI

Post by darkfrei »

Gunroar:Cannon() wrote: Mon May 01, 2023 7:23 am This is the main boid code I use

It works okay for simple AI that follows the player with seek and pursuit, but when it tries to dodge bullets, using evade, it works poorly by kind of going in one direction.

Any ideas to improve it?
Can you please provide a small working example as .love file?
:awesome: in Lua we Löve
:awesome: Platformer Guide
:awesome: freebies
User avatar
Gunroar:Cannon()
Party member
Posts: 1091
Joined: Thu Dec 10, 2020 1:57 am

Re: How to make good dodging AI

Post by Gunroar:Cannon() »

I don't really think the love file would help. Has to much unnecessary code to be readable by someone else. The void library is available on github I think.

What I'm hoping for is if anyone can see any problem with the evade code.

If you really want a love file I'll put it but my code's really messy. :)
The risk I took was calculated,
but man, am I bad at math.

-How to be saved and born again :huh:
User avatar
darkfrei
Party member
Posts: 1184
Joined: Sat Feb 08, 2020 11:09 pm

Re: How to make good dodging AI

Post by darkfrei »

Looks like typo:
elseif hunter.agent then
return hunner.agent
:awesome: in Lua we Löve
:awesome: Platformer Guide
:awesome: freebies
User avatar
pgimeno
Party member
Posts: 3582
Joined: Sun Oct 18, 2015 2:58 pm

Re: How to make good dodging AI

Post by pgimeno »

darkfrei wrote: Mon May 01, 2023 7:54 am Can you please provide a small working example as .love file?
Gunroar:Cannon() wrote: Mon May 01, 2023 10:43 am I don't really think the love file would help. Has to much unnecessary code to be readable by someone else. The void library is available on github I think.

What I'm hoping for is if anyone can see any problem with the evade code.

If you really want a love file I'll put it but my code's really messy. :)
Where did you get that a "small working example" means to post your whole project?
User avatar
Gunroar:Cannon()
Party member
Posts: 1091
Joined: Thu Dec 10, 2020 1:57 am

Re: How to make good dodging AI

Post by Gunroar:Cannon() »

darkfrei wrote: Mon May 01, 2023 10:55 am Looks like typo:
elseif hunter.agent then
return hunner.agent
Heheh. I doubt though. "Check if there's still an agent and return one if there is, but if there isn't then throw an error".
pgimeno wrote: Mon May 01, 2023 1:25 pm Where did you get that a "small working example" means to post your whole project?
Okay, fair enough. But, unfortunately for me, these days my coding is dependant on the toybox/LGML lib I use and I kind of don't have enough will to do a raw example from scratch (which is why I posted on general, just looking for ideas).

And also to me, the project is kind of "small" (but no the code base is not) :rofl: .
I might still post my other messed up example, but basically I 'm asking whether the steering behaviour lib works well.
The lib
The risk I took was calculated,
but man, am I bad at math.

-How to be saved and born again :huh:
Ross
Citizen
Posts: 99
Joined: Tue Mar 13, 2018 12:12 pm
Contact:

Re: How to make good dodging AI

Post by Ross »

Well if I wanted to dodge a bullet, I would try to move perpendicular to the bullet's velocity vector.
User avatar
pgimeno
Party member
Posts: 3582
Joined: Sun Oct 18, 2015 2:58 pm

Re: How to make good dodging AI

Post by pgimeno »

Ross wrote: Mon May 01, 2023 8:36 pm Well if I wanted to dodge a bullet, I would try to move perpendicular to the bullet's velocity vector.
Curiously enough, that is not always optimal, but I have no idea how to calculate the optimal angle.

The program below shows that all other things things being equal, moving slightly backwards (a=-100) instead of perpendicular (a=-90) buys the second one just enough time to move a bit more out of the way and avoid the bullet.

Code: Select all

local bullet1 = {x = 300, y = 300, w = 10, h = 10, a = math.rad(180), sp = 20}
local bullet2 = {x = 700, y = 300, w = 10, h = 10, a = math.rad(180), sp = 20}

local dodger1 = {x = 100, y = 260, w = 80, h = 80, a = math.rad(-90), sp = 6.6}
local dodger2 = {x = 500, y = 260, w = 80, h = 80, a = math.rad(-100), sp = 6.6}

local hit1 = false
local hit2 = false

local function collide(ax1, ay1, ax2, ay2, bx1, by1, bx2, by2)
  return math.max(ax1, bx1) < math.min(ax2, bx2)
     and math.max(ay1, by1) < math.min(ay2, by2)
end

local function objectUpdate(self, dt)
  self.x = self.x + math.cos(self.a) * self.sp * dt
  self.y = self.y + math.sin(self.a) * self.sp * dt
end

function love.update(dt)
  objectUpdate(bullet1, dt)
  objectUpdate(bullet2, dt)
  objectUpdate(dodger1, dt)
  objectUpdate(dodger2, dt)
  if collide(bullet1.x, bullet1.y, bullet1.x + bullet1.w, bullet1.y + bullet1.h,
             dodger1.x, dodger1.y, dodger1.x + dodger1.w, dodger1.y + dodger1.h)
  then
    hit1 = true
  end

  if collide(bullet2.x, bullet2.y, bullet2.x + bullet2.w, bullet2.y + bullet2.h,
             dodger2.x, dodger2.y, dodger2.x + dodger2.w, dodger2.y + dodger2.h)
  then
    hit2 = true
  end
end

local function objectDraw(self)
  love.graphics.rectangle("fill", self.x, self.y, self.w, self.h)
end

function love.draw()
  if hit1 then
    love.graphics.setColor(1, 0, 0)
  else
    love.graphics.setColor(1, 1, 1)
  end
  objectDraw(bullet1)
  objectDraw(dodger1)

  if hit2 then
    love.graphics.setColor(1, 0, 0)
  else
    love.graphics.setColor(1, 1, 1)
  end
  objectDraw(bullet2)
  objectDraw(dodger2)
end
User avatar
knorke
Party member
Posts: 260
Joined: Wed Jul 14, 2010 7:06 pm
Contact:

Re: How to make good dodging AI

Post by knorke »

I think it is hard to answer the question without knowing more details.
For example what kind of movement physics are there?
Is it "instant" like Pacman or is there inertia?
How many bullets are there?
Is it possible to use tactics that makes it harder for the enemy to line up a good shot in the first place?
User avatar
Gunroar:Cannon()
Party member
Posts: 1091
Joined: Thu Dec 10, 2020 1:57 am

Re: How to make good dodging AI

Post by Gunroar:Cannon() »

I guess it's instant. If I add inertia I'll just try and tweak the code. My main problem is that the library doesn't work for fleeing as I thought it should when I use it.
The risk I took was calculated,
but man, am I bad at math.

-How to be saved and born again :huh:
Post Reply

Who is online

Users browsing this forum: Bing [Bot], Google [Bot] and 3 guests