PYTHON: Comparing rotation of two objects

Rather than using the radar sensor, which as you know, effects performance quite a lot, I decided for my games AI to use two objects:

One is an empty, which always faces the player, and triggers with a ray sensor.
A script then compares the rotation of the empty to the rotation of the enemy.
Depending on the angle between them it does different things.

So I know that you get an objects rotation with

obj.localOrientation

or:

obj.worldOrientation

Seeing as the one is parented to the other you could probably ignore the rotation of the enemy itself.
But, for each enemy you have to find the ID of the aimer empty. (because there are multiple enemies added with add object actuator, and so would have the same name)

So I suppose this is two questions:

  1. How do you get an objects unique ID (child of current object)
  2. How to compare the rotations in an “if then” statement

Thanks

I suggest to calculate the vector between the enemy and the player. You can calculate the angle between this vector and the forward vector of the enemy.

Mathutils.Vector provides you the necessary methods.

I’ve hit another problem, which is that the rotation is in a 3x3 matrix, but the way I was going to do it, with:

 angle = mathutils.Vector.angle(rot, rot2)

Requires the rotation to be a vector.
How do you convert a rotation to a vector. Am I even going about this the right way?

create a matrix from the orientation (in 2.5 it should be a matrix already)

multiply the matrix with a vector of your choice:
x = Vector((1,0,0)); -x = Vector((-1,0,0))
y = Vector((0,1,0)); -y = Vector((0,-1,0))
z = Vector((0,0,1)); -z = Vector((0,0,-1))

After the multiplication you get the orientation vector along the local vector you choosed (e.g. along x)

KX_GameObject.getAxisVect() might also be of some use to you. If you consider +y to be forward, and if player is your player, then the player’s forward vector can be had with:

forward_vec = player.getAxisVec((0, 1, 0))

I was testing this out, and it worked. It’s only 2D detection because it just compares only the Z axes’ rotations. Though if you want to compare all axes of rotation you’d probably want to go with what Monster was saying.

ori test.blend (83.1 KB)

Now I have a working copy of a script, but I can forsee a problem.

Firstly, here is the script:

#############################################
#           AI for game Defender            #
#     Written by sdfgeoff (and others)      #
#############################################

#Setup...
import bge
import GameLogic
import mathutils

#Shortening names
cont = bge.logic.getCurrentController()
obj = cont.owner

#Getting list of objects in this scene
allObj = GameLogic.getCurrentScene()
aimer = allObj.objects["Aimer"]
shootControl = cont.sensors["Ray"]

aim_vec = aimer.getAxisVect((0, 1, 0))
enemy_vec = obj.getAxisVect((0, 1, 0))

#print (aim_vec)
#print (enemy_vec)

angle = mathutils.Vector.angle(aim_vec, enemy_vec)
print (angle)

if angle <= 2:
    print ("I see you")
    cont.activate ("trackTo")
    
    if angle <= 0.1:
        print ("Time to die!!!")
        if shootControl.positive: 
           cont.activate ("shoot")

else:
    print ("Are you there?")
    cont.deactivate ("trackTo")
    cont.deactivate ("shoot")

But say I duplicate the object aimer and so forth, then you have to find a different aimer for every instance.
I’m assuming I will have to find the ID of the object, but how can I do that?
They are linked by parenting, and by a logic brick.

If you want to have a general code avoid the use of object names within the script as much as you can
… NO …
just do not do that!

It makes the code very, very, very inflexible. Especially if the required name is hidden in a bunch of code lines (as in your case).

If you really need it that way, define the string at the top of your code. Add some comments, that the reader can easily identify it as requirement.
e.g.:


AimerName = "Aimer" # requires an object with this name
...
aimer = allObj.objects[AimerName]

So it might be better to define the name in a property of the owner. Which should be setup at the top of the code too:


# mandatory configuration properties
PaimerName = "aimer" # the name of the aimer object (the object must exist)
...
aimerName = own[PaimerName]
aimer = allObj.objects[AimerName]

With that you can setup for each object that executes this code with an individual “Aimer’s name” in a string property “aimer”.

This is flexible.

But it can be more efficient.
You can cache the already found object reference:


# mandatory configuration properties
PaimerName = "aimer" # the name of the aimer object (must exist)
 
#internal properties
Paimer = "_aimer"
...
aimer = getByNameCached(own, Paimer, PaimerName)
 
...
def getByNameCached(own, key, nameProperty):
  '''
  own = owner
  key = lookup key of the cached object
  nameProperty= property to find the object name if it is not cached
  '''
  obj = own.get(key)
  if obj is not None:
    return obj 
 
  obj = getByName(own, nameProperty)
  own[key] = obj
  return obj
 
def getByName(own, nameProperty):
  '''
  own = owner
  nameProperty= property to find the object name if it is not cached
  '''
  objName = own[nameProperty]
  obj = allObj.objects[objName]
  return obj
 

That means only the first access is looking for the object (inperformant). All further accesses are simple property reading (much more efficient).

I guess from your post that you have the aim object as child of the code owner?
Than you have much better chances with:

own.children

You just need to know what children to look for. Use an property to mark the object as aimer. It does not matter what value or type it has. If the property “aimer” is present the object is marked as an aimer. Avoid multiple aimers at one object. Only one will be taken:


...
# optional configuration properties
PisAimer = "aimer" # marks a child of owner as aimer
 
#internal properties
Paimer = "_aimer"
...
aimer = getChildWithPropCached(own, Paimer, PisAimer)
 
...
def getChildWithPropCached(own, key, indicatorProperty)
  '''
  own = owner
  key = lookup key of the cached object
  indicatorProperty= property to identify an object
  '''
  obj = own.get(key)
  if obj is not None:
    return obj 
 
  obj = getChildWithProp(own, indicatorProperty)
  own[key] = obj
  return obj
 
def getChildWithProp(own, indicatorProperty):
  '''
  own = owner
  indicatorProperty= property to find the object name if it is not cached
  ''' 
  for child in own.children:
    if indicatorProperty in child:
      return child
 

Rather to look through all objects, this looks through the children only. The found gameObject will be cached.

I wrote down the code from scratch. It is untested und can contain errors.

I hope you get the idea

Thanks all. Working well now, with multiple objects all working perfectly.
I did manage to simplify your function a bit though.

######################################################
#                                                    #
#                AI Control Script                   #
#                                                    #
#Written by sdfgeoff (with help from others) for game#
#                project "Defender"                  #
#                                                    #
#   If you use this script please keep this header   #
######################################################
#  AI script, finding if an enemy can see the player #
######################################################

#Setup...
import bge
import mathutils

#Shortening names
cont = bge.logic.getCurrentController()
obj = cont.owner

#Getting gun control
shootControl = cont.sensors["Ray"]

#getting a list of all children to the enemy, and finding one with the property "aimer"
aimerProp = "Aimer"
def getAimer(aimerProp):
    '''Get the aimer attached to this enemy'''
    for child in obj.children:
        if aimerProp in child:
            return child

#Getting Aimer
aimer = getAimer(aimerProp)


#Getting the rotations of the aimer and enemy
aim_vec = aimer.getAxisVect((0, 1, 0))
enemy_vec = obj.getAxisVect((0, 1, 0))

#print (aim_vec)
#print (enemy_vec)

#Get angle
angle = mathutils.Vector.angle(aim_vec, enemy_vec)
print (angle)



#Face him and kill him if you can see him
if angle <= 1.9:
    print ("I see you")
    cont.activate ("trackTo")
    
    if angle <= 0.1:
        print ("Time to die!!!")
        if shootControl.positive: 
           cont.activate ("shoot")

else:
    print ("Are you there?")
    cont.deactivate ("trackTo")
    cont.deactivate ("shoot")

Now all I have to do is get the enemy to move and so on…