Add touch joystick movement
This commit is contained in:
parent
23fa7646d6
commit
8b49b44ebf
7 changed files with 161 additions and 95 deletions
|
|
@ -30,6 +30,7 @@
|
||||||
"eslint-plugin-unused-imports": "^0.1.3",
|
"eslint-plugin-unused-imports": "^0.1.3",
|
||||||
"firebase": "^7.22.0",
|
"firebase": "^7.22.0",
|
||||||
"gl-matrix": "^3.3.0",
|
"gl-matrix": "^3.3.0",
|
||||||
|
"html-webpack-inline-source-plugin": "^1.0.0-beta.2",
|
||||||
"html-webpack-inline-svg-plugin": "^2.3.0",
|
"html-webpack-inline-svg-plugin": "^2.3.0",
|
||||||
"html-webpack-plugin": "^4.5.0",
|
"html-webpack-plugin": "^4.5.0",
|
||||||
"image-config-webpack-plugin": "^2.0.0",
|
"image-config-webpack-plugin": "^2.0.0",
|
||||||
|
|
@ -41,7 +42,6 @@
|
||||||
"shared": "file:../shared",
|
"shared": "file:../shared",
|
||||||
"socket.io-client": "^2.3.1",
|
"socket.io-client": "^2.3.1",
|
||||||
"source-map-loader": "^1.1.1",
|
"source-map-loader": "^1.1.1",
|
||||||
"style-ext-html-webpack-plugin": "^4.1.2",
|
|
||||||
"svg-url-loader": "^6.0.0",
|
"svg-url-loader": "^6.0.0",
|
||||||
"terser-webpack-plugin": "^4.2.2",
|
"terser-webpack-plugin": "^4.2.2",
|
||||||
"ts-config-webpack-plugin": "^2.0.0",
|
"ts-config-webpack-plugin": "^2.0.0",
|
||||||
|
|
|
||||||
|
|
@ -83,18 +83,25 @@ body {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
margin: $large-padding 0;
|
margin: $large-padding 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.player-tag {
|
.player-tag {
|
||||||
position: absolute;
|
|
||||||
transform: translateX(-50%) translateY(-50%) rotate(-15deg);
|
transform: translateX(-50%) translateY(-50%) rotate(-15deg);
|
||||||
transition: left 200ms, top 200ms;
|
transition: left 200ms, top 200ms;
|
||||||
border-radius: 1000px;
|
border-radius: 1000px;
|
||||||
|
|
@ -139,7 +146,6 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
.ownership {
|
.ownership {
|
||||||
position: absolute;
|
|
||||||
font-size: 0;
|
font-size: 0;
|
||||||
transform: translateX(-50%) translateY(-50%);
|
transform: translateX(-50%) translateY(-50%);
|
||||||
|
|
||||||
|
|
@ -165,6 +171,25 @@ body {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.joystick {
|
||||||
|
@include square($large-icon * 1.3);
|
||||||
|
background-color: white;
|
||||||
|
box-shadow: inset 0 0 8px 3px rgba(0, 0, 0, 0.33);
|
||||||
|
opacity: 0.35;
|
||||||
|
border-radius: 1000px;
|
||||||
|
|
||||||
|
div {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
background-color: #444;
|
||||||
|
box-shadow: 0 0 8px 3px rgba(0, 0, 0, 0.33);
|
||||||
|
|
||||||
|
@include square($small-icon);
|
||||||
|
border-radius: 1000px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.planet-progress {
|
.planet-progress {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: $small-padding;
|
top: $small-padding;
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -24,7 +24,7 @@ import {
|
||||||
import io from 'socket.io-client';
|
import io from 'socket.io-client';
|
||||||
import { KeyboardListener } from './commands/generators/keyboard-listener';
|
import { KeyboardListener } from './commands/generators/keyboard-listener';
|
||||||
import { MouseListener } from './commands/generators/mouse-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 { CommandReceiverSocket } from './commands/receivers/command-receiver-socket';
|
||||||
import { PlayerDecision } from './join-form-handler';
|
import { PlayerDecision } from './join-form-handler';
|
||||||
import { GameObjectContainer } from './objects/game-object-container';
|
import { GameObjectContainer } from './objects/game-object-container';
|
||||||
|
|
@ -129,15 +129,15 @@ export class Game {
|
||||||
const delta = vec2.fromValues(deltaX, deltaY);
|
const delta = vec2.fromValues(deltaX, deltaY);
|
||||||
const center = vec2.fromValues(width / 2, height / 2);
|
const center = vec2.fromValues(width / 2, height / 2);
|
||||||
const p = vec2.add(center, center, delta);
|
const p = vec2.add(center, center, delta);
|
||||||
const arrowPadding = 16;
|
const arrowPadding = 24;
|
||||||
vec2.set(
|
vec2.set(
|
||||||
p,
|
p,
|
||||||
clamp(p.x, arrowPadding, width - 3 * arrowPadding),
|
clamp(p.x, arrowPadding, width - arrowPadding),
|
||||||
clamp(height - p.y, arrowPadding, height - 3 * arrowPadding),
|
clamp(height - p.y, arrowPadding, height - arrowPadding),
|
||||||
);
|
);
|
||||||
e.style.transform = `translateX(${p.x}px) translateY(${p.y}px) rotate(${
|
e.style.transform = `translateX(${p.x}px) translateY(${
|
||||||
-angle + Math.PI / 2
|
p.y
|
||||||
}rad) translateX(-50%) translateY(-50%)`;
|
}px) translateX(-50%) translateY(-50%) rotate(${-angle + Math.PI / 2}rad) `;
|
||||||
});
|
});
|
||||||
} else this.gameObjects.sendCommand(command);
|
} else this.gameObjects.sendCommand(command);
|
||||||
});
|
});
|
||||||
|
|
@ -154,7 +154,7 @@ export class Game {
|
||||||
[
|
[
|
||||||
new KeyboardListener(document.body),
|
new KeyboardListener(document.body),
|
||||||
new MouseListener(this.canvas, this),
|
new MouseListener(this.canvas, this),
|
||||||
new TouchListener(this.canvas, this),
|
new TouchJoystickListener(this.canvas, this.overlay, this),
|
||||||
],
|
],
|
||||||
[this.gameObjects, new CommandReceiverSocket(this.socket)],
|
[this.gameObjects, new CommandReceiverSocket(this.socket)],
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@ const ScssConfigWebpackPlugin = require('scss-config-webpack-plugin');
|
||||||
const TsConfigWebpackPlugin = require('ts-config-webpack-plugin');
|
const TsConfigWebpackPlugin = require('ts-config-webpack-plugin');
|
||||||
const HtmlWebpackInlineSVGPlugin = require('html-webpack-inline-svg-plugin');
|
const HtmlWebpackInlineSVGPlugin = require('html-webpack-inline-svg-plugin');
|
||||||
const TerserJSPlugin = require('terser-webpack-plugin');
|
const TerserJSPlugin = require('terser-webpack-plugin');
|
||||||
|
const HtmlWebpackInlineSourcePlugin = require('html-webpack-inline-source-plugin');
|
||||||
//const FaviconsWebpackPlugin = require('favicons-webpack-plugin');
|
//const FaviconsWebpackPlugin = require('favicons-webpack-plugin');
|
||||||
//const StyleExtHtmlWebpackPlugin = require('style-ext-html-webpack-plugin');
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
devServer: {
|
devServer: {
|
||||||
|
|
@ -18,10 +18,14 @@ module.exports = {
|
||||||
plugins: [
|
plugins: [
|
||||||
// Cleans the dist folder before the build starts
|
// Cleans the dist folder before the build starts
|
||||||
new CleanWebpackPlugin(),
|
new CleanWebpackPlugin(),
|
||||||
|
new ScssConfigWebpackPlugin(),
|
||||||
|
|
||||||
// Generate a base html file and injects all generated css and js files
|
// Generate a base html file and injects all generated css and js files
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
template: './src/index.html',
|
template: './src/index.html',
|
||||||
|
inlineSource: '.(css)$',
|
||||||
}),
|
}),
|
||||||
|
new HtmlWebpackInlineSourcePlugin(HtmlWebpackPlugin),
|
||||||
new HtmlWebpackInlineSVGPlugin({
|
new HtmlWebpackInlineSVGPlugin({
|
||||||
inlineAll: true,
|
inlineAll: true,
|
||||||
svgoConfig: [
|
svgoConfig: [
|
||||||
|
|
@ -30,11 +34,10 @@ module.exports = {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
//new StyleExtHtmlWebpackPlugin('main.css'),
|
|
||||||
//new FaviconsWebpackPlugin('static/logo.svg'),
|
//new FaviconsWebpackPlugin('static/logo.svg'),
|
||||||
// SCSS Configuration for .css .module.css and .scss .module.scss files
|
// SCSS Configuration for .css .module.css and .scss .module.scss files
|
||||||
// see https://github.com/namics/webpack-config-plugins/tree/master/packages/scss-config-webpack-plugin/config
|
// see https://github.com/namics/webpack-config-plugins/tree/master/packages/scss-config-webpack-plugin/config
|
||||||
new ScssConfigWebpackPlugin(),
|
|
||||||
// Multi threading typescript loader configuration with caching for .ts and .tsx files
|
// Multi threading typescript loader configuration with caching for .ts and .tsx files
|
||||||
// see https://github.com/namics/webpack-config-plugins/tree/master/packages/ts-config-webpack-plugin/config
|
// see https://github.com/namics/webpack-config-plugins/tree/master/packages/ts-config-webpack-plugin/config
|
||||||
new TsConfigWebpackPlugin(),
|
new TsConfigWebpackPlugin(),
|
||||||
|
|
@ -57,6 +60,16 @@ module.exports = {
|
||||||
enforce: 'pre',
|
enforce: 'pre',
|
||||||
use: ['source-map-loader'],
|
use: ['source-map-loader'],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
test: /\.css$/,
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
loader: 'style-loader',
|
||||||
|
options: { injectType: 'singletonStyleTag' },
|
||||||
|
},
|
||||||
|
'css-loader',
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
test: /\.svg/,
|
test: /\.svg/,
|
||||||
use: {
|
use: {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
export function last<T>(a: Array<T>): T | null {
|
export function last<T>(a: ArrayLike<T>): T | null {
|
||||||
return a.length > 0 ? a[a.length - 1] : null;
|
return a.length > 0 ? a[a.length - 1] : null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue