Options Menu -> Controls / Key Setup

Well, first off, hello to everybody, and speaking of which, everybody knows the famous “Control Setup” found in most games, accessible via the main menu of the game, and the options menu.
So, i’m trying to design an efficient key mapping system, that would store global variables named as the function of the key, e. g. ‘Gas’, that stores the custom key itself, e. g. ‘W’ or ‘UpArrow’. This would ensure an essential part of gameplay customizing, by the player himself.

I’ve built a script that detects what key is being pressed, at a given time, that would be, when the player selects ‘Gas’ in the controls list, for example. But, i don’t know if it’s best to simply store this values in a text file, to be read later on. or just create Global Variables, that would be used in all game scripts for all actions. Or whatever is cooler.

So, the more ideas i get to build something like this conveniently, the better, any ideas are welcome.
Also, i’m still a beginner at coding, so, providing with information as to what code would work better/faster, or if it’s just nicer to use logic bricks, would be welcome.

Many thanks in advance.

Thales

You can take a look at this config setup script I made awhile ago. It could use a bit of improvement (for example right now it will only write the default configuration string), but it’ll give you a good idea of how to make a config file. Feel free to use and modify it.

import bge

CONF = bge.logic.expandPath('//').split('Scripts')[0] + '\config.cfg' # config file path+name


# Default config file
DEFAULT = \
'[Controls]
' \
'CamUp = WKEY
' \
'CamDown = SKEY
' \
'CamLeft = AKEY
' \
'CamRight = DKEY
' \
'Left = LEFTARROWKEY
' \
'Right = RIGHTARROWKEY
' \
'Up = UPARROWKEY
' \
'Down = DOWNARROWKEY
' \
'LeftClick = LEFTMOUSE
' \
'[End]

' \
'[Gameplay Settings]
' \
'CameraSensitivity = .003
' \
'CameraSmoothing = .8
' \
'[End]'


