Wrectified - control proxy discussion

In Wrectified I am using a control proxy to control actors or vehicles etc.

A ‘Controller’ converts joystick or keyboard presses into generic commands
example joystick up, dpad up, and keyboard arrow up, and w could all send ‘Forward’ command.

A list of these keypress commands is generated each frame per controller and piped to a targeted actor or vehicle.

this does a number of things.

  • Distributed complexity and organisation.
  • Actor control logic uses common input to have unique behaviors.
  • AI systems may send these low level commands (‘forward’ etc) to target actors
  • Actors/vehicles can be added in the future without recoding any control logic.(even shared between projects)
  • Easy keymapping
  • Easily have multiple players on 1 pc
  • Potentially a ‘master server’ type multiplayer host server for games

Currently my next ‘level up’ for the design is to use a dictionary and call a function based on the input inside the actor, removing my old nested elif statments.

so in a car ‘Forward’ can be a accelerator but for a player it means walk forward,
and all the control logic is then isolated into nice little independent functions.

Problems = if people don’t understand a system they won’t use it, so the system adds ‘intellectual overhead’

can anyone think of ways of improving this idea?

controller(generate list/lists of keypresses)—(actor calls function based on state and lists)

actors also use a common method to interact with props (weapons etc)

Use the components in the upbge.That is my two cents.

Simply convert the “input event” (e.g. keyboard press) into messages.

The logic used to listen to the keyboard sensor then listens to a message sensor.

No Python code necessary.

Before:

Object Player:

Keyboard <w> --> AND --> Motion Actuator

After:

Object Keyboard.input:

Keyboard <w> --> AND --> Message “player move forward”

Object Player:

Message “player move forward” --> AND --> Motion Actuator

Benefits:

  • any number of input objects
  • input objects maps input to unified “requests” (messages) -> mapping
  • can reside in any active scene
  • easy to dynamically add/delete
  • allows any input: keyboard, mouse, touchscreen, network (Python), serial (Python), joystick, “AI” …

Drawback:

  • one frame delay due to the message transfer duration

I like doing all of the above in code (whatever language that might be).

I use a “Controller” object, where I do, for example.

controller.buttonPress("move_forward");

to check if the “move_forward” button has been pressed. That button has been mapped behind the scenes to maybe multiple types of input.
This is closer to what you described, but I don’t use any if statements since I only use two types of inputs.

Using dictionaries only really works well when you have a one-to-one mapping scheme. Meaning one type of input maps to one type of output. In other words, “Arrow Key Up is Move Up”, “Arrow Key Down is Move Down” are one-to-one - you can’t combine these with “Analogue Stick Vector Direction is Move in Direction of Vector” because they use different logic. So, you’d need “Analogue Stick Vector Up is Move Up”.

When you have a one-to-one mapping scheme, then you can use dictionaries.
You can do one of two things:

  1. You can have a “type” dictionary for finding a dictionary with the inputs. So input[Keyboard] would get you the dictionary with Keyboard input types. Now you can do, input[Keyboard][UpArrow] to get the action for that combination of type and input.

  2. You can use the type and input as integers (or enums), concat them and then add + search in a dictionary. For example, you can do, input[concat((int)Keyboard, (int)ArrowUp)] to get the action for that combination of type and input. You can concat integers (or enum values) by bit-shifting (you’d store the concat integer in a long). Then you’d have one dictionary that you can search things through, rather than two.

intial controller movement input gathering code (missing joystick input calls still)

import bge



