Add KD stats and joystick deadzone
This commit is contained in:
parent
c74b2f14a0
commit
1cd8f5fbe6
10 changed files with 123 additions and 44 deletions
|
|
@ -95,12 +95,14 @@ export class PlayerCharacterPhysical
|
|||
|
||||
constructor(
|
||||
name: string,
|
||||
killCount: number,
|
||||
deathCount: number,
|
||||
public readonly colorIndex: number,
|
||||
team: CharacterTeam,
|
||||
private readonly container: PhysicalContainer,
|
||||
startPosition: vec2,
|
||||
) {
|
||||
super(id(), name, colorIndex, team, settings.playerMaxHealth);
|
||||
super(id(), name, killCount, deathCount, colorIndex, team, settings.playerMaxHealth);
|
||||
|
||||
this.head = new CirclePhysical(
|
||||
vec2.add(vec2.create(), startPosition, PlayerCharacterPhysical.headOffset),
|
||||
|
|
@ -133,7 +135,13 @@ export class PlayerCharacterPhysical
|
|||
}
|
||||
|
||||
public calculateUpdates(): UpdateObjectMessage {
|
||||
return new UpdateGameObjectMessage(this, ['head', 'leftFoot', 'rightFoot', 'health']);
|
||||
return new UpdateGameObjectMessage(this, [
|
||||
'head',
|
||||
'leftFoot',
|
||||
'rightFoot',
|
||||
'health',
|
||||
'killCount',
|
||||
]);
|
||||
}
|
||||
|
||||
public handleMovementAction(c: MoveActionCommand) {
|
||||
|
|
@ -141,11 +149,16 @@ export class PlayerCharacterPhysical
|
|||
}
|
||||
|
||||
public onCollision(other: GameObject) {
|
||||
if (other instanceof ProjectilePhysical && other.team !== this.team) {
|
||||
if (
|
||||
other instanceof ProjectilePhysical &&
|
||||
other.team !== this.team &&
|
||||
other.isAlive
|
||||
) {
|
||||
other.destroy();
|
||||
this.health -= other.strength;
|
||||
if (this.health <= 0) {
|
||||
this.destroy();
|
||||
other.originator.killCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -167,6 +180,7 @@ export class PlayerCharacterPhysical
|
|||
strength,
|
||||
this.team,
|
||||
velocity,
|
||||
this,
|
||||
this.container,
|
||||
);
|
||||
this.container.addObject(projectile);
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ export class ProjectilePhysical
|
|||
public strength: number,
|
||||
public team: CharacterTeam,
|
||||
private velocity: vec2,
|
||||
public readonly originator: PlayerCharacterPhysical,
|
||||
readonly container: PhysicalContainer,
|
||||
) {
|
||||
super(id(), center, radius, colorIndex, strength);
|
||||
|
|
@ -46,6 +47,10 @@ export class ProjectilePhysical
|
|||
this.moveOutsideOfObject();
|
||||
}
|
||||
|
||||
public get isAlive(): boolean {
|
||||
return !this.isDestroyed;
|
||||
}
|
||||
|
||||
private moveOutsideOfObject() {
|
||||
let wasCollision = true;
|
||||
const delta = vec2.scale(
|
||||
|
|
|
|||
|
|
@ -37,6 +37,9 @@ export class Player extends CommandReceiver {
|
|||
private aspectRatio: number = 16 / 9;
|
||||
private isActive = true;
|
||||
|
||||
private sumKills = 0;
|
||||
private sumDeaths = 0;
|
||||
|
||||
private objectsPreviouslyInViewArea: Array<GameObject> = [];
|
||||
|
||||
private pingTime?: number;
|
||||
|
|
@ -64,17 +67,20 @@ export class Player extends CommandReceiver {
|
|||
};
|
||||
|
||||
private findEmptyPositionForPlayer(): vec2 {
|
||||
let possibleCenter = this.players.find((p) => p.team === this.team)?.center;
|
||||
let possibleCenter = this.players.find(
|
||||
(p) => p.character?.isAlive && p.team === this.team,
|
||||
)?.center;
|
||||
|
||||
if (!possibleCenter) {
|
||||
possibleCenter = vec2.create();
|
||||
}
|
||||
|
||||
let rotation = Math.atan2(possibleCenter.y, possibleCenter.x);
|
||||
let radius = vec2.length(possibleCenter);
|
||||
let rotation = 0;
|
||||
let radius = 0;
|
||||
for (;;) {
|
||||
const playerPosition = vec2.fromValues(
|
||||
radius * Math.cos(rotation),
|
||||
radius * Math.sin(rotation),
|
||||
radius * Math.cos(rotation) + possibleCenter.x,
|
||||
radius * Math.sin(rotation) + possibleCenter.y,
|
||||
);
|
||||
|
||||
const playerBoundingCircle = new Circle(
|
||||
|
|
@ -121,6 +127,8 @@ export class Player extends CommandReceiver {
|
|||
private createCharacter() {
|
||||
this.character = new PlayerCharacterPhysical(
|
||||
this.playerInfo.name.slice(0, 20),
|
||||
this.sumKills,
|
||||
this.sumDeaths,
|
||||
this.colorIndex,
|
||||
this.team,
|
||||
this.objects,
|
||||
|
|
@ -143,6 +151,9 @@ export class Player extends CommandReceiver {
|
|||
this.center = this.character?.center;
|
||||
|
||||
if (!this.character.isAlive) {
|
||||
this.sumDeaths++;
|
||||
this.sumKills = this.character.killCount;
|
||||
|
||||
this.socket.emit(
|
||||
TransportEvents.ServerToPlayer,
|
||||
serialize(new PlayerDiedCommand(settings.playerDiedTimeout)),
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
margin: 0;
|
||||
box-sizing: border-box;
|
||||
color: white;
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
font-family: 'Open Sans', 'Segoe UI Emoji', sans-serif;
|
||||
|
||||
&::selection {
|
||||
color: white;
|
||||
|
|
@ -43,10 +43,6 @@ h1 {
|
|||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 4rem;
|
||||
}
|
||||
|
||||
.red {
|
||||
color: $red;
|
||||
&::selection {
|
||||
|
|
@ -97,10 +93,6 @@ body {
|
|||
left: 0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: $large-padding 0;
|
||||
}
|
||||
|
||||
.player-tag {
|
||||
transform: translateX(-50%) translateY(-50%) rotate(-15deg);
|
||||
transition: left 200ms, top 200ms;
|
||||
|
|
@ -108,8 +100,7 @@ body {
|
|||
|
||||
&.decla {
|
||||
color: $bright-decla;
|
||||
|
||||
div {
|
||||
div:first-child {
|
||||
background-color: $bright-decla;
|
||||
&:before {
|
||||
background-color: $bright-decla;
|
||||
|
|
@ -120,7 +111,7 @@ body {
|
|||
|
||||
&.red {
|
||||
color: $bright-red;
|
||||
div {
|
||||
div:first-child {
|
||||
background-color: $bright-red;
|
||||
&:before {
|
||||
background-color: $bright-red;
|
||||
|
|
@ -129,7 +120,7 @@ body {
|
|||
}
|
||||
}
|
||||
|
||||
div {
|
||||
div:first-child {
|
||||
position: relative;
|
||||
height: 5px;
|
||||
border-radius: 1000px;
|
||||
|
|
@ -143,6 +134,11 @@ body {
|
|||
border-radius: 1000px;
|
||||
}
|
||||
}
|
||||
|
||||
div:not(:first-child) {
|
||||
letter-spacing: 2px;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
}
|
||||
|
||||
.ownership {
|
||||
|
|
@ -190,6 +186,17 @@ body {
|
|||
}
|
||||
}
|
||||
|
||||
.announcement {
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
font-size: 3rem;
|
||||
@include background;
|
||||
z-index: 1000;
|
||||
padding: $medium-padding;
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.planet-progress {
|
||||
position: absolute;
|
||||
top: $small-padding;
|
||||
|
|
|
|||
|
|
@ -73,11 +73,19 @@ export class TouchJoystickListener extends CommandGenerator {
|
|||
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);
|
||||
|
||||
if (length > 10) {
|
||||
this.sendCommandToSubcribers(
|
||||
new MoveActionCommand(movement, OptionsHandler.options.relativeMovementEnabled),
|
||||
new MoveActionCommand(
|
||||
vec2.normalize(movement, movement),
|
||||
OptionsHandler.options.relativeMovementEnabled,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
this.sendCommandToSubcribers(
|
||||
new MoveActionCommand(
|
||||
vec2.create(),
|
||||
OptionsHandler.options.relativeMovementEnabled,
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -41,12 +41,14 @@ export class Game {
|
|||
private declaPlanetCountElement = document.createElement('div');
|
||||
private redPlanetCountElement = document.createElement('div');
|
||||
private neutralPlanetCountElement = document.createElement('div');
|
||||
private announcementText = document.createElement('h2');
|
||||
|
||||
constructor(
|
||||
private readonly playerDecision: PlayerDecision,
|
||||
private readonly canvas: HTMLCanvasElement,
|
||||
private readonly overlay: HTMLElement,
|
||||
) {
|
||||
this.announcementText.className = 'announcement';
|
||||
const progressBar = document.createElement('div');
|
||||
progressBar.className = 'planet-progress';
|
||||
overlay.appendChild(progressBar);
|
||||
|
|
@ -75,7 +77,7 @@ export class Game {
|
|||
if (OptionsHandler.options.vibrationEnabled) {
|
||||
navigator.vibrate(150);
|
||||
}
|
||||
this.overlay.appendChild(this.announcmentText);
|
||||
this.overlay.appendChild(this.announcementText);
|
||||
} else if (command instanceof UpdateGameState) {
|
||||
const all = command.declaCount + command.redCount + command.neutralCount;
|
||||
this.declaPlanetCountElement.style.width = (command.declaCount / all) * 100 + '%';
|
||||
|
|
@ -84,13 +86,13 @@ export class Game {
|
|||
this.redPlanetCountElement.style.width = (command.redCount / all) * 100 + '%';
|
||||
|
||||
if (command.declaCount > all * 0.5) {
|
||||
this.overlay.appendChild(this.announcmentText);
|
||||
this.announcmentText.innerText = 'Decla team won 🎉';
|
||||
this.overlay.appendChild(this.announcementText);
|
||||
this.announcementText.innerText = 'Decla team won 🎉';
|
||||
}
|
||||
|
||||
if (command.redCount > all * 0.5) {
|
||||
this.overlay.appendChild(this.announcmentText);
|
||||
this.announcmentText.innerText = 'Red team won 🎉';
|
||||
this.overlay.appendChild(this.announcementText);
|
||||
this.announcementText.innerText = 'Red team won 🎉';
|
||||
}
|
||||
|
||||
this.arrowElements
|
||||
|
|
@ -233,7 +235,6 @@ export class Game {
|
|||
this.isActive = false;
|
||||
}
|
||||
|
||||
private announcmentText = document.createElement('h2');
|
||||
private gameLoop(
|
||||
renderer: Renderer,
|
||||
currentTime: DOMHighResTimeStamp,
|
||||
|
|
@ -242,9 +243,9 @@ export class Game {
|
|||
this.renderer = renderer;
|
||||
|
||||
if ((this.deadTimeout -= deltaTime / 1000) > 0) {
|
||||
this.announcmentText.innerText = `Respawning in ${Math.floor(this.deadTimeout)} …`;
|
||||
this.announcementText.innerText = `Reviving in ${Math.floor(this.deadTimeout)}…`;
|
||||
} else {
|
||||
this.announcmentText.parentElement?.removeChild(this.announcmentText);
|
||||
this.announcementText.parentElement?.removeChild(this.announcementText);
|
||||
}
|
||||
|
||||
this.gameObjects.stepObjects(deltaTime);
|
||||
|
|
|
|||
|
|
@ -21,6 +21,9 @@ export class GameObjectContainer extends CommandReceiver {
|
|||
|
||||
protected commandExecutors: CommandExecutors = {
|
||||
[CreatePlayerCommand.type]: (c: CreatePlayerCommand) => {
|
||||
if (this.camera) {
|
||||
this.deleteObject(this.camera.id);
|
||||
}
|
||||
this.player = c.character as PlayerCharacterView;
|
||||
this.camera = new Camera(this.game);
|
||||
this.addObject(this.player);
|
||||
|
|
|
|||
|
|
@ -8,14 +8,17 @@ import { ViewObject } from './view-object';
|
|||
|
||||
export class PlayerCharacterView extends PlayerCharacterBase implements ViewObject {
|
||||
private shape: BlobShape;
|
||||
private nameElement: HTMLElement;
|
||||
private healthElement: HTMLElement;
|
||||
private nameElement: HTMLElement = document.createElement('div');
|
||||
private statsElement: HTMLElement = document.createElement('div');
|
||||
private healthElement: HTMLElement = document.createElement('div');
|
||||
private timeSinceLastNameElementUpdate = 0;
|
||||
private previousHealth;
|
||||
|
||||
constructor(
|
||||
id: Id,
|
||||
name: string,
|
||||
killCount: number,
|
||||
deathCount: number,
|
||||
colorIndex: number,
|
||||
team: CharacterTeam,
|
||||
health: number,
|
||||
|
|
@ -23,15 +26,25 @@ export class PlayerCharacterView extends PlayerCharacterBase implements ViewObje
|
|||
leftFoot?: Circle,
|
||||
rightFoot?: Circle,
|
||||
) {
|
||||
super(id, name, colorIndex, team, health, head, leftFoot, rightFoot);
|
||||
super(
|
||||
id,
|
||||
name,
|
||||
killCount,
|
||||
deathCount,
|
||||
colorIndex,
|
||||
team,
|
||||
health,
|
||||
head,
|
||||
leftFoot,
|
||||
rightFoot,
|
||||
);
|
||||
this.shape = new BlobShape(colorIndex);
|
||||
|
||||
this.previousHealth = this.health;
|
||||
this.nameElement = document.createElement('div');
|
||||
this.nameElement.className = 'player-tag ' + this.team;
|
||||
this.nameElement.innerText = this.name;
|
||||
this.healthElement = document.createElement('div');
|
||||
this.nameElement.appendChild(this.healthElement);
|
||||
this.nameElement.appendChild(this.statsElement);
|
||||
}
|
||||
|
||||
public get position(): vec2 {
|
||||
|
|
@ -41,10 +54,11 @@ export class PlayerCharacterView extends PlayerCharacterBase implements ViewObje
|
|||
public step(deltaTimeInMilliseconds: number): void {
|
||||
this.timeSinceLastNameElementUpdate += deltaTimeInMilliseconds;
|
||||
this.healthElement.style.width = (50 * this.health) / settings.playerMaxHealth + 'px';
|
||||
this.statsElement.innerText = this.getStatsText();
|
||||
if (this.previousHealth > this.health) {
|
||||
this.previousHealth = this.health;
|
||||
if (OptionsHandler.options.vibrationEnabled) {
|
||||
navigator.vibrate(50);
|
||||
navigator.vibrate(75);
|
||||
}
|
||||
}
|
||||
this.previousHealth = this.health;
|
||||
|
|
@ -72,6 +86,10 @@ export class PlayerCharacterView extends PlayerCharacterBase implements ViewObje
|
|||
renderer.addDrawable(this.shape);
|
||||
}
|
||||
|
||||
private getStatsText(): string {
|
||||
return `${this.killCount}⚔/${this.deathCount}☠`;
|
||||
}
|
||||
|
||||
private calculateTextPosition(): vec2 {
|
||||
const footAverage = vec2.add(
|
||||
vec2.create(),
|
||||
|
|
@ -82,7 +100,7 @@ export class PlayerCharacterView extends PlayerCharacterBase implements ViewObje
|
|||
|
||||
const headFeetDelta = vec2.subtract(footAverage, this.head!.center, footAverage);
|
||||
vec2.normalize(headFeetDelta, headFeetDelta);
|
||||
const textOffset = vec2.scale(headFeetDelta, headFeetDelta, this.head!.radius + 60);
|
||||
const textOffset = vec2.scale(headFeetDelta, headFeetDelta, this.head!.radius + 80);
|
||||
return vec2.add(textOffset, this.head!.center, textOffset);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@
|
|||
top: 0;
|
||||
right: $medium-padding + $small-icon + $small-padding;
|
||||
}
|
||||
pointer-events: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
|
@ -61,11 +62,10 @@
|
|||
opacity: 0;
|
||||
transition: transform $animation-time, opacity $animation-time;
|
||||
|
||||
pointer-events: none;
|
||||
&.open {
|
||||
transform: none;
|
||||
opacity: 1;
|
||||
pointer-events: inherit;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
img,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ export class PlayerCharacterBase extends CharacterBase {
|
|||
constructor(
|
||||
id: Id,
|
||||
public name: string,
|
||||
public killCount: number,
|
||||
public deathCount: number,
|
||||
colorIndex: number,
|
||||
team: CharacterTeam,
|
||||
health: number,
|
||||
|
|
@ -20,7 +22,17 @@ export class PlayerCharacterBase extends CharacterBase {
|
|||
}
|
||||
|
||||
public toArray(): Array<any> {
|
||||
const { id, name, colorIndex, team, health, head, leftFoot, rightFoot } = this;
|
||||
return [id, name, colorIndex, team, health, head, leftFoot, rightFoot];
|
||||
return [
|
||||
this.id,
|
||||
this.name,
|
||||
this.killCount,
|
||||
this.deathCount,
|
||||
this.colorIndex,
|
||||
this.team,
|
||||
this.health,
|
||||
this.head,
|
||||
this.leftFoot,
|
||||
this.rightFoot,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue