Game Development Community

My Degree Thesis - A I for non-player charachters

by City University (#0001) · in Torque Game Engine · 10/03/2005 (7:49 am) · 32 replies

Hi everone,

I am in my last year studying Computer Science with Games Technology at City University London and would like to do my thesis on Torque. The title of my thesis will be:

Artificial Intelligence for non-player characters

So this is the plan -

1) Use the Torque FPS demo as a base.
2) Give the non-player character (NPC) basic sight by sending out loads of pic-rays from it so it can recognise you.
3) Give the NPC a small brain using a fuzzy logic based system. There will be three stages to this. The NPC can either run toward you, Stand still or run away and this will be triggered by how high the enemy health is.
4) Discuss why unpredictable NPC's in games makes for a better game.

Heres where you guys come in. Please could somebody tell me what they think of this idea and give me an idea of how to go about this, bearing in mind that I am a n00b with Torque and my C is now a little rusty after the holidays. Most importantly is it possible to do this in say 4-6 months and how much work will this entail.

Thanks in advance to everyone who replies :)
Page«First 1 2 Next»
#21
11/02/2005 (5:24 am)
function AIPlayerDB::onTargetEnterLOS(%this,%obj)
{
   // If an aim target object is set, this method is invoked when
   // that object becomes visible.
      DebugPrint( "%this:"@%this@"~AIPlayerDB::onTargetEnterLOS LOS TARGET ! (from:"@%obj@")", "onTargetEnterLOS");
   if(!isObject(%obj))
      return;
   %obj.attentionLevel = %this.attention;
   %this.schedule($MIN_ITCHY_FINGER+getRandom($MAX_ITCHY_FINGER), "pauseFire", %obj);
   %this.schedule($MIN_SCAN_GAP+getRandom($MAX_SCAN_GAP/%this.alertness), "doScan", %obj);
   %obj.setImageTrigger(0,true);
}

function AIPlayerDB::onTargetExitLOS(%this,%obj)
{
   // If an aim target object is set, this method is invoked when
   // the object is no longer visible.
   DebugPrint( "%this:"@%this@"~AIPlayerDB::onTargetExitLOS Fuhgetaboutit (from:"@%obj@")", onTargetExitLOS);
   if(!isObject(%obj))
      return;
   %obj.setImageTrigger(0,false);
   %obj.clearAim();
      DebugPrint( "> %obj.clearAim (from:"@%obj@")", "onTargetExitLOS");
   %obj.currentTarget = 0;  // forget this target
   %this.schedule($MIN_SCAN_GAP, "doScan", %obj);
}


function AIPlayerDB::pauseFire(%this,%obj)
{
      DebugPrint("%this:"@%this@"~AIPlayerDB::ceaseFire (from:"@%obj@")", pauseFire);
   if(!isObject(%obj))
      return;
   %obj.setImageTrigger(0,false);
   %this.schedule($MIN_TRIGGER_HOLD+getRandom($MAX_TRIGGER_HOLD), "ceaseFire", %obj);
}

function AIPlayerDB::ceaseFire(%this,%obj)
{
      DebugPrint( "%this:"@%this@"~AIPlayerDB::ceaseFire (from:"@%obj@")", ceaseFire);
   if(!isObject(%obj))
      return;
   %obj.setImageTrigger(0,false);
   %obj.clearAim();
   %obj.currentTarget = 0; // forget this target
}

