-
-
Save netpro2k/16e2f79c8774d1f1959216ca9018d487 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Action { | |
constructor() { | |
// input sources would be managed by the lib somehow, creating and destroying them as devices are detected | |
this.inputSources = [ | |
new GamepadInputSource("oculus", 0, oculusGamepadMapping), | |
new GamepadInputSource("oculus", 1, oculusGamepadMapping), | |
]; | |
// filters would be managed by the lib, constructor properties coming from binding definition | |
this.filters = [ | |
new InvertVector2Filter("inverted_move", { y: true }, { vec: Action.idForPath("/devices/oculus_touch/left/thumbstick") }) | |
]; | |
// Filled from the binding definition, changes as | |
this.bindings = [ | |
{ | |
source: "/devices/oculus_touch/left/x_pressed", | |
destination: "/gameplay/in/jump" | |
}, | |
{ | |
source: "/filters/invert_vector2/inverted_move", | |
destination: "/gameplay/in/move", | |
}, | |
// ... | |
] | |
// These would be filled by methods like | |
// Action.onBoolActionOn(pathId, function(e) { | |
// e.details = { | |
// pathId | |
// } | |
// }) | |
// Action.onBoolActionOff(pathId, function(e) { | |
// e.details = { | |
// pathId | |
// } | |
// }) | |
// Action.onActionChanged(pathId, function(e) { | |
// e.details = { | |
// pathId, value, prevValue | |
// } | |
// }) | |
this.boolOnListeners = { | |
"/actions/gameplay/in/jump": [function() {}] | |
} | |
this.boolOffListeners = { | |
"/actions/gameplay/in/jump": [function() {}] | |
} | |
this.changeListeners = { | |
"/actions/gameplay/in/move": [function() {}] | |
} | |
} | |
tick(dt) { | |
this.prevInputFrame = this.curInputFrame; | |
this.curInputFrame = new InputFrame(); // Probably want to do a tripple buffer here instead where we re-use the input frame from 2 frames ago to avoid GC | |
for (let i = 0; i < inputSource.length; i++) { | |
const inputSource = inputSource[i]; | |
// I don't love the ideao of passing around this mutable thing and would rather each InputSource and Filter returned some subset of the input frame but not sure of a GC friendly way to do that | |
inputSource.fillInputFrame(this.curInputFrame); | |
} | |
for (let i = 0; i < filters.length; i++) { | |
const filter = filters[i]; | |
filter.fillInputFrame(this.curInputFrame); | |
} | |
for (let i = 0; i < bindings.length; i++) { | |
const binding = bindings[i]; | |
const actionDef = this.getActionDefForBinding(binding) | |
switch (actionDef.type) { | |
case "bool": | |
frame.setBool(Action.idForPath(binding.destination), frame.getBool(Action.idForPath(binding.source))) | |
break; | |
// ... same for all data types | |
default: | |
break; | |
} | |
} | |
this.fireEvents(); | |
} | |
// This could arguably also be in a different lib, but including here just to work through how it would work | |
fireEvents() { | |
for (const path of boolOnListeners) { | |
if(this.curInputFrame.getBoolOn(Action.idForPath(path))) { | |
fireAll(boolOnListeners[path]) | |
} | |
} | |
for (const path of boolOffListeners) { | |
if(this.curInputFrame.getBoolOff(Action.idForPath(path))) { | |
fireAll(boolOffListeners[path]) | |
} | |
} | |
for (const path of changeListeners) { | |
const pathId = Action.idForPath(path) | |
const actionDef = this.getActionDef(path) | |
switch (actionDef.type) { | |
case "vector2": | |
const prevValue = this.prevInputFrame.getVector2(pathId); | |
const value = this.curInputFrame.getVector2(pathId); | |
if(!equal(prevValue, value)) { | |
fireAll(changeListeners[path], {value, prevValue}); | |
} | |
break; | |
// ... same for all data types | |
default: | |
break; | |
} | |
} | |
} | |
getBoolOn(pathId) { | |
this.curInputFrame.getBool(pathId) && !this.prevInputFrame.getBool(pathId); | |
} | |
fillVector2(pathId, vec) { | |
this.curInputFrame.fillVector2(pathId, vec); | |
} | |
// .. more accessors | |
} | |
class InputFrame { | |
setBool(pathId, pressed) {} | |
setFloat(pathId, value) {} | |
setVector2(pathId, vec) {} | |
setPose(pathId, pose) {} | |
getBool(pathId) {} | |
getFloat(pathId) {} | |
getVector2(pathId) {} | |
getPose(pathId) {} | |
fillVector2(pathId, outVec) {} | |
fillPose(pathId, outPose) {} | |
} | |
// Filters and InputSources should maybe just be functions, but some might want to be stateful | |
class Filter { | |
static schema = { | |
type: "", | |
properties: {}, | |
sources: {}, | |
destination: {} | |
} | |
constructor(name, props, sources) { | |
this.name = name; | |
this.props = props; | |
this.sources = sources; | |
this.validateInputsAgainstSchema() | |
} | |
} | |
class InvertVector2Filter extends Filter { | |
static schema = { | |
type: "invert_vector2", | |
properties: { | |
x: { type: "bool", default: false }, | |
y: { type: "bool", default: false } | |
}, | |
sources: { | |
vec: { type: "vector2" } | |
}, | |
destination: { type: "vector2" } | |
} | |
fillInputFrame(frame) { | |
const inVec = frame.getVector2(this.sources.vec); | |
frame.setVector2( | |
Action.idForPath(`/filters/${InvertVector2Filter.schema.type}/${this.name}`), | |
[ | |
this.props.x ? !inVec[0] : inVec[0], | |
this.props.y ? !inVec[1] : inVec[1], | |
] | |
) | |
} | |
} | |
class InputSource { | |
fillInputFrame(frame) { } | |
} | |
class GamepadInputSource extends InputSource { | |
constructor(deviceName, gamepadIndex, gamepadMapping) { | |
this.deviceName = deviceName; | |
this.gamepadMapping = gamepadMapping | |
this.gamepadIndex = gamepadIndex | |
} | |
// All the string concating seems obviously bad, there should probably be methods for generating specifc kinds of semantic paths | |
// function pathIdForDeviceAction(deviceName, actionName) { | |
// Action.idForPath(`/devices/${deviceName}/${actionName}`) | |
// } | |
getPathId(gamepad, property) { | |
return Action.idForPath(`/device/${this.deviceName}/${this.gamepadMapping.handed ? gamepad.hand : gamepad.index}/${property}`); | |
} | |
fillInputFrame(frame) { | |
const gamepad = navigator.getGamepads()[this.gamepadIndex]; | |
if(this.gamepadMapping.pose) { | |
frame.setPose(this.getPathId(gamepad, "pose"), [gamepad.pose.position, gamepad.pose.orientation]); | |
} | |
for (const buttonName of this.gamepadMapping.buttons) { | |
frame.setBool(this.getPathId(gamepad, buttonName), gamepad.buttons[this.gamepadMapping.buttons[buttonName]].pressed); | |
} | |
for (const analogName of this.gamepadMapping.analogs) { | |
frame.setFloat(this.getPathId(gamepad, analogNames), gamepad.buttons[this.gamepadMapping.analogs[analogName]].value); | |
} | |
for (const axisPairName of this.gamepadMapping.axisPairs) { | |
const pair = this.gamepadMapping.axisPairs[axisPairName]; | |
frame.setVector2( | |
this.getPathId(gamepad, axisPairName), | |
[gamepad.axes[pair[0]], gamepad.axes[pair[1]]] | |
); | |
} | |
} | |
} | |
// ignoring touch events and differences between left/right hand buttons for now | |
const oculusGamepadMapping = { | |
buttons: { | |
'thumbstick_pressed': 0, | |
'grip': 2, | |
'a': 3, | |
'b': 4, | |
'x': 3, | |
'y': 4 | |
}, | |
analogs: { | |
"trigger": 1 | |
}, | |
axisPairs: { | |
"thumbstick": [0, 1] | |
}, | |
pose: true, | |
handed: true | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment