playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
378 lines (377 loc) • 11.2 kB
JavaScript
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
import {
ACTION_GAMEPAD,
ACTION_KEYBOARD,
ACTION_MOUSE,
PAD_1,
PAD_L_STICK_X,
PAD_L_STICK_Y,
PAD_R_STICK_X,
PAD_R_STICK_Y
} from "./constants.js";
import { Keyboard } from "./keyboard.js";
import { Mouse } from "./mouse.js";
class Controller {
/**
* Create a new instance of a Controller.
*
* @param {Element} [element] - Element to attach Controller to.
* @param {object} [options] - Optional arguments.
* @param {Keyboard} [options.keyboard] - A Keyboard object to use.
* @param {Mouse} [options.mouse] - A Mouse object to use.
* @param {GamePads} [options.gamepads] - A Gamepads object to use.
* @example
* const c = new pc.Controller(document);
*
* // Register the "fire" action and assign it to both the Enter key and the space bar.
* c.registerKeys("fire", [pc.KEY_ENTER, pc.KEY_SPACE]);
*/
constructor(element, options = {}) {
/**
* @type {Keyboard|null}
* @private
*/
__publicField(this, "_keyboard");
/**
* @type {Mouse|null}
* @private
*/
__publicField(this, "_mouse");
/**
* @type {GamePads|null}
* @private
*/
__publicField(this, "_gamepads");
/**
* @type {Element|null}
* @private
*/
__publicField(this, "_element", null);
/** @private */
__publicField(this, "_actions", {});
/** @private */
__publicField(this, "_axes", {});
/** @private */
__publicField(this, "_axesValues", {});
this._keyboard = options.keyboard || null;
this._mouse = options.mouse || null;
this._gamepads = options.gamepads || null;
if (element) {
this.attach(element);
}
}
/**
* Attach Controller to an Element. This is required before you can monitor for key/mouse
* inputs.
*
* @param {Element} element - The element to attach mouse and keyboard event handler too.
*/
attach(element) {
this._element = element;
if (this._keyboard) {
this._keyboard.attach(element);
}
if (this._mouse) {
this._mouse.attach(element);
}
}
/**
* Detach Controller from an Element. This should be done before the Controller is destroyed.
*/
detach() {
if (this._keyboard) {
this._keyboard.detach();
}
if (this._mouse) {
this._mouse.detach();
}
this._element = null;
}
/**
* Disable the context menu usually activated with the right mouse button.
*/
disableContextMenu() {
if (!this._mouse) {
this._enableMouse();
}
this._mouse.disableContextMenu();
}
/**
* Enable the context menu usually activated with the right mouse button. This is enabled by
* default.
*/
enableContextMenu() {
if (!this._mouse) {
this._enableMouse();
}
this._mouse.enableContextMenu();
}
/**
* Update the Keyboard and Mouse handlers.
*
* @param {object} dt - The time since the last frame.
*/
update(dt) {
if (this._keyboard) {
this._keyboard.update();
}
if (this._mouse) {
this._mouse.update();
}
if (this._gamepads) {
this._gamepads.update();
}
this._axesValues = {};
for (const key in this._axes) {
this._axesValues[key] = [];
}
}
/**
* Helper function to append an action.
*
* @param {string} action_name - The name of the action.
* @param {object} action - An action object to add.
* @param {ACTION_KEYBOARD | ACTION_MOUSE | ACTION_GAMEPAD} action.type - The name of the action.
* @param {number[]} [action.keys] - Keyboard: A list of keycodes e.g. `[pc.KEY_A, pc.KEY_ENTER]`.
* @param {number} [action.button] - Mouse: e.g. `pc.MOUSEBUTTON_LEFT` - Gamepad: e.g. `pc.PAD_FACE_1`
* @param {number} [action.pad] - Gamepad: An index of the pad to register (use {@link PAD_1}, etc).
*/
appendAction(action_name, action) {
this._actions[action_name] = this._actions[action_name] || [];
this._actions[action_name].push(action);
}
/**
* Register a new action which is enabled when the supplied keys are pressed.
*
* @param {string} action - The name of the action.
* @param {number[]} keys - A list of keycodes.
* @throws {Error} If the action is already registered, or if `keys` is undefined.
*/
registerKeys(action, keys) {
if (!this._keyboard) {
this._enableKeyboard();
}
if (this._actions[action]) {
throw new Error(`Action: ${action} already registered`);
}
if (keys === void 0) {
throw new Error("Invalid button");
}
if (!keys.length) {
keys = [keys];
}
this.appendAction(action, {
type: ACTION_KEYBOARD,
keys
});
}
/**
* Create or update an action which is enabled when the supplied mouse button is pressed.
*
* @param {string} action - The name of the action.
* @param {number} button - The mouse button.
*/
registerMouse(action, button) {
if (!this._mouse) {
this._enableMouse();
}
if (button === void 0) {
throw new Error("Invalid button");
}
this.appendAction(action, {
type: ACTION_MOUSE,
button
});
}
/**
* Create or update an action which is enabled when the gamepad button is pressed.
*
* @param {string} action - The name of the action.
* @param {number} pad - The index of the pad to register (use {@link PAD_1}, etc).
* @param {number} button - The pad button.
*/
registerPadButton(action, pad, button) {
if (button === void 0) {
throw new Error("Invalid button");
}
this.appendAction(action, {
type: ACTION_GAMEPAD,
button,
pad
});
}
/**
* Register an action against a controller axis.
*
* @param {object} [options] - Optional options object.
* @param {number} [options.pad] - The index of the game pad to register for (use {@link PAD_1}, etc).
*/
registerAxis(options) {
const name = options.name;
if (!this._axes[name]) {
this._axes[name] = [];
}
const i = this._axes[name].push(name);
options = options || {};
options.pad = options.pad || PAD_1;
const bind = function(controller, source, value, key) {
switch (source) {
case "mousex":
controller._mouse.on("mousemove", (e) => {
controller._axesValues[name][i] = e.dx / 10;
});
break;
case "mousey":
controller._mouse.on("mousemove", (e) => {
controller._axesValues[name][i] = e.dy / 10;
});
break;
case "key":
controller._axes[name].push(() => {
return controller._keyboard.isPressed(key) ? value : 0;
});
break;
case "padrx":
controller._axes[name].push(() => {
return controller._gamepads.getAxis(options.pad, PAD_R_STICK_X);
});
break;
case "padry":
controller._axes[name].push(() => {
return controller._gamepads.getAxis(options.pad, PAD_R_STICK_Y);
});
break;
case "padlx":
controller._axes[name].push(() => {
return controller._gamepads.getAxis(options.pad, PAD_L_STICK_X);
});
break;
case "padly":
controller._axes[name].push(() => {
return controller._gamepads.getAxis(options.pad, PAD_L_STICK_Y);
});
break;
default:
throw new Error("Unknown axis");
}
};
bind(this, options.positive, 1, options.positiveKey);
if (options.negativeKey || options.negative !== options.positive) {
bind(this, options.negative, -1, options.negativeKey);
}
}
/**
* Returns true if the current action is enabled.
*
* @param {string} actionName - The name of the action.
* @returns {boolean} True if the action is enabled.
*/
isPressed(actionName) {
if (!this._actions[actionName]) {
return false;
}
const length = this._actions[actionName].length;
for (let index = 0; index < length; ++index) {
const action = this._actions[actionName][index];
switch (action.type) {
case ACTION_KEYBOARD:
if (this._keyboard) {
const len = action.keys.length;
for (let i = 0; i < len; i++) {
if (this._keyboard.isPressed(action.keys[i])) {
return true;
}
}
}
break;
case ACTION_MOUSE:
if (this._mouse && this._mouse.isPressed(action.button)) {
return true;
}
break;
case ACTION_GAMEPAD:
if (this._gamepads && this._gamepads.isPressed(action.pad, action.button)) {
return true;
}
break;
}
}
return false;
}
/**
* Returns true if the action was enabled this since the last update.
*
* @param {string} actionName - The name of the action.
* @returns {boolean} True if the action was enabled this since the last update.
*/
wasPressed(actionName) {
if (!this._actions[actionName]) {
return false;
}
const length = this._actions[actionName].length;
for (let index = 0; index < length; ++index) {
const action = this._actions[actionName][index];
switch (action.type) {
case ACTION_KEYBOARD:
if (this._keyboard) {
const len = action.keys.length;
for (let i = 0; i < len; i++) {
if (this._keyboard.wasPressed(action.keys[i])) {
return true;
}
}
}
break;
case ACTION_MOUSE:
if (this._mouse && this._mouse.wasPressed(action.button)) {
return true;
}
break;
case ACTION_GAMEPAD:
if (this._gamepads && this._gamepads.wasPressed(action.pad, action.button)) {
return true;
}
break;
}
}
return false;
}
getAxis(name) {
let value = 0;
if (this._axes[name]) {
const len = this._axes[name].length;
for (let i = 0; i < len; i++) {
if (typeof this._axes[name][i] === "function") {
const v = this._axes[name][i]();
if (Math.abs(v) > Math.abs(value)) {
value = v;
}
} else if (this._axesValues[name]) {
if (Math.abs(this._axesValues[name][i]) > Math.abs(value)) {
value = this._axesValues[name][i];
}
}
}
}
return value;
}
_enableMouse() {
this._mouse = new Mouse();
if (!this._element) {
throw new Error("Controller must be attached to an Element");
}
this._mouse.attach(this._element);
}
_enableKeyboard() {
this._keyboard = new Keyboard();
if (!this._element) {
throw new Error("Controller must be attached to an Element");
}
this._keyboard.attach(this._element);
}
}
export {
Controller
};