Restructuring Player Script With Classes, and Def's

Before I get started, I’m not entirely new to scripting. I’ve been doing it for a year now, but I’m not too experienced with it. With that said, I’ll proceed…

As most of my titles imply, I’m restructuring my player’s script. Since I have like 5 different scripts for various functions, such as walking, jumping, crouching, and proning. Which makes it difficult to correlate the scripts, and incorporate transitions.

I also realized it would be easier to define actions (Walking forward, backward, left, right, jumping, etc), and then execute with the main function. Like if the up arrow key is pressed, it will execute the walking forward function (or def).

Now I realized you can do classes and definitions. Except, I have no clue how to make them correlate, or how they function. So as you can tell, I’m struggling with this new concept. (Before you plan on directing me somewhere, I’ve been in plenty of places, best that I have you guys assist me.)

So far, I have this, which isn’t much:



import bge

cont = bge.logic.getCurrentController()
own = cont.owner


keyboard = bge.logic.keyboard
mouse = bge.logic.mouse
scene = bge.logic.getCurrentScene()


UPARROWKEY = keyboard.events[bge.events.UPARROWKEY]
DOWNARROWKEY = keyboard.events[bge.events.DOWNARROWKEY]


key_none = bge.logic.KX_INPUT_NONE
key_tap = bge.logic.KX_INPUT_JUST_ACTIVATED
key_active = bge.logic.KX_INPUT_ACTIVE 


class Walking:
    
    def __init__(self, Foward, Backward):
        
        self.Foward = Foward
        self.Backward = Backward
    
    def Forward(self):
        
        own.applyMovement((0, 1, 0), True)
        
def main():

    if UPARROWKEY == key_active:
       
       Walking(self, Walking.Foward)


As again, I’m not too sure on how this works. Any form of assistance is appreciated.

I like using modules instead of running the whole script at once (even though this still technically does, more on that later). This way, I can split different actions into different methods, and keep them all organized on a per-script basis. You don’t even need a main() method, this just cherry-picks particular methods to run.

You run scripts modularly by choosing “module” from the dropdown in the Python controller, and manually typing in the module name. (The script name needs to end in .py for this to work.) For instance, I have a “player” script, which holds all of the functions and methods pertaining to controlling the player object. If I want to run the “walking()” method, I’d type “player.walking” into the module field. Now, that controller will only run that one section of that script.

(I think there are some tech-side downsides to this approach, but I’m not knowledgable enough about them to go into detail.)

Also, when writing systems that rely on player input, it’s REALLY important to think about dynamic modularity (IE “allowing the player to change keybinds”). I fixed this by using an external file that I load into any script that relies on player input, and reading that for the player’s stored keybinds.

Okay, let me try to put this all into an example format, in case I’m not making sense. I want my player to walk. But later, I’ll want my player to do other things as well, such as jump, take damage, use a weapon, etc. So I make a new script file and call it “player.py”. I write it like this:


## This is all global definition stuff that I'd want to use in every module.
import bge
import json
key = bge.logic.keyboard
mouse = bge.logic.mouse
sce = bge.logic.getCurrentScene()
obl = sce.objects

## This is where I load that external keybinds file, which I named "controls.json". 
filePath = bge.logic.expandPath("//files/")
ctrl = json.load(open(filePath+"controls.json", "r"))


def walk(cont):
## I use "cont" as the call tag here because the script's controller is always sent to the method as a variable. "cont" is the current controller.
    
    own = cont.owner
    sen = cont.sensors
    act = cont.actuators
    
    ## And of course, these are the particular keybinds. This is a script for a first-person game, so I'll be setting my controls up to strafe left and right.
    key_fw = key.events[getattr(bge.events, ctrl['fw'])] == 2
    key_bk = key.events[getattr(bge.events, ctrl['bk'])] == 2
    key_lf = key.events[getattr(bge.events, ctrl['lf'])] == 2
    key_rt = key.events[getattr(bge.events, ctrl['rt'])] == 2
    
    ## Everything from here to the end is just stuff to make the walking work.
    act_walk = act['walk']
    var_speed = 0.12
    var_x = 0.0
    var_y = 0.0
    
    # Get inputs
    if own['active']:
        if key_fw or key_bk:
            if key_fw:
                var_y = var_speed
            elif key_bk:
                var_y = -var_speed
        if key_lf or key_rt:
            if key_lf:
                var_x = -var_speed
            elif key_rt:
                var_x = var_speed
    
    # Start walking
    ## Oh yeah, I have a character motion actuator attached to this script. The object's physics type is "character" so the "character motion" type will work here. I named that actuator "walk", by the way.
    act_walk.dLoc = [var_x, var_y, 0]
    cont.activate(act_walk)
    
    


