UNPKG

@dcl/ecs

Version:
223 lines (222 loc) • 8.92 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.createInputSystem = void 0; const components = __importStar(require("../components")); const InputCommands = [ 0 /* InputAction.IA_POINTER */, 1 /* InputAction.IA_PRIMARY */, 2 /* InputAction.IA_SECONDARY */, 4 /* InputAction.IA_FORWARD */, 5 /* InputAction.IA_BACKWARD */, 6 /* InputAction.IA_RIGHT */, 7 /* InputAction.IA_LEFT */, 8 /* InputAction.IA_JUMP */, 9 /* InputAction.IA_WALK */, 10 /* InputAction.IA_ACTION_3 */, 11 /* InputAction.IA_ACTION_4 */, 12 /* InputAction.IA_ACTION_5 */, 13 /* InputAction.IA_ACTION_6 */ ]; const InputStateUpdateSystemPriority = 1 << 20; /** * @public * ____DO NOT USE ____ use inputSystem instead */ function createInputSystem(engine) { const PointerEventsResult = components.PointerEventsResult(engine); const globalState = { previousFrameMaxTimestamp: 0, currentFrameMaxTimestamp: 0, buttonState: new Map(), thisFrameCommands: [] }; function findLastAction(pointerEventType, inputAction, entity) { const ascendingTimestampIterator = PointerEventsResult.get(entity); for (const command of Array.from(ascendingTimestampIterator).reverse()) { if (command.button === inputAction && command.state === pointerEventType) { return command; } } } function* findCommandsByActionDescending(inputAction, entity) { const ascendingTimestampIterator = PointerEventsResult.get(entity); for (const command of Array.from(ascendingTimestampIterator).reverse()) { if (command.button === inputAction) { yield command; } } } function buttonStateUpdateSystem() { // first store the previous' frame timestamp let maxTimestamp = globalState.currentFrameMaxTimestamp; globalState.previousFrameMaxTimestamp = maxTimestamp; if (globalState.thisFrameCommands.length) { globalState.thisFrameCommands = []; } // then iterate over all new commands for (const [, commands] of engine.getEntitiesWith(PointerEventsResult)) { // TODO: adapt the gset component to have a cached "reversed" option by default const arrayCommands = Array.from(commands); for (let i = arrayCommands.length - 1; i >= 0; i--) { const command = arrayCommands[i]; if (command.timestamp > maxTimestamp) { maxTimestamp = command.timestamp; } if (command.timestamp > globalState.previousFrameMaxTimestamp) { globalState.thisFrameCommands.push(command); } if (command.state === 0 /* PointerEventType.PET_UP */ || command.state === 1 /* PointerEventType.PET_DOWN */) { const prevCommand = globalState.buttonState.get(command.button); if (!prevCommand || command.timestamp > prevCommand.timestamp) { globalState.buttonState.set(command.button, command); } else { // since we are iterating a descending array, we can early finish the // loop break; } } } } // update current frame's max timestamp globalState.currentFrameMaxTimestamp = maxTimestamp; } engine.addSystem(buttonStateUpdateSystem, InputStateUpdateSystemPriority, '@dcl/ecs#inputSystem'); function timestampIsCurrentFrame(timestamp) { if (timestamp > globalState.previousFrameMaxTimestamp && timestamp <= globalState.currentFrameMaxTimestamp) { return true; } else { return false; } } function getClick(inputAction, entity) { if (inputAction !== 3 /* InputAction.IA_ANY */) { return findClick(inputAction, entity); } for (const input of InputCommands) { const cmd = findClick(input, entity); if (cmd) return cmd; } return null; } function findClick(inputAction, entity) { let down = null; let up = null; // We search the last UP & DOWN command sorted by timestamp descending for (const it of findCommandsByActionDescending(inputAction, entity)) { if (!up) { if (it.state === 0 /* PointerEventType.PET_UP */) { up = it; continue; } } else if (!down) { if (it.state === 1 /* PointerEventType.PET_DOWN */) { down = it; break; } } } if (!up || !down) return null; // If the DOWN command has happen before the UP commands, it means that that a clicked has happen if (down.timestamp < up.timestamp && timestampIsCurrentFrame(up.timestamp)) { return { up, down }; } return null; } function getInputCommandFromEntity(inputAction, pointerEventType, entity) { if (inputAction !== 3 /* InputAction.IA_ANY */) { return findInputCommand(inputAction, pointerEventType, entity); } for (const input of InputCommands) { const cmd = findInputCommand(input, pointerEventType, entity); if (cmd) return cmd; } return null; } function getInputCommand(inputAction, pointerEventType, entity) { if (entity) { return getInputCommandFromEntity(inputAction, pointerEventType, entity); } else { for (const command of globalState.thisFrameCommands) { if ((command.button === inputAction || inputAction === 3 /* InputAction.IA_ANY */) && command.state === pointerEventType) { return command; } } return null; } } function findInputCommand(inputAction, pointerEventType, entity) { // We search the last pointer Event command sorted by timestamp const command = findLastAction(pointerEventType, inputAction, entity); if (!command) return null; if (timestampIsCurrentFrame(command.timestamp)) { return command; } else { return null; } } // returns true if there was a DOWN (in any past frame), and then an UP in the last frame function isClicked(inputAction, entity) { return getClick(inputAction, entity) !== null; } // returns true if the provided last action was triggered in the last frame function isTriggered(inputAction, pointerEventType, entity) { if (entity) { const command = findLastAction(pointerEventType, inputAction, entity); return (command && timestampIsCurrentFrame(command.timestamp)) || false; } else { for (const command of globalState.thisFrameCommands) { if ((command.button === inputAction || inputAction === 3 /* InputAction.IA_ANY */) && command.state === pointerEventType) { return true; } } return false; } } // returns the global state of the input. This global state is updated from the system function isPressed(inputAction) { return globalState.buttonState.get(inputAction)?.state === 1 /* PointerEventType.PET_DOWN */; } return { isPressed, getClick, getInputCommand, isClicked, isTriggered }; } exports.createInputSystem = createInputSystem;