type3games-engine
Version:
Modulární herní engine pro 2D hry v JavaScriptu
290 lines (236 loc) • 8.76 kB
JavaScript
/**
* @class Control
* @classdesc A key binding and input handling system with recording and playback support.
*/
class Control {
/**
* Initializes a new instance of the Control class.
* @param {Object} [obj] - The object that the bindings will apply to.
* @param {Object} [callbackMap=null] - A map of callback functions for imported bindings.
*/
constructor(obj, callbackMap = null) {
this.obj = obj;
this.bindings = new Map();
this.onceBindings = new Map();
this.releaseBindings = new Map();
this.releaseOnceBindings = new Map();
this.releaseTickKeys = {};
this.keyStates = new Map();
this.pressedThisFrame = new Set();
this.namedBindings = new Map();
this.namedCallbacks = new Map();
this.namedTypes = new Map();
this.capturing = null;
this.paused = false;
this.recording = false;
this.recordedEvents = [];
this.playingBack = false;
this.playbackFrame = 0;
this.playbackFrameCounter = 0;
if (callbackMap) {
this._importBindingsInternal(callbackMap);
}
this._setupEventListeners();
}
_setupEventListeners() {
window.addEventListener("keydown", (e) => this._handleKeyDown(e));
window.addEventListener("keyup", (e) => this._handleKeyUp(e));
}
_handleKeyDown(e) {
const key = e.key;
if (this.capturing) {
const { name, type, callback } = this.capturing;
this.setBinding(name, type, key, callback);
this.capturing = null;
return;
}
if (!this.keyStates.has(key)) {
this.keyStates.set(key, 0);
this.pressedThisFrame.add(key);
}
if (this.recording) {
this.recordedEvents.push({
type: "keydown",
key,
frame: this.playbackFrameCounter +1
});
}
}
_handleKeyUp(e) {
const key = e.key;
const duration = this.keyStates.get(key);
this.keyStates.delete(key);
this.pressedThisFrame.delete(key);
if (this.releaseBindings.has(key)) {
this.releaseBindings.get(key)(this.obj, duration);
}
if (this.releaseOnceBindings.has(key)) {
this.releaseOnceBindings.get(key)(this.obj, duration);
this.releaseOnceBindings.delete(key);
}
if (this.recording) {
this.recordedEvents.push({
type: "keyup",
key,
frame: this.playbackFrameCounter +1
});
}
}
update() {
if (!this.paused) {
for (const [key, duration] of this.keyStates.entries()) {
this.keyStates.set(key, duration + 1);
if (this.bindings.has(key)) {
this.bindings.get(key)(this.obj, duration + 1);
}
if (this.onceBindings.has(key) && this.pressedThisFrame.has(key)) {
this.onceBindings.get(key)(this.obj);
}
}
for (const key in this.releaseTickKeys) {
const data = this.releaseTickKeys[key];
if (data.ticks > 0) {
data.ticks--;
data.callback(this.obj);
} else {
delete this.releaseTickKeys[key];
}
}
this.pressedThisFrame.clear();
}
if (this.playingBack) {
while (this.recordedEvents.length && this.recordedEvents[0].frame == this.playbackFrameCounter) {
const event = this.recordedEvents.shift();
const simulatedEvent = new KeyboardEvent(event.type, { key: event.key });
//console.log(`Simulating ${event.type} for key: ${event.key} at frame ${event.frame}`);
if (event.type === "keydown") {
this._handleKeyDown(simulatedEvent);
} else {
this._handleKeyUp(simulatedEvent);
}
}
if (this.recordedEvents.length === 0) {
this.stopPlayback();
}
}
this.playbackFrameCounter++;
}
/** Starts recording key events. */
startRecording() {
this.recording = true;
this.recordedEvents = [];
}
/** Stops recording and returns the recorded data. */
stopRecording() {
this.recording = false;
return [...this.recordedEvents];
}
/** Starts playback of recorded events. */
startPlayback(events) {
this.recordedEvents = [...events];
this.playingBack = true;
}
/** Stops playback. */
stopPlayback() {
this.playingBack = false;
this.recordedEvents = [];
}
bind(name, key, callback) {
this.namedBindings.set(name, key);
this.namedCallbacks.set(name, callback);
this.namedTypes.set(name, "hold");
this.bindings.set(key, callback);
}
bindOnce(name, key, callback) {
this.namedBindings.set(name, key);
this.namedCallbacks.set(name, callback);
this.namedTypes.set(name, "once");
this.onceBindings.set(key, callback);
}
bindRelease(name, key, callback) {
this.namedBindings.set(name, key);
this.namedCallbacks.set(name, callback);
this.namedTypes.set(name, "release");
this.releaseBindings.set(key, callback);
}
bindReleaseOnce(name, key, callback) {
this.namedBindings.set(name, key);
this.namedCallbacks.set(name, callback);
this.namedTypes.set(name, "releaseOnce");
this.releaseOnceBindings.set(key, callback);
}
bindWithReleaseTick(name, key, callback) {
const combinedCallback = (obj, ticks) => callback(obj, ticks);
this.namedBindings.set(name, key);
this.namedCallbacks.set(name, combinedCallback);
this.namedTypes.set(name, "withReleaseTick");
this.bindings.set(key, combinedCallback);
this.releaseBindings.set(key, (obj, ticks) => {
this.releaseTickKeys[key] = {
ticks: 1,
callback: () => callback(obj, ticks),
};
});
}
captureBinding(name, type, callback) {
this.capturing = { name, type, callback };
}
setBinding(name, type, key, callback) {
const oldKey = this.namedBindings.get(name);
if (oldKey) {
this.bindings.delete(oldKey);
this.onceBindings.delete(oldKey);
this.releaseBindings.delete(oldKey);
this.releaseOnceBindings.delete(oldKey);
}
this.namedBindings.set(name, key);
this.namedCallbacks.set(name, callback);
this.namedTypes.set(name, type);
if (type === "hold") this.bind(name, key, callback);
else if (type === "once") this.bindOnce(name, key, callback);
else if (type === "release") this.bindRelease(name, key, callback);
else if (type === "releaseOnce") this.bindReleaseOnce(name, key, callback);
}
updateBinding(name, newCallback) {
const key = this.namedBindings.get(name);
const type = this.namedTypes.get(name);
if (!key || !type) return;
this.namedCallbacks.set(name, newCallback);
if (type === "hold") this.bindings.set(key, newCallback);
else if (type === "once") this.onceBindings.set(key, newCallback);
else if (type === "release") this.releaseBindings.set(key, newCallback);
else if (type === "releaseOnce") this.releaseOnceBindings.set(key, newCallback);
}
updateBindingKey(name, newKey) {
const callback = this.namedCallbacks.get(name);
const type = this.namedTypes.get(name);
if (!callback || !type) return;
this.setBinding(name, type, newKey, callback);
}
exportBindings() {
const out = {};
for (const [name, key] of this.namedBindings.entries()) {
out[name] = {
key,
type: this.namedTypes.get(name),
};
}
return out;
}
getBoundKey(name) {
return this.namedBindings.get(name) || null;
}
pause() {
this.paused = true;
}
unpause() {
this.paused = false;
}
_importBindingsInternal(callbackMap) {
for (const [name, { key, type }] of Object.entries(callbackMap)) {
const cb = callbackMap[name].callback;
if (cb) this.setBinding(name, type, key, cb);
}
}
}
export default Control;