I plug everything in that’s necessary, such as the “always” sensor and the “active” Boolean property set to true, and it should work. If I want to add anything new to this script, I can just jot it into the method here. If I want to add a new function to the player, such as shooting or entering a vehicle, I’d just add another method, like this:


def enter_vehicle(cont):
    pass

I’m sure other people have much better systems than I do. I kind of… really suck at making games, despite having been using Blender for like 7 and a half years.

For classes, there might be a lot to read, but I recommend doing tests on the side.

Basically the idea around classes is to have instances, and when you use the BGE adopting a OOP model is a bit weird given the way you apply your classes on the objects:
https://docs.blender.org/api/blender_python_api_current/bge.types.KX_GameObject.html#bge.types.KX_GameObject

Basically it mutate the living object. Like replacing the old KX_GameObject type by your new type.
Problem is that you now need to make sure you mutate yourself every object you want to use your class on.

But as said by MichelleSea, you can use module functions, like


def myFunction(controller):
    doThing()

You can then either play with module variables, or gameobject properties, or you can indeed try to play with type mutation, although it will need a bit more thoughts to make it work :slight_smile:

So is there a way to make sub-definitions? Like to make the walking definition, but also specific ones like walk-forward, backward, etc…

Kind of like this, but the proper way:



def Walking(cont or self?):

    def WalkingForward(cont):
       
    #Animation for moving

    #Movement actuator

    def WalkingBackward(cont):
       
        #Animation for moving

        #Movement actuator

   def WalkingKeys(cont):

       if UPARROW == key_active:
       
           WalkingForward(cont)
 

So it’s probable that I don’t utilize classes, just definitions?

unless you want to enter a whole new world of pain, then stay clear from classes. classes need planning and a good hierarchy to be worth the effort.

Haha, thanks. You know what? Thanks for the warning, didn’t see the sign until now. Your input was much appreciated.

You COULD do it that way. But it would be kind of inefficient, since you’re making a bunch of module calls for no reason. I use different modules for the sake of organizing specific actions and events, and only call away when I want to do something singularly complex or that I want to do in other methods without copying the code (such as parsing display text to include linebreaks and capitalization). If-statement spaghetti is fine, as long as you’re commenting your code to remind you of what specific lines do. xD

Ahaha, thanks for your input!

So you’re suggesting it should look like this then?



def WalkingForward():
       
    #Animation for moving

    #Movement actuator


def WalkingBackward():
       
     #Animation for moving

     #Movement actuator


def WalkingKeys():

    if UPARROW == key_active:
       
       WalkingForward()

I’m more suggesting something like this:


def walking(cont):

     key_fw = your forward key
     key_bk = your backward key
     move_actuator = cont.actuators['NameOfYourMovementActuator']
     walk_y = 0

     if key_fw:
          walk_y = 0.2
     if key_bk:
          walk_y = -0.2

     move_actuator.dLoc = [0, walk_y, 0]
     cont.activate(move_actuator)

One method to control everything that you’d call “walking”.

Ah, okay. Except I’ll try to contain the animation and movement within the script. Since I can control variables, such as speed.

So if I wanted to add different functions, such as crouching, proning, and jumping. Is it suggested that I do this:

#Walking
def walking(cont):


     key_fw = your forward key
     key_bk = your backward key
     move_actuator = cont.actuators['NameOfYourMovementActuator']
     walk_y = 0


     if key_fw:
          walk_y = 0.2
     if key_bk:
          walk_y = -0.2


     move_actuator.dLoc = [0, walk_y, 0]
     cont.activate(move_actuator)