# Define key dictionary
KEYS = { \
'LEFTMOUSE': bge.events.LEFTMOUSE, \
'MIDDLEMOUSE': bge.events.MIDDLEMOUSE, \
'RIGHTMOUSE': bge.events.RIGHTMOUSE, \
'WHEELUPMOUSE': bge.events.WHEELUPMOUSE, \
'WHEELDOWNMOUSE': bge.events.WHEELDOWNMOUSE, \
'MOUSEX': bge.events.MOUSEX, \
'MOUSEY': bge.events.MOUSEY, \
'AKEY': bge.events.AKEY, \
'BKEY': bge.events.BKEY, \
'CKEY': bge.events.CKEY, \
'DKEY': bge.events.DKEY, \
'EKEY': bge.events.EKEY, \
'FKEY': bge.events.FKEY, \
'GKEY': bge.events.GKEY, \
'HKEY': bge.events.HKEY, \
'IKEY': bge.events.IKEY, \
'JKEY': bge.events.JKEY, \
'KKEY': bge.events.KKEY, \
'LKEY': bge.events.LKEY, \
'MKEY': bge.events.MKEY, \
'NKEY': bge.events.NKEY, \
'OKEY': bge.events.OKEY, \
'PKEY': bge.events.PKEY, \
'QKEY': bge.events.QKEY, \
'RKEY': bge.events.RKEY, \
'SKEY': bge.events.SKEY, \
'TKEY': bge.events.TKEY, \
'UKEY': bge.events.UKEY, \
'VKEY': bge.events.VKEY, \
'WKEY': bge.events.WKEY, \
'XKEY': bge.events.XKEY, \
'YKEY': bge.events.YKEY, \
'ZKEY': bge.events.ZKEY, \
'ZEROKEY': bge.events.ZEROKEY, \
'ONEKEY': bge.events.ONEKEY, \
'TWOKEY': bge.events.TWOKEY, \
'THREEKEY': bge.events.THREEKEY, \
'FOURKEY': bge.events.FOURKEY, \
'FIVEKEY': bge.events.FIVEKEY, \
'SIXKEY': bge.events.SIXKEY, \
'SEVENKEY': bge.events.SEVENKEY, \
'EIGHTKEY': bge.events.EIGHTKEY, \
'NINEKEY': bge.events.NINEKEY, \
'CAPSLOCKKEY': bge.events.CAPSLOCKKEY, \
'LEFTCTRLKEY': bge.events.LEFTCTRLKEY, \
'LEFTALTKEY': bge.events.LEFTALTKEY, \
'RIGHTALTKEY': bge.events.RIGHTALTKEY, \
'RIGHTCTRLKEY': bge.events.RIGHTCTRLKEY, \
'RIGHTSHIFTKEY': bge.events.RIGHTSHIFTKEY, \
'LEFTSHIFTKEY': bge.events.LEFTSHIFTKEY, \
'LEFTARROWKEY': bge.events.LEFTARROWKEY, \
'DOWNARROWKEY': bge.events.DOWNARROWKEY, \
'RIGHTARROWKEY': bge.events.RIGHTARROWKEY, \
'UPARROWKEY': bge.events.UPARROWKEY, \
'PAD0': bge.events.PAD0, \
'PAD1': bge.events.PAD1, \
'PAD2': bge.events.PAD2, \
'PAD3': bge.events.PAD3, \
'PAD4': bge.events.PAD4, \
'PAD5': bge.events.PAD5, \
'PAD6': bge.events.PAD6, \
'PAD7': bge.events.PAD7, \
'PAD8': bge.events.PAD8, \
'PAD9': bge.events.PAD9, \
'PADPERIOD': bge.events.PADPERIOD, \
'PADSLASHKEY': bge.events.PADSLASHKEY, \
'PADASTERKEY': bge.events.PADASTERKEY, \
'PADMINUS': bge.events.PADMINUS, \
'PADENTER': bge.events.PADENTER, \
'PADPLUSKEY': bge.events.PADPLUSKEY, \
'F1KEY': bge.events.F1KEY, \
'F2KEY': bge.events.F2KEY, \
'F3KEY': bge.events.F3KEY, \
'F4KEY': bge.events.F4KEY, \
'F5KEY': bge.events.F5KEY, \
'F6KEY': bge.events.F6KEY, \
'F7KEY': bge.events.F7KEY, \
'F8KEY': bge.events.F8KEY, \
'F9KEY': bge.events.F9KEY, \
'F10KEY': bge.events.F10KEY, \
'F11KEY': bge.events.F11KEY, \
'F12KEY': bge.events.F12KEY, \
'F13KEY': bge.events.F13KEY, \
'F14KEY': bge.events.F14KEY, \
'F15KEY': bge.events.F15KEY, \
'F16KEY': bge.events.F16KEY, \
'F17KEY': bge.events.F17KEY, \
'F18KEY': bge.events.F18KEY, \
'F19KEY': bge.events.F19KEY, \
'ACCENTGRAVEKEY': bge.events.ACCENTGRAVEKEY, \
'BACKSLASHKEY': bge.events.BACKSLASHKEY, \
'BACKSPACEKEY': bge.events.BACKSPACEKEY, \
'COMMAKEY': bge.events.COMMAKEY, \
'DELKEY': bge.events.DELKEY, \
'ENDKEY': bge.events.ENDKEY, \
'EQUALKEY': bge.events.EQUALKEY, \
'ESCKEY': bge.events.ESCKEY, \
'HOMEKEY': bge.events.HOMEKEY, \
'INSERTKEY': bge.events.INSERTKEY, \
'LEFTBRACKETKEY': bge.events.LEFTBRACKETKEY, \
'LINEFEEDKEY': bge.events.LINEFEEDKEY, \
'MINUSKEY': bge.events.MINUSKEY, \
'PAGEDOWNKEY': bge.events.PAGEDOWNKEY, \
'PAGEUPKEY': bge.events.PAGEUPKEY, \
'PAUSEKEY': bge.events.PAUSEKEY, \
'PERIODKEY': bge.events.PERIODKEY, \
'QUOTEKEY': bge.events.QUOTEKEY, \
'RIGHTBRACKETKEY': bge.events.RIGHTBRACKETKEY, \
'ENTERKEY': bge.events.ENTERKEY, \
'SEMICOLONKEY': bge.events.SEMICOLONKEY, \
'SLASHKEY': bge.events.SLASHKEY, \
'SPACEKEY': bge.events.SPACEKEY, \
'TABKEY': bge.events.TABKEY, \
}


