Triggers

Could someone please point me to a tutorial of using triggers in panda.

… /me has nothing to point to

Triggers are an in game concept. For such things as mods they make sense but in panda3d has no clue how it should treat triggers or what the game you are making is?

Triggers can be quite different depending on RTS or FPS game. What kind of triggers are you looking for?

Hmmm… Panda3D has built-in triggers, but I never have spent time on exploring this part of Panda3D. This is from the egg syntax documentation:

It really depends on what events you want to fire a trigger. NxPanda has triggers too, and a tutorial on triggers (if you are willing to cope with experimental code).

enn0x

Well I was hoping to find tutorials on simple things such as;

Keying next to an object - removes the object from view & the object is added to an inventory

Walking to certain points on a map would trigger a camera change

Picking up certain objects would trigger a stop in a timer

etc etc

Inventory?

Walking? Map?

Picking up? Object?

Panda3d is not a game for which you write mods. Its not an rpg game this is an engine with collisions systems, terrain rendering, animation playing, and scripting systems.

In other words you would have to write those things yourself and even if some one would have written a tutorial on this it would be very very different from your needs. How do you want to represent an inventory? Map? Objects?

Panda is not a point-and-click engine, so you won’t find a button to do those sort of things, but what you ask for could easily be done if you know at least some of the basics of Python and Panda.

For example, you could probably create any trigger you want just by putting an ‘if’ statement inside a task, or something like that.

Ok, thanks a lot. I’m not really into python but know more c++. i know panda supports both but is it possible to add c++ code into a python script?
is there any special format needed when converting from python to c++ in the script? Or can I just code away?

