Improve gameplay and design

This commit is contained in:
schmelczerandras 2020-10-22 21:50:39 +02:00
parent af616042f3
commit 1d1bc6655c
15 changed files with 163 additions and 74 deletions

View file

@ -155,20 +155,13 @@ export class PlayerCharacterPhysical
return; return;
} }
const start = vec2.clone(this.center); const direction = vec2.subtract(vec2.create(), position, this.center);
const direction = vec2.subtract(vec2.create(), position, start);
vec2.normalize(direction, direction); vec2.normalize(direction, direction);
vec2.add(
start,
start,
vec2.scale(vec2.create(), direction, settings.projectileStartOffset),
);
const velocity = vec2.scale(direction, direction, settings.projectileSpeed); const velocity = vec2.scale(direction, direction, settings.projectileSpeed);
vec2.add(velocity, velocity, this.velocity);
const strength = this.projectileStrength / 2; const strength = this.projectileStrength / 2;
this.projectileStrength -= strength; this.projectileStrength -= strength;
const projectile = new ProjectilePhysical( const projectile = new ProjectilePhysical(
start, vec2.clone(this.center),
20, 20,
this.colorIndex, this.colorIndex,
strength, strength,
@ -192,10 +185,6 @@ export class PlayerCharacterPhysical
return this.head.center; return this.head.center;
} }
public get velocity(): vec2 {
return this.head.velocity;
}
public distance(target: vec2): number { public distance(target: vec2): number {
return ( return (
Math.min( Math.min(
@ -272,17 +261,18 @@ export class PlayerCharacterPhysical
} else { } else {
const leftFootGravity = this.currentPlanet!.getForce(this.leftFoot.center); const leftFootGravity = this.currentPlanet!.getForce(this.leftFoot.center);
const rightFootGravity = this.currentPlanet!.getForce(this.rightFoot.center); const rightFootGravity = this.currentPlanet!.getForce(this.rightFoot.center);
if (movementForce.y > settings.maxAcceleration / 4) {
if (this.lastMovementWasRelative) {
vec2.rotate(movementForce, movementForce, vec2.create(), this.direction);
}
if (vec2.dot(movementForce, actualGravity) < -vec2.length(movementForce) * 0.8) {
vec2.scale(leftFootGravity, leftFootGravity, 0.35); vec2.scale(leftFootGravity, leftFootGravity, 0.35);
vec2.scale(rightFootGravity, rightFootGravity, 0.35); vec2.scale(rightFootGravity, rightFootGravity, 0.35);
} }
this.applyForce(this.leftFoot, leftFootGravity, deltaTime); this.applyForce(this.leftFoot, leftFootGravity, deltaTime);
this.applyForce(this.rightFoot, rightFootGravity, deltaTime); this.applyForce(this.rightFoot, rightFootGravity, deltaTime);
if (this.lastMovementWasRelative) {
vec2.rotate(movementForce, movementForce, vec2.create(), this.direction);
}
const headGravity = this.currentPlanet!.getForce(this.head.center); const headGravity = this.currentPlanet!.getForce(this.head.center);
if (vec2.length(headGravity) < vec2.length(actualGravity) / 2) { if (vec2.length(headGravity) < vec2.length(actualGravity) / 2) {

View file

@ -16,6 +16,7 @@ import { ReactsToCollision } from '../physics/physicals/reacts-to-collision';
import { UpdateObjectMessage } from 'shared/lib/src/objects/update-object-message'; import { UpdateObjectMessage } from 'shared/lib/src/objects/update-object-message';
import { UpdateGameObjectMessage } from '../update-game-object-message'; import { UpdateGameObjectMessage } from '../update-game-object-message';
import { PlayerCharacterPhysical } from './player-character-physical'; import { PlayerCharacterPhysical } from './player-character-physical';
import { moveCircle } from '../physics/functions/move-circle';
@serializesTo(ProjectileBase) @serializesTo(ProjectileBase)
export class ProjectilePhysical export class ProjectilePhysical
@ -41,6 +42,26 @@ export class ProjectilePhysical
) { ) {
super(id(), center, radius, colorIndex, strength); super(id(), center, radius, colorIndex, strength);
this.object = new CirclePhysical(center, radius, this, container, 0.9); this.object = new CirclePhysical(center, radius, this, container, 0.9);
this.moveOutsideOfObject();
}
private moveOutsideOfObject() {
let wasCollision = true;
const delta = vec2.scale(
vec2.create(),
vec2.normalize(vec2.create(), this.velocity),
10,
);
while (wasCollision) {
const intersecting = this.container
.findIntersecting(this.boundingBox)
.filter((g) => g instanceof PlayerCharacterPhysical && g.team === this.team);
const { hitSurface } = moveCircle(this.object, delta, intersecting, true);
wasCollision = hitSurface;
}
vec2.add(this.center, this.center, delta);
vec2.add(this.center, this.center, delta);
} }
public calculateUpdates(): UpdateObjectMessage { public calculateUpdates(): UpdateObjectMessage {

View file

@ -4,4 +4,4 @@ import { PhysicalBase } from '../physicals/physical-base';
export const evaluateSdf = (target: vec2, objects: Array<PhysicalBase>) => export const evaluateSdf = (target: vec2, objects: Array<PhysicalBase>) =>
objects objects
.filter((i) => i.canCollide) .filter((i) => i.canCollide)
.reduce((min, i) => (min = Math.min(min, i.distance(target))), 1000); .reduce((min, i) => (min = Math.min(min, i.distance(target))), 1000000);

View file

@ -9,6 +9,7 @@ export const moveCircle = (
circle: CirclePhysical, circle: CirclePhysical,
delta: vec2, delta: vec2,
possibleIntersectors: Array<Physical>, possibleIntersectors: Array<Physical>,
ignoreCollision = false,
): { ): {
realDelta: vec2; realDelta: vec2;
hitSurface: boolean; hitSurface: boolean;
@ -38,6 +39,9 @@ export const moveCircle = (
(i) => i.distance(nextCircle.center) <= circle.radius, (i) => i.distance(nextCircle.center) <= circle.radius,
)!; )!;
if (ignoreCollision) {
circle.center = vec2.add(circle.center, circle.center, delta);
} else {
if (reactsToCollision(intersecting)) { if (reactsToCollision(intersecting)) {
intersecting.onCollision(circle.gameObject); intersecting.onCollision(circle.gameObject);
} }
@ -45,6 +49,7 @@ export const moveCircle = (
if (reactsToCollision(circle)) { if (reactsToCollision(circle)) {
circle.onCollision(intersecting.gameObject); circle.onCollision(intersecting.gameObject);
} }
}
const dx = const dx =
evaluateSdf(vec2.add(vec2.create(), nextCircle.center, vec2.fromValues(0.01, 0)), [ evaluateSdf(vec2.add(vec2.create(), nextCircle.center, vec2.fromValues(0.01, 0)), [

View file

@ -28,7 +28,6 @@ import { PhysicalContainer } from '../physics/containers/physical-container';
import { getBoundingBoxOfCircle } from '../physics/functions/get-bounding-box-of-circle'; import { getBoundingBoxOfCircle } from '../physics/functions/get-bounding-box-of-circle';
import { isCircleIntersecting } from '../physics/functions/is-circle-intersecting'; import { isCircleIntersecting } from '../physics/functions/is-circle-intersecting';
import { PlayerCharacterPhysical } from '../objects/player-character-physical'; import { PlayerCharacterPhysical } from '../objects/player-character-physical';
import { Physical } from '../physics/physicals/physical';
import { freeTeam, requestTeam } from './player-team-service'; import { freeTeam, requestTeam } from './player-team-service';
import { PlanetPhysical } from '../objects/planet-physical'; import { PlanetPhysical } from '../objects/planet-physical';

View file

@ -50,25 +50,43 @@
<section id="settings"> <section id="settings">
<label for="enable-relative-movement"> <label for="enable-relative-movement">
<input id="enable-relative-movement" type="checkbox" /> <input id="enable-relative-movement" type="checkbox" />
<img alt="a dashed circle" src="../static/circle.svg" /> <img
title="Make movement be relative to the gravitational field"
alt="a dashed circle"
src="../static/circle.svg"
/>
</label> </label>
<label for="enable-vibration"> <label for="enable-vibration">
<input id="enable-vibration" type="checkbox" /> <input id="enable-vibration" type="checkbox" />
<img alt="vibrating mobile" src="../static/vibrate.svg" /> <img
title="Enable vibration on mobile"
alt="vibrating mobile"
src="../static/vibrate.svg"
/>
</label> </label>
<label for="enable-sounds"> <label for="enable-sounds">
<input id="enable-sounds" type="checkbox" /> <input id="enable-sounds" type="checkbox" />
<img alt="speaker with sound waves" src="../static/volume.svg" /> <img
title="Enable sounds"
alt="speaker with sound waves"
src="../static/volume.svg"
/>
</label> </label>
<img id="logout" alt="logout" src="../static/logout.svg" /> <img
title="Exit from current game"
id="logout"
alt="logout"
src="../static/logout.svg"
/>
</section> </section>
</div> </div>
<div id="toggle-settings-container"> <div id="toggle-settings-container">
<img <img
title="Toggle settings"
id="toggle-settings" id="toggle-settings"
class="icon" class="icon"
alt="toggle-settings" alt="toggle-settings"

View file

@ -94,21 +94,58 @@ body {
} }
.player-tag { .player-tag {
font-size: 1.3rem;
position: absolute; 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;
&.decla {
color: $bright-decla;
div { div {
height: 3px; background-color: $bright-decla;
background-color: rebeccapurple; &:before {
background-color: $bright-decla;
opacity: 0.3;
}
}
}
&.red {
color: $bright-red;
div {
background-color: $bright-red;
&:before {
background-color: $bright-red;
opacity: 0.4;
}
}
}
div {
position: relative;
height: 5px;
border-radius: 1000px;
&:before {
content: '';
position: absolute;
height: 5px;
width: 50px;
box-sizing: border-box;
border-radius: 1000px;
}
} }
} }
.ownership { .ownership {
font-size: 1.3rem;
position: absolute; position: absolute;
font-size: 0;
transform: translateX(-50%) translateY(-50%); transform: translateX(-50%) translateY(-50%);
@include square(50px);
border-radius: 1000px;
mask: url('../static/mask.svg');
} }
.planet-progress { .planet-progress {
@ -122,21 +159,24 @@ body {
height: $height; height: $height;
border-radius: 4px; border-radius: 4px;
z-index: 100;
div { div {
height: $height; height: $height;
} }
div:nth-child(1) { div:nth-child(1) {
background: rebeccapurple; border-radius: 100px 0 0 100px;
background: $bright-decla;
} }
div:nth-child(2) { div:nth-child(2) {
background: gray; background: $bright-neutral;
} }
div:nth-child(3) { div:nth-child(3) {
background: red; border-radius: 0 100px 100px 0;
background: $bright-red;
} }
} }
} }

View file

@ -2,7 +2,6 @@ import { vec2 } from 'gl-matrix';
import { import {
CircleLight, CircleLight,
ColorfulCircle, ColorfulCircle,
compile,
FilteringOptions, FilteringOptions,
Flashlight, Flashlight,
Renderer, Renderer,
@ -17,7 +16,6 @@ import {
settings, settings,
TransportEvents, TransportEvents,
SetAspectRatioActionCommand, SetAspectRatioActionCommand,
rgb,
PlayerInformation, PlayerInformation,
PlayerDiedCommand, PlayerDiedCommand,
UpdatePlanetOwnershipCommand, UpdatePlanetOwnershipCommand,
@ -66,6 +64,8 @@ export class Game {
this.socket.io.opts.transports = ['polling', 'websocket']; this.socket.io.opts.transports = ['polling', 'websocket'];
}); });
this.socket.on('disconnect', this.destroy.bind(this));
this.socket.on(TransportEvents.ServerToPlayer, (serialized: string) => { this.socket.on(TransportEvents.ServerToPlayer, (serialized: string) => {
const command = deserialize(serialized); const command = deserialize(serialized);
if (command instanceof PlayerDiedCommand) { if (command instanceof PlayerDiedCommand) {
@ -145,7 +145,6 @@ export class Game {
//enableStopwatch: true, //enableStopwatch: true,
}, },
{ {
ambientLight: rgb(0.45, 0.4, 0.45),
colorPalette: settings.palette, colorPalette: settings.palette,
enableHighDpiRendering: true, enableHighDpiRendering: true,
lightCutoffDistance: settings.lightCutoffDistance, lightCutoffDistance: settings.lightCutoffDistance,

View file

@ -1,6 +1,6 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { Renderer } from 'sdf-2d'; import { Renderer } from 'sdf-2d';
import { calculateViewArea, GameObject, mixRgb, settings, UpdateMessage } from 'shared'; import { calculateViewArea, GameObject, mixRgb, settings } from 'shared';
import { Game } from '../game'; import { Game } from '../game';
import { ViewObject } from './view-object'; import { ViewObject } from './view-object';
@ -32,8 +32,7 @@ export class Camera extends GameObject implements ViewObject {
ambientLight: mixRgb( ambientLight: mixRgb(
settings.backgroundGradient[0], settings.backgroundGradient[0],
settings.backgroundGradient[1], settings.backgroundGradient[1],
(this.center.x - settings.worldLeftEdge) / vec2.length(this.center) / settings.worldRadius,
(Math.abs(settings.worldLeftEdge) + Math.abs(settings.worldRightEdge)),
), ),
}); });
} }

View file

@ -1,13 +1,6 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { Drawable, Renderer } from 'sdf-2d'; import { Renderer } from 'sdf-2d';
import { import { CommandExecutors, Id, Random, PlanetBase } from 'shared';
CommandExecutors,
Id,
Random,
PlanetBase,
UpdateMessage,
settings,
} from 'shared';
import { RenderCommand } from '../commands/types/render'; import { RenderCommand } from '../commands/types/render';
import { PlanetShape } from '../shapes/planet-shape'; import { PlanetShape } from '../shapes/planet-shape';
import { ViewObject } from './view-object'; import { ViewObject } from './view-object';
@ -32,18 +25,19 @@ export class PlanetView extends PlanetBase implements ViewObject {
public step(deltaTimeInMilliseconds: number): void { public step(deltaTimeInMilliseconds: number): void {
this.shape.randomOffset += deltaTimeInMilliseconds / 4000; this.shape.randomOffset += deltaTimeInMilliseconds / 4000;
this.shape.colorMixQ = this.ownership; this.shape.colorMixQ = this.ownership;
let teamName = 'Neutral';
if (this.ownership < 0.5 - settings.planetControlThreshold) {
teamName = 'Decla';
} else if (this.ownership > 0.5 + settings.planetControlThreshold) {
teamName = 'Red';
} }
this.ownershipProgess.innerText = `${teamName} ${Math.round( private getGradient(): string {
(Math.abs(this.ownership - 0.5) / 0.5) * 100, const sideDecla = this.ownership < 0.5;
)}%`; const sidePercent = (Math.abs(this.ownership - 0.5) / 0.5) * 100;
const color = sideDecla ? 'var(--bright-decla)' : 'var(--bright-red)';
return `conic-gradient(
${color} ${sidePercent}%,
${color} ${sidePercent}%,
var(--bright-neutral) ${sidePercent}%,
var(--bright-neutral) 100%
)`;
} }
public beforeDestroy(): void { public beforeDestroy(): void {
this.ownershipProgess.parentElement?.removeChild(this.ownershipProgess); this.ownershipProgess.parentElement?.removeChild(this.ownershipProgess);
} }
@ -56,6 +50,7 @@ export class PlanetView extends PlanetBase implements ViewObject {
const screenPosition = renderer.worldToDisplayCoordinates(this.center); const screenPosition = renderer.worldToDisplayCoordinates(this.center);
this.ownershipProgess.style.left = screenPosition.x + 'px'; this.ownershipProgess.style.left = screenPosition.x + 'px';
this.ownershipProgess.style.top = screenPosition.y + 'px'; this.ownershipProgess.style.top = screenPosition.y + 'px';
this.ownershipProgess.style.background = this.getGradient();
renderer.addDrawable(this.shape); renderer.addDrawable(this.shape);
} }

View file

@ -1,13 +1,6 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { Renderer } from 'sdf-2d'; import { Renderer } from 'sdf-2d';
import { import { Circle, Id, PlayerCharacterBase, CharacterTeam, settings } from 'shared';
Circle,
Id,
PlayerCharacterBase,
UpdateMessage,
CharacterTeam,
settings,
} from 'shared';
import { OptionsHandler } from '../options-handler'; import { OptionsHandler } from '../options-handler';
import { BlobShape } from '../shapes/blob-shape'; import { BlobShape } from '../shapes/blob-shape';
@ -35,7 +28,7 @@ export class PlayerCharacterView extends PlayerCharacterBase implements ViewObje
this.previousHealth = this.health; this.previousHealth = this.health;
this.nameElement = document.createElement('div'); this.nameElement = document.createElement('div');
this.nameElement.className = 'player-tag'; this.nameElement.className = 'player-tag ' + this.team;
this.nameElement.innerText = this.name; this.nameElement.innerText = this.name;
this.healthElement = document.createElement('div'); this.healthElement = document.createElement('div');
this.nameElement.appendChild(this.healthElement); this.nameElement.appendChild(this.healthElement);
@ -47,7 +40,7 @@ export class PlayerCharacterView extends PlayerCharacterBase implements ViewObje
public step(deltaTimeInMilliseconds: number): void { public step(deltaTimeInMilliseconds: number): void {
this.timeSinceLastNameElementUpdate += deltaTimeInMilliseconds; this.timeSinceLastNameElementUpdate += deltaTimeInMilliseconds;
this.healthElement.style.width = this.health + '%'; this.healthElement.style.width = (50 * this.health) / settings.playerMaxHealth + 'px';
if (this.previousHealth > this.health) { if (this.previousHealth > this.health) {
this.previousHealth = this.health; this.previousHealth = this.health;
if (OptionsHandler.options.vibrationEnabled) { if (OptionsHandler.options.vibrationEnabled) {
@ -89,7 +82,7 @@ export class PlayerCharacterView extends PlayerCharacterBase implements ViewObje
const headFeetDelta = vec2.subtract(footAverage, this.head!.center, footAverage); const headFeetDelta = vec2.subtract(footAverage, this.head!.center, footAverage);
vec2.normalize(headFeetDelta, headFeetDelta); vec2.normalize(headFeetDelta, headFeetDelta);
const textOffset = vec2.scale(headFeetDelta, headFeetDelta, this.head!.radius + 50); const textOffset = vec2.scale(headFeetDelta, headFeetDelta, this.head!.radius + 60);
return vec2.add(textOffset, this.head!.center, textOffset); return vec2.add(textOffset, this.head!.center, textOffset);
} }
} }

View file

@ -96,7 +96,8 @@ form {
padding: $small-padding; padding: $small-padding;
border: $border; border: $border;
cursor: pointer; cursor: pointer;
margin: $border-width-focused - $border-width $small-padding / 2; margin: $border-width-focused - $border-width $border-width-focused -
$border-width + $small-padding / 2;
} }
&:focus { &:focus {
@ -107,7 +108,7 @@ form {
&:checked + label { &:checked + label {
border-color: $accent; border-color: $accent;
border-width: $border-width-focused; border-width: $border-width-focused;
margin-right: 0 $small-padding / 2; margin: 0 $small-padding / 2;
} }
} }
} }

View file

@ -8,14 +8,22 @@
justify-content: center; justify-content: center;
top: 0; top: 0;
right: 0; right: 0;
font-size: 0;
} }
#toggle-settings-container { #toggle-settings-container {
padding: $medium-padding; padding: $medium-padding;
cursor: pointer; cursor: pointer;
padding: $medium-padding $medium-padding 0 $medium-padding;
@media (max-width: $breakpoint) {
padding: $medium-padding $medium-padding $medium-padding 0;
}
#toggle-settings { #toggle-settings {
animation: spin 32s linear infinite; animation: spin 32s linear infinite;
@keyframes spin { @keyframes spin {
100% { 100% {
transform: rotate(360deg); transform: rotate(360deg);
@ -53,9 +61,11 @@
opacity: 0; opacity: 0;
transition: transform $animation-time, opacity $animation-time; transition: transform $animation-time, opacity $animation-time;
pointer-events: none;
&.open { &.open {
transform: none; transform: none;
opacity: 1; opacity: 1;
pointer-events: inherit;
} }
img, img,

View file

@ -13,3 +13,12 @@ $breakpoint: 700px;
$height-breakpoint: 500px; $height-breakpoint: 500px;
$large-icon: 48px; $large-icon: 48px;
$small-icon: 32px; $small-icon: 32px;
$bright-decla: #4069a5;
$bright-red: #d15652;
$bright-neutral: #88888877;
:root {
--bright-decla: #{$bright-decla};
--bright-red: #{$bright-red};
--bright-neutral: #{$bright-neutral};
}

10
frontend/static/mask.svg Normal file
View file

@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" fill="000000" >
<defs>
<mask id="hole">
<rect width="100%" height="100%" fill="white"/>
<circle r="20" cx="50" cy="50" fill="black"/>
</mask>
</defs>
<rect width="100%" height="100%" fill="black" mask="url(#hole)"/>
</svg>

After

Width:  |  Height:  |  Size: 311 B