def main():


    cont = bge.logic.getCurrentController()
    own = cont.owner
    keyboard = cont.sensors['Keyboard']
    
    #local dictionary of functions to call movement keys
    dict = { }
    dict.update({'WKEY':"Forward"})
    dict.update({'UPARROWKEY':"Forward"})
    
    dict.update({'AKEY':"Left"})
    dict.update({'LEFTARROWKEY':"Left"})
    
    dict.update({'SKEY':"Back"})
    dict.update({'DOWNARROWKEY':"Back"})
    
    dict.update({'DKEY':"Right"})
    dict.update({'RIGHTARROWKEY':"Right"})
    
    
    
    
    
    
    
    list = []
    #Deisgned to be combined with joystick inputs later
    for event in keyboard.inputs:
        #print(bge.events.EventToString(event[0]))
        try:
            event = dict[bge.events.EventToString(event[0])]
            if event not in list:
                list.append(event)
        except:
            pass    
    
    
    
    #feed data to target and debug
    own['Read']=str(list) 
    if 'TargetOB' not in own:
        own['TargetOB']=own.scene.objects[own['Target']]
    if len(list)&gt;0:    
        own['TargetOB']['Move']=list
        own['TargetOB']['Run']=True
    if keyboard.positive:
        own['Run']=True
main()



here is the initial actor controller


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


def WalkForward():
    own.applyMovement((.1,0,0),1)
    
def WalkForwardLeft():
    own.applyMovement((.1,0,0),1)
    own.applyRotation((0,0,.025),1)
    
def WalkForwardRight():
    own.applyMovement((.1,0,0),1)
    own.applyRotation((0,0,-.025),1)
    
def WalkBackLeft():
    own.applyMovement((-.1,0,0),1)
    own.applyRotation((0,0,.025),1) 
             
def WalkBackRight():
    own.applyMovement((-.1,0,0),1)
    own.applyRotation((0,0,-.025),1)        
        
def WalkLeft():
    own.applyRotation((0,0,.025),1)
    
def WalkRight():
    own.applyRotation((0,0,-.025),1)
    
def WalkBack():
    own.applyMovement((-.1,0,0),1)    






dict = {}
dict.update({"['Forward']":WalkForward})
dict.update({"['Forward', 'Left']":WalkForwardLeft}) 
dict.update({"['Left', 'Forward']":WalkForwardLeft})
dict.update({"['Right', 'Forward']":WalkForwardRight})
dict.update({"['Back', 'Right']":WalkBackRight})
dict.update({"['Right', 'Back']":WalkBackRight})
dict.update({"['Back', 'Left']":WalkBackLeft})
dict.update({"['Left', 'Back']":WalkBackLeft})
dict.update({"['Left']":WalkLeft})
dict.update({"['Back']":WalkBack})
dict.update({"['Right']":WalkRight})






def main():


    cont = bge.logic.getCurrentController()
    own = cont.owner
    try:
        call = dict[str(own['Move'])]
        call()
        
    except:
        pass    
    
                
        
    own['Move']=""
    own['Run']=False    


main()



Attachments

Wrectified_Actor_Control_Proxy_V4.blend (460 KB)

This is a discussion, isn’t it?

Here is my input:

Why do you create that many dictionaries? It seams you just need a single dictionary.


dict = { 
"['Forward']":WalkForward,
"['Forward', 'Left']":WalkForwardLeft,
"['Left', 'Forward']":WalkForwardLeft,
"['Right', 'Forward']":WalkForwardRight,
"['Back', 'Right']":WalkBackRight,
"['Right', 'Back']":WalkBackRight,
"['Back', 'Left']":WalkBackLeft,
"['Left', 'Back']":WalkBackLeft,
"['Left']":WalkLeft,
"['Back']":WalkBack,
"['Right']":WalkRight,
}

Even that looks necessary. Why not use reflection. This way you do not even need the dictionary:


locals()[str(own['Move'])]()

(yes own[‘Move’] should contain “WalkForward”, “WalkForwardLeft”…)

@BluePrintRandom
This will not scale well if you need a new function for every button combinations, an integral example here will be to call applyMovement/Rotation function once only for all accumulated button combinations, eg. pseudo