As a group project at uni we’re assigned to make a game in 10 weeks. We’ve chosen to go along with a concept of scrap heap challenge - presenter presents a riddle - ie) “it’s smells good & often comes in a vase”…then you as the character would search the environment for a vase. Once found the vase would be added to the HUD (1st preconceived trigger format needed - when the correct object is found it needs to be removed from the environment & placed into the HUD. At this point the timer would stop (2nd preconceived trigger) & whilst searching for the item, when the character is at certain points of the map, the camera angle would auto change (3rd preconceived trigger)

there maybe more needed but these are just come of the preconceived triggers i need to work out by Tuesday. I was hoping to find some code snippets of this kind of thing & then edit them to my needs. If they need to be programmed I would be better of attempting this in c++.

Hi,

I am implementing a point-and-click game and I have implemented

triggers in my game. I have used rectangles and circles because
it is very easy to check whether a point is inside the shape.
So in every frame (in the main loop), the game checks whether the
current actor is inside a trigger defined either by a rectangle or a circle.
For that, it uses the actor coordinates. It uses only (x,y) because it is a
2d shape.
When, the point is inside, you must execute the function or action you
want. I have defined a Trigger class with a condition method and an
action method. Every scene has a list of Triggers that is check in every
frame. You only have to derivate the base class for concrete triggers.
I hope this help.
Regards.
Alberto

thanks ajfg that does help. Would it be possible to post ant code snippets? So i can see where u implemented the loop / how the areas are defined etc…if you do that it would be very helpful

also…

Ok, thanks a lot. I’m not really into python but know more c++. i know panda supports both but is it possible to add c++ code into a python script?
is there any special format needed when converting from python to c++ in the script? Or can I just code away?

thanks for your time

Hi,

I cannot put all the code because it is very big. However, I can put some snippets.
You need to create a Rectangle and/or Circle class with a 'IsInto' method. Check whether a point (in 2D - x,y) is inside a Rectangle or Circle it is trivial. For example, for Rectangles:
def isInto(self, position):
        if not isinstance(position, Point3):
            raise "Position parameter is not of Point3 type"

        if (self.data[0] <= position.getX() and position.getX() < self.data[2]) \
            and (self.data[1] <= position.getY() and position.getY() < self.data[3]):
            return True
        else:
            return False
For creating triggers, I use Blender as world editor. It allows me to create Python code directly. This is a snippet of a room.
tri_rec_bridge1 = TriggerRectangle(5368, 'tri_rec_bridge1')
tri_rec_bridge1.setRectangle(-21.596169, 10.659777, -16.012001, 12.598551)
tri_rec_bridge1.setFunctionName('function2')
tri_rec_bridge1.setNumberTimes(2)
tri_rec_bridge1.activate()
self.addTrigger(tri_rec_bridge1)

In your case, probably, you must do it by hand. I would create the scene in a 3D software and will note the coordinates of the Rectangles or the centre and radius in the case of Circles. Then, I would insert them in your C++ code.
This is my trigger class:

# Panda 3d
from pandac.PandaModules             import *

# Class base for all triggers
from base               import Base
from constants          import *
from rectangle          import Rectangle
from logger             import defaultLogger
from polygon            import Polygon

from game_globals       import globals
from function_manager   import *

NO_TYPE               = 0
CONDITIONAL_TRIGGER   = 1
UNCONDITIONAL_TRIGGER = 2

class Trigger(Base):
    def __init__(self, triggerId, triggerName):
        Base.__init__(self, triggerId, triggerName)
        
        # Is the trigger active?
        self.active   = False
        
        # This flag indicates whether there is a transition between
        # condition not met and met. It will avoid to execute multiple times
        # the action when after the condition is met for first time 
        self.firstConditionMet = False
        
        # This number indicates how many times the condition can be met or 
        # executed
        # -1 = infinite
        # 0 .. inf = number times
        # By default, the trigger will be executed an infinite number of times
        self.numTimes = -1


    # If the condition is 'true' then the action is executed
    # It receives the number of miliseconds passed from the last frame
    def update(self, timeElapsed = 0):
        #defaultLogger.log("Inicio trigger: %s Activo: %s" % (self.name, self.active) )
        
        # If it is not active, then nothing to do
        if not self.active:
            return
        
        # If the condition is met, it will check the number of times the
        # action must be executed
        if self.condition(timeElapsed) == True:
            # The condition is already met and is not the first time
            if self.firstConditionMet == True:
                return
            
            # The condition is met
            self.firstConditionMet = True
            
            #defaultLogger.log("Num. times: %d" % (self.numTimes))
            
            # Check number of times
            if self.numTimes == -1:
                # If infinite, execute the action
                self.action()

            else:
                if self.numTimes > 0:
                    # Execute the action
                    self.action()
                        
                    # Update the counter
                    self.numTimes -= 1
                    
                    # If it reaches the maximum number of times, the trigger
                    # will be deactivated
                    if self.numTimes == 0:
                        self.active = False                
        
        else:
            # The condition is not met
            self.firstConditionMet = False

    # - ABSTRACT METHODS -----------------------------------------
    def condition(self, timeElapsed = 0):
        return False


    def action(self):
        return
    
    
    # - PUBLIC METHODS -----------------------------------------   
    def isActive(self):
        return self.active


    def activate(self):
        self.active = True


    def deactivate(self):
        self.active = False
        
        
    def setNumberTimes(self, newNumTimes):
        self.numTimes = newNumTimes
        return
    
    
    def getNumberTimes(self):
        return self.numTimes
    
    
    def decNumberTimes(self):
        if self.numTimes > 1:
            self.numTimes -= 1
        


"""
This class defines a trigger based on a 3d mesh. When the actor will collide
with it, Panda will generate an event. It will be captured by this class and
processed.
Depending on the result of a function the player will be able to pass across
the mesh or not. Thus the trigger is conditional or unconditi-onal.

Unconditional triggers cannot be crossed under any condition.
Conditional, they can be crossed depending on the result of the related
function.
The function will return True or False.
"""
class Trigger3D(Trigger):
    def __init__(self, triggerId, triggerName):
        Trigger.__init__(self, triggerId, triggerName)
    
        self.functionName     = ""
        self.condFunctionName = ""
        
        # Collision model
        self.modelName    = ""
        self.model        = None
        
        # Collision variables
        self.colNode      = None
        self.colNp        = None
        self.handler      = None
        self.cTrav        = None 
        
        # 0 = No type, 1 = Conditional, 2 = Unconditional
        self.type         = NO_TYPE
    
    
    def setModelName(self, newName):
        if newName == None or newName == "":
            defaultLogger.log("Trigger 3D: %s Empty model name" % (self.name))
            return False
        
        self.modelName = newName
        
        # Re-set the collision mechanism
        self.initCollision()
        return True
    
    
    # 1 = Conditional, 2 = Unconditional
    def setType(self, newType):
        self.type = newType
    
    
    def setFunctionName(self, newName):
        if newName == None or newName == "":
            defaultLogger.log("Trigger 3D: %s Empty function name" % (self.name))
            return False
        
        self.functionName = newName
        return True
    
    
    def setCondFunctionName(self, newName):
        if newName == None or newName == "":
            defaultLogger.log("Trigger 3D: %s Empty conditiion function name" % (self.name))
            return False
        
        self.condFunctionName = newName
        return True
    
    
    def initCollision(self):
        if self.model == None:
            # Load the model
            self.model = loader.loadModel(self.modelName)
            if self.model == None:
                raise 'Error reading the model %s of trigger %s' % \
                      (self.modelName, self.name)
        
        # Set the collision masks
        self.colNode = CollisionNode(self.name)
        self.colNp   = self.model.attachNewNode(self.colNode)
        
        self.colNode.setFromCollideMask(BitMask32.bit(TRIGGER_BITMASK))
        self.colNode.setIntoCollideMask(BitMask32.allOff())

        # Create the handler
        self.handler = CollisionHandlerQueue()
        
        # Create collision traverser
        self.cTrav = CollisionTraverser()
        
        self.cTrav.addCollider(self.colNp, self.handler)
        
        #Uncomment this line to show a visual representation of the 
        #collisions occuring
        #self.cTrav.showCollisions(render)
        
        # Temporary
        self.model.reparentTo(hidden)

    
    # - TRIGGER METHODS ---------------------------------------------
    # The condition will be verified taking into account the collision with
    # the barrier
    def condition(self, timeElapsed = 0):
        # Verify the collision
        self.cTrav.traverse(hidden)
            
        # Has the actor collide with the model?
        if self.handler.getNumEntries() > 0:
            # Verify the type
            if self.type == UNCONDITIONAL_TRIGGER:
                # Send a stop event to current actor
                globals.currentActor.stop()
                
                # To execute or not the action. ???
                return True
                
            elif self.type == CONDITIONAL_TRIGGER:
                # Execute the function and check the result
                # The condition is met can traverse the barrier
                if functionManager.execute(self.condFunctionName):
                    return True
                
            else:
                # No type do nothing
                return False
                
        return False
    
    
    def action(self):
        if self.functionName != "":
            functionManager.execute(self.functionName)

"""
This class defines a trigger that depends on a 2D area, defined as a set
of data points (x,y). Only 2 dimensions (x,y).
When the actor enters into the area the action defined by the function name
will be executed.
"""
class Trigger2D(Trigger):
    def __init__(self, triggerId, triggerName):
        Trigger.__init__(self, triggerId, triggerName)
        
        self.functionName = ""
        
        # It defines the area
        self.polygon = Polygon()
        return
    
    
    def setMeshData(self, newDataX, newDataY):
        if newDataX == None or newDataY == None:
            defaultLogger.log("Trigger 2D: %s Empty mesh data" % (self.name))
            return False
        
        self.polygon.setData(newDataX, newDataY)
        return True
    
    
    def setFunctionName(self, newName):
        if newName == None or newName == "":
            defaultLogger.log("Trigger 2D: %s Empty function name" % (self.name))
            return False
        
        self.functionName = newName
        return True
    
    
    # - TRIGGER METHODS ---------------------------------------------
    def condition(self, timeElapsed = 0):
        return self.polygon.isInto(globals.currentActor.getPosition())
    
    
    def action(self):
        if self.functionName != "":
            functionManager.execute(self.functionName)
    
    
"""
This class defines a trigger that depends on a 2D area, a rectangle. 
Only 2 dimensions (x,y).
When the actor enters into the area the action defined by the function name
will be executed.
"""
class TriggerRectangle(Trigger):
    def __init__(self, triggerId, triggerName):
        Trigger.__init__(self, triggerId, triggerName)
        
        self.functionName = ""
        
        # Rectangle
        self.rectangle = Rectangle()
        
        return
    
    def setFunctionName(self, newName):
        if newName == None or newName == "":
            defaultLogger.log("Trigger Rec: %s Empty function name" % (self.name))
            return False
        
        self.functionName = newName
        return True
    
    def setRectangle(self, x1, y1, x2, y2):
        self.rectangle.set(x1, y1, x2, y2)
        return True


    def __str__(self):
        # Function name
        # Times
        # Active
        return "Trigger Rectangle: %s  Position: %f,%f,%f" % (self.rectangle)
    
    
    def condition(self, timeElapsed = 0):
        return self.rectangle.isInto(globals.currentActor.getPosition())
    
    
    def action(self):
        if self.functionName != "":
            functionManager.execute(self.functionName)




# Tests
if __name__ == "__main__":     
    t1 = Trigger(1, 'trigger1')
    
    print "Active: ", t1.isActive()
    t1.activate()
    print "Active: (T)", t1.isActive()
    t1.deactivate()
    print "Active: (F)", t1.isActive()
    
    print "Num. times: ", t1.getNumberTimes()
    t1.setNumberTimes(2)
    t1.decNumberTimes()
    print "Num. times: (1)", t1.getNumberTimes()
    
    t1.activate()
    t1.setNumberTimes(2)
    print "Num. times: %d Active: %s" % (t1.getNumberTimes(), t1.isActive())
    t1.update()
    print "Num. times: %d Active: %s" % (t1.getNumberTimes(), t1.isActive())
    t1.update()
    print "Num. times: %d Active: %s" % (t1.getNumberTimes(), t1.isActive())
    t1.update()
    print "Num. times: %d Active: %s" % (t1.getNumberTimes(), t1.isActive())
    
    t2 = TriggerBarrier(2, "prueba")
    
    t3 = TriggerMesh(3, "prueba 1")
Excuse me, if there is some comment in non-perfect English.
Basically, a trigger is composed of a condition and an action. The action is executed when the condition is meet. Condition can be whatever you want. In my case, it is an actor entering a shape but it can be a flag passing from inactive to active.
Finally, you should have a main loop with at least three main functions; handle inputs from user, update everything and draw.

You have to put the triggers check in the update method.
As I am doing an adventure game, I have a set of rooms and each room has a list of triggers. Then, in the update method of a room, it runs:

# It receives the number of miliseconds passed since last frame
    def update(self, timeElapsed = 0):
        # Verify time passed
        ##       print "Delta: "
        ##       print timeElapsed
       
        # Verify triggers, only if the actor is walking
        if self.triggersActive:
            if globals.currentActor != None:
                if globals.currentActor.isMoving():
                    dictionary = self.triggerList.getItems()
                    for i in dictionary:
                        # Check the trigger
                        i[1].update(timeElapsed)
        
        # Verify objecs
        dictionary = self.objManager.getItems()        
        for i in dictionary:
            i[1].update(timeElapsed)

As you can see, it updates the triggers and the objects.
I hope this help.
Regards.
Alberto

I can’t thank you enough

This will help tremendously with what we are trying to accomplish. Thank youuuuuuuuu =)