From NPC combat to path following and all AI related tasks.

onSensorContact Issues

onSensorContact Issues

Postby Shando » 02 Jan 2015, 23:39

Hi Guys,

As you know, I'm playing around with the FPS system (thanks for all your help so far :)).

Unfortunately, I've come across an issue with the "onSensorContact" functions :(

When logging to the Console, I get the following (based on an AIS Bounds Radius of 10):

Code: Select all
Orc1 - onSensorContact_Stay: 10
Orc1 - onSensorContact_Stay: 9
Orc1 - onSensorContact_Stay: 8
Orc1 - onSensorContact_Stay: 7
Orc1 - onSensorContact_Enter
Orc1 - onSensorContact_Stay: 6
Orc1 - onSensorContact_Exit
Orc1 - onSensorContact_Enter
Orc1 - onSensorContact_Stay: 7
Orc1 - onSensorContact_Stay: 8
Orc1 - onSensorContact_Stay: 9
Orc1 - onSensorContact_Stay: 10


For some reason, "Enter" doesn't get called until "Stay" has been called a few times, then "Exit" gets called almost straight away, then "Enter" gets called again??

Also, is it possible to access the AIS Bounds Radius through Script, as I would like to alter it based on which waypoint the character is heading towards?

Or, another option would be to have a larger part of the sensor Sphere in FRONT of the character, with a smaller part behind (to simulate being able to see further in front?, and hear a little of what is behind?)

Or, another option would be to limit the sensor Sphere to a half Sphere going from the character FORWARD, which would simulate forward vision, and then have another sensor Sphere to detect sounds?

I suppose this could all be done in code, by working out which direction the character is heading, and then ignore the sensor contact if the character is heading AWAY from the Player, unless the Player is close behind?? I'll see if I can do this, but I'm not sure my maths will be up to it!!

Regards

Shando
User avatar
Shando
Skyline Moderator
Skyline Moderator
 
Posts: 492
Joined: 06 Mar 2013, 22:35
Location: Kings Beach, Queensland
Skill: Programmer
Skill: Scripter
Skill: Level Designer

Re: onSensorContact Issues

Postby Shando » 03 Jan 2015, 03:17

UPDATE......

OK, so I didn't need maths after all 8-)

Thanks to the "SensorContact" distance parameter it was all pretty simple in the end ;)

my entire code for the "Orc AI" is this:

Code: Select all
-----------------------------------------------------------------------------------------
-- User Vars
-----------------------------------------------------------------------------------------
mode = "walk";
      
ANIM_Walk = "weapon_moveslow";         -- walk animation
walkSpeed = 25;                     -- walk speed
rotDelay_Walk = 160;               -- walk turn speed, larger values for quicker turns
      
ANIM_Idle = "weapon_idle"
ANIM_KO = "impact_front";

ANIM_Run = "weapon_movefast";         -- run animation
runSpeed = 60;                     -- run speed
rotDelay_Run = 200;                -- run turn speed, larger values for quicker turns
rotDelay_Strafe = 50;
faction = "Red";
pathTurnSpeed_Target = 20;
pathTurnSpeed_Normal = 50;

myCount = 0;
myDist1 = 100;
myDist2 = 100;
bAttack = false;

-- sensor ranges based on a bot sensor radius = 20
distanceToAttack = 16;
distanceToWalk = 10;
distanceDisengage = 19;
distanceToStand = 6;
distanceMin = 1;
 
startPathNode = 0;
pathName = "Orc1"

-----------------------------------------------------------------------------------------
-- System Vars
-----------------------------------------------------------------------------------------
strafeSpd = 0;
followPath = true;                  -- follow the path or follow a position
targetPos = {}
currentWaypoint = 0;
wx, wy, wz = 0;
obj = 0;
currentTarget = -1;

ai_Mode = "closest"                -- AI mode to start with

aimode = ai_Mode;
lockedOn = false;
offLine = false;
offLineTargetID = -1;
isEvading = false;
shootTimerStarted = false;
distanceMinVariation = 0;
accuracyTimerEnabled = false;
isTauntUsed = false;
health = 200;

audioFile_Ork_Laugh = "OrcLaugh.ogg";
audioFile_Ork_KO = "OrcDeath.ogg";
audioFile_Ork_Spawn = "OrcSpawn.ogg";
audioFile_Ork_Taunt = "OrcTaunt.ogg";
audioFile_Ork_Footstep = "Footstep_Grass_00.ogg";

