mirror of
https://github.com/panda3d/panda3d.git
synced 2025-10-01 01:07:51 -04:00
samples: fixes and improvements to gamepad and mappingGUI sample
This commit is contained in:
parent
85752b06f5
commit
c4b3b558c9
@ -9,21 +9,51 @@ move the camera where the right stick will rotate the camera.
|
||||
|
||||
from direct.showbase.ShowBase import ShowBase
|
||||
from panda3d.core import TextNode, InputDevice, loadPrcFileData, Vec3
|
||||
from panda3d.core import TextPropertiesManager
|
||||
from direct.gui.OnscreenText import OnscreenText
|
||||
|
||||
loadPrcFileData("", "notify-level-device debug")
|
||||
loadPrcFileData("", """
|
||||
default-fov 60
|
||||
notify-level-device debug
|
||||
""")
|
||||
|
||||
# Informational text in the bottom-left corner. We use the special \5
|
||||
# character to embed an image representing the gamepad buttons.
|
||||
INFO_TEXT = """Move \5lstick\5 to strafe, \5rstick\5 to turn
|
||||
Press \5ltrigger\5 and \5rtrigger\5 to go down/up
|
||||
Press \5face_x\5 to reset camera"""
|
||||
|
||||
|
||||
class App(ShowBase):
|
||||
def __init__(self):
|
||||
ShowBase.__init__(self)
|
||||
# print all events sent through the messenger
|
||||
self.messenger.toggleVerbose()
|
||||
# Print all events sent through the messenger
|
||||
#self.messenger.toggleVerbose()
|
||||
|
||||
# Load the graphics for the gamepad buttons and register them, so that
|
||||
# we can embed them in our information text.
|
||||
graphics = loader.loadModel("models/xbone-icons.egg")
|
||||
mgr = TextPropertiesManager.getGlobalPtr()
|
||||
for name in ["face_a", "face_b", "face_x", "face_y", "ltrigger", "rtrigger", "lstick", "rstick"]:
|
||||
graphic = graphics.find("**/" + name)
|
||||
graphic.setScale(1.5)
|
||||
mgr.setGraphic(name, graphic)
|
||||
graphic.setZ(-0.5)
|
||||
|
||||
# Show the informational text in the corner.
|
||||
self.lblInfo = OnscreenText(
|
||||
parent = self.a2dBottomLeft,
|
||||
pos = (0.1, 0.3),
|
||||
fg = (1, 1, 1, 1),
|
||||
bg = (0.2, 0.2, 0.2, 0.9),
|
||||
align = TextNode.A_left,
|
||||
text = INFO_TEXT)
|
||||
self.lblInfo.textNode.setCardAsMargin(0.5, 0.5, 0.5, 0.2)
|
||||
|
||||
self.lblWarning = OnscreenText(
|
||||
text = "No devices found",
|
||||
fg=(1,0,0,1),
|
||||
scale = .25)
|
||||
self.lblWarning.hide()
|
||||
|
||||
self.lblAction = OnscreenText(
|
||||
text = "Action",
|
||||
@ -31,99 +61,133 @@ class App(ShowBase):
|
||||
scale = .15)
|
||||
self.lblAction.hide()
|
||||
|
||||
self.checkDevices()
|
||||
# Is there a gamepad connected?
|
||||
self.gamepad = None
|
||||
devices = self.devices.getDevices(InputDevice.DC_gamepad)
|
||||
if devices:
|
||||
self.connect(devices[0])
|
||||
|
||||
# Accept device dis-/connection events
|
||||
# NOTE: catching the events here will overwrite the accept in showbase, hence
|
||||
# we need to forward the event in the functions we set here!
|
||||
self.accept("connect-device", self.connect)
|
||||
self.accept("disconnect-device", self.disconnect)
|
||||
|
||||
self.accept("escape", exit)
|
||||
self.accept("gamepad0-start", exit)
|
||||
self.accept("flight_stick0-start", exit)
|
||||
|
||||
# Accept button events of the first connected gamepad
|
||||
self.accept("gamepad0-action_a", self.doAction, extraArgs=[True, "Action"])
|
||||
self.accept("gamepad0-action_a-up", self.doAction, extraArgs=[False, "Release"])
|
||||
self.accept("gamepad0-action_b", self.doAction, extraArgs=[True, "Action 2"])
|
||||
self.accept("gamepad0-action_b-up", self.doAction, extraArgs=[False, "Release"])
|
||||
self.accept("gamepad-back", exit)
|
||||
self.accept("gamepad-start", exit)
|
||||
self.accept("gamepad-face_x", self.reset)
|
||||
self.accept("gamepad-face_a", self.action, extraArgs=["face_a"])
|
||||
self.accept("gamepad-face_a-up", self.actionUp)
|
||||
self.accept("gamepad-face_b", self.action, extraArgs=["face_b"])
|
||||
self.accept("gamepad-face_b-up", self.actionUp)
|
||||
self.accept("gamepad-face_y", self.action, extraArgs=["face_y"])
|
||||
self.accept("gamepad-face_y-up", self.actionUp)
|
||||
|
||||
self.environment = loader.loadModel("environment")
|
||||
self.environment.reparentTo(render)
|
||||
|
||||
# disable pandas default mouse-camera controls so we can handle the camera
|
||||
# movements by ourself
|
||||
# Disable the default mouse-camera controls since we need to handle
|
||||
# our own camera controls.
|
||||
self.disableMouse()
|
||||
|
||||
# list of connected gamepad devices
|
||||
gamepads = base.devices.getDevices(InputDevice.DC_gamepad)
|
||||
|
||||
# set the center position of the control sticks
|
||||
# NOTE: here we assume, that the wheel is centered when the application get started.
|
||||
# In real world applications, you should notice the user and give him enough time
|
||||
# to center the wheel until you store the center position of the controler!
|
||||
self.lxcenter = gamepads[0].findControl(InputDevice.C_left_x).state
|
||||
self.lycenter = gamepads[0].findControl(InputDevice.C_left_y).state
|
||||
self.rxcenter = gamepads[0].findControl(InputDevice.C_right_x).state
|
||||
self.rycenter = gamepads[0].findControl(InputDevice.C_right_y).state
|
||||
|
||||
self.reset()
|
||||
|
||||
self.taskMgr.add(self.moveTask, "movement update task")
|
||||
|
||||
def connect(self, device):
|
||||
# we need to forward the event to the connectDevice function of showbase
|
||||
self.connectDevice(device)
|
||||
# Now we can check for ourself
|
||||
self.checkDevices()
|
||||
"""Event handler that is called when a device is discovered."""
|
||||
|
||||
# We're only interested if this is a gamepad and we don't have a
|
||||
# gamepad yet.
|
||||
if device.device_class == InputDevice.DC_gamepad and not self.gamepad:
|
||||
print("Found %s" % (device))
|
||||
self.gamepad = device
|
||||
|
||||
# Enable this device to ShowBase so that we can receive events.
|
||||
# We set up the events with a prefix of "gamepad-".
|
||||
self.attachInputDevice(device, prefix="gamepad")
|
||||
|
||||
# Hide the warning that we have no devices.
|
||||
self.lblWarning.hide()
|
||||
|
||||
def disconnect(self, device):
|
||||
# we need to forward the event to the disconnectDevice function of showbase
|
||||
self.disconnectDevice(device)
|
||||
# Now we can check for ourself
|
||||
self.checkDevices()
|
||||
"""Event handler that is called when a device is removed."""
|
||||
|
||||
def checkDevices(self):
|
||||
# check if we have gamepad devices connected
|
||||
if self.devices.get_devices(InputDevice.DC_gamepad):
|
||||
# we have at least one gamepad device
|
||||
self.lblWarning.hide()
|
||||
if self.gamepad != device:
|
||||
# We don't care since it's not our gamepad.
|
||||
return
|
||||
|
||||
# Tell ShowBase that the device is no longer needed.
|
||||
print("Disconnected %s" % (device))
|
||||
self.detachInputDevice(device)
|
||||
self.gamepad = None
|
||||
|
||||
# Do we have any other gamepads? Attach the first other gamepad.
|
||||
devices = self.devices.getDevices(InputDevice.DC_gamepad)
|
||||
if devices:
|
||||
self.connect(devices[0])
|
||||
else:
|
||||
# no devices connected
|
||||
# No devices. Show the warning.
|
||||
self.lblWarning.show()
|
||||
|
||||
def doAction(self, showText, text):
|
||||
if showText and self.lblAction.isHidden():
|
||||
self.lblAction.show()
|
||||
else:
|
||||
self.lblAction.hide()
|
||||
def reset(self):
|
||||
"""Reset the camera to the initial position."""
|
||||
|
||||
self.camera.setPosHpr(0, -200, 10, 0, 0, 0)
|
||||
|
||||
def action(self, button):
|
||||
# Just show which button has been pressed.
|
||||
self.lblAction.text = "Pressed \5%s\5" % button
|
||||
self.lblAction.show()
|
||||
|
||||
def actionUp(self):
|
||||
# Hide the label showing which button is pressed.
|
||||
self.lblAction.hide()
|
||||
|
||||
def moveTask(self, task):
|
||||
dt = globalClock.getDt()
|
||||
movementVec = Vec3()
|
||||
|
||||
gamepads = base.devices.getDevices(InputDevice.DC_gamepad)
|
||||
if len(gamepads) == 0:
|
||||
# savety check
|
||||
if not self.gamepad:
|
||||
return task.cont
|
||||
|
||||
strafe_speed = 85
|
||||
vert_speed = 50
|
||||
turn_speed = 100
|
||||
|
||||
# If the left stick is pressed, we will go faster.
|
||||
lstick = self.gamepad.findButton("lstick")
|
||||
if lstick.pressed:
|
||||
strafe_speed *= 2.0
|
||||
|
||||
# we will use the first found gamepad
|
||||
# Move the camera left/right
|
||||
left_x = gamepads[0].findControl(InputDevice.C_left_x)
|
||||
movementVec.setX(left_x.state - self.lxcenter)
|
||||
# Move the camera forward/backward
|
||||
left_y = gamepads[0].findControl(InputDevice.C_left_y)
|
||||
movementVec.setY(left_y.state - self.lycenter)
|
||||
# Control the cameras heading
|
||||
right_x = gamepads[0].findControl(InputDevice.C_right_x)
|
||||
base.camera.setH(base.camera, 100 * dt * (right_x.state - self.rxcenter))
|
||||
# Control the cameras pitch
|
||||
right_y = gamepads[0].findControl(InputDevice.C_right_y)
|
||||
base.camera.setP(base.camera, 100 * dt * (right_y.state - self.rycenter))
|
||||
strafe = Vec3(0)
|
||||
left_x = self.gamepad.findAxis(InputDevice.Axis.left_x)
|
||||
left_y = self.gamepad.findAxis(InputDevice.Axis.left_y)
|
||||
strafe.x = left_x.value
|
||||
strafe.y = left_y.value
|
||||
|
||||
# calculate movement
|
||||
base.camera.setX(base.camera, 100 * dt * movementVec.getX())
|
||||
base.camera.setY(base.camera, 100 * dt * movementVec.getY())
|
||||
# Apply some deadzone, since the sticks don't center exactly at 0
|
||||
if strafe.lengthSquared() >= 0.01:
|
||||
self.camera.setPos(self.camera, strafe * strafe_speed * dt)
|
||||
|
||||
# Use the triggers for the vertical position.
|
||||
trigger_l = self.gamepad.findAxis(InputDevice.Axis.left_trigger)
|
||||
trigger_r = self.gamepad.findAxis(InputDevice.Axis.right_trigger)
|
||||
lift = trigger_r.value - trigger_l.value
|
||||
self.camera.setZ(self.camera.getZ() + (lift * vert_speed * dt))
|
||||
|
||||
# Move the camera forward/backward
|
||||
right_x = self.gamepad.findAxis(InputDevice.Axis.right_x)
|
||||
right_y = self.gamepad.findAxis(InputDevice.Axis.right_y)
|
||||
|
||||
# Again, some deadzone
|
||||
if abs(right_x.value) >= 0.1 or abs(right_y.value) >= 0.1:
|
||||
self.camera.setH(self.camera, turn_speed * dt * -right_x.value)
|
||||
self.camera.setP(self.camera, turn_speed * dt * right_y.value)
|
||||
|
||||
# Reset the roll so that the camera remains upright.
|
||||
self.camera.setR(0)
|
||||
|
||||
return task.cont
|
||||
|
||||
|
@ -16,62 +16,75 @@ from panda3d.core import (
|
||||
TextNode,
|
||||
Vec2,
|
||||
InputDevice,
|
||||
loadPrcFileData)
|
||||
loadPrcFileData,
|
||||
GamepadButton,
|
||||
KeyboardButton)
|
||||
|
||||
# Make sure the textures look crisp on every device that supports
|
||||
# non-power-2 textures
|
||||
loadPrcFileData("", "textures-auto-power-2 #t")
|
||||
|
||||
class App(ShowBase):
|
||||
# How much an axis should have moved for it to register as a movement.
|
||||
DEAD_ZONE = 0.33
|
||||
|
||||
|
||||
class InputMapping(object):
|
||||
"""A container class for storing a mapping from a string action to either
|
||||
an axis or a button. You could extend this with additional methods to load
|
||||
the default mappings from a configuration file. """
|
||||
|
||||
# Define all the possible actions.
|
||||
actions = (
|
||||
"Move forward", "Move backward", "Move left", "Move right", "Jump",
|
||||
"Buy", "Use", "Break", "Fix", "Trash", "Change", "Mail", "Upgrade",
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
ShowBase.__init__(self)
|
||||
self.__map = dict.fromkeys(self.actions)
|
||||
|
||||
self.setBackgroundColor(0, 0, 0)
|
||||
# make the font look nice at a big scale
|
||||
DGG.getDefaultFont().setPixelsPerUnit(100)
|
||||
def mapButton(self, action, button):
|
||||
self.__map[action] = ("button", str(button))
|
||||
|
||||
# a dict of actions and button/axis events
|
||||
self.gamepadMapping = {
|
||||
"Move forward":"Left Stick Y",
|
||||
"Move backward":"Left Stick Y",
|
||||
"Move left":"Left Stick X",
|
||||
"Move right":"Left Stick X",
|
||||
"Jump":"a",
|
||||
"Action":"b",
|
||||
"Sprint":"x",
|
||||
"Map":"y",
|
||||
"action-1":"c",
|
||||
"action-2":"d",
|
||||
"action-3":"e",
|
||||
"action-4":"f",
|
||||
"action-5":"g",
|
||||
"action-6":"h",
|
||||
"action-7":"i",
|
||||
"action-8":"j",
|
||||
"action-9":"k",
|
||||
"action-10":"l",
|
||||
"action-11":"m",
|
||||
}
|
||||
# this will store the action that we want to remap
|
||||
self.actionToMap = ""
|
||||
# this will store the key/axis that we want to asign to an action
|
||||
self.newActionKey = ""
|
||||
# this will store the label that needs to be actualized in the list
|
||||
self.actualizeLabel = None
|
||||
def mapAxis(self, action, axis):
|
||||
self.__map[action] = ("axis", axis.name)
|
||||
|
||||
# The geometry for our basic buttons
|
||||
maps = loader.loadModel("models/button_map")
|
||||
self.buttonGeom = (
|
||||
maps.find("**/ready"),
|
||||
maps.find("**/click"),
|
||||
maps.find("**/hover"),
|
||||
maps.find("**/disabled"))
|
||||
def unmap(self):
|
||||
self.__map[action] = None
|
||||
|
||||
# Create the dialog that asks the user for input on a given
|
||||
# action to map a key to.
|
||||
DGG.setDefaultDialogGeom("models/dialog.png")
|
||||
# setup a dialog to ask for device input
|
||||
self.dlgInput = OkCancelDialog(
|
||||
def formatMapping(self, action):
|
||||
"""Returns a string label describing the mapping for a given action,
|
||||
for displaying in a GUI. """
|
||||
mapping = self.__map.get(action)
|
||||
if not mapping:
|
||||
return "Unmapped"
|
||||
|
||||
# Format the symbolic string from Panda nicely. In a real-world game,
|
||||
# you might want to look these up in a translation table, or so.
|
||||
label = mapping[1].replace('_', ' ').title()
|
||||
if mapping[0] == "axis":
|
||||
return "Axis: " + label
|
||||
else:
|
||||
return "Button: " + label
|
||||
|
||||
|
||||
class ChangeActionDialog(object):
|
||||
"""Encapsulates the UI dialog that opens up when changing a mapping. It
|
||||
holds the state of which action is being set and which button is pressed
|
||||
and invokes a callback function when the dialog is exited."""
|
||||
|
||||
def __init__(self, action, button_geom, command):
|
||||
# This stores which action we are remapping.
|
||||
self.action = action
|
||||
|
||||
# This will store the key/axis that we want to asign to an action
|
||||
self.newInputType = ""
|
||||
self.newInput = ""
|
||||
self.setKeyCalled = False
|
||||
|
||||
self.__command = command
|
||||
|
||||
# Initialize the DirectGUI stuff.
|
||||
self.dialog = OkCancelDialog(
|
||||
dialogName="dlg_device_input",
|
||||
pos=(0, 0, 0.25),
|
||||
text="Hit desired key:",
|
||||
@ -82,7 +95,7 @@ class App(ShowBase):
|
||||
text_align=TextNode.ACenter,
|
||||
fadeScreen=0.65,
|
||||
frameColor=VBase4(0.3, 0.3, 0.3, 1),
|
||||
button_geom=self.buttonGeom,
|
||||
button_geom=button_geom,
|
||||
button_scale=0.15,
|
||||
button_text_scale=0.35,
|
||||
button_text_align=TextNode.ALeft,
|
||||
@ -93,13 +106,73 @@ class App(ShowBase):
|
||||
button_frameColor=VBase4(0, 0, 0, 0),
|
||||
button_frameSize=VBase4(-1.0, 1.0, -0.25, 0.25),
|
||||
button_pressEffect=False,
|
||||
command=self.closeDialog)
|
||||
self.dlgInput.setTransparency(True)
|
||||
self.dlgInput.configureDialog()
|
||||
scale = self.dlgInput["image_scale"]
|
||||
self.dlgInput["image_scale"] = (scale[0]/2.0, scale[1], scale[2]/2.0)
|
||||
self.dlgInput["text_pos"] = (self.dlgInput["text_pos"][0], self.dlgInput["text_pos"][1] + 0.06)
|
||||
self.dlgInput.hide()
|
||||
command=self.onClose)
|
||||
self.dialog.setTransparency(True)
|
||||
self.dialog.configureDialog()
|
||||
scale = self.dialog["image_scale"]
|
||||
self.dialog["image_scale"] = (scale[0]/2.0, scale[1], scale[2]/2.0)
|
||||
self.dialog["text_pos"] = (self.dialog["text_pos"][0], self.dialog["text_pos"][1] + 0.06)
|
||||
|
||||
def buttonPressed(self, button):
|
||||
if any(button.guiItem.getState() == 1 for button in self.dialog.buttonList):
|
||||
# Ignore events while any of the dialog buttons are active, because
|
||||
# otherwise we register mouse clicks when the user is trying to
|
||||
# exit the dialog.
|
||||
return
|
||||
|
||||
text = str(button).replace('_', ' ').title()
|
||||
self.dialog["text"] = "New event will be:\n\nButton: " + text
|
||||
self.newInputType = "button"
|
||||
self.newInput = button
|
||||
|
||||
def axisMoved(self, axis):
|
||||
text = axis.name.replace('_', ' ').title()
|
||||
self.dialog["text"] = "New event will be:\n\nAxis: " + text
|
||||
self.newInputType = "axis"
|
||||
self.newInput = axis
|
||||
|
||||
def onClose(self, result):
|
||||
"""Called when the OK or Cancel button is pressed."""
|
||||
self.dialog.cleanup()
|
||||
|
||||
# Call the constructor-supplied callback with our new setting, if any.
|
||||
if self.newInput and result == DGG.DIALOG_OK:
|
||||
self.__command(self.action, self.newInputType, self.newInput)
|
||||
else:
|
||||
# Cancel (or no input was pressed)
|
||||
self.__command(self.action, None, None)
|
||||
|
||||
|
||||
class MappingGUIDemo(ShowBase):
|
||||
def __init__(self):
|
||||
ShowBase.__init__(self)
|
||||
|
||||
self.setBackgroundColor(0, 0, 0)
|
||||
# make the font look nice at a big scale
|
||||
DGG.getDefaultFont().setPixelsPerUnit(100)
|
||||
|
||||
# Store our mapping, with some sensible defaults. In a real game, you
|
||||
# will want to load these from a configuration file.
|
||||
self.mapping = InputMapping()
|
||||
self.mapping.mapAxis("Move forward", InputDevice.Axis.left_y)
|
||||
self.mapping.mapAxis("Move backward", InputDevice.Axis.left_y)
|
||||
self.mapping.mapAxis("Move left", InputDevice.Axis.left_x)
|
||||
self.mapping.mapAxis("Move right", InputDevice.Axis.left_x)
|
||||
self.mapping.mapButton("Jump", GamepadButton.face_a())
|
||||
self.mapping.mapButton("Use", GamepadButton.face_b())
|
||||
self.mapping.mapButton("Break", GamepadButton.face_x())
|
||||
self.mapping.mapButton("Fix", GamepadButton.face_y())
|
||||
|
||||
# The geometry for our basic buttons
|
||||
maps = loader.loadModel("models/button_map")
|
||||
self.buttonGeom = (
|
||||
maps.find("**/ready"),
|
||||
maps.find("**/click"),
|
||||
maps.find("**/hover"),
|
||||
maps.find("**/disabled"))
|
||||
|
||||
# Change the default dialog skin.
|
||||
DGG.setDefaultDialogGeom("models/dialog.png")
|
||||
|
||||
# create a sample title
|
||||
self.textscale = 0.1
|
||||
@ -135,6 +208,7 @@ class App(ShowBase):
|
||||
decMaps.find("**/dec_click"),
|
||||
decMaps.find("**/dec_hover"),
|
||||
decMaps.find("**/dec_disabled"))
|
||||
|
||||
# create the scrolled frame that will hold our list
|
||||
self.lstActionMap = DirectScrolledFrame(
|
||||
# make the frame occupy the whole window
|
||||
@ -167,135 +241,116 @@ class App(ShowBase):
|
||||
idx = 0
|
||||
self.listBGEven = base.loader.loadModel("models/list_item_even")
|
||||
self.listBGOdd = base.loader.loadModel("models/list_item_odd")
|
||||
for key, value in self.gamepadMapping.items():
|
||||
item = self.__makeListItem(key, key, value, idx)
|
||||
self.actionLabels = {}
|
||||
for action in self.mapping.actions:
|
||||
mapped = self.mapping.formatMapping(action)
|
||||
item = self.__makeListItem(action, mapped, idx)
|
||||
item.reparentTo(self.lstActionMap.getCanvas())
|
||||
idx += 1
|
||||
|
||||
# recalculate the canvas size to set scrollbars if necesary
|
||||
self.lstActionMap["canvasSize"] = (
|
||||
base.a2dLeft+0.05, base.a2dRight-0.05,
|
||||
-(len(self.gamepadMapping.keys())*0.1), 0.09)
|
||||
-(len(self.mapping.actions)*0.1), 0.09)
|
||||
self.lstActionMap.setCanvasSize()
|
||||
|
||||
def closeDialog(self, result):
|
||||
self.dlgInput.hide()
|
||||
if result == DGG.DIALOG_OK:
|
||||
def closeDialog(self, action, newInputType, newInput):
|
||||
"""Called in callback when the dialog is closed. newInputType will be
|
||||
"button" or "axis", or None if the remapping was cancelled."""
|
||||
|
||||
self.dlgInput = None
|
||||
|
||||
if newInputType is not None:
|
||||
# map the event to the given action
|
||||
self.gamepadMapping[self.actionToMap] = self.newActionKey
|
||||
if newInputType == "axis":
|
||||
self.mapping.mapAxis(action, newInput)
|
||||
else:
|
||||
self.mapping.mapButton(action, newInput)
|
||||
|
||||
# actualize the label in the list that shows the current
|
||||
# event for the action
|
||||
self.actualizeLabel["text"] = self.newActionKey
|
||||
self.actionLabels[action]["text"] = self.mapping.formatMapping(action)
|
||||
|
||||
# cleanup
|
||||
self.dlgInput["text"] ="Hit desired key:"
|
||||
self.actionToMap = ""
|
||||
self.newActionKey = ""
|
||||
self.actualizeLabel = None
|
||||
for bt in base.buttonThrowers:
|
||||
bt.node().setSpecificFlag(True)
|
||||
bt.node().setButtonDownEvent("")
|
||||
for bt in base.deviceButtonThrowers:
|
||||
bt.node().setSpecificFlag(True)
|
||||
bt.node().setButtonDownEvent("")
|
||||
taskMgr.remove("checkControls")
|
||||
|
||||
def changeMapping(self, action, label):
|
||||
# set the action that we want to map a new key to
|
||||
self.actionToMap = action
|
||||
# set the label that needs to be actualized
|
||||
self.actualizeLabel = label
|
||||
# show our dialog
|
||||
self.dlgInput.show()
|
||||
# Now detach all the input devices.
|
||||
for device in self.attachedDevices:
|
||||
base.detachInputDevice(device)
|
||||
self.attachedDevices.clear()
|
||||
|
||||
# catch all button events
|
||||
def changeMapping(self, action):
|
||||
# Create the dialog window
|
||||
self.dlgInput = ChangeActionDialog(action, button_geom=self.buttonGeom, command=self.closeDialog)
|
||||
|
||||
# Attach all input devices.
|
||||
devices = base.devices.getDevices()
|
||||
for device in devices:
|
||||
base.attachInputDevice(device)
|
||||
self.attachedDevices = devices
|
||||
|
||||
# Disable regular button events on all button event throwers, and
|
||||
# instead broadcast a generic event.
|
||||
for bt in base.buttonThrowers:
|
||||
bt.node().setSpecificFlag(False)
|
||||
bt.node().setButtonDownEvent("keyListenEvent")
|
||||
for bt in base.deviceButtonThrowers:
|
||||
bt.node().setSpecificFlag(False)
|
||||
bt.node().setButtonDownEvent("deviceListenEvent")
|
||||
self.setKeyCalled = False
|
||||
self.accept("keyListenEvent", self.setKey)
|
||||
self.accept("deviceListenEvent", self.setDeviceKey)
|
||||
|
||||
# As there are no events thrown for control changes, we set up
|
||||
# a task to check if the controls got moved
|
||||
# This list will help us for checking which controls got moved
|
||||
self.controlStates = {None:{}}
|
||||
self.accept("keyListenEvent", self.dlgInput.buttonPressed)
|
||||
self.accept("deviceListenEvent", self.dlgInput.buttonPressed)
|
||||
|
||||
# As there are no events thrown for control changes, we set up a task
|
||||
# to check if the controls were moved
|
||||
# This list will help us for checking which controls were moved
|
||||
self.axisStates = {None: {}}
|
||||
# fill it with all available controls
|
||||
for device in base.devices.get_devices():
|
||||
for ctrl in device.controls:
|
||||
if device not in self.controlStates.keys():
|
||||
self.controlStates.update({device: {ctrl.axis: ctrl.state}})
|
||||
for device in devices:
|
||||
for axis in device.axes:
|
||||
if device not in self.axisStates.keys():
|
||||
self.axisStates.update({device: {axis.axis: axis.value}})
|
||||
else:
|
||||
self.controlStates[device].update({ctrl.axis: ctrl.state})
|
||||
self.axisStates[device].update({axis.axis: axis.value})
|
||||
# start the task
|
||||
taskMgr.add(self.watchControls, "checkControls")
|
||||
|
||||
def watchControls(self, task):
|
||||
# move through all devices and all it's controls
|
||||
for device in base.devices.get_devices():
|
||||
for ctrl in device.controls:
|
||||
# if a control got changed more than the given puffer zone
|
||||
if self.controlStates[device][ctrl.axis] + 0.2 < ctrl.state or \
|
||||
self.controlStates[device][ctrl.axis] - 0.2 > ctrl.state:
|
||||
for device in self.attachedDevices:
|
||||
if device.device_class == InputDevice.DC_mouse:
|
||||
# Ignore mouse axis movement, or the user can't even navigate
|
||||
# to the OK/Cancel buttons!
|
||||
continue
|
||||
|
||||
for axis in device.axes:
|
||||
# if a control got changed more than the given dead zone
|
||||
if self.axisStates[device][axis.axis] + DEAD_ZONE < axis.value or \
|
||||
self.axisStates[device][axis.axis] - DEAD_ZONE > axis.value:
|
||||
# set the current state in the dict
|
||||
self.controlStates[device][ctrl.axis] = ctrl.state
|
||||
# check which axis got moved
|
||||
if ctrl.axis == InputDevice.C_left_x:
|
||||
self.setKey("Left Stick X")
|
||||
elif ctrl.axis == InputDevice.C_left_y:
|
||||
self.setKey("Left Stick Y")
|
||||
elif ctrl.axis == InputDevice.C_left_trigger:
|
||||
self.setKey("Left Trigger")
|
||||
elif ctrl.axis == InputDevice.C_right_x:
|
||||
self.setKey("Right Stick X")
|
||||
elif ctrl.axis == InputDevice.C_right_y:
|
||||
self.setKey("Right Stick Y")
|
||||
elif ctrl.axis == InputDevice.C_right_trigger:
|
||||
self.setKey("Right Trigger")
|
||||
elif ctrl.axis == InputDevice.C_x:
|
||||
self.setKey("X")
|
||||
elif ctrl.axis == InputDevice.C_y:
|
||||
self.setKey("Y")
|
||||
elif ctrl.axis == InputDevice.C_trigger:
|
||||
self.setKey("Trigger")
|
||||
elif ctrl.axis == InputDevice.C_throttle:
|
||||
self.setKey("Throttle")
|
||||
elif ctrl.axis == InputDevice.C_rudder:
|
||||
self.setKey("Rudder")
|
||||
elif ctrl.axis == InputDevice.C_hat_x:
|
||||
self.setKey("Hat X")
|
||||
elif ctrl.axis == InputDevice.C_hat_y:
|
||||
self.setKey("Hat Y")
|
||||
elif ctrl.axis == InputDevice.C_wheel:
|
||||
self.setKey("Wheel")
|
||||
elif ctrl.axis == InputDevice.C_accelerator:
|
||||
self.setKey("Acclerator")
|
||||
elif ctrl.axis == InputDevice.C_brake:
|
||||
self.setKey("Break")
|
||||
self.axisStates[device][axis.axis] = axis.value
|
||||
|
||||
# Format the axis for being displayed.
|
||||
if axis.axis != InputDevice.Axis.none:
|
||||
#label = axis.axis.name.replace('_', ' ').title()
|
||||
self.dlgInput.axisMoved(axis.axis)
|
||||
|
||||
return task.cont
|
||||
|
||||
def setKey(self, args):
|
||||
self.setKeyCalled = True
|
||||
if self.dlgInput.buttonList[0].guiItem.getState() == 1:
|
||||
# this occurs if the OK button was clicked. To prevent to
|
||||
# always set the mouse1 event whenever the OK button was
|
||||
# pressed, we instantly return from this function
|
||||
return
|
||||
self.dlgInput["text"] = "New event will be:\n\n" + args
|
||||
self.newActionKey = args
|
||||
|
||||
def setDeviceKey(self, args):
|
||||
if not self.setKeyCalled:
|
||||
self.setKey(args)
|
||||
self.setKeyCalled = False
|
||||
|
||||
def __makeListItem(self, itemName, action, event, index):
|
||||
def __makeListItem(self, action, event, index):
|
||||
def dummy(): pass
|
||||
if index % 2 == 0:
|
||||
bg = self.listBGEven
|
||||
else:
|
||||
bg = self.listBGOdd
|
||||
item = DirectFrame(
|
||||
text=itemName,
|
||||
text=action,
|
||||
geom=bg,
|
||||
geom_scale=(base.a2dRight-0.05, 1, 0.1),
|
||||
frameSize=VBase4(base.a2dLeft+0.05, base.a2dRight-0.05, -0.05, 0.05),
|
||||
@ -317,6 +372,8 @@ class App(ShowBase):
|
||||
)
|
||||
lbl.reparentTo(item)
|
||||
lbl.setTransparency(True)
|
||||
self.actionLabels[action] = lbl
|
||||
|
||||
buttonScale = 0.15
|
||||
btn = DirectButton(
|
||||
text="Change",
|
||||
@ -333,10 +390,10 @@ class App(ShowBase):
|
||||
pos=(base.a2dRight-(0.898*buttonScale+0.3), 0, 0),
|
||||
pressEffect=False,
|
||||
command=self.changeMapping,
|
||||
extraArgs=[action, lbl])
|
||||
extraArgs=[action])
|
||||
btn.setTransparency(True)
|
||||
btn.reparentTo(item)
|
||||
return item
|
||||
|
||||
app = App()
|
||||
app = MappingGUIDemo()
|
||||
app.run()
|
||||
|
384
samples/gamepad/models/xbone-icons.egg
Normal file
384
samples/gamepad/models/xbone-icons.egg
Normal file
@ -0,0 +1,384 @@
|
||||
<CoordinateSystem> { Y-Up }
|
||||
|
||||
<Texture> xbone-icons {
|
||||
xbone-icons.png
|
||||
<Scalar> format { rgba }
|
||||
<Scalar> alpha { dual }
|
||||
}
|
||||
|
||||
<VertexPool> vpool {
|
||||
<Vertex> 0 {
|
||||
-0.5 0.5 0
|
||||
<UV> { 0.00390625 0.9921875 }
|
||||
}
|
||||
<Vertex> 1 {
|
||||
-0.5 -0.5 0
|
||||
<UV> { 0.00390625 0.7578125 }
|
||||
}
|
||||
<Vertex> 2 {
|
||||
0.5 -0.5 0
|
||||
<UV> { 0.12109375 0.7578125 }
|
||||
}
|
||||
<Vertex> 3 {
|
||||
0.5 0.5 0
|
||||
<UV> { 0.12109375 0.9921875 }
|
||||
}
|
||||
<Vertex> 4 {
|
||||
-0.5 0.5 0
|
||||
<UV> { 0.12890625 0.9921875 }
|
||||
}
|
||||
<Vertex> 5 {
|
||||
-0.5 -0.5 0
|
||||
<UV> { 0.12890625 0.7578125 }
|
||||
}
|
||||
<Vertex> 6 {
|
||||
0.5 -0.5 0
|
||||
<UV> { 0.24609375 0.7578125 }
|
||||
}
|
||||
<Vertex> 7 {
|
||||
0.5 0.5 0
|
||||
<UV> { 0.24609375 0.9921875 }
|
||||
}
|
||||
<Vertex> 8 {
|
||||
-0.5 0.5 0
|
||||
<UV> { 0.25390625 0.9921875 }
|
||||
}
|
||||
<Vertex> 9 {
|
||||
-0.5 -0.5 0
|
||||
<UV> { 0.25390625 0.7578125 }
|
||||
}
|
||||
<Vertex> 10 {
|
||||
0.5 -0.5 0
|
||||
<UV> { 0.37109375 0.7578125 }
|
||||
}
|
||||
<Vertex> 11 {
|
||||
0.5 0.5 0
|
||||
<UV> { 0.37109375 0.9921875 }
|
||||
}
|
||||
<Vertex> 12 {
|
||||
-0.5 0.5 0
|
||||
<UV> { 0.37890625 0.9921875 }
|
||||
}
|
||||
<Vertex> 13 {
|
||||
-0.5 -0.5 0
|
||||
<UV> { 0.37890625 0.7578125 }
|
||||
}
|
||||
<Vertex> 14 {
|
||||
0.5 -0.5 0
|
||||
<UV> { 0.49609375 0.7578125 }
|
||||
}
|
||||
<Vertex> 15 {
|
||||
0.5 0.5 0
|
||||
<UV> { 0.49609375 0.9921875 }
|
||||
}
|
||||
<Vertex> 16 {
|
||||
-0.5 0.5 0
|
||||
<UV> { 0.50390625 0.9921875 }
|
||||
}
|
||||
<Vertex> 17 {
|
||||
-0.5 -0.5 0
|
||||
<UV> { 0.50390625 0.7578125 }
|
||||
}
|
||||
<Vertex> 18 {
|
||||
0.5 -0.5 0
|
||||
<UV> { 0.62109375 0.7578125 }
|
||||
}
|
||||
<Vertex> 19 {
|
||||
0.5 0.5 0
|
||||
<UV> { 0.62109375 0.9921875 }
|
||||
}
|
||||
<Vertex> 20 {
|
||||
-0.5 0.5 0
|
||||
<UV> { 0.62890625 0.9921875 }
|
||||
}
|
||||
<Vertex> 21 {
|
||||
-0.5 -0.5 0
|
||||
<UV> { 0.62890625 0.7578125 }
|
||||
}
|
||||
<Vertex> 22 {
|
||||
0.5 -0.5 0
|
||||
<UV> { 0.74609375 0.7578125 }
|
||||
}
|
||||
<Vertex> 23 {
|
||||
0.5 0.5 0
|
||||
<UV> { 0.74609375 0.9921875 }
|
||||
}
|
||||
<Vertex> 24 {
|
||||
-0.5 0.5 0
|
||||
<UV> { 0.75390625 0.9921875 }
|
||||
}
|
||||
<Vertex> 25 {
|
||||
-0.5 -0.5 0
|
||||
<UV> { 0.75390625 0.7578125 }
|
||||
}
|
||||
<Vertex> 26 {
|
||||
0.5 -0.5 0
|
||||
<UV> { 0.87109375 0.7578125 }
|
||||
}
|
||||
<Vertex> 27 {
|
||||
0.5 0.5 0
|
||||
<UV> { 0.87109375 0.9921875 }
|
||||
}
|
||||
<Vertex> 28 {
|
||||
-0.5 0.5 0
|
||||
<UV> { 0.87890625 0.9921875 }
|
||||
}
|
||||
<Vertex> 29 {
|
||||
-0.5 -0.5 0
|
||||
<UV> { 0.87890625 0.7578125 }
|
||||
}
|
||||
<Vertex> 30 {
|
||||
0.5 -0.5 0
|
||||
<UV> { 0.99609375 0.7578125 }
|
||||
}
|
||||
<Vertex> 31 {
|
||||
0.5 0.5 0
|
||||
<UV> { 0.99609375 0.9921875 }
|
||||
}
|
||||
<Vertex> 32 {
|
||||
-0.5 0.5 0
|
||||
<UV> { 0.12890625 0.7421875 }
|
||||
}
|
||||
<Vertex> 33 {
|
||||
-0.5 -0.5 0
|
||||
<UV> { 0.12890625 0.5078125 }
|
||||
}
|
||||
<Vertex> 34 {
|
||||
0.5 -0.5 0
|
||||
<UV> { 0.24609375 0.5078125 }
|
||||
}
|
||||
<Vertex> 35 {
|
||||
0.5 0.5 0
|
||||
<UV> { 0.24609375 0.7421875 }
|
||||
}
|
||||
<Vertex> 36 {
|
||||
-0.5 0.5 0
|
||||
<UV> { 0.00390625 0.7421875 }
|
||||
}
|
||||
<Vertex> 37 {
|
||||
-0.5 -0.5 0
|
||||
<UV> { 0.00390625 0.5078125 }
|
||||
}
|
||||
<Vertex> 38 {
|
||||
0.5 -0.5 0
|
||||
<UV> { 0.12109375 0.5078125 }
|
||||
}
|
||||
<Vertex> 39 {
|
||||
0.5 0.5 0
|
||||
<UV> { 0.12109375 0.7421875 }
|
||||
}
|
||||
<Vertex> 40 {
|
||||
-0.5 0.5 0
|
||||
<UV> { 0.25390625 0.7421875 }
|
||||
}
|
||||
<Vertex> 41 {
|
||||
-0.5 -0.5 0
|
||||
<UV> { 0.25390625 0.5078125 }
|
||||
}
|
||||
<Vertex> 42 {
|
||||
0.5 -0.5 0
|
||||
<UV> { 0.37109375 0.5078125 }
|
||||
}
|
||||
<Vertex> 43 {
|
||||
0.5 0.5 0
|
||||
<UV> { 0.37109375 0.7421875 }
|
||||
}
|
||||
<Vertex> 44 {
|
||||
-0.5 0.5 0
|
||||
<UV> { 0.37890625 0.7421875 }
|
||||
}
|
||||
<Vertex> 45 {
|
||||
-0.5 -0.5 0
|
||||
<UV> { 0.37890625 0.5078125 }
|
||||
}
|
||||
<Vertex> 46 {
|
||||
0.5 -0.5 0
|
||||
<UV> { 0.49609375 0.5078125 }
|
||||
}
|
||||
<Vertex> 47 {
|
||||
0.5 0.5 0
|
||||
<UV> { 0.49609375 0.7421875 }
|
||||
}
|
||||
<Vertex> 48 {
|
||||
-0.5 0.5 0
|
||||
<UV> { 0.62890625 0.7421875 }
|
||||
}
|
||||
<Vertex> 49 {
|
||||
-0.5 -0.5 0
|
||||
<UV> { 0.62890625 0.5078125 }
|
||||
}
|
||||
<Vertex> 50 {
|
||||
0.5 -0.5 0
|
||||
<UV> { 0.74609375 0.5078125 }
|
||||
}
|
||||
<Vertex> 51 {
|
||||
0.5 0.5 0
|
||||
<UV> { 0.74609375 0.7421875 }
|
||||
}
|
||||
<Vertex> 52 {
|
||||
-0.5 0.5 0
|
||||
<UV> { 0.50390625 0.7421875 }
|
||||
}
|
||||
<Vertex> 53 {
|
||||
-0.5 -0.5 0
|
||||
<UV> { 0.50390625 0.5078125 }
|
||||
}
|
||||
<Vertex> 54 {
|
||||
0.5 -0.5 0
|
||||
<UV> { 0.62109375 0.5078125 }
|
||||
}
|
||||
<Vertex> 55 {
|
||||
0.5 0.5 0
|
||||
<UV> { 0.62109375 0.7421875 }
|
||||
}
|
||||
<Vertex> 56 {
|
||||
-0.5 0.5 0
|
||||
<UV> { 0.75390625 0.7421875 }
|
||||
}
|
||||
<Vertex> 57 {
|
||||
-0.5 -0.5 0
|
||||
<UV> { 0.75390625 0.5078125 }
|
||||
}
|
||||
<Vertex> 58 {
|
||||
0.5 -0.5 0
|
||||
<UV> { 0.87109375 0.5078125 }
|
||||
}
|
||||
<Vertex> 59 {
|
||||
0.5 0.5 0
|
||||
<UV> { 0.87109375 0.7421875 }
|
||||
}
|
||||
<Vertex> 60 {
|
||||
-0.5 0.5 0
|
||||
<UV> { 0.87890625 0.7421875 }
|
||||
}
|
||||
<Vertex> 61 {
|
||||
-0.5 -0.5 0
|
||||
<UV> { 0.87890625 0.5078125 }
|
||||
}
|
||||
<Vertex> 62 {
|
||||
0.5 -0.5 0
|
||||
<UV> { 0.99609375 0.5078125 }
|
||||
}
|
||||
<Vertex> 63 {
|
||||
0.5 0.5 0
|
||||
<UV> { 0.99609375 0.7421875 }
|
||||
}
|
||||
<Vertex> 64 {
|
||||
-0.5 0.5 0
|
||||
<UV> { 0.00390625 0.4921875 }
|
||||
}
|
||||
<Vertex> 65 {
|
||||
-0.5 -0.5 0
|
||||
<UV> { 0.00390625 0.2578125 }
|
||||
}
|
||||
<Vertex> 66 {
|
||||
0.5 -0.5 0
|
||||
<UV> { 0.12109375 0.2578125 }
|
||||
}
|
||||
<Vertex> 67 {
|
||||
0.5 0.5 0
|
||||
<UV> { 0.12109375 0.4921875 }
|
||||
}
|
||||
}
|
||||
<Group> face_a {
|
||||
<Polygon> {
|
||||
<TRef> { xbone-icons }
|
||||
<VertexRef> { 0 1 2 3 <Ref> { vpool } }
|
||||
}
|
||||
}
|
||||
<Group> face_b {
|
||||
<Polygon> {
|
||||
<TRef> { xbone-icons }
|
||||
<VertexRef> { 4 5 6 7 <Ref> { vpool } }
|
||||
}
|
||||
}
|
||||
<Group> dpad {
|
||||
<Polygon> {
|
||||
<TRef> { xbone-icons }
|
||||
<VertexRef> { 8 9 10 11 <Ref> { vpool } }
|
||||
}
|
||||
}
|
||||
<Group> dpad_down {
|
||||
<Polygon> {
|
||||
<TRef> { xbone-icons }
|
||||
<VertexRef> { 12 13 14 15 <Ref> { vpool } }
|
||||
}
|
||||
}
|
||||
<Group> dpad_left {
|
||||
<Polygon> {
|
||||
<TRef> { xbone-icons }
|
||||
<VertexRef> { 16 17 18 19 <Ref> { vpool } }
|
||||
}
|
||||
}
|
||||
<Group> dpad_right {
|
||||
<Polygon> {
|
||||
<TRef> { xbone-icons }
|
||||
<VertexRef> { 20 21 22 23 <Ref> { vpool } }
|
||||
}
|
||||
}
|
||||
<Group> dpad_up {
|
||||
<Polygon> {
|
||||
<TRef> { xbone-icons }
|
||||
<VertexRef> { 24 25 26 27 <Ref> { vpool } }
|
||||
}
|
||||
}
|
||||
<Group> XboxOne_LB {
|
||||
<Polygon> {
|
||||
<TRef> { xbone-icons }
|
||||
<VertexRef> { 28 29 30 31 <Ref> { vpool } }
|
||||
}
|
||||
}
|
||||
<Group> lstick {
|
||||
<Polygon> {
|
||||
<TRef> { xbone-icons }
|
||||
<VertexRef> { 32 33 34 35 <Ref> { vpool } }
|
||||
}
|
||||
}
|
||||
<Group> ltrigger {
|
||||
<Polygon> {
|
||||
<TRef> { xbone-icons }
|
||||
<VertexRef> { 36 37 38 39 <Ref> { vpool } }
|
||||
}
|
||||
}
|
||||
<Group> start {
|
||||
<Polygon> {
|
||||
<TRef> { xbone-icons }
|
||||
<VertexRef> { 40 41 42 43 <Ref> { vpool } }
|
||||
}
|
||||
}
|
||||
<Group> rshoulder {
|
||||
<Polygon> {
|
||||
<TRef> { xbone-icons }
|
||||
<VertexRef> { 44 45 46 47 <Ref> { vpool } }
|
||||
}
|
||||
}
|
||||
<Group> rstick {
|
||||
<Polygon> {
|
||||
<TRef> { xbone-icons }
|
||||
<VertexRef> { 48 49 50 51 <Ref> { vpool } }
|
||||
}
|
||||
}
|
||||
<Group> rtrigger {
|
||||
<Polygon> {
|
||||
<TRef> { xbone-icons }
|
||||
<VertexRef> { 52 53 54 55 <Ref> { vpool } }
|
||||
}
|
||||
}
|
||||
<Group> back {
|
||||
<Polygon> {
|
||||
<TRef> { xbone-icons }
|
||||
<VertexRef> { 56 57 58 59 <Ref> { vpool } }
|
||||
}
|
||||
}
|
||||
<Group> face_x {
|
||||
<Polygon> {
|
||||
<TRef> { xbone-icons }
|
||||
<VertexRef> { 60 61 62 63 <Ref> { vpool } }
|
||||
}
|
||||
}
|
||||
<Group> face_y {
|
||||
<Polygon> {
|
||||
<TRef> { xbone-icons }
|
||||
<VertexRef> { 64 65 66 67 <Ref> { vpool } }
|
||||
}
|
||||
}
|
BIN
samples/gamepad/models/xbone-icons.png
Normal file
BIN
samples/gamepad/models/xbone-icons.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
Loading…
x
Reference in New Issue
Block a user