survivors: restricting player to world bounds

reworking how repeated collisions are handled for projectiles for the purpose of piercing projectiles
This commit is contained in:
Sheldan
2025-08-21 20:54:56 +02:00
parent 8ca64a19b7
commit 603bf3addc
6 changed files with 73 additions and 39 deletions

View File

@@ -6,7 +6,7 @@ export class Player implements Drawable, Acting, Healthy {
private _position: Vector; private _position: Vector;
private _stats: Stats; private _stats: Stats;
private _color: string; private _color: string;
private _status: Status; private _status: PlayerStatus;
private _weapons: [Weapon] = [] private _weapons: [Weapon] = []
// temp // temp
@@ -27,7 +27,7 @@ export class Player implements Drawable, Acting, Healthy {
player._color = 'blue'; player._color = 'blue';
player._stats = Stats.defaultPlayerStats(); player._stats = Stats.defaultPlayerStats();
player._speed = new Vector(0, 0) player._speed = new Vector(0, 0)
player._status = new Status(10, 0); player._status = new PlayerStatus(10, 0);
return player; return player;
} }
@@ -64,7 +64,7 @@ export class Player implements Drawable, Acting, Healthy {
return this._stats; return this._stats;
} }
get status(): Status { get status(): PlayerStatus {
return this._status; return this._status;
} }
@@ -92,7 +92,7 @@ export class Player implements Drawable, Acting, Healthy {
} }
} }
export class Status { export class PlayerStatus {
constructor(private _health: number, private _wealth: number) { constructor(private _health: number, private _wealth: number) {
} }

View File

@@ -1,5 +1,4 @@
import {Enemy} from "./Enemies.ts"; import {Enemy} from "./Enemies.ts";
import type {Player} from "./Player.ts";
import {Player} from "./Player.ts"; import {Player} from "./Player.ts";
import {Projectile} from "./projectile.ts"; import {Projectile} from "./projectile.ts";
import {Vector} from "./base.ts"; import {Vector} from "./base.ts";
@@ -40,6 +39,15 @@ export class World {
this._drops.push(drop) this._drops.push(drop)
} }
movePlayer(vector: Vector) {
this._player.position.x += vector.x;
this._player.position.y += vector.y;
this._player.position.x = Math.min(this.size.x - this._player.getSize(), this._player.position.x)
this._player.position.x = Math.max(this._player.getSize(), this._player.position.x)
this._player.position.y = Math.min(this.size.y -this._player.getSize(), this._player.position.y)
this._player.position.y = Math.max(this._player.getSize(), this._player.position.y)
}
removeDrop(drop: Drop) { removeDrop(drop: Drop) {
this._drops = this._drops.filter(item => item !== drop) this._drops = this._drops.filter(item => item !== drop)
} }
@@ -75,9 +83,16 @@ export class World {
} }
getClosestTargetTo(point: Vector): [number, Placeable | undefined] | undefined { getClosestTargetTo(point: Vector): [number, Placeable | undefined] | undefined {
return this.getClosestTargetToButNot(point, undefined)
}
getClosestTargetToButNot(point: Vector, placeAble?: Placeable): [number, Placeable | undefined] | undefined {
let currentTarget; let currentTarget;
let currentDistance = Number.MAX_SAFE_INTEGER; let currentDistance = Number.MAX_SAFE_INTEGER;
this._enemies.forEach(enemy => { this._enemies.forEach(enemy => {
if(placeAble && enemy === placeAble) {
return;
}
let distance = point.distanceTo(enemy.getPosition()); let distance = point.distanceTo(enemy.getPosition());
if(distance < currentDistance) { if(distance < currentDistance) {
currentDistance = distance; currentDistance = distance;

View File

@@ -63,7 +63,7 @@ export class MoneyDrop extends BasicDrop {
let drop = new MoneyDrop(world, position) let drop = new MoneyDrop(world, position)
drop.worth = 1; drop.worth = 1;
drop._size = 1; drop._size = 1;
drop._color = 'yellow'; drop._color = 'orange';
world.addDrop(drop) world.addDrop(drop)
return drop; return drop;
} }

View File

@@ -65,16 +65,16 @@ function makeKey(char, fun) {
let keys = {}; let keys = {};
makeKey('w', function () { makeKey('w', function () {
world.player.position.y += -world.player.stats.speed world.movePlayer(new Vector(0, -world.player.stats.speed))
}) })
makeKey('s', function () { makeKey('s', function () {
world.player.position.y += world.player.stats.speed world.movePlayer(new Vector(0, world.player.stats.speed))
}) })
makeKey('a', function () { makeKey('a', function () {
world.player.position.x += -world.player.stats.speed world.movePlayer(new Vector(-world.player.stats.speed, 0))
}) })
makeKey('d', function () { makeKey('d', function () {
world.player.position.x += world.player.stats.speed world.movePlayer(new Vector(world.player.stats.speed, 0))
}) })

View File

@@ -12,8 +12,10 @@ export abstract class Projectile implements Acting, Placeable {
protected world: World; protected world: World;
protected parent: any; protected parent: any;
protected color: string protected color: string
private stats: ProjectileStats; protected stats: ProjectileStats;
private status: ProjectileStatus; protected status: ProjectileStatus;
protected lastPosition: Vector;
protected lastColliding?: Placeable;
constructor(position: Vector, speedVec: Vector, stats: ProjectileStats, world: World, parent: any) { constructor(position: Vector, speedVec: Vector, stats: ProjectileStats, world: World, parent: any) {
this.position = position.clone(); this.position = position.clone();
@@ -26,20 +28,20 @@ export abstract class Projectile implements Acting, Placeable {
act() { act() {
this.move() this.move()
if(this.status.collisionCooldown.cooledDown()) { if(this.parent !== this.world.player) {
if(this.parent !== this.world.player) { if(this.position.distanceTo(this.world.player.position) < (this.stats.size + this.world.player.stats.size) && this.status.collisionCooldown.cooledDown()) {
if(this.position.distanceTo(this.world.player.position) < (this.stats.size + this.world.player.stats.size)) { this.impactPlayer()
this.impactPlayer() this.status.collisionCooldown.resetCooldown()
this.status.collisionCooldown.resetCooldown() }
} } else if(this.parent === this.world.player) {
} else if(this.parent === this.world.player) { let closestTargetTo = this.world.getClosestTargetToButNot(this.position, this.lastColliding);
let closestTargetTo = this.world.getClosestTargetTo(this.position); if(closestTargetTo !== undefined && closestTargetTo[1] !== undefined && closestTargetTo[1]?.getPosition().distanceTo(this.position) < (this.stats.size + closestTargetTo[1]?.getSize())) {
if(closestTargetTo !== undefined && closestTargetTo[1] !== undefined && closestTargetTo[1]?.getPosition().distanceTo(this.position) < (this.stats.size + closestTargetTo[1]?.getSize())) { let target: Placeable = closestTargetTo[1]!;
let target: Placeable = closestTargetTo[1]!; if(target !== this.lastColliding) {
if(InstanceOfUtils.instanceOfHealthy(target)) { if(InstanceOfUtils.instanceOfHealthy(target)) {
let healthy = target as Healthy; let healthy = target as Healthy;
healthy.takeDamage(this.stats.damage) healthy.takeDamage(this.stats.damage)
if(this.status.piercingsLeft <= 0) { if(!this.status.hasPiercingLeft()) {
this.world.removeProjectile(this) this.world.removeProjectile(this)
} }
this.status.decreasePiercings() this.status.decreasePiercings()
@@ -47,9 +49,12 @@ export abstract class Projectile implements Acting, Placeable {
this.world.removeProjectile(this) this.world.removeProjectile(this)
} }
} }
this.lastColliding = target;
} else {
this.lastColliding = undefined;
} }
} }
this.status.collisionCooldown.decreaseCooldown(); this.status.collisionCooldown.decreaseCooldown()
this.checkWorldBorder() this.checkWorldBorder()
} }
@@ -61,7 +66,9 @@ export abstract class Projectile implements Acting, Placeable {
impactPlayer() { impactPlayer() {
this.world.player.takeDamage(this.stats.damage) this.world.player.takeDamage(this.stats.damage)
this.world.removeProjectile(this) if(!this.status.hasPiercingLeft()) {
this.world.removeProjectile(this)
}
}; };
draw(ctx: CanvasRenderingContext2D) { draw(ctx: CanvasRenderingContext2D) {
@@ -69,6 +76,7 @@ export abstract class Projectile implements Acting, Placeable {
} }
move() { move() {
this.lastPosition = this.position.clone()
} }
getPosition(): Vector { getPosition(): Vector {
@@ -89,6 +97,7 @@ export class StraightProjectile extends Projectile {
} }
move() { move() {
super.move()
this.position = straightMove(this.position, this.speedVec) this.position = straightMove(this.position, this.speedVec)
} }
@@ -111,35 +120,35 @@ export class HomingProjectile extends Projectile {
} }
move() { move() {
super.move()
this.position = moveInDirectionOf(this.position, this.target.getPosition(), this.speedVec.vecLength())
if(InstanceOfUtils.instanceOfHealthy(this.target)) { if(InstanceOfUtils.instanceOfHealthy(this.target)) {
let target = this.target as Healthy let target = this.target as Healthy
if(target.dead()) { if(target.dead()) {
if(this.position.distanceTo(this.target.getPosition()) < (this.target.getSize() + this.getSize())) { let closestTargetTo = this.world.getClosestTargetTo(this.position)
this.world.removeProjectile(this)
return;
}
let closestTargetTo = this.world.getClosestTargetTo(this.world.player.position)
let dir = Vector.createVector(this.target.getPosition(), this.position).normalize()
let oldDir = Vector.createVector(this.position, this.lastPosition).normalize()
if (closestTargetTo !== undefined && closestTargetTo[1] !== undefined) { if (closestTargetTo !== undefined && closestTargetTo[1] !== undefined) {
let dir = Vector.createVector(this.target.getPosition(), this.position).normalize()
let newTargetPosition = closestTargetTo[1]!.getPosition(); let newTargetPosition = closestTargetTo[1]!.getPosition();
let newDir = Vector.createVector(newTargetPosition, this.position).normalize() let newDir = Vector.createVector(newTargetPosition, this.position).normalize()
let newDirAngle = newDir.angleTo(dir); let newDirAngle = newDir.angleTo(dir);
if(Math.abs(newDirAngle) < toRad(60)) { if(Math.abs(newDirAngle) >= toRad(150)) {
this.target = closestTargetTo[1]!; this.target = closestTargetTo[1]!;
} else { } else {
this.target = new Point(this.target.getPosition().add(dir.normalize().multiply(Math.max(this.world.size.x, this.world.size.y)))) this.target = new Point(this.position.add(oldDir.multiply(Math.max(this.world.size.x, this.world.size.y))))
} }
} else {
this.target = new Point(this.position.add(oldDir.multiply(Math.max(this.world.size.x, this.world.size.y))))
} }
} }
} }
this.position = moveInDirectionOf(this.position, this.target.getPosition(), this.speedVec.vecLength())
this.checkWorldBorder() this.checkWorldBorder()
} }
static createHomingProjectile(world: World, start: Vector, parent: any, target: Placeable, stats: ProjectileStats, color?: string) { static createHomingProjectile(world: World, start: Vector, parent: any, target: Placeable, stats: ProjectileStats, color?: string) {
let projectile = new HomingProjectile(start, new Vector(5, 1), stats, world, parent, target) let projectile = new HomingProjectile(start, new Vector(0, stats.speed), stats, world, parent, target)
projectile.color = color === undefined ? 'red' : color!; projectile.color = color === undefined ? 'red' : color!;
world.addProjectile(projectile) world.addProjectile(projectile)
return projectile; return projectile;
@@ -160,6 +169,10 @@ export class ProjectileStatus {
return this._piercingsLeft; return this._piercingsLeft;
} }
hasPiercingLeft(): boolean {
return this.piercingsLeft >= 0;
}
get collisionCooldown(): Cooldown { get collisionCooldown(): Cooldown {
return this._collisionCooldown; return this._collisionCooldown;
@@ -175,11 +188,13 @@ export class ProjectileStats {
private _piercings: number; private _piercings: number;
private _size: number; private _size: number;
private _damage: number; private _damage: number;
private _speed: number;
constructor(piercings: number, size: number, damage: number) { constructor(piercings: number, size: number, damage: number, _speed: number) {
this._piercings = piercings; this._piercings = piercings;
this._size = size; this._size = size;
this._damage = damage this._damage = damage
this._speed = _speed;
} }
get piercings(): number { get piercings(): number {
@@ -191,6 +206,10 @@ export class ProjectileStats {
} }
get speed(): number {
return this._speed;
}
get damage(): number { get damage(): number {
return this._damage; return this._damage;
} }

View File

@@ -7,10 +7,10 @@ import {Vector} from "./base.ts";
export class Pistol implements Weapon { export class Pistol implements Weapon {
private player: Player private readonly player: Player
private shootInterval: number; private shootInterval: number;
private shootCooldown: number = 0; private shootCooldown: number = 0;
private world: World; private readonly world: World;
private offset: Vector; private offset: Vector;
private projectiles: [Projectile] = [] private projectiles: [Projectile] = []
private color: string; private color: string;
@@ -37,7 +37,7 @@ export class Pistol implements Weapon {
private createProjectile(): boolean { private createProjectile(): boolean {
let closestTargetTo = this.world.getClosestTargetTo(this.world.player.position); let closestTargetTo = this.world.getClosestTargetTo(this.world.player.position);
if(closestTargetTo !== undefined && closestTargetTo[1] !== undefined) { if(closestTargetTo !== undefined && closestTargetTo[1] !== undefined) {
let stats = new ProjectileStats(0, 1, 5) let stats = new ProjectileStats(2, 1, 5, 1)
let projectile = HomingProjectile.createHomingProjectile(this.world, this.getPosition(), this.player, closestTargetTo[1]!, stats, 'yellow') let projectile = HomingProjectile.createHomingProjectile(this.world, this.getPosition(), this.player, closestTargetTo[1]!, stats, 'yellow')
this.projectiles.push(projectile) this.projectiles.push(projectile)
return true return true