audioID_Ork_Laugh = -1;
audioID_Ork_KO = -1;
audioID_Ork_Spawn = -1;
audioID_Ork_Taunt = -1;
audioID_Ork_Footstep = -1;

SHOOT_TIMER = 1;
SHOOTPAUSE_TIMER = 2;
ACCURACY_TIMER = 3;
TARGET_AXIS_TIMER = 4;
STRAFE_TIMER = 5;
RESPAWN = 6;

function onInit ( objID )
   obj = objID;
   lockedOn = false;
   targetPos = newType.vec3 ( entity.getPosition ( obj ) );
   wx, wy, wz = entity.getPosition ( obj );
   character.setPosition ( obj, wx, wy + 1, wz )            -- ensure the character is above the ground, so no falling through
   character.setPathMaxTurn ( obj, pathTurnSpeed_Normal );    -- increase to turn faster
   anim.playAnimation ( obj, ANIM_Idle, 30, 0 )
   doMove ( mode );
   setupSounds ( )   
end

function setupSounds ( )
   sound.setLooped ( 0 );
   -- Ork Footstep
   sound.setVolume ( 2 );
   sound.setSoundFile ( audioFile_Ork_Footstep );
   audioID_Ork_Footstep = sound.create2DSound ( );
   -- Ork Laugh
   sound.setVolume ( 2 );
   sound.setSoundFile ( audioFile_Ork_Laugh );
   audioID_Ork_Laugh = sound.create2DSound ( );
   -- Ork KO
   sound.setVolume ( 5 );
   sound.setSoundFile ( audioFile_Ork_KO );
   audioID_Ork_KO = sound.create2DSound ( );
   -- Ork Spawn
   sound.setVolume ( 1 );
   sound.setSoundFile ( audioFile_Ork_Spawn );
   audioID_Ork_Spawn = sound.create2DSound ( );
   -- Ork Taunt
   sound.setVolume ( 5 );
   sound.setSoundFile ( audioFile_Ork_Taunt );
   audioID_Ork_Taunt = sound.create2DSound ( );
end

function postInit ( )
   sensor.enableVisibilityCheck ( obj, 1 );
   setupPathData ( pathName );
   setAIMode ( ai_Mode );
   character.followPath ( obj, pathName, 0 );
   character.setPathNodeIndex ( obj, startPathNode );
   reSpawn ( );
end

function setupPathData ( pathName )
   numNodes = path.getNumNodes ( pathName );
   
   for i = 0, numNodes - 1 do
      x, y, z = path.getNodePosition ( pathName, i );
      path[i] = {x = x, y = y, z = z}   
   end
end

function doMove ( md )
   looped = 1;

   if ( md == "run" ) then
      rotDelay = rotDelay_Run;
      moveSpd = runSpeed;
      ANIM = ANIM_Run;
   elseif ( md == "walk" ) then
      rotDelay = rotDelay_Walk;
      moveSpd = walkSpeed;
      ANIM = ANIM_Walk;
      sound.play3DSound ( audioID_Ork_Footstep, entity.getPosition ( obj ) );
   elseif ( md == "idle" ) then
      rotDelay = rotDelay_Run;
      moveSpd = 0;
      ANIM = ANIM_Idle;   
   elseif ( md == "walk_backwards" ) then
      rotDelay = rotDelay_Walk;
      moveSpd = -walkSpeed * 1.5;
      ANIM = ANIM_Walk;
      sound.play3DSound ( audioID_Ork_Footstep, entity.getPosition ( obj ) );
   elseif ( md == "strafe_right" ) then
      rotDelay = rotDelay_Strafe;
      strafeSpd = -walkSpeed;
      ANIM = ANIM_Walk;
      sound.play3DSound ( audioID_Ork_Footstep, entity.getPosition ( obj ) );
   elseif ( md == "strafe_left" ) then
      rotDelay = rotDelay_Strafe;
      strafeSpd = walkSpeed;
      ANIM = ANIM_Walk;   
      sound.play3DSound ( audioID_Ork_Footstep, entity.getPosition ( obj ) );
   elseif ( md == "ko" ) then
      ANIM = ANIM_KO;   
      moveSpd = 0;
      looped = 0;
   end
   
   anim.playAnimation ( obj, ANIM, 30, looped );
end

function onUpdate ( td )
   if ( followPath ) then
      currentWaypoint = character.followPath ( obj, pathName, moveSpd );
      
      if ( currentWaypoint == 1 ) then
         distanceToAttack = 26;
         distanceToWalk = 16;
         distanceDisengage = 29;
         distanceToStand = 10;
         distanceMin = 6;
      else
         distanceToAttack = 16;
         distanceToWalk = 10;
         distanceDisengage = 19;
         distanceToStand = 6;
         distanceMin = 1;
      end
   else
      if ( isEvading == true ) then   
         character.doStrafe ( obj, strafeSpd );
      else
         character.followPath ( obj, "", moveSpd );
      
         if ( character.isDestinationReached ( obj ) == 1 ) then
            character.setPathNodeIndex ( obj, currentWaypoint + 1 );
            followPath = true;   
         end
      end
   end
end

---------------------------------------------------------
-- GAME EVENT A: This Player's AI Sensor Events
---------------------------------------------------------
function onSensorContact_Enter ( targetID )
   sky.lprint ( "Orc1 - onSensorContact_Enter" );
   if ( targetID ~= -1 and offLine ~= true and bAttack ) then
      followPath = false;
      currentMovementType = "walk"
      doMove ( currentMovementType );
      currentTarget = targetID;
   end
   
   offLineTargetID = targetID;
end

function onSensorContact_Stay ( targetID, distance )
   if ( distance ~= myCount ) then
      sky.lprint ( "Orc1 - onSensorContact_Stay: " .. distance );
      myCount = distance;
   end
   
   if ( myDist1 == 100 ) then
      myDist2 = distance;
   end
   
   myDist1 = distance;
   
   if ( myDist1 > myDist2 ) then
      --Orc moving away from Player
      --IGNORE CONTACT if distance is greater than 10
      if ( distance >= 10 ) then
         bAttack = false;
      end
   elseif ( myDist1 < myDist2 ) then
      bAttack = true;
   end

   if ( bAttack ) then
      --check if Player is VISIBLE
      if ( sensor.isTargetVisible ( obj ) < 1 ) then
         --Player NOT VISIBLE so do nothing
         followPath = true;
      else
         if ( targetID ~= -1 and not offLine ) then
            if ( distance <= distanceToAttack ) then
               -- start attacking
               setAIMode ( "target" );
               shoot ( 1 );
            end
            
            if ( distance <= distanceToWalk ) then
               -- slow to a walk
               currentMovementType = "walk";
               shoot ( 1 );
            else
               currentMovementType = "run";            
            end
            
            if ( distance >= distanceDisengage and lockedOn == true ) then
               -- disengage attack
               -- free the target until in range again
               lockedOn = false;
               shoot ( 0 );
            end   
            
            if ( distance < distanceToStand ) then
               -- stand still
               if ( isEvading == false ) then
                  if ( distance < distanceToStand - 1 ) then
                     currentMovementType = "walk_backwards";
                  else
                     currentMovementType = "idle";
                  end
               end
               
               shoot ( 1 );
            end
            
            doMove ( currentMovementType );
            getTargetPosition ( targetID );

            if ( accuracyTimerEnabled == false ) then
               -- start the accuracy timer
               time.startMultiTimer ( ACCURACY_TIMER, obj, 100 );
               accuracyTimerEnabled = true;
            end
         end
      end
   end
   
   myDist2 = myDist1;
end

function onSensorContact_Exit ( targetID )
   sky.lprint ( "Orc1 - onSensorContact_Exit" );
   if ( targetID ~= -1 and not offLine ) then
      shoot ( 0 );
      time.stopMultiTimer ( ACCURACY_TIMER, obj );               -- stop the accuracy timer
      accuracyTimerEnabled = false;
      followPath = true;
      sky.ui_clear ( );
      doMove ( "walk" );
      game.setTargetID ( obj,currentTarget, 0 );
      setAIMode ( "closest" );
      myDist1 = 100;
      myDist2 = 100;
      bAttack = false;
   end   
end

function getTargetPosition ( tID )
   followPath = false;
   currentTarget = tID;
   desc = entity.getDescription ( tID );
   
   if ( desc == "character" ) then
      setTargetIDState = 0;
   else
      setTargetIDState = 1;
   end

   game.setTargetID ( obj, currentTarget, setTargetIDState );
   targetPos = newType.vec3 ( entity.getPosition ( tID ) );
   character.followPathPosition ( obj, targetPos.x, targetPos.y, targetPos.z );
end