function spawnbot(%index,%role)
{
   DebugPrint( "%index:"@%index@"%role:"@%role@"~", spawnbot);
  %me = new AIPlayer() {
    dataBlock = AIPlayerDB;
    aiPlayer = true;
  };
  MissionCleanup.add(%me);
  AIGroup.add(%me);
  
  %me.setAimObject( 0 );
  %me.look = 0;
  %me.range = 100;
  %spawn=pickAISpawn(%index,%role);
  %me.range     = %spawn.range      < $MAX_THREAT_ENGAGE_RANGE ? %spawn.range              :  $MAX_THREAT_ENGAGE_RANGE;
  %me.attention = %spawn.attention  < $MAX_ATTENTION_LEVEL     ? %spawn.attention/5        :  $MAX_ATTENTION_LEVEL/5;
  %me.alertness = %spawn.alertness  < $MAX_ALERTNESS           ? %spawn.alertness/10       :  $MAX_ALERTNESS/10;
  %me.aggression   = %spawn.aggressiveness < $MAX_AGGRESSIVENESS  ? %spawn.aggressiveness * 0.015 :  $MAX_AGGRESSIVENESS * 0.015;
  
  %me.index = %index;
  %me.setTransform(%spawn.getTransform());
  %me.setEnergyLevel(60);
  %me.role = %role;
  %me.setShapeName(%me.getName() SPC %me.role);
  %me.attentionLevel = 0;
  %me.nextBlockCheck = 0;
  %me.conformToGround = 0;
  %me.Equip(CrossBow,CrossBowAmmo);
  %me.setMoveSpeed($STATIONARY);
  
  echo("Added [" SPC %me SPC "] :" SPC %me.role SPC "#" SPC  %me.index );

  %me.setAimLocation( $cardinalDirection[%me.look]);
  %me.getDataBlock().schedule(2000, "doScan", %me);

  return %me;
}
#22
11/02/2005 (5:25 am)
function AIPlayer::Equip(%this,%weaponDBName,%ammoDBName)
{
   DebugPrint( "%this:"@%this@"~AIPlayer::Equip", Equip);
   %weapon = new Item() {
      dataBlock = %weaponDBName;
   };
   %ammo = new Item() {
      dataBlock = %ammoDBName;
   };
   DebugPrint("weapon:"@%weaponDBName, "Equip");
   DebugPrint("ammo:"@%ammoDBName, "Equip");
   MissionCleanup.add(%weapon);
   MissionCleanup.add(%ammo);
   %weaponImageName = %weaponDBName @"Image";
  %this.mountImage(%weaponImageName,0);
  %this.setInventory(%ammoDBName,1000);
  %this.use(%weaponDBName);
}

function AIPlayer::GetTargetRange(%this, %target)
{
   DebugPrint( "%this:"@%this@"~AIPlayer::GetTargetRange", GetTargetRange);
   %tgtPos = %target.getPosition();
   %eyePoint = %this.getWorldBoxCenter();
   %distance = VectorDist(%tgtPos, %eyePoint);
      DebugPrint("Actual range to target: " @ %distance , GetTargetRange);
   return %distance;
}

function AIPlayer::getClosestEnemy(%this) 
{
   DebugPrint( "%this:"@%this@"~AIPlayer::getClosestEnemy", getClosestEnemy);

   %index = -1;
   %botPos = %this.getPosition();
   %count = ClientGroup.getCount();
   for(%i = 0; %i < %count; %i++)
   {
      %client = ClientGroup.getObject(%i);
      if (%client.player $= "" || %client.player == 0 )
         return -1;
      %playPos = %client.player.getPosition();

      %tempDist = VectorDist(%playPos, %botPos);
      if(%i == 0) 
      {
         %distance = %tempDist;
         %index = %i;
      }
      else 
      {
         if(%distance > %tempDist) 
         {
            %distance = %tempDist;
            %index = %i;
         }
      }
   }
   return %index;
}