#Crouching
def crouching(cont):


     key_fw = your forward key
     key_bk = your backward key
     move_actuator = cont.actuators['NameOfYourMovementActuator']
     walk_y = 0


     if key_fw:
          crouch_y = 0.2
     if key_bk:
          crouch_y = -0.2


    #Movement
    #Animation

#Proning
def crawling(cont):


     key_fw = your forward key
     key_bk = your backward key
     move_actuator = cont.actuators['NameOfYourMovementActuator']
     walk_y = 0


     if key_fw:
          crawl_y = 0.2
     if key_bk:
          crawl_y = -0.2


     # Animation
     # Movement



How do you suppose I control which one it functions on? I’m trying to keep them contained so I can easily incorporate transitions between the states. Should I develop another script, or a def function for that?

Also, I have a clue on how to set it to alternate to other positions, so don’t’ worry about that. Haha, excuse my excessive questions, just want to ensure that this operates correctly.

make a state system? or a system that runs a function when you press the right key?


def player_movement(cont):


    key_fw = key.events[getattr(bge.logic.events, ctrl['fw'])] == 2
    key_bk = key.events[getattr(bge.logic.events, ctrl['bk'])] == 2
    key_lf = key.events[getattr(bge.logic.events, ctrl['lf'])] == 2
    key_rt = key.events[getattr(bge.logic.events, ctrl['rt'])] == 2
    
    if key_fw:
        walk_fw()
    elif key_bk:
        walk_bk()
    elif key_lf:
        strafe_lf()
    elif key_rt:
        strafe_rt()
    else:
        idle()


def idle(): 
    #your stuff to do...here.
    
def walk_fw():
    #your stuff to do...here.
         
def walk_bk():
    #your stuff to do...here.  
    
def strafe_lf():  
    #your stuff to do...here.
    
def strafe_rt():  
    #your stuff to do...here.

and if you add a run option, you can do something like this:


def player_movement(cont):




    key_fw = key.events[getattr(bge.logic.events, ctrl['fw'])] == 2
    key_bk = key.events[getattr(bge.logic.events, ctrl['bk'])] == 2
    key_lf = key.events[getattr(bge.logic.events, ctrl['lf'])] == 2
    key_rt = key.events[getattr(bge.logic.events, ctrl['rt'])] == 2
    
    run = key.events[getattr(bge.logic.events, ctrl['run'])] == 2
    
    if key_fw:
        
        if run:
            run_fw()
        else:
            walk_fw()
               
    elif key_bk:
        
        if run:
            run_bk()
        else:
            walk_bk()
            
    elif key_lf:
        strafe_lf()
        
    elif key_rt:
        strafe_rt()
        
    else:
        idle()




def idle(): 
    #your stuff to do...here.
    
def walk_fw():
    #your stuff to do...here.


def run_fw():
    #your stuff to do...here.   
         
def walk_bk():
    #your stuff to do...here.


def run_bk():
    #your stuff to do...here.       
    
def strafe_lf():  
    #your stuff to do...here.
    
def strafe_rt():  
    #your stuff to do...here.

But i like to work with a property that keeps track of what i am doing, so i can play the right animations as well, from within the script, or from a new script.

Three copies of the same code seems like a waste of good space to me. If walk_y is equal to property instead of a static value, you can change it on the fly as you transition between states. Not having to repeat lines over and over sounds like a plan.

I like to have the last state always active and put my main controller there. I ran the armature entirely through python until just recently, it’s not much fun. Too much work, results are more or less the same as with using different action actuators in each state. Dynamically setting all animation values requires way too many variables, so in that case I feel using a more static approach is much easier. I do have a module that automatically sets frame length after I pass a string with an action name to it though, really happy with it.

Also changing states through code I’m usually down with. Only problem is an object’s state has to be expressed in bitmask, which is tricky. Because 1 is state one, 2 is 2, but 3 is 1 and 2, then 4 is state 3. Afterwards, 23 is 4, 24 is 5, and so forth until 229 equals state 30. What to do, just add them together: so 23 + 24 + 229 equals states 4, 5 and 30 are active. I’d rather not to bother with bricks when I can more easily read and write to cont.owner.state, but to each their own.

Cheers.



def Animate(data):
    Animate_Stuff()

def Move(data):
    MoveStuff