---------------------------------------------------------
-- GAME EVENT B: This Player's Damage Action Events
---------------------------------------------------------
-- if this character is ko'd then tell the player controller action.
function ds_onKo ( )
   setAIMode ( "offline" )
   doMove ( "ko" );
   sound.play3DSound ( audioID_Ork_KO, entity.getPosition ( obj ) );
   character.ko ( obj );
   timeToRespawn = 2000;
   time.startMultiTimer ( RESPAWN, obj, timeToRespawn );
   bAttack = false;
end

function ds_onDamage ( healthRemaining, damage, attackingID )
   health = healthRemaining;
   
   if ( isTauntUsed == false ) then
      sound.play3DSound ( audioID_Ork_Taunt, entity.getPosition ( obj ) );
      isTauntUsed = true;
   end
   
   if ( lockedOn ~= true ) then
      currentTarget = attackingID;
      setAIMode ( "target" );                         -- change AI mode to specified target - [High Killer Instinct]
   else
      desc = entity.getDescription ( attackingID );
      
      if ( desc == "character" ) then
         lockedOn = false;
         game.setTargetID ( obj, attackingID, 0 );
      end
   end
   
   if ( isEvading == false and lockedOn == true ) then
      setAIMode ( "evade" );
   end
end

---------------------------------------------------------
-- Activate / Deactivate this players AI hostility
---------------------------------------------------------
function deactivate ( )
   offLine = true;
   followPath = true;
   lockedOn = false;
   isEvading = false;
   shoot ( 0 );
   sky.ui_clear ( );
   doMove ( "walk" );
   accuracyTimerEnabled = false;
   character.setPathMaxTurn ( obj, pathTurnSpeed_Normal );
   bAttack = false;
end

function activate ( )
   offLine = false;
   currentTarget = offLineTargetID;
   followPath = false;
   bAttack = false;
end

---------------------------------------------------------
--AI STATE
---------------------------------------------------------
function setAIMode ( mode )
   time.stopMultiTimer ( TARGET_AXIS_TIMER, obj );
   aimode = mode;
   
   if ( mode == "offline" ) then
      deactivate ( );
   elseif ( mode == "closest" ) then
      offLine = false;
      lockedOn = false;
      followPath = true;
      sensor.setTargetMode_Closest ( obj );
      game.setTargetID ( obj, currentTarget, 1 );
      time.startMultiTimer ( TARGET_AXIS_TIMER,obj, math.random ( 100, 500 ) );
      bAttack = false;
   elseif ( mode == "target" ) then
      offLine = false;
      followPath = false;
      lockedOn = true;
      game.setTargetID ( obj, currentTarget, 1 );
      sensor.setTargetMode_Single ( obj, currentTarget );
      shoot ( 1 );
      character.setPathMaxTurn ( obj, pathTurnSpeed_Target );
      bAttack = false;
   elseif ( mode == "evade" ) then
      rnd = math.random ( 10 );
      
      if ( rnd > 5 ) then
         doMove ( "strafe_right" )
      else
         doMove ( "strafe_left" );
      end
      
      if ( rnd < 5 and health < 70 ) then
         sound.play3DSound ( audioID_Ork_Laugh, entity.getPosition ( obj ) );
      end
      
      isEvading = true;
      time.startMultiTimer ( STRAFE_TIMER, obj, math.random ( 1000, 2500 ) );
      shoot ( 0 );
      game.setTargetID ( obj, currentTarget, 0 );
   end
end

function shoot ( state )
   if ( state == 1 and sensor.isTargetVisible ( obj ) > 0 ) then
      if ( shootTimerStarted == false ) then
         game.shoot ( obj, 1 );
         time.startMultiTimer ( SHOOT_TIMER, obj, 10 );
         shootTimerStarted = true;
      end
   else
      shootTimerStarted = false;
      game.shoot ( obj, 0 );
      time.stopMultiTimer ( SHOOT_TIMER, obj );
      time.stopMultiTimer ( SHOOTPAUSE_TIMER, obj );
      time.stopMultiTimer ( TARGET_AXIS_TIMER, obj );
   end
end
 
