refactor: input greatness

This commit is contained in:
cha0s 2019-10-11 02:17:07 -05:00
parent 350ca435c1
commit fcf09044dc
4 changed files with 75 additions and 97 deletions

View File

@ -18,6 +18,7 @@ import {clearAnimation, setAnimation} from '@avocado/timing';
import {World} from '@avocado/physics/matter/world'; import {World} from '@avocado/physics/matter/world';
import {Room, RoomView} from '@avocado/topdown'; import {Room, RoomView} from '@avocado/topdown';
// 1st party. // 1st party.
import {actionIds} from '../common/action-ids';
import {augmentParserWithThroughput} from '../common/parser-throughput'; import {augmentParserWithThroughput} from '../common/parser-throughput';
import {SelfEntityPacket} from '../common/packets/self-entity.packet'; import {SelfEntityPacket} from '../common/packets/self-entity.packet';
import {WorldTime} from '../common/world-time.synchronized'; import {WorldTime} from '../common/world-time.synchronized';
@ -83,14 +84,21 @@ export class App extends decorate(class {}) {
// Input. // Input.
this.actionRegistry = new ActionRegistry(); this.actionRegistry = new ActionRegistry();
this.actionRegistry.mapKeysToActions(config.actionKeyMap); this.actionRegistry.mapKeysToActions(config.actionKeyMap);
this.actionState = this.actionRegistry.state; this.actionRegistry.setActionTransformerFor('UseItem', (type) => {
if ('keyDown' === type) {
return this.selfEntity.activeSlotIndex;
}
else {
return -1;
}
})
InputPacket.setActionIds(actionIds());
this.inputHandle = undefined; this.inputHandle = undefined;
this.actionRegistry.listen(this.stage.inputNormalizer); this.actionRegistry.listen(this.stage.inputNormalizer);
this.onBlur = this.onBlur.bind(this); this.onBlur = this.onBlur.bind(this);
this.onFocus = this.onFocus.bind(this); this.onFocus = this.onFocus.bind(this);
// Global keys. // Global keys.
this.pointingAt = [-1, -1]; this.pointingAt = [-1, -1];
this.pointerMovementHandle = undefined;
// Net. // Net.
this.AugmentedParser = augmentParserWithThroughput(SocketIoParser); this.AugmentedParser = augmentParserWithThroughput(SocketIoParser);
this.hasReceivedState = false; this.hasReceivedState = false;
@ -219,7 +227,7 @@ export class App extends decorate(class {}) {
} }
createMoveToNormal(position) { createMoveToNormal(position) {
if (!this.selfEntity) { if (!this.selfEntity || !this.selfEntity.is('existent')) {
return; return;
} }
const realEntityPosition = Vector.sub( const realEntityPosition = Vector.sub(
@ -286,14 +294,6 @@ export class App extends decorate(class {}) {
const slotIndex = (parseInt(key) + 9) % 10; const slotIndex = (parseInt(key) + 9) % 10;
this.setActiveSlotIndex(slotIndex); this.setActiveSlotIndex(slotIndex);
break; break;
case 'ArrowLeft':
if (this.selfEntity) {
this.actionRegistry.state = this.actionRegistry.state.set(
'UseItem',
this.selfEntity.activeSlotIndex
);
}
break;
} }
} }
@ -415,10 +415,12 @@ export class App extends decorate(class {}) {
readConfig() { readConfig() {
return { return {
actionKeyMap: { actionKeyMap: {
'w': 'MoveUp', 'MoveUp': 'w',
'a': 'MoveLeft', 'MoveLeft': 'a',
's': 'MoveDown', 'MoveDown': 's',
'd': 'MoveRight', 'MoveRight': 'd',
'UseItem': 'ArrowLeft',
'Interact': 'Enter',
}, },
connectionUrl: window.location.protocol + '//' + window.location.hostname + ':8420/', connectionUrl: window.location.protocol + '//' + window.location.hostname + ':8420/',
doPhysicsSimulation: true, doPhysicsSimulation: true,
@ -533,43 +535,15 @@ export class App extends decorate(class {}) {
this.stage.element.addEventListener('focus', this.onFocus); this.stage.element.addEventListener('focus', this.onFocus);
// Input messages. // Input messages.
this.inputHandle = setInterval(() => { this.inputHandle = setInterval(() => {
if (this.actionState !== this.actionRegistry.state) { const inputStream = this.actionRegistry.drain();
this.actionState = this.actionRegistry.state; if (inputStream.length > 0) {
this.socket.send(InputPacket.fromState(this.actionState)); // Inject input.
this.actionRegistry.state = this.actionRegistry.state.delete( if (this.selfEntity) {
'UseItem' this.selfEntity.inputStream = inputStream;
); }
this.socket.send(new InputPacket(inputStream));
} }
}, 1000 * config.inputFrequency); }, 1000 * config.inputFrequency);
// Mouse/touch movement.
this.pointerMovementHandle = setInterval(() => {
do {
let normal;
if (this.isPointingAtAnything()) {
const toVector = Vector.scale(this.pointingAt, this.isDebugging ? 2 : 1);
normal = this.createMoveToNormal(toVector);
if (normal) {
this.actionRegistry.state = this.actionRegistry.state.withMutations((state) => {
if (normal[0]) {
state.set('MoveToX', Math.floor(normal[0] * 127));
}
if (normal[1]) {
state.set('MoveToY', Math.floor(normal[1] * 127));
}
});
break;
}
}
this.actionRegistry.state = this.actionRegistry.state.withMutations((state) => {
if (!normal || 0 === normal[0]) {
state.delete('MoveToX');
}
if (!normal || 0 === normal[1]) {
state.delete('MoveToY');
}
});
} while (false);
}, 1000 * config.pointerMovementFrequency);
// Focus the stage. // Focus the stage.
this.stage.focus(); this.stage.focus();
this.isFocused = true; this.isFocused = true;
@ -606,10 +580,6 @@ export class App extends decorate(class {}) {
const now = performance.now(); const now = performance.now();
const elapsed = (now - lastTime) / 1000; const elapsed = (now - lastTime) / 1000;
lastTime = now; lastTime = now;
// Inject input.
if (this.selfEntity) {
this.selfEntity.inputState = this.actionState;
}
// Tick. // Tick.
this.worldTime.tick(elapsed); this.worldTime.tick(elapsed);
if (this.room) { if (this.room) {
@ -621,8 +591,6 @@ export class App extends decorate(class {}) {
} }
stopProcessingInput() { stopProcessingInput() {
clearInterval(this.pointerMovementHandle);
this.pointerMovementHandle = undefined;
clearInterval(this.inputHandle); clearInterval(this.inputHandle);
this.inputHandle = undefined; this.inputHandle = undefined;
this.stage.element.removeEventListener('focus', this.onFocus); this.stage.element.removeEventListener('focus', this.onFocus);

11
common/action-ids.js Normal file
View File

@ -0,0 +1,11 @@
export function actionIds() {
return {
'MoveUp': 0,
'MoveLeft': 1,
'MoveDown': 2,
'MoveRight': 3,
'UseItem': 4,
'Interact': 5,
};
}

View File

@ -1,7 +1,6 @@
import * as I from 'immutable';
import {TickingPromise} from '@avocado/core'; import {TickingPromise} from '@avocado/core';
import {Trait} from '@avocado/entity'; import {Trait} from '@avocado/entity';
import {Vector} from '@avocado/math';
// Input handling. // Input handling.
export class Controllable extends Trait { export class Controllable extends Trait {
@ -12,56 +11,54 @@ export class Controllable extends Trait {
constructor(entity, params, state) { constructor(entity, params, state) {
super(entity, params, state); super(entity, params, state);
this._inputState = I.Map();
this._itemPromise = undefined; this._itemPromise = undefined;
this._movementVector = [0, 0];
} }
set inputState(inputState) { set inputStream(inputStream) {
this._inputState = I.Map(inputState); for (let i = 0; i < inputStream.length; i++) {
const {action, value} = inputStream[i];
const normalized = 0 === value ? -1 : 1;
switch (action) {
case 'MoveUp':
this._movementVector[1] -= normalized;
break;
case 'MoveRight':
this._movementVector[0] += normalized;
break;
case 'MoveDown':
this._movementVector[1] += normalized;
break;
case 'MoveLeft':
this._movementVector[0] -= normalized;
break;
case 'UseItem':
if (AVOCADO_SERVER) { if (AVOCADO_SERVER) {
if (inputState.has('UseItem')) { if (-1 !== value && !this._itemPromise) {
const slotIndex = inputState.get('UseItem'); this._itemPromise = this.entity.useItemInSlot(value);
this._itemPromise = this.entity.useItemInSlot(slotIndex);
Promise.resolve(this._itemPromise).then(() => { Promise.resolve(this._itemPromise).then(() => {
this._itemPromise = undefined; this._itemPromise = undefined;
}); });
} }
} }
break;
}
}
if (Vector.isZero(this._movementVector)) {
this.entity.currentAnimation = 'idle';
}
else {
this.entity.currentAnimation = 'moving';
}
} }
tick(elapsed) { tick(elapsed) {
if (this._itemPromise && this._itemPromise instanceof TickingPromise) { if (this._itemPromise && this._itemPromise instanceof TickingPromise) {
this._itemPromise.tick(elapsed); this._itemPromise.tick(elapsed);
} }
const {_inputState: inputState} = this; if (!Vector.isZero(this._movementVector)) {
if (0 === inputState.size) { this.entity.requestMovement(this._movementVector);
this.entity.currentAnimation = 'idle';
return;
} }
const movementVector = [0, 0];
if (inputState.has('MoveToX')) {
movementVector[0] = inputState.get('MoveToX') / 127;
}
if (inputState.has('MoveToY')) {
movementVector[1] = inputState.get('MoveToY') / 127;
}
if (inputState.has('MoveUp')) {
movementVector[1] -= 1;
}
if (inputState.has('MoveRight')) {
movementVector[0] += 1;
}
if (inputState.has('MoveDown')) {
movementVector[1] += 1;
}
if (inputState.has('MoveLeft')) {
movementVector[0] -= 1;
}
if (0 === movementVector[0] && 0 === movementVector[1]) {
return;
}
this.entity.requestMovement(movementVector);
this.entity.currentAnimation = 'moving';
} }
} }

View File

@ -8,6 +8,7 @@ import {World} from '@avocado/physics/matter/world';
import {Ticker} from '@avocado/timing'; import {Ticker} from '@avocado/timing';
import {Room} from '@avocado/topdown'; import {Room} from '@avocado/topdown';
// 1st party. // 1st party.
import {actionIds} from '../common/action-ids';
import {SelfEntityPacket} from '../common/packets/self-entity.packet'; import {SelfEntityPacket} from '../common/packets/self-entity.packet';
import {WorldTime} from '../common/world-time.synchronized'; import {WorldTime} from '../common/world-time.synchronized';
import {createEntityForConnection} from './create-entity-for-connection'; import {createEntityForConnection} from './create-entity-for-connection';
@ -31,6 +32,7 @@ export default class Game {
// State synchronization. // State synchronization.
this.informTicker = new Ticker(config.informInterval); this.informTicker = new Ticker(config.informInterval);
this.informTicker.on('tick', this.inform, this); this.informTicker.on('tick', this.inform, this);
InputPacket.setActionIds(actionIds());
// Simulation. // Simulation.
this.mainLoopHandle = setInterval( this.mainLoopHandle = setInterval(
this.createMainLoop(), this.createMainLoop(),
@ -104,7 +106,7 @@ export default class Game {
const {entity} = socket; const {entity} = socket;
return (packet) => { return (packet) => {
if (packet instanceof InputPacket) { if (packet instanceof InputPacket) {
entity.inputState = packet.toState(); entity.inputStream = packet.data;
} }
}; };
} }