This commit is contained in:
schmelczerandras 2020-11-04 22:05:21 +01:00
commit 57d7009342
10 changed files with 204 additions and 251 deletions

View file

@ -48,11 +48,11 @@
minlength="1"
maxlength="20"
placeholder=" "
id="playerName"
name="playerName"
id="name"
name="name"
type="text"
/>
<label for="playerName">Choose a name</label>
<label for="name">Choose a name</label>
</div>
<div id="server-container"></div>

View file

@ -3,7 +3,7 @@ import {
LampBase,
overrideDeserialization,
PlanetBase,
PlayerCharacterBase,
CharacterBase,
ProjectileBase,
} from 'shared';
import { LampView } from './scripts/objects/lamp-view';
@ -17,9 +17,9 @@ import '../static/favicons/favicon-32x32.png';
import '../static/favicons/favicon.ico';
import { LandingPageBackground } from './scripts/landing-page-background';
import { JoinFormHandler } from './scripts/join-form-handler';
import { handleFullScreen } from './scripts/handle-full-screen';
import { handleFullScreen } from './scripts/helper/handle-full-screen';
import { Game } from './scripts/game';
import { PlayerCharacterView } from './scripts/objects/player-character-view';
import { CharacterView } from './scripts/objects/character-view';
import { handleInsights } from './scripts/handle-insights';
import { getInsightsFromRenderer } from './scripts/get-insights-from-renderer';
import { Renderer } from 'sdf-2d';
@ -32,12 +32,13 @@ import { VibrationHandler } from './scripts/vibration-handler';
glMatrix.setMatrixArrayType(Array);
overrideDeserialization(PlayerCharacterBase, PlayerCharacterView);
overrideDeserialization(CharacterBase, CharacterView);
overrideDeserialization(PlanetBase, PlanetView);
overrideDeserialization(LampBase, LampView);
overrideDeserialization(ProjectileBase, ProjectileView);
const landingUI = document.querySelector('#landing-ui') as HTMLElement;
const nameInput = document.querySelector('#name') as HTMLInputElement;
const joinGameForm = document.querySelector('#join-game-form') as HTMLFormElement;
const serverContainer = document.querySelector('#server-container') as HTMLElement;
const canvas = document.querySelector('canvas') as HTMLCanvasElement;
@ -114,6 +115,11 @@ const main = async () => {
try {
let game: Game;
const storedUserName = localStorage?.getItem('userName');
if (storedUserName) {
nameInput.value = JSON.parse(storedUserName);
}
const firstClickListener = () => {
SoundHandler.initialize(
() => {
@ -166,6 +172,9 @@ const main = async () => {
hide(spinner);
const playerDecision = await joinHandler.getPlayerDecision();
localStorage?.setItem('userName', JSON.stringify(playerDecision.name));
if (!history.state) {
history.pushState(true, '');
}

View file

@ -3,11 +3,11 @@ import { CommandGenerator, PrimaryActionCommand, SecondaryActionCommand } from '
import { Game } from '../game';
export class MouseListener extends CommandGenerator {
constructor(private readonly game: Game) {
constructor(private target: HTMLElement, private readonly game: Game) {
super();
addEventListener('mousedown', this.mouseDownListener);
addEventListener('contextmenu', this.contextMenuListener);
target.addEventListener('mousedown', this.mouseDownListener);
target.addEventListener('contextmenu', this.contextMenuListener);
}
private mouseDownListener = (event: MouseEvent) => {
@ -32,7 +32,7 @@ export class MouseListener extends CommandGenerator {
}
public destroy() {
removeEventListener('mousedown', this.mouseDownListener);
removeEventListener('contextmenu', this.contextMenuListener);
this.target.removeEventListener('mousedown', this.mouseDownListener);
this.target.removeEventListener('contextmenu', this.contextMenuListener);
}
}

View file

@ -1,5 +1,12 @@
import { vec2 } from 'gl-matrix';
import { Renderer, renderNoise } from 'sdf-2d';
import {
CircleLight,
FilteringOptions,
Renderer,
renderNoise,
runAnimation,
WrapOptions,
} from 'sdf-2d';
import {
deserialize,
TransportEvents,
@ -9,22 +16,23 @@ import {
clamp,
UpdateGameState,
GameEndCommand,
CharacterTeam,
ServerAnnouncement,
GameStartCommand,
CommandReceiver,
CommandExecutors,
Command,
settings,
} from 'shared';
import io from 'socket.io-client';
import { KeyboardListener } from './commands/keyboard-listener';
import { MouseListener } from './commands/mouse-listener';
import { TouchListener } from './commands/touch-listener';
import { CommandSocket } from './commands/command-socket';
import { startAnimation } from './start-animation';
import { PlayerDecision } from './join-form-handler';
import { GameObjectContainer } from './objects/game-object-container';
import parser from 'socket.io-msgpack-parser';
import { BlobShape } from './shapes/blob-shape';
import { PlanetShape } from './shapes/planet-shape';
export class Game extends CommandReceiver {
public gameObjects = new GameObjectContainer(this);
@ -59,8 +67,8 @@ export class Game extends CommandReceiver {
this.progressBar.appendChild(this.redPlanetCountElement);
this.keyboardListener = new KeyboardListener();
this.mouseListener = new MouseListener(this);
this.touchListener = new TouchListener(this.overlay, this.overlay, this);
this.mouseListener = new MouseListener(this.canvas, this);
this.touchListener = new TouchListener(this.canvas, this.overlay, this);
}
private initialize() {
@ -111,9 +119,7 @@ export class Game extends CommandReceiver {
this.isBetweenGames = false;
this.socket.emit(TransportEvents.PlayerJoining, {
name: this.playerDecision.playerName,
} as PlayerInformation);
this.socket.emit(TransportEvents.PlayerJoining, this.playerDecision);
}
protected defaultCommandExecutor(c: Command) {
@ -200,7 +206,35 @@ export class Game extends CommandReceiver {
this.initialize();
await startAnimation(this.canvas, this.gameLoop.bind(this), noiseTexture);
await runAnimation(
this.canvas,
[
PlanetShape.descriptor,
BlobShape.descriptor,
{
...CircleLight.descriptor,
shaderCombinationSteps: [0, 1, 2, 4, 8, 16],
},
],
this.gameLoop.bind(this),
{
shadowTraceCount: 16,
paletteSize: settings.palette.length,
colorPalette: settings.palette,
enableHighDpiRendering: true,
lightCutoffDistance: settings.lightCutoffDistance,
textures: {
noiseTexture: {
source: noiseTexture,
overrides: {
maxFilter: FilteringOptions.LINEAR,
wrapS: WrapOptions.MIRRORED_REPEAT,
wrapT: WrapOptions.MIRRORED_REPEAT,
},
},
},
},
);
this.socket.close();
this.overlay.innerHTML = '';
this.keyboardListener.destroy();

View file

@ -0,0 +1,52 @@
import { SoundHandler, Sounds } from '../sound-handler';
export const handleFullScreen = (
minimizeButton: HTMLElement,
maximizeButton: HTMLElement,
) => {
if (!document.fullscreenEnabled) {
minimizeButton.style.visibility = 'hidden';
maximizeButton.style.visibility = 'hidden';
return;
}
const isInFullScreen = (): boolean => document.fullscreenElement !== null;
const showButtons = () => {
minimizeButton.style.visibility = isInFullScreen() ? 'visible' : 'hidden';
maximizeButton.style.visibility = isInFullScreen() ? 'hidden' : 'visible';
};
showButtons();
let currentWindowHeight = innerHeight;
const followToggle = () => {
SoundHandler.play(Sounds.click);
showButtons();
currentWindowHeight = innerHeight;
};
const triggerToggle = async () => {
await (isInFullScreen()
? document.exitFullscreen()
: document.body.requestFullscreen());
followToggle();
};
addEventListener('keydown', (e) => {
if (e.key === 'F11') {
triggerToggle();
e.preventDefault();
}
});
addEventListener('resize', () => {
if (isInFullScreen && currentWindowHeight > innerHeight) {
followToggle();
}
});
maximizeButton.addEventListener('click', triggerToggle);
minimizeButton.addEventListener('click', triggerToggle);
};

View file

@ -5,7 +5,7 @@ import parser from 'socket.io-msgpack-parser';
import { SoundHandler, Sounds } from './sound-handler';
export type PlayerDecision = {
playerName: string;
name: string;
server: string;
};

View file

@ -1,7 +1,6 @@
import { vec2 } from 'gl-matrix';
import {
CircleLight,
compile,
FilteringOptions,
hsl,
NoisyPolygonFactory,

View file

@ -0,0 +1,146 @@
import { vec2 } from 'gl-matrix';
import { Renderer } from 'sdf-2d';
import {
Circle,
Id,
CharacterBase,
CharacterTeam,
settings,
UpdateProperty,
} from 'shared';
import { CircleExtrapolator } from '../helper/circle-extrapolator';
import { BlobShape } from '../shapes/blob-shape';
import { SoundHandler, Sounds } from '../sound-handler';
import { VibrationHandler } from '../vibration-handler';
import { ViewObject } from './view-object';
export class CharacterView extends CharacterBase implements ViewObject {
private shape: BlobShape;
private nameElement: HTMLElement = document.createElement('div');
private statsElement: HTMLElement = document.createElement('div');
private healthElement: HTMLElement = document.createElement('div');
public isMainCharacter = false;
private leftFootExtrapolator: CircleExtrapolator;
private rightFootExtrapolator: CircleExtrapolator;
private headExtrapolator: CircleExtrapolator;
constructor(
id: Id,
name: string,
killCount: number,
deathCount: number,
team: CharacterTeam,
health: number,
head?: Circle,
leftFoot?: Circle,
rightFoot?: Circle,
) {
super(id, name, killCount, deathCount, team, health, head, leftFoot, rightFoot);
this.shape = new BlobShape(settings.colorIndices[team]);
this.leftFootExtrapolator = new CircleExtrapolator(this.leftFoot!);
this.rightFootExtrapolator = new CircleExtrapolator(this.rightFoot!);
this.headExtrapolator = new CircleExtrapolator(this.head!);
this.nameElement.className = 'player-tag ' + this.team;
this.nameElement.innerText = this.name;
this.nameElement.appendChild(this.healthElement);
this.nameElement.appendChild(this.statsElement);
}
public get position(): vec2 {
return this.head!.center;
}
public updateProperties(update: Array<UpdateProperty>) {
update.forEach((u) => {
if (u.propertyKey === 'head') {
this.headExtrapolator.addFrame(u.propertyValue, u.rateOfChange);
}
if (u.propertyKey === 'leftFoot') {
this.leftFootExtrapolator.addFrame(u.propertyValue, u.rateOfChange);
}
if (u.propertyKey === 'rightFoot') {
this.rightFootExtrapolator.addFrame(u.propertyValue, u.rateOfChange);
}
});
}
public setHealth(health: number) {
const previousHealth = this.health;
super.setHealth(health);
SoundHandler.play(
Sounds.hit,
(0.4 * 2 * (previousHealth - health)) / settings.playerMaxStrength,
);
if (this.isMainCharacter) {
VibrationHandler.vibrate(Math.min(200, (previousHealth - this.health) * 4));
}
}
public onDie() {
if (this.isMainCharacter) {
VibrationHandler.vibrate(150);
}
}
public step(deltaTimeInSeconds: number): void {
this.head! = this.headExtrapolator.getValue(deltaTimeInSeconds);
this.leftFoot! = this.leftFootExtrapolator.getValue(deltaTimeInSeconds);
this.rightFoot! = this.rightFootExtrapolator.getValue(deltaTimeInSeconds);
}
public onShoot(strength: number) {
SoundHandler.play(Sounds.shoot, (0.6 * strength) / settings.playerMaxStrength);
}
public beforeDestroy(): void {
this.nameElement.parentElement?.removeChild(this.nameElement);
}
public draw(
renderer: Renderer,
overlay: HTMLElement,
shouldChangeLayout: boolean,
): void {
if (shouldChangeLayout) {
if (!this.nameElement.parentElement) {
overlay.appendChild(this.nameElement);
}
const screenPosition = renderer.worldToDisplayCoordinates(
this.calculateTextPosition(),
);
this.nameElement.style.transform = `translateX(${screenPosition.x}px) translateY(${screenPosition.y}px) translateX(-50%) translateY(-50%) rotate(-15deg)`;
this.healthElement.style.width =
(50 * this.health) / settings.playerMaxHealth + 'px';
this.statsElement.innerText = this.getStatsText();
}
this.shape.setCircles([this.head!, this.leftFoot!, this.rightFoot!]);
renderer.addDrawable(this.shape);
}
private getStatsText(): string {
return `${this.killCount}⚔/${this.deathCount}☠`;
}
private calculateTextPosition(): vec2 {
const footAverage = vec2.add(
vec2.create(),
this.leftFoot!.center,
this.rightFoot!.center,
);
vec2.scale(footAverage, footAverage, 0.5);
const headFeetDelta = vec2.subtract(footAverage, this.head!.center, footAverage);
vec2.normalize(headFeetDelta, headFeetDelta);
const textOffset = vec2.scale(headFeetDelta, headFeetDelta, this.head!.radius + 80);
return vec2.add(textOffset, this.head!.center, textOffset);
}
}

View file

@ -11,12 +11,12 @@ import {
} from 'shared';
import { Game } from '../game';
import { Camera } from './camera';
import { PlayerCharacterView } from './player-character-view';
import { CharacterView } from './character-view';
import { ViewObject } from './view-object';
export class GameObjectContainer extends CommandReceiver {
protected objects: Map<Id, ViewObject> = new Map();
public player!: PlayerCharacterView;
public player!: CharacterView;
public camera!: Camera;
protected commandExecutors: CommandExecutors = {
@ -25,7 +25,7 @@ export class GameObjectContainer extends CommandReceiver {
this.deleteObject(this.camera.id);
}
this.player = c.character as PlayerCharacterView;
this.player = c.character as CharacterView;
this.player.isMainCharacter = true;
this.camera = new Camera(this.game);

View file

@ -1,45 +0,0 @@
import {
CircleLight,
FilteringOptions,
Renderer,
runAnimation,
WrapOptions,
} from 'sdf-2d';
import { settings } from 'shared';
import { BlobShape } from './shapes/blob-shape';
import { PlanetShape } from './shapes/planet-shape';
export const startAnimation = async (
canvas: HTMLCanvasElement,
draw: (r: Renderer, current: number, delta: number) => boolean,
noiseTexture: TexImageSource,
): Promise<void> =>
await runAnimation(
canvas,
[
PlanetShape.descriptor,
BlobShape.descriptor,
{
...CircleLight.descriptor,
shaderCombinationSteps: [0, 1, 2, 4, 8, 16],
},
],
draw,
{
shadowTraceCount: 16,
paletteSize: settings.palette.length,
colorPalette: settings.palette,
enableHighDpiRendering: true,
lightCutoffDistance: settings.lightCutoffDistance,
textures: {
noiseTexture: {
source: noiseTexture,
overrides: {
maxFilter: FilteringOptions.LINEAR,
wrapS: WrapOptions.MIRRORED_REPEAT,
wrapT: WrapOptions.MIRRORED_REPEAT,
},
},
},
},
);