function CreateBots()
{
   new SimSet (AIGroup);
   warn("# # # # # # # # # # # # Creating NPC characters # # # # # # # # # # # # ");
   if ( (%role=nameToID("MissionGroup/AIDropPoints/Guard")) >= 0 )
   {
      %count=%role.getCount();
      for ( %i = 0; %i < %count; %i++)
      {
      	$PLAYER_TYPE = "Guard";
         spawnbot(%i,"Guard");
      }
   }
   if ( (%role=nameToID("MissionGroup/AIDropPoints/Chaser")) >= 0 )
   {
      %count=%role.getCount();
      for ( %i = 0; %i < %count; %i++)
      {
      	$PLAYER_TYPE = "Chaser";
         spawnbot(%i,"Chaser");
      }
   }
   if ( (%role=nameToID("MissionGroup/AIDropPoints/Follower")) >= 0 )
   {
      %count=%role.getCount();
      for ( %i = 0; %i < %count; %i++)
      {
      	$PLAYER_TYPE = "Follower";
         spawnbot(%i,"Follower");
      }
   }
}
#23
11/02/2005 (5:25 am)
function pickAISpawn(%index,%role)
{
   %groupName = "MissionGroup/AIDropPoints/" @ %role;
   %group = nameToID(%groupName);

   if (%group != -1) 
   {
      %count = %group.getCount();
      if (%count != 0) 
      {
        // %index = getRandom(%count-1);
         %spawn = %group.getObject(%index);
         return %spawn;
      }
      else
            DebugPrint("No spawn points found in " @ %groupName, "pickAISpawn");
   }
   else
       DebugPrint("Missing spawn points group " @ %groupName, "pickAISpawn");

   return %spawn;
}

//  Return the bearing angle of a target's position (%there)
//  from an object's position (%here)
function AIPlayer::GetBearing(%this, %that)
{
  DebugPrint("%this:"@%this@"~AIPlayer::GetBearing",GetBearing,AI_Whisper);
  %here = %this.getPosition();
  %there = %that.getPosition();
  %xHere = getWord(%here,0);
  %yHere = getWord(%here,1);
  DebugPrint("xhere:"@%xHere SPC "yHere"@%yHere,GetBearing,AI_Whisper);
  %xThere = getWord(%there,0);
  %yThere = getWord(%there,1);
  DebugPrint("xThere:"@%xThere SPC "yThere"@%yThere,GetBearing,AI_Whisper);
  
  %x = %xThere - %xHere;
  DebugPrint("x:"@%x,AI_Whisper);
  %y = %yThere - %yHere;
  DebugPrint("y:"@%y,AI_Whisper);
  if (%x!=0 )
  {
    %slope = %y/%x;
     DebugPrint("slope:"@%slope,GetBearing,AI_Whisper);
    %angle=mRadToDeg(mATan(%slope,-1) );//-1 is only 2nd arg that works
    DebugPrint("Angle in:" SPC %angle,GetBearing,AI_WhisperLoud);
  }
  else
  {
    DebugPrint("vertical",GetBearing,AI_Whisper);
    %angle = 90.000;
  }
  if( (%x>=0) && (%y>=0) )    // target in quadrant 1, 0-89.999 degrees
    %adjustment = -90;
  else if( (%x>=0) && (%y<0) )  //quadrant 2,  90-179.999 degrees
    %adjustment = 270;
  else if( (%x<0) && (%y<0) ) // quadrant 3, 180-269.999 degrees
    %adjustment = 90;
  else                              //quadrant 4, 270-359.999 degrees
    %adjustment = 90+360;
  %angle += %adjustment;
  return %angle;
}

function AIPlayer::GetHeading(%this)
{
  DebugPrint("%this:"@%this@"~AIPlayer::GetHeading",GetHeading,AI_Whisper);
  %hdg = GetWord( %this.rotation,3);
  if (GetWord( %this.rotation,2)$="-1")
    %hdg = 360-%hdg;
  return %hdg;
}