# open and read from config file
# create it if it does not exist
def ReadFile(path=CONF):
    try:
        file = open(bge.logic.expandPath(path), 'r')
        print("Loading Successful!
Reading from Config...")
        return(SetConfig(file))
    except IOError: # failed to load - create new config
        print("Loading Failed!")
        print("Creating New Config...")
        return(SetConfig(MakeConfig()))


# create the default config file and read from it      
def MakeConfig(path=CONF):
    file = open(bge.logic.expandPath(path), 'w')
    file.write(DEFAULT)
    file.close()
    file = open(bge.logic.expandPath(path), 'r')
    return(file)
    #float()
    #int()
    
# create configuration dictionary
def SetConfig(file):
    dic = {}
    
    # controls loop
    s = file.readline().rstrip()
    while s != '[Controls]':
        s = file.readline().rstrip()
    s = file.readline().rstrip()
    while s != '[End]':
        s = str.split(s, ' = ')
        try:
            dic[s[0]] = KEYS[s[1]]
        except KeyError:
            print('Warning:', s[1], 'not found in key dictionary')
        s = file.readline().rstrip()
    
    # gameplay settings loop
    while s != '[Gameplay Settings]':
        s = file.readline().rstrip()
    s = file.readline().rstrip()
    while s != '[End]':
        s = str.split(s, ' = ')
        dic[s[0]] = id_type(s[1])
        s = file.readline().rstrip()
    file.close()
    return(dic)


# Identify what type a string should be cast as and return that object
def id_type(s):
    
    # booleans
    if s == 'True':
        return True
    elif s == 'False':
        return False
    
    # numbers
    if '.' not in s:
        try:
            n = int(s)
            return n
        except ValueError:
            pass
    try:
        n = float(s)
        return n
    except ValueError:
        pass

Thanls man, that is really useful, what i still don’t know, is how to make the game read from that file as the game starts and use those variables as keyboard events.

All you need to do is have an empty with a Python logic brick that runs a script or function that calls the ReadFile() method. ReadFile() returns a dictionary that contains all the settings and controls from the config file. You can save this dictionary as a property on the empty, another object, or in the bge global dictionary.

Got it! Thank you very much Mobious, may your gaming development be always well succeeded.

You can simply check all game objects for keyboard sensors. Then use the name of the sensor as display value (or key to an according label). You can grab the current key from the keyboard sensor as well.

(Remark: This requires that you do not hide the keyboard input in Python code)

Well i didn’t quite understood what you meant, but, bascially, i made a script that detects what key is being pressed, for instance, i go to options menu, controls, click on ‘Forward’, a pop-up screen appears, written: Press a Key, i press W, automatically, the string WKEY is written in a cfg file containing the variable ‘Forward’ with this value.

What you describe is good to as keyboard mapping editor (A UI to enter changed keys). But you still need a way to find out the purpose of a key. AKEY is not a the meaning it is a sort of label of the key (to be honest it is only correct on US keyboards).
You need the purpose to tell the user what the key is used for.

e.g. “move forward”, “jump”, “crouch”

You want to map this to the pressed key:

“move forward”: UP_ARROW_KEY
“jump”: SPACE_KEY

As this purpose is inside the logic, it is very easy to read the keyboard sensors - assuming the meaning is the name of the sensor (which is a good idea anyway).

E.g.
Keyboard sensor “move forward” Key: [W] –> whatever logic
Keyboard sensor “jump” Key: [space] –> whatever logic

To establish the new keys, you just reconfigure the sensors. This is pretty easy to do.

Just an idea

I thought about some algorithm like this:

-Load the cfg file, reading all it contents and puting them in the globaldict
here, variables will be created like:
forward = bge.events.AKEY (just an example)

-Now, this variable will be used to perform an action
if forward == ACTIVE
walk forward

something like this, everything through python coding

This will work if you have the evaluation of keyboard events at one central place.

The keyboard sensors do that already and come with a nice GUI. If you use Python code you need to create your own keyboard event -> actions system.

In either way, loading a configuration file is exactly the same (file-> mapping data)
Restoring the read data is pretty similar.

yeah, but how to change the key defined in the sensor, through an ingame menu?

You “scan” through all active game objects. When it has an keyboard sensor, you can read its name and its current key setup (max. three concurrent keys). The nice thing is this works with any game that uses keyboard sensors. So you can “plug” it into other games too.

To change the key(s) you simply assign new values to the keyboard sensor.

This is pretty easy. The most work is to create an appropriate gui for the player.

Scanning through all the game objects and than checking all sensors has a complexity of O(n^2) which can propbably get pretty intensive when having many objects. (you could use list comprehension to reduce the amount of objects to scan through).
In my opinion, putting all keyboard input in one central location, is much more efficient. When changing a key, you only need to change one little bit.

It is no square afford it is linear O(n) with n as number of sensors within the active scenes.

You check or reconfigure sensors just one time when needed. A need for that is pretty low. I guess an average player changes the keyboard settings once regularly (atstartup to load the saved mapping)and additional one or two times during a game. While loading the gui a player usually does not playe the game w hich will hide any possible one tome delays.

Finally you need really much gameObjects/sensors to slow down this processing. It is more likely to get performance problems during normal game play in this situation.

As I mentioned above, the keyboard sensors are access to a central keyboard handler. It uses compiled native code rather than Python code (which finally interacts with the same processing but adds a little overhead - just a little)

Tell me Monster, what kind of code can i use to assign a new value to a sensor key?
for example, i have configured W as the key to be pressed in my sensor, but as i reconfigured it ingame, how will the new key be assigned to the sensor?

If you look at the BGE API (remember this link) you will see there are two kind of attributes:

A) Configuration attributes
B) Status attributes

