Skip to content

Instantly share code, notes, and snippets.

@netpro2k
Last active February 15, 2018 02:09
Show Gist options
  • Save netpro2k/16e2f79c8774d1f1959216ca9018d487 to your computer and use it in GitHub Desktop.
Save netpro2k/16e2f79c8774d1f1959216ca9018d487 to your computer and use it in GitHub Desktop.
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