def move():
    speed = 0.1
    forward_backward = 0
    left_right = 0
    z_axis = 0

    if walk_forward:
        forward_backward = speed
    elif walk_backward:
        forwad_backward = -speed 

    if walk_right:
        left_right = speed
    elif walk_left:
        left_right = -speed

    if turn_left:
        z_axis = speed
    elif turn_right:
        z_axis = -speed

    obj.applyMovement((forward_backward,left_right,0),True)
    obj.applyRotation((0,0,z_axis),True)

one dictionary is for keymapping

w, uparrow and joystick up can all call forward*

the other dictionary is for calling functions local to each actor.

only 1 function is ever called,

and this won’t only be controlling people, it will also control vehicles and stationary puzzles like robot arms etc.

it’s designed so you can code the actor without recoding any controls.

It doesn’t make any sense to call


data = {}
data.update({k: v})
data.update({k: v})
data.update({k: v})

Instead, just use


data = {k1: v1, k2: v2, ...}

it’s for read ability and adding on later

anything over x characters in a row jumbles things up.

Typically you do the mapping with a sensor.

The purpose of the sensor is to measure “up request” rather then “up key” as the source of the request can be something else than a key. That is why I recommend to name the sensor for it’s purpose rather then the implementation.

Example:

I often see:

Keyboard sensor “w” listen on key “w”.

Clearly this sensor listens to key <w> (obvious as it is configured that way). But thy is the sensor checking this key (and no other)? in other words what is the purpose of the sensor.

In your case it might be -> “the user wants to let the character move forward”. Pretty long sentence but should tell the intention. Why not create a shorter name that at least expresses the intention. Do not mention <w> as this is implementation detail. So why not call it: “move forward”.

The result is a sensor called “move forward”.

Now the important part. From now on it does not matter if that is a keyboard sensor or something else. As soon as it evaluates positive, you know the “user requested the character to move forward”. The mapping from user input to request is already done <w> -> “walk forward”. The controllers/actuators can act accordingly.

You can even replace the sensor with a complete different sensor type. For example I often replace a keyboard sensor with a message sensor. This way many other objects can “feed” the sensor (keyboard, joystick, touch screen …).

But you should be aware some sensors needs post processing, such as mouse sensors, near sensors etc… This sensors are not so good to replace, but with the message method it is no big deal either: mouse key sensor + mouse over any sensor -> message actuators = touch screen to request mapping.

Your code can finally focus on the current business without to worry keyboard input joystick axis validation as this is isolated and already processed.

Your code will become incredible simple:


isWalkForward = sensors["walk forward"].positive

if isWalkForward:
   walkForward()

you can decide if you want to treat multiple inputs as multiple requests (move forward, move left), which can be handled in combination:


isWalkForward = sensors["walk forward"].positive
isWalkLeft = sensors["walk left"].positive

if isWalkForward:
    isWalkLeft:
        walkForwardLeft()
    else:
        walkForward()

or you treat them as single requests(move forward and left):


isWalkForward = sensors["walk forward"].positive
isWalkForwardLeft = sensors["walk forward left"].positive

if isWalkForward:
    walkForwardLeft()
if isWalkForwardLeft:
    walkForward()

.

It is up to you to decide what option fits best in your situation.

control is in one object, and pipes commands to targeted game object and then marks the object to run.

this reduces complexity and it is quite fast

each actor gets it’s own dictionary of functions to call for actor portability

(I intend for people to be able to import a actor, import a controller, target the intial actor and be ready to go)

the game object converts the keypresses to a list of strings

the actor converts the list to a string and uses the string to call the function.

basically it should be setup to be flexible without being overly complex. Q = switch actors


data = {}
data['x'] = ...
data['y'] = ...

Is far more readable than


data = {}
data.update({'x': ...})
data.update({'y': ...})

Why you need strings why not call the desired functions directly?

I can’t see the flexibility right now. Everything in one file sounds very static.

Where is the dynamic processing? How do you process the input from mouse or joystick? How can you switch between different input devices? How do you setup alternative keys? How do you reconfigure input mapping (other key on a command)? Why do you not use the keyboard keys as provided by bge.events?