While status attributes provide you the result of the last evaluation, the configuration properties provide you with the current settings of the logic brick. This means you can read and write the parameters as set via GUI.

If you follow the API to the Keyboard sensor you can see there are three different settings for keys:

key, hold1, hod2 - they represent the GUI fields: Key, First Modifier, Second Modifier

The names are a bit misleading. All three contain a keycode. The order does not matter. If no keycode is set the field it is simply ignored.
Why are there three of them?
This is simply an implicit AND concatenation. The sensor evaluates True if all configured keys are pressed.

Be aware these are keycodes. They do not necessarily match the character printed on the key itself.

I hope this helps you.
Monster

Monster, i’m sorry, but i still don’t get it, how will i change the defined key in the sensor, ingame?
The key is set to say, W, ingame i want to change it to uparrow, how would you change the key mapping in the keyboard sensor, during game execution?

You trigger a python controller.

The python code grabs the keyboard sensor.
Then it sets new values (keycodes) to the attributes key, hold1 and hold2. Usually hold1 and hold2 are simply set to None.
You can find the available key codes at the BGE API.

Or if you want to by flexible you can let the user enter a new key code via another keyboard sensor (set to all keys).
Example:

targetKeyboardSensor.key = sourceKeyboardSensor.key [edit: this is wrong --> the event needs to be analysed see post#20]

The controller does not need to be connected to the sensor it configures (just to the source sensor). This allows the creation of a “keyboard sensor configurator”.

Yeah, but i tried something like that, defining the ‘key’ attribute through a python script, it simply doesn’t work, nothing is ever assigned to the keyboard sensor. that is odd.

i tried assigning the event, tried assigning the keycode, nothing is assigned to key attribute.

My fault…
… it is not supposed to read the configuration from the source sensor.
Better read the current status of the source sensor and set the configuration at the target sensor. See Demo File

Be aware when you need to read multiple key presses the logic (code) will gets more complex.


    pressedKeys = getJustPressedKeyCodes(sourceSensor)
    targetSensor.key = pressedKeys[0]  # assuming just one keypress at the time


Attachments

KeyBoardSensorSetter.blend (319 KB)