function AIPlayer::GetRelativeBearing(%this,%that)
{
  DebugPrint("%this:"@%this@"~AIPlayer::GetRelativeBearing",GetRelativeBearing,AI_Whisper);
  %azimuth = %this.GetBearing(%that);
  DebugPrint("ANGLE:"@%azimuth,AI_Alert);
  
  %heading = %this.GetHeading();
  DebugPrint("HEADING:"@%heading,GetRelativeBearing,AI_Alert);
  if (%heading >= %azimuth)
  {
    %bearing = %heading - %azimuth;
    %bearing *= -1;
  }
  else
  {
    %bearing = %azimuth - %heading;
  }
  DebugPrint("BEARING:"@%bearing,GetRelativeBearing,AI_Alert);
  return %bearing;
}
#24
11/02/2005 (5:25 am)
function AIPlayer::CheckArcOfSight(%this,%that)
{
  DebugPrint("%this:"@%this@"~AIPlayer::CheckArcOfSight",CheckArcOfSight,AI_Whisper);
  DebugPrint("%that:"@%that,GetRelativeBearing,AI_Whisper);
  %relbearing = %this.GetRelativeBearing(%that);
  DebugPrint("relbearing:"@%relbearing,CheckArcOfSight,AI_Alert);
  if ( (%relbearing > -($ARC_OF_SIGHT/2)) && (%relbearing < ($ARC_OF_SIGHT/2)) ) 
    %result = true;
  else
    %result = false;
  DebugPrint("result:"@%result,CheckArcOfSight,AI_Alert);
  return %result;
}

function showDebug(%which)
{
  echo($debugSwitch[%which]);
}
function showAllDebug()
{
    echo("onStuck:"@$debugSwitch[onStuck]);
    echo("unBlock:"@$debugSwitch[unBlock]);
    echo("movePlayer:"@$debugSwitch[movePlayer]);
    echo("checkForThreat:"@$debugSwitch[checkForThreat]);
    echo("DoScan:"@$debugSwitch[DoScan]);
    echo("openFire:"@$debugSwitch[openFire]);
    echo("ceaseFire:"@$debugSwitch[ceaseFire]);
    echo("onTargetEnterLOS:"@$debugSwitch[onTargetEnterLOS]);
    echo("onTargetExitLOS:"@$debugSwitch[onTargetExitLOS]);
    echo("spawn:"@$debugSwitch[spawn]);
    echo("Equip:"@$debugSwitch[Equip]);
    echo("GetTargetRange:"@$debugSwitch[GetTargetRange]);
    echo("getClosestEnemy:"@$debugSwitch[getClosestEnemy]);
    echo("CreateBots:"@$debugSwitch[CreateBots]);
    echo("pickAISpawn:"@$debugSwitch[pickAISpawn]);
    echo("GetBearing:"@$debugSwitch[GetBearing]);
    echo("GetHeading:"@$debugSwitch[GetHeading]);
    echo("GetRelativeBearing:"@$debugSwitch[GetRelativeBearing]);
    echo("CheckArcOfSight:"@$debugSwitch[CheckArcOfSight]);
    echo("Misc:"@$debugSwitch[Misc]);
}
function clearAllDebug()
{
$debugSwitch[onStuck] = false;
$debugSwitch[unBlock] = false;
$debugSwitch[movePlayer] = false;
$debugSwitch[checkForThreat] = false;
$debugSwitch[DoScan] = false;
$debugSwitch[openFire] = false;
$debugSwitch[ceaseFire] = false;
$debugSwitch[onTargetEnterLOS] = false;
$debugSwitch[onTargetExitLOS] = false;
$debugSwitch[spawn] = false;
$debugSwitch[Equip] = false;
$debugSwitch[GetTargetRange] = false;
$debugSwitch[getClosestEnemy] = false;
$debugSwitch[CreateBots] = false;
$debugSwitch[pickAISpawn] = false;
$debugSwitch[GetBearing] = false;
$debugSwitch[GetHeading] = false;
$debugSwitch[GetRelativeBearing] = false;
$debugSwitch[CheckArcOfSight] = false;
$debugSwitch[Misc] = false;
}
function setAllDebug()
{
$debugSwitch[onStuck] = true;
$debugSwitch[unBlock] = true;
$debugSwitch[movePlayer] = true;
$debugSwitch[checkForThreat] = true;
$debugSwitch[DoScan] = true;
$debugSwitch[openFire] = true;
$debugSwitch[ceaseFire] = true;
$debugSwitch[onTargetEnterLOS] = true;
$debugSwitch[onTargetExitLOS] = true;
$debugSwitch[spawn] = true;
$debugSwitch[Equip] = true;
$debugSwitch[GetTargetRange] = true;
$debugSwitch[getClosestEnemy] = true;
$debugSwitch[CreateBots] = true;
$debugSwitch[pickAISpawn] = true;
$debugSwitch[GetBearing] = true;
$debugSwitch[GetHeading] = true;
$debugSwitch[GetRelativeBearing] = true;
$debugSwitch[CheckArcOfSight] = true;
$debugSwitch[Misc] = true;
}
#25
11/02/2005 (5:28 am)
function DebugPrint(%theMsg,%facility,%priority)
{
  if ($debugSwitch[%facility])
  {
    %theMsg = %priority@"~~~"@%theMsg;
    if (%priority $= "")
    {
      echo(%theMsg);
    }
    else
    {
      switch$ (%priority)
      {
        case "AI_Panic":
          error("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
          error("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
          error(%theMsg);
          error("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
          error("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
        case "AI_Alert":
          error(%theMsg);
        case "AI_AlertLoud":
          error("~~~~~~~~~~~~~~" SPC %theMsg SPC "~~~~~~~~~~~~~~");
        case "AI_Normal":
          echo(%theMsg);
        case "AI_NormalLoud":
          echo("~~~~~~~~~~~~~~" SPC %theMsg SPC "~~~~~~~~~~~~~~");
        case "AI_Whisper":
          warn(%theMsg);
        case "AI_WhisperLoud":
          warn("~~~~~~~~~~~~~~" SPC %theMsg SPC "~~~~~~~~~~~~~~");
        default:
          echo(%theMsg);
      }
    }
  }
}


I hvae been looking around the forums and have found that there are various ways of calling jump. What I want to know is which way should I use and how do I stop it interfering with the other actions that my NPC is supposed to perform. Currently Im using playThread near the end of the doScan function.

Thanks in advance!
#26
11/02/2005 (4:10 pm)
One sugestion...checkout aiGuard and aiPatrol resource. I learn a lot from that resources.
#27
11/02/2005 (4:18 pm)
You should do the actual jumping in the C++ code. Perhaps you could just add a ConsoleMethod to allow you to make an NPC jump from script?
#28
11/03/2005 (1:14 am)
Denis

Ive checked out aiGuard and aiPatrol (Both of these are used in the new advanced 3DGPAI1 book). The code you see above is built over the aiGuard from the book. Hence the name aiNPC.cs.

Josh

Could you please elaborate. I think what your saying is to put in a new ConsoleMethod in the aiPlayer.cc file. Thats not a problem but what do I put in it?? Do you mean something like this:

ConsoleMethod( AIPlayer, jump, void, 2, 2, "()" 
			  "Make the bot jump")
{
	if (canJump() == true)
	{
		object->jump();
	}
}

with a seperate method:

/**
 * makes the bot jump
 *
 * @param targetObject The object to target
 */
void AIPlayer::jump()
{   
   if (canJump() == true)
   {
	mActionAnimation.action = PlayerData::JumpAnim;
	setActionThread(mActionAnimation.action,mActionAnimation.forward,false,false);
   }
}

I believe this to be correct. Is it??

when I use the code

%obj.jump();

in aiNPC (several posts above) the terminal tells me

"unknown command jump."

My script isnt recognizing the new C++ code.

Thanks in advance!

:)
#29
11/03/2005 (11:59 am)
Sorry Rehan, I haven't get my hands on the second book yet ;)
#30
11/06/2005 (1:58 am)
Can anyone else help??
#31
11/06/2005 (7:43 am)
Rehan, I'd love to help you further, but I lost track of what exactly your problem was.

As a rule of thumb, as much as you keep each problem in a separate thread the more help you get. Someone who reads this thread now will read your first post to see what your problem is and if he can't help you with that he will ignore the rest.
#32
11/06/2005 (9:09 am)
Ive created a new thread detailing my problem here:

http://www.garagegames.com/mg/forums/result.thread.php?qt=36314
Page«First 1 2 Next»