the actor contains the function dictionary

meaning targeting a different actor will call different functions.

@BluePrintRandom
See, your actors are calling different function for each key, eg.WalkForward(),WalkForwardLeft()… imagine 1 day, you realise you needed to add something to the “movement” control, what happen is you will need to synchronously modify ALL of the functions for each key combined, now instead of modifying 1 function, you have to repeat N times for N functions, that kind of workflow will not scale as your game continues to grow and uses more key combinations

That’s what I was trying to point out as a problem, all keys should be sufficiently evaluated by just 1 movement() function instead, whether the actor is a car or birdplane, should be handle by polymorphism

eg.


cancel_gravity = False
speed = 0.1

if actor is car:
    speed = 10
elif actor is birdplane:
    speed = 100
    cancel_gravity = True

the thing is actions are split that way, not movement control input.

if ‘Forward’ in own[‘Move’]:

do stuff* works just fine :slight_smile:

it all depends on what you are doing
and you can do it that way usingthe same set-up.

(instead of calling dict[str(own[‘Move’])] you do if ‘Forward’ in own[‘Move’]: )

I use the movement’s split from actions at the moment so one can walk, aim and shoot/reload etc independently of motion

if 'Action_1' in own['Act'] and 'Shift' in own['Act']:
    do_stuff()

the dictionary is just faster then an if tree*

Just have a class with one dictionary. The keys are inputs; so keys could be something like (keyboard, w) or (joystick, button 0). The values are the commands; (move up) or (jump) for example.

The dictionary would look something like this:


dict = {
     (keyboard, w) : MoveUp,
     (joystick, stick up) : MoveUp,
     (keyboard, space) : Jump,
     (joystick, button 0) : Jump,
     .....
}

Then, when you get an input you look up the command in the dictionary.

Another way of doing it (though a tiny bit slower), is to have the dictionary the other way around:


dict = {
     MoveUp : [(keyboard, w), (joystick, stick up)],
     Jump : [(keyboard, space), (joystick, button 0)],
     .....
}

With this way you “poll” the status of the keys (by iterating through the list of inputs) whenever you want to check a command status.

With this class, you can map things easily as well


Controller.map(Keyboard, W, MoveUp) # maps Keyboard W to Move Up

def map(type, code, command):
     dict[(type, code)] = command

# or

def map(type, code, command):
     dict[command] += (type, code)

and polling commands is easy:


# pretend status gets the "overall" status of the command (JustPressed, HeldDown, Up, JustReleased)
def poll(type, code):
     return status(dict[(type, code)])

def poll(command):
     return status(dict[command])

Obviously there are more things to work out, but the latter version is simple to implement and fairly easy to expand.

Lucracious check this out-

I have 2 actor types using 2 scripts that are different

the controller does not care who it’s targeting or what code is in them

the actor does not care whom feeds it input (AI ? P1? P2? )

This way we can all code in our own style, using the common input so that it is possible to swap actors
(even though those actors controllers may be unique)

Attachments

Wrectified_Actor_Control_Proxy_V4.A.blend (495 KB)

I understand, you already have something very similar to what I’ve suggested (from what I can tell at least).

But using functions is weird, use “enums”.


class Action:
     MoveForward = 1,
     MoveBackward = 2,
     .....

Instead of a function call, you can do:


if controller.status(Action.MoveForward) == Active:
     # do whatever

This is better than using functions because what if MoveForward does more than one thing on the same object? i.e. say Jump “jumps”, but also Jump “talks to NPCs”. The way you’re setting this up actually makes it harder to do this because you need to bundle everything into the MoveForward function - this limits flexibility, which is, from what I understand, what you’re trying to attain with this.

Edit:
You’re tying inputs to actions, rather than inputs to signals. You want to tie inputs to signals so others can do whatever they want with the signals.