Add touch joystick movement

This commit is contained in:
schmelczerandras 2020-10-23 18:06:56 +02:00
parent 23fa7646d6
commit 8b49b44ebf
7 changed files with 161 additions and 95 deletions

View file

@ -0,0 +1,108 @@
import { vec2 } from 'gl-matrix';
import {
CommandGenerator,
SecondaryActionCommand,
TernaryActionCommand,
MoveActionCommand,
last,
} from 'shared';
import { Game } from '../../game';
import { OptionsHandler } from '../../options-handler';
export class TouchJoystickListener extends CommandGenerator {
private joystick: HTMLElement;
private joystickButton: HTMLElement;
private isJoystickActive = false;
private touchStartPosition!: vec2;
constructor(
target: HTMLElement,
private overlay: HTMLElement,
private readonly game: Game,
) {
super();
this.joystick = document.createElement('div');
this.joystick.className = 'joystick';
this.joystickButton = document.createElement('div');
this.joystick.appendChild(this.joystickButton);
target.addEventListener('touchstart', (event: TouchEvent) => {
event.preventDefault();
if (this.isJoystickActive) {
const center = vec2.fromValues(
last(event.touches)!.clientX,
last(event.touches)!.clientY,
);
this.sendCommandToSubcribers(
new SecondaryActionCommand(this.game.displayToWorldCoordinates(center)),
);
}
});
target.addEventListener('touchmove', (event: TouchEvent) => {
event.preventDefault();
if (!this.isJoystickActive) {
this.isJoystickActive = true;
this.overlay.appendChild(this.joystick);
this.touchStartPosition = vec2.fromValues(
event.touches[0].clientX,
event.touches[0].clientY,
);
this.joystickButton.style.transform = `translateX(-50%) translateY(-50%)`;
this.joystick.style.transform = `translateX(${this.touchStartPosition.x}px) translateY(${this.touchStartPosition.y}px) translateX(-50%) translateY(-50%)`;
}
const touchPosition = vec2.fromValues(
event.touches[0].clientX,
event.touches[0].clientY,
);
const movement = vec2.subtract(
vec2.create(),
touchPosition,
this.touchStartPosition,
);
vec2.scale(movement, movement, 1 / 3);
const length = vec2.length(movement);
const maxLength = 20;
vec2.scale(movement, movement, Math.min(1, maxLength / length));
this.joystickButton.style.transform = `translateX(${movement.x}px) translateY(${movement.y}px) translateX(-50%) translateY(-50%)`;
vec2.set(movement, movement.x, -movement.y);
if (vec2.squaredLength(movement) > 0) {
vec2.normalize(movement, movement);
this.sendCommandToSubcribers(
new MoveActionCommand(movement, OptionsHandler.options.relativeMovementEnabled),
);
}
});
target.addEventListener('touchend', (event: TouchEvent) => {
event.preventDefault();
if (!this.isJoystickActive) {
const center = vec2.fromValues(
event.changedTouches[0].clientX,
event.changedTouches[0].clientY,
);
this.sendCommandToSubcribers(
new SecondaryActionCommand(this.game.displayToWorldCoordinates(center)),
);
} else if (event.touches.length === 0) {
this.isJoystickActive = false;
this.joystick.parentElement?.removeChild(this.joystick);
this.sendCommandToSubcribers(
new MoveActionCommand(
vec2.create(),
OptionsHandler.options.relativeMovementEnabled,
),
);
}
});
}
}

View file

@ -1,80 +0,0 @@
import { vec2 } from 'gl-matrix';
import {
CommandGenerator,
PrimaryActionCommand,
SecondaryActionCommand,
TernaryActionCommand,
MoveActionCommand,
} from 'shared';
import { Game } from '../../game';
import { OptionsHandler } from '../../options-handler';
export class TouchListener extends CommandGenerator {
private previousPosition = vec2.create();
constructor(target: HTMLElement, private readonly game: Game) {
super();
target.addEventListener('touchstart', (event: TouchEvent) => {
event.preventDefault();
const touchCount = event.touches.length;
const position = this.positionFromEvent(event);
this.previousPosition = position;
if (touchCount == 1) {
this.sendCommandToSubcribers(new PrimaryActionCommand(position));
} else if (touchCount == 2) {
this.sendCommandToSubcribers(new SecondaryActionCommand(position));
} else {
this.sendCommandToSubcribers(new TernaryActionCommand(position));
}
});
target.addEventListener('touchmove', (event: TouchEvent) => {
event.preventDefault();
const position = this.positionFromEvent(event, true);
const movement = vec2.subtract(vec2.create(), position, this.previousPosition);
if (vec2.squaredLength(movement) > 0) {
vec2.normalize(movement, movement);
this.sendCommandToSubcribers(
new MoveActionCommand(movement, OptionsHandler.options.relativeMovementEnabled),
);
}
this.previousPosition = position;
});
target.addEventListener('touchend', (event: TouchEvent) => {
event.preventDefault();
this.sendCommandToSubcribers(
new MoveActionCommand(
vec2.create(),
OptionsHandler.options.relativeMovementEnabled,
),
);
});
}
private positionFromEvent(event: TouchEvent, invert = false): vec2 {
const touches = Array.prototype.slice.call(event.touches);
const center = touches.reduce(
(center: vec2, touch: Touch) =>
vec2.add(
center,
center,
vec2.fromValues(
touch.clientX * (invert ? -1 : 1),
touch.clientY * (invert ? -1 : 1),
),
),
vec2.create(),
);
return this.game.displayToWorldCoordinates(
vec2.scale(center, center, 1 / event.touches.length),
);
}
}

View file

@ -24,7 +24,7 @@ import {
import io from 'socket.io-client';
import { KeyboardListener } from './commands/generators/keyboard-listener';
import { MouseListener } from './commands/generators/mouse-listener';
import { TouchListener } from './commands/generators/touch-listener';
import { TouchJoystickListener } from './commands/generators/touch-joystick-listener';
import { CommandReceiverSocket } from './commands/receivers/command-receiver-socket';
import { PlayerDecision } from './join-form-handler';
import { GameObjectContainer } from './objects/game-object-container';
@ -129,15 +129,15 @@ export class Game {
const delta = vec2.fromValues(deltaX, deltaY);
const center = vec2.fromValues(width / 2, height / 2);
const p = vec2.add(center, center, delta);
const arrowPadding = 16;
const arrowPadding = 24;
vec2.set(
p,
clamp(p.x, arrowPadding, width - 3 * arrowPadding),
clamp(height - p.y, arrowPadding, height - 3 * arrowPadding),
clamp(p.x, arrowPadding, width - arrowPadding),
clamp(height - p.y, arrowPadding, height - arrowPadding),
);
e.style.transform = `translateX(${p.x}px) translateY(${p.y}px) rotate(${
-angle + Math.PI / 2
}rad) translateX(-50%) translateY(-50%)`;
e.style.transform = `translateX(${p.x}px) translateY(${
p.y
}px) translateX(-50%) translateY(-50%) rotate(${-angle + Math.PI / 2}rad) `;
});
} else this.gameObjects.sendCommand(command);
});
@ -154,7 +154,7 @@ export class Game {
[
new KeyboardListener(document.body),
new MouseListener(this.canvas, this),
new TouchListener(this.canvas, this),
new TouchJoystickListener(this.canvas, this.overlay, this),
],
[this.gameObjects, new CommandReceiverSocket(this.socket)],
);