Improve gameplay and design
This commit is contained in:
parent
af616042f3
commit
1d1bc6655c
15 changed files with 163 additions and 74 deletions
|
|
@ -155,20 +155,13 @@ export class PlayerCharacterPhysical
|
|||
return;
|
||||
}
|
||||
|
||||
const start = vec2.clone(this.center);
|
||||
const direction = vec2.subtract(vec2.create(), position, start);
|
||||
const direction = vec2.subtract(vec2.create(), position, this.center);
|
||||
vec2.normalize(direction, direction);
|
||||
vec2.add(
|
||||
start,
|
||||
start,
|
||||
vec2.scale(vec2.create(), direction, settings.projectileStartOffset),
|
||||
);
|
||||
const velocity = vec2.scale(direction, direction, settings.projectileSpeed);
|
||||
vec2.add(velocity, velocity, this.velocity);
|
||||
const strength = this.projectileStrength / 2;
|
||||
this.projectileStrength -= strength;
|
||||
const projectile = new ProjectilePhysical(
|
||||
start,
|
||||
vec2.clone(this.center),
|
||||
20,
|
||||
this.colorIndex,
|
||||
strength,
|
||||
|
|
@ -192,10 +185,6 @@ export class PlayerCharacterPhysical
|
|||
return this.head.center;
|
||||
}
|
||||
|
||||
public get velocity(): vec2 {
|
||||
return this.head.velocity;
|
||||
}
|
||||
|
||||
public distance(target: vec2): number {
|
||||
return (
|
||||
Math.min(
|
||||
|
|
@ -272,17 +261,18 @@ export class PlayerCharacterPhysical
|
|||
} else {
|
||||
const leftFootGravity = this.currentPlanet!.getForce(this.leftFoot.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(rightFootGravity, rightFootGravity, 0.35);
|
||||
}
|
||||
this.applyForce(this.leftFoot, leftFootGravity, 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);
|
||||
|
||||
if (vec2.length(headGravity) < vec2.length(actualGravity) / 2) {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import { ReactsToCollision } from '../physics/physicals/reacts-to-collision';
|
|||
import { UpdateObjectMessage } from 'shared/lib/src/objects/update-object-message';
|
||||
import { UpdateGameObjectMessage } from '../update-game-object-message';
|
||||
import { PlayerCharacterPhysical } from './player-character-physical';
|
||||
import { moveCircle } from '../physics/functions/move-circle';
|
||||
|
||||
@serializesTo(ProjectileBase)
|
||||
export class ProjectilePhysical
|
||||
|
|
@ -41,6 +42,26 @@ export class ProjectilePhysical
|
|||
) {
|
||||
super(id(), center, radius, colorIndex, strength);
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -4,4 +4,4 @@ import { PhysicalBase } from '../physicals/physical-base';
|
|||
export const evaluateSdf = (target: vec2, objects: Array<PhysicalBase>) =>
|
||||
objects
|
||||
.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);
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ export const moveCircle = (
|
|||
circle: CirclePhysical,
|
||||
delta: vec2,
|
||||
possibleIntersectors: Array<Physical>,
|
||||
ignoreCollision = false,
|
||||
): {
|
||||
realDelta: vec2;
|
||||
hitSurface: boolean;
|
||||
|
|
@ -38,12 +39,16 @@ export const moveCircle = (
|
|||
(i) => i.distance(nextCircle.center) <= circle.radius,
|
||||
)!;
|
||||
|
||||
if (reactsToCollision(intersecting)) {
|
||||
intersecting.onCollision(circle.gameObject);
|
||||
}
|
||||
if (ignoreCollision) {
|
||||
circle.center = vec2.add(circle.center, circle.center, delta);
|
||||
} else {
|
||||
if (reactsToCollision(intersecting)) {
|
||||
intersecting.onCollision(circle.gameObject);
|
||||
}
|
||||
|
||||
if (reactsToCollision(circle)) {
|
||||
circle.onCollision(intersecting.gameObject);
|
||||
if (reactsToCollision(circle)) {
|
||||
circle.onCollision(intersecting.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
const dx =
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ import { PhysicalContainer } from '../physics/containers/physical-container';
|
|||
import { getBoundingBoxOfCircle } from '../physics/functions/get-bounding-box-of-circle';
|
||||
import { isCircleIntersecting } from '../physics/functions/is-circle-intersecting';
|
||||
import { PlayerCharacterPhysical } from '../objects/player-character-physical';
|
||||
import { Physical } from '../physics/physicals/physical';
|
||||
import { freeTeam, requestTeam } from './player-team-service';
|
||||
import { PlanetPhysical } from '../objects/planet-physical';
|
||||
|
||||
|
|
|
|||
|
|
@ -50,25 +50,43 @@
|
|||
<section id="settings">
|
||||
<label for="enable-relative-movement">
|
||||
<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 for="enable-vibration">
|
||||
<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 for="enable-sounds">
|
||||
<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>
|
||||
|
||||
<img id="logout" alt="logout" src="../static/logout.svg" />
|
||||
<img
|
||||
title="Exit from current game"
|
||||
id="logout"
|
||||
alt="logout"
|
||||
src="../static/logout.svg"
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div id="toggle-settings-container">
|
||||
<img
|
||||
title="Toggle settings"
|
||||
id="toggle-settings"
|
||||
class="icon"
|
||||
alt="toggle-settings"
|
||||
|
|
|
|||
|
|
@ -94,21 +94,58 @@ body {
|
|||
}
|
||||
|
||||
.player-tag {
|
||||
font-size: 1.3rem;
|
||||
position: absolute;
|
||||
transform: translateX(-50%) translateY(-50%) rotate(-15deg);
|
||||
transition: left 200ms, top 200ms;
|
||||
border-radius: 1000px;
|
||||
|
||||
&.decla {
|
||||
color: $bright-decla;
|
||||
|
||||
div {
|
||||
background-color: $bright-decla;
|
||||
&: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 {
|
||||
height: 3px;
|
||||
background-color: rebeccapurple;
|
||||
position: relative;
|
||||
height: 5px;
|
||||
border-radius: 1000px;
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
height: 5px;
|
||||
width: 50px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 1000px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ownership {
|
||||
font-size: 1.3rem;
|
||||
position: absolute;
|
||||
font-size: 0;
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
|
||||
@include square(50px);
|
||||
border-radius: 1000px;
|
||||
mask: url('../static/mask.svg');
|
||||
}
|
||||
|
||||
.planet-progress {
|
||||
|
|
@ -122,21 +159,24 @@ body {
|
|||
height: $height;
|
||||
|
||||
border-radius: 4px;
|
||||
z-index: 100;
|
||||
|
||||
div {
|
||||
height: $height;
|
||||
}
|
||||
|
||||
div:nth-child(1) {
|
||||
background: rebeccapurple;
|
||||
border-radius: 100px 0 0 100px;
|
||||
background: $bright-decla;
|
||||
}
|
||||
|
||||
div:nth-child(2) {
|
||||
background: gray;
|
||||
background: $bright-neutral;
|
||||
}
|
||||
|
||||
div:nth-child(3) {
|
||||
background: red;
|
||||
border-radius: 0 100px 100px 0;
|
||||
background: $bright-red;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import { vec2 } from 'gl-matrix';
|
|||
import {
|
||||
CircleLight,
|
||||
ColorfulCircle,
|
||||
compile,
|
||||
FilteringOptions,
|
||||
Flashlight,
|
||||
Renderer,
|
||||
|
|
@ -17,7 +16,6 @@ import {
|
|||
settings,
|
||||
TransportEvents,
|
||||
SetAspectRatioActionCommand,
|
||||
rgb,
|
||||
PlayerInformation,
|
||||
PlayerDiedCommand,
|
||||
UpdatePlanetOwnershipCommand,
|
||||
|
|
@ -66,6 +64,8 @@ export class Game {
|
|||
this.socket.io.opts.transports = ['polling', 'websocket'];
|
||||
});
|
||||
|
||||
this.socket.on('disconnect', this.destroy.bind(this));
|
||||
|
||||
this.socket.on(TransportEvents.ServerToPlayer, (serialized: string) => {
|
||||
const command = deserialize(serialized);
|
||||
if (command instanceof PlayerDiedCommand) {
|
||||
|
|
@ -145,7 +145,6 @@ export class Game {
|
|||
//enableStopwatch: true,
|
||||
},
|
||||
{
|
||||
ambientLight: rgb(0.45, 0.4, 0.45),
|
||||
colorPalette: settings.palette,
|
||||
enableHighDpiRendering: true,
|
||||
lightCutoffDistance: settings.lightCutoffDistance,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
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 { ViewObject } from './view-object';
|
||||
|
|
@ -32,8 +32,7 @@ export class Camera extends GameObject implements ViewObject {
|
|||
ambientLight: mixRgb(
|
||||
settings.backgroundGradient[0],
|
||||
settings.backgroundGradient[1],
|
||||
(this.center.x - settings.worldLeftEdge) /
|
||||
(Math.abs(settings.worldLeftEdge) + Math.abs(settings.worldRightEdge)),
|
||||
vec2.length(this.center) / settings.worldRadius,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,6 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
import { Drawable, Renderer } from 'sdf-2d';
|
||||
import {
|
||||
CommandExecutors,
|
||||
Id,
|
||||
Random,
|
||||
PlanetBase,
|
||||
UpdateMessage,
|
||||
settings,
|
||||
} from 'shared';
|
||||
import { Renderer } from 'sdf-2d';
|
||||
import { CommandExecutors, Id, Random, PlanetBase } from 'shared';
|
||||
import { RenderCommand } from '../commands/types/render';
|
||||
import { PlanetShape } from '../shapes/planet-shape';
|
||||
import { ViewObject } from './view-object';
|
||||
|
|
@ -32,18 +25,19 @@ export class PlanetView extends PlanetBase implements ViewObject {
|
|||
public step(deltaTimeInMilliseconds: number): void {
|
||||
this.shape.randomOffset += deltaTimeInMilliseconds / 4000;
|
||||
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(
|
||||
(Math.abs(this.ownership - 0.5) / 0.5) * 100,
|
||||
)}%`;
|
||||
}
|
||||
|
||||
private getGradient(): string {
|
||||
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 {
|
||||
this.ownershipProgess.parentElement?.removeChild(this.ownershipProgess);
|
||||
}
|
||||
|
|
@ -56,6 +50,7 @@ export class PlanetView extends PlanetBase implements ViewObject {
|
|||
const screenPosition = renderer.worldToDisplayCoordinates(this.center);
|
||||
this.ownershipProgess.style.left = screenPosition.x + 'px';
|
||||
this.ownershipProgess.style.top = screenPosition.y + 'px';
|
||||
this.ownershipProgess.style.background = this.getGradient();
|
||||
|
||||
renderer.addDrawable(this.shape);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,6 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
import { Renderer } from 'sdf-2d';
|
||||
import {
|
||||
Circle,
|
||||
Id,
|
||||
PlayerCharacterBase,
|
||||
UpdateMessage,
|
||||
CharacterTeam,
|
||||
settings,
|
||||
} from 'shared';
|
||||
import { Circle, Id, PlayerCharacterBase, CharacterTeam, settings } from 'shared';
|
||||
import { OptionsHandler } from '../options-handler';
|
||||
|
||||
import { BlobShape } from '../shapes/blob-shape';
|
||||
|
|
@ -35,7 +28,7 @@ export class PlayerCharacterView extends PlayerCharacterBase implements ViewObje
|
|||
|
||||
this.previousHealth = this.health;
|
||||
this.nameElement = document.createElement('div');
|
||||
this.nameElement.className = 'player-tag';
|
||||
this.nameElement.className = 'player-tag ' + this.team;
|
||||
this.nameElement.innerText = this.name;
|
||||
this.healthElement = document.createElement('div');
|
||||
this.nameElement.appendChild(this.healthElement);
|
||||
|
|
@ -47,7 +40,7 @@ export class PlayerCharacterView extends PlayerCharacterBase implements ViewObje
|
|||
|
||||
public step(deltaTimeInMilliseconds: number): void {
|
||||
this.timeSinceLastNameElementUpdate += deltaTimeInMilliseconds;
|
||||
this.healthElement.style.width = this.health + '%';
|
||||
this.healthElement.style.width = (50 * this.health) / settings.playerMaxHealth + 'px';
|
||||
if (this.previousHealth > this.health) {
|
||||
this.previousHealth = this.health;
|
||||
if (OptionsHandler.options.vibrationEnabled) {
|
||||
|
|
@ -89,7 +82,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 + 50);
|
||||
const textOffset = vec2.scale(headFeetDelta, headFeetDelta, this.head!.radius + 60);
|
||||
return vec2.add(textOffset, this.head!.center, textOffset);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,7 +96,8 @@ form {
|
|||
padding: $small-padding;
|
||||
border: $border;
|
||||
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 {
|
||||
|
|
@ -107,7 +108,7 @@ form {
|
|||
&:checked + label {
|
||||
border-color: $accent;
|
||||
border-width: $border-width-focused;
|
||||
margin-right: 0 $small-padding / 2;
|
||||
margin: 0 $small-padding / 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,14 +8,22 @@
|
|||
justify-content: center;
|
||||
top: 0;
|
||||
right: 0;
|
||||
font-size: 0;
|
||||
}
|
||||
|
||||
#toggle-settings-container {
|
||||
padding: $medium-padding;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
padding: $medium-padding $medium-padding 0 $medium-padding;
|
||||
@media (max-width: $breakpoint) {
|
||||
padding: $medium-padding $medium-padding $medium-padding 0;
|
||||
}
|
||||
|
||||
#toggle-settings {
|
||||
animation: spin 32s linear infinite;
|
||||
|
||||
@keyframes spin {
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
|
|
@ -53,9 +61,11 @@
|
|||
opacity: 0;
|
||||
transition: transform $animation-time, opacity $animation-time;
|
||||
|
||||
pointer-events: none;
|
||||
&.open {
|
||||
transform: none;
|
||||
opacity: 1;
|
||||
pointer-events: inherit;
|
||||
}
|
||||
|
||||
img,
|
||||
|
|
|
|||
|
|
@ -13,3 +13,12 @@ $breakpoint: 700px;
|
|||
$height-breakpoint: 500px;
|
||||
$large-icon: 48px;
|
||||
$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
10
frontend/static/mask.svg
Normal 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 |
Loading…
Add table
Add a link
Reference in a new issue