---------------------------------------------------------
-- TIMERS
---------------------------------------------------------
function onMultiTimer ( timerID )
   if ( timerID == SHOOT_TIMER ) then                  -- start auto shooting and call pause timer
      game.shoot ( obj, 1 );                        -- shoot
      time.startMultiTimer ( SHOOTPAUSE_TIMER, obj, math.random ( 400, 1500 ) );
      time.stopMultiTimer ( SHOOT_TIMER, obj );
   elseif ( timerID == SHOOTPAUSE_TIMER ) then            -- pause shooting timer
      game.shoot ( obj, 0 );                        -- stop shooting
      time.stopMultiTimer ( SHOOTPAUSE_TIMER, obj );
      time.startMultiTimer ( SHOOT_TIMER, obj, math.random ( 200, 800 ) )
   elseif ( timerID == ACCURACY_TIMER ) then            -- makes the bot more accurate if called more frequently
      getTargetPosition ( currentTarget );   
   elseif ( timerID == TARGET_AXIS_TIMER ) then         -- enable target axis shooting
      game.setTargetID ( obj, currentTarget, 1 )    
   elseif ( timerID == STRAFE_TIMER ) then               -- stop strafing
      isEvading = false;
      lockedOn = false;
      time.stopMultiTimer ( STRAFE_TIMER, obj );
      character.doStrafe ( obj, 0 );                  --reset the strafe amount to zero
   elseif ( timerID == RESPAWN ) then
      time.stopMultiTimer ( RESPAWN, obj );
      reSpawn ( );
   end
end

function globalFn ( callingID, value )
   if ( value == "offline" ) then
      setAIMode ( "offline" );
   end

   if ( value == "online" ) then
      setAIMode ( "closest" );
   end
end

function reSpawn ( )
   isTauntUsed = false;
end


This code (which is taken from the microscript attached to the Orc FPS entity) basically gets an Orc to follow a preset path and, if the Orc is heading TOWARDS the Player AND the Sensor "finds" the Player AND the Visibility Check is TRUE, then the Orc will attack. However, if the Orc is heading AWAY FROM the Player then, UNLESS the Player is within 10 units OR the Player fires at the Orc, then the Orc will NOT attack :P

Now, to get the other Orcs to start patrolling :|

I'm still not 100% sure how the onSensorContact events are fired :? but the above code seems to work just fine for my little test level :D

Regards

Shando

PS: As the Orc is an .ark file, I have to keep a separate copy of the Script that is used to overwrite the script in the .ark file each time I load up Skyline. However, once I have either purchased the Orc, or got hold of a different model, this will, of course, no longer be necessary :P
User avatar
Shando
Skyline Moderator
Skyline Moderator
 
Posts: 492
Joined: 06 Mar 2013, 22:35
Location: Kings Beach, Queensland
Skill: Programmer
Skill: Scripter
Skill: Level Designer

Re: onSensorContact Issues

Postby SolarPortal » 03 Jan 2015, 12:08

Cool that your making changes to the AI.
I am sure StarFire can answer you more on the sensor, but I can answer the last .ark part.

The Orc is stored in the .ark package, but there is no script in the ark, just a mesh, Skeleton, materials and textures.
The script is apart of the preset stored in the preset folder.

You can change the script and just save another preset file of the ork.
Simply open the asset manager, goto a folder anywhere, then drag from the scene entity list to the asset manager view.
This will create a preset of that entity.
There is also a button in the asset manager toolbar for preset import.
Or right click in the scene and choose save as preset.

Then drag or spawn your version.

The current ork preset will be in the "Presets/Skyline game system/fps kit" or something around that structure.
User avatar
SolarPortal
Skyline Founder
Skyline Founder
 
Posts: 3156
Joined: 29 Jul 2012, 15:56
Location: UK
Skill: 3D Modeller
Skill: 2D Artist
Skill: Programmer
Skill: Level Designer

Re: onSensorContact Issues

Postby StarFire » 03 Jan 2015, 20:07

Just had a play with the sensor and all looks ok.

The sensor detects and processes events for the closest object to the sensor center. So if we had only one object enter the sensor, ensuring that the ground is not detected, it all works as you would expect. Now if 2 objects enter the detection range only the closest object is processed, that means any other objects in the sphere will not send an exit event, only the processed object will send the events. This can be confusing :? but most checks can be done via script as you have done ;)

The sensor is like every other feature an ongoing development, so far its been great for most circumstances in AI detection, proximity detection and very fast triggers in the vehicle AI. Most eventualities have been fine with its current status and with careful use of object groups and lua pretty much can be achieved. If you find you can not overcome a situation, please let me know and if possible we can upgrade the sensor for you. :D
User avatar
StarFire
Skyline Founder
Skyline Founder
 
Posts: 1427
Joined: 03 Jan 2012, 18:50
Location: UK
Skill: Great creative
Skill: Programmer
Skill: 3D Modeller
Skill: 2D Artist
Skill: Level Designer


Return to AI

Who is online

Users browsing this forum: No registered users and 1 guest

cron