def Teleport(data):
     teleport_stuff()

def add_damage_sensor(data):
   add_damage_sensor()


functionDictionary = { "add_damage_sensor":add_damage_sensor, "Teleport":Teleport, "Move":Move, Animate_Stuff() }


def main():
    if 'Actstrip' not in agent:
        act_on_inputs()

    elif len(agent['ActStrip'])>1:
         for functionData in agent['ActStrip'][0]:
             function = functionDictionary[functionData[0]]
             function(functionData[1])
main()

you can go a little less abstracted and call states instead of a bunch of functions,

Excuse my late reply here, but I think I got the gist of this now. Thank you all for your help!

Here’s what I have so far. Yes, it’s incomplete, but either way, it could benefit someone in need.


import bge


scene = bge.logic.getCurrentScene()
cont = bge.logic.getCurrentController()
own = cont.owner


keyboard = bge.logic.keyboard


#Keys


UPARROWKEY = keyboard.events[bge.events.UPARROWKEY]
DOWNARROWKEY = keyboard.events[bge.events.DOWNARROWKEY]
LEFTARROWKEY = keyboard.events[bge.events.LEFTARROWKEY]
RIGHTARROWKEY = keyboard.events[bge.events.RIGHTARROWKEY]
SPACEKEY = keyboard.events[bge.events.SPACEKEY]
LEFTSHIFTKEY = keyboard.events[bge.events.LEFTSHIFTKEY]


#Key Function Presets


if UPARROWKEY or DOWNARROWKEY or RIGHTARROWKEY or LEFTARROWKEY == key_active:
    
    Movement = True


else:
    
    Movement = False
    
#Key Actautors


key_none = bge.logic.KX_INPUT_NONE
key_tap = bge.logic.KX_INPUT_JUST_ACTIVATED
key_active = bge.logic.KX_INPUT_ACTIVE 


##Double Tap to Run


delay = 10
cool = 10
walkingSpeed = 1


if UPARROWKEY == key_tap:
    own["TAP"] += 1
    if own["TAP"] >= 2:
        own["TAP"] = 2
        own["TIMER"] = 0
        own["RUN"] = True
        
#Double Tap Timer


    if own["TAP"] == 1:
    	own["TIMER"] += 1
    	if own["TIMER"] > delay:
    		own["TIMER"] = 0
    		own["TAP"] = 0


elif UPARROWKEY == key_none:
    
    if own["TAP"] == 2:
        own["TIMER"] -= 1


        if own["TIMER"] < -cool:
            own["TAP"] = 0
            own["TIMER"] = 0
            own["RUN"] = False


def idle():
    
    if own["Standing"] == True:
        
        own.playAction("PlayerIdle", 0, 50, 2, 0, 5, 1)
        
    
    if own["Armed"] == False:


        own.playAction("PlayerArmsIdle", 0, 50, 1, 2, 5, 1)
    
def walking():
    
    #Actuators


    if UPARROWKEY == key_active:
        
        own.applyMovement((0, -0.09*walkingSpeed, 0), True)
        
        own.playAction("PlayerWalking", 1, 9, 2, 0, 5, 0, 0, 0, walkingSpeed, 1) 
        
        if own["Armed"] == False:
            
            own.playAction("PlayerWalkingArms", 1, 9, 1, 2, 5, 0, 0, 0, walkingSpeed, 1)
    
def main():
    
    if own["Standing"] == True:
        
        if UPARROWKEY == key_active:
            
            walking()
        
        else:
            
            idle()
            
main()

Hi Marco.
I’m trying to do the same thing, only on enemies.
Question: how are you getting the armature to playAction on the owner? I get an error.
“own has no attribute playAction” or something like that (not at my gaming PC ATM)
instead. . . I use sce = bge.logic.getCurrentScene()
Something like . . . arm = sce.objects(“Armature”)
then arm.playAction(“walking”, 1, 20, etc. etc. )
(Can’t remember exactly)
It works, but, Am I doing it wrong?

It works, but, Am I doing it wrong?

No you are doing it the right way.

He has probably set the armature as owner, that is why it works for him (running the script directly from the armature)

Thanks Cotaks, very helpful, as usual.:slight_smile: