Search Results

Saturday, May 21, 2011

Python in the BGE - Part 8 - AI

Hey. So here's part 8 of the MazeCrawl tutorial for enemy AI. We won't be covering too much in this one - it's pretty simple.
So, we have a Player who can shoot bullets at an enemy to destroy him, and should the Player touch the Enemy, the game restarts. Now, then - let's make the enemy a little more assertive.

We'll modify the code to make the enemy move towards the Player, making him more of a threat. It's not too hard to do, at all, actually. While the enemy isn't very difficult to make, he demonstrates a few good concepts that can be reused in many ways. So, let's take a look at the Enemy's source code.
 
from bge import logic
from bge import events

from math import * 
from mathutils import *

def Enemy():

 cont = logic.getCurrentController()
 sce = logic.getCurrentScene()
 obj = cont.owner
 
 bulletcol = cont.sensors['BulletCol']
 
 player = sce.objects['Player']
 
 def Init():
 
  if not 'init' in obj:
  
   obj['init'] = 1
   obj['hp'] = 3     # Set your HP at the beginning
   obj['target'] = player   # Set your target to default to the Player
   
   obj['movespd'] = 0.1
 
 def Update():
 
  if bulletcol.positive:
   obj['hp'] -= 1     # Decrement your HP one
   bullet = bulletcol.hitObject # Get the bullet you collided with
   bullet.endObject()    # And destroy it
   
  tp = obj['target'].position
  op = obj.position
  
  a = atan2(op.y - tp.y, op.x - tp.x) + (pi * 0.5) 
 
# Get the angle between the enemy 
# and the target (defaults to the player), and add half a rotation to it (line above)
  
  o = Euler([0, 0, a]) 
 
# Create a simple Euler angle representing the rotation of the Enemy (line above)
 
  obj.worldOrientation = o 
 
# And set the enemy's orientation to the angle (face the target; line above)
  
  obj.applyMovement([0, obj['movespd'], 0], 1)  # Move towards the Player
  
  if obj['hp'] <= 0:     # If you have 0 HP
   obj.endObject()     # Then destroy yourself
   
 Init()
 Update()

As you can see, there's not too much change here. First, we import everything from the mathutils and math modules. This way, we don't have to type so much. After this, we go right into the Enemy function definition.

player = sce.objects['Player']

After this, we get the player as a variable reference. After this, in the Init() function, we set the enemy's target to default to the Player. Also, we define a new movespd variable that designates how fast the enemy will move toward the Player. The biggest change is really only in this portion in the Update() function:

tp = obj['target'].position
op = obj.position
  
a = atan2(op.y - tp.y, op.x - tp.x) + (pi * 0.5)
  
o = Euler([0, 0, a])        # Create a simple Euler angle
obj.worldOrientation = o       # And set it to the angle
  
obj.applyMovement([0, obj['movespd'], 0], 1)
 
As you can see, we get the target's position and the Enemy object's position, as well. If you recall your high-school geometry, a full rotation is formed (in radians) by 2 pi. So, half a rotation is pi, and a quarter of a rotation, or a 90-degree turn, is half of pi. Also, to get the angle from one point to another, we can use the arc tangent function ( atan2() ). By subtracting the target's (Player's) position from the Enemy's position, we can get the angle from the Enemy to the target. We get the angle from the Enemy to the Player, and then rotate it by half of pi to make it face the correct direction.

Once we have the correct angle, we create a new angle with the Euler() function - it's really mathutils.Euler(), but since we imported all from the mathutils module, we don't have to write that part. We set it to rotate on the Z-axis, so that he faces the Player. Finally, we apply a motion on the local axis, toward the direction the Enemy is facing (toward the Player). That's it - the Enemy will now track the Player around until one of them gets it. >:D

Anyway, that's it. Download Part 8 of the MazeCrawl source code here.

Mirror 1: MediaFire
Mirror 2: Box.net

7 comments:

  1. Nice post, as usual.

    I've got a question (or perhaps request) related to the bullet collision with the enemy. Suppose I want to include a door or similar object that the player activates by standing nearby and pressing a button or switch. Not knowing how to do that myself, I can see one way to kludge it by adopting what you've done here, but I know there's an easier way to do this.

    In this tutorial, and the last one, your enemy has a Collision sensor added in the logic blocks, which only activates if it's hit by a bullet. I could do something similar -- add a Door to the scene with a collision sensor for bullets, or the character's hand, and then implement the hand as a bullet that only travels for a fraction of a Blender Unit, and so on -- but obviously this is much more complicated than it should be.

    So long story short, could you give us any tips on interacting with other game objects like that? (Now that I think of it, this could be a way to implement melee combat as well!)

    ReplyDelete
  2. Well, if you wanted to implement a door, I would go with a simple ray (either in Python, or using the logic brick) or Radar sensor (which is a bit heavier) going forward from the Player looking for an 'interact' property. If it finds it, get the hit object and set its 'interact' property to 1. Then, have the door do whatever it needs to when the property is one.

    Now, for a melee combat type of game, I probably would go with your 'split-second spawn' idea - when you press the attack button, it plays an animation for the Player and creates a 'hit' cube / object that only sticks around for a couple of frames. If an enemy gets hit, then he was within range and within the timeframe, and then you can proceed from there.

    ReplyDelete
  3. your final dosn't work. the enemy just goes straight ahead

    ReplyDelete
  4. That's odd. I just tested it out. Are you using Blender 2.5? Is there an error in the console?

    ReplyDelete
  5. I change the ambient color and added some mist and after the enemy touches you and the game restarts, the mist and ambient light go away. Any idea?

    ReplyDelete
  6. Hello!

    I had problems trying to download part 7 and part 8 on Mediafire : never ending "Preparing Download" button...
    Is there any other place where i could find the source files?

    Thanks.

    ReplyDelete
  7. @Jesse Werner - I'm sorry about not getting back to you until now - I'm not sure why I missed your comment. Restarting the game is restarting the blend file, so ensure you save the blend file before you play to have restarting it work. :)

    @Sythu - Uploading alternate mirrors now...

    ReplyDelete