Skip to content

Commit

Permalink
feat: improve systems
Browse files Browse the repository at this point in the history
  • Loading branch information
ufocoder committed May 30, 2024
1 parent 411edd4 commit 4590671
Show file tree
Hide file tree
Showing 28 changed files with 544 additions and 347 deletions.
3 changes: 2 additions & 1 deletion src/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ interface Сharacter {

interface Enemy extends Сharacter {
ai?: number;
type: 'zombie' | 'flyguy' | 'soldier' | 'commando' | 'tank' | 'slayer'
type: 'zombie' | 'flyguy' | 'soldier' | 'commando' | 'tank' | 'slayer';
attack: number;
sprite: string;
radius: number;
}
Expand Down
43 changes: 43 additions & 0 deletions src/levels/base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { generateSoldier } from "./generators";

const level: Level = {
world: {
colors: {
top: { r: 0, g: 0, b: 0, a: 255 },
bottom: { r: 84, g: 98, b: 92, a: 255 },
},
},
music: 'dead-lift-yeti',
map: [
[1, 1, 1, 1, 2, 1, 1, 1],
[1, 0, 0, 0, 0, 0, 0, 1],
[5, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 1],
[1, 1, 1, 1, 1, 1, 1, 1],
],
textures: {
1: "TECH_1C",
2: "TECH_1E",
3: "TECH_2F",
4: "DOOR_1A",
5: "DOOR_1E",
},
player: {
x: 1.5,
y: 2.5,
angle: 0,
health: 100,
},
enemies: [
generateSoldier(4, 4, 4),
generateSoldier(4, 4, 4),
],
exit: {
x: 18,
y: 2,
},
};

export default level;
13 changes: 10 additions & 3 deletions src/levels/generators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,21 @@ const random = (from: number, to: number) => {
};

export const generateEntities =
<T>(generator: (x: number, y: number) => T) =>
<T>(generator: (x: number, y: number, ai: number) => T) =>
(limit: number, x: number, y: number, dx: number, dy: number, ai: number = 0) => {
return new Array(limit)
.fill(0)
.map(() => generator(random(x - dx, x + dx), random(y - dy, y + dy)), ai);
.map(() => generator(
random(x - dx, x + dx),
random(y - dy, y + dy),
ai
));
};

export const generateZombie = (x: number, y: number, ai: number = 0) =>
({
type: "zombie",
attack: 5,
health: 100,
radius: 0.4,
ai,
Expand All @@ -26,6 +31,7 @@ export const generateFlyguy = (x: number, y: number, ai: number = 0) =>
type: "flyguy",
health: 150,
radius: 0.4,
attack: 10,
ai,
x,
y,
Expand All @@ -35,7 +41,8 @@ export const generateSoldier = (x: number, y: number, ai: number = 0) =>
({
type: "soldier",
health: 200,
radius: 0.6,
radius: 0.4,
attack: 20,
ai,
x,
y,
Expand Down
24 changes: 14 additions & 10 deletions src/levels/level_1.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { generateSoldier } from "./generators";
import { generateSoldier, generateZombies } from "./generators";

const level: Level = {
world: {
Expand All @@ -9,11 +9,13 @@ const level: Level = {
},
music: 'dead-lift-yeti',
map: [
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1],
[5, 0, 0, 0, 2, 0, 0, 2, 0, 0, 0, 2, 0, 0, 2, 0, 0, 0, 0, 4],
[1, 0, 0, 0, 2, 0, 0, 2, 0, 0, 0, 2, 0, 0, 2, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1],
[1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1],
],
textures: {
1: "TECH_1C",
Expand All @@ -23,14 +25,16 @@ const level: Level = {
5: "DOOR_1E",
},
player: {
x: 15,
x: 1.5,
y: 2.5,
angle: 17,
angle: 0,
health: 100,
},
enemies: [
generateSoldier(18.5, 1.75),
generateSoldier(18.5, 3.25),
generateSoldier(18, 1.75, 4),
generateSoldier(18, 3.25, 4),
...generateZombies(4, 6, 2.5, 0.75, 0.75, 2),
...generateZombies(4, 9.5, 4.5, 1, 0.75, 2),
],
exit: {
x: 18,
Expand Down
2 changes: 1 addition & 1 deletion src/lib/Canvas/BufferCanvas.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { minmax } from "../utils";
import { minmax } from "src/lib/utils";

interface CanvasProps {
id?: string;
Expand Down
50 changes: 50 additions & 0 deletions src/lib/ecs/ExtendedECS.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import ECS from "src/lib/ecs";
import { Entity } from "./Entity";
import { Component } from "./Component";
// import { Component } from "./Component";

type EntityCallback = (entity: Entity) => void;

export default class ExtendedECS extends ECS {
protected cbComponentAdd: Map<Function, Set<EntityCallback>> = new Map();
protected cbComponentDelete: Map<Function, Set<EntityCallback>> = new Map();
protected entitiesByQuery = new Map<string, Set<Entity>>();

public query(componentClasses: Function[]): Set<Entity> {
// const key = componentClasses.map(type => type.name).sort().join(',');
// if (!this.entitiesByQuery.has(key)) {
const matchingEntities = new Set<Entity>();
for (const [entity, components] of this.entities.entries()) {
if (components.all(componentClasses)) {
matchingEntities.add(entity);
}
}
// this.entitiesByQuery.set(key, matchingEntities);
// }
// return this.entitiesByQuery.get(key)!;
return matchingEntities
}

public onComponentAdd(componentClass: Function, cb: EntityCallback) {
if (this.cbComponentAdd.has(componentClass)) {
this.cbComponentAdd.set(componentClass, new Set());
}
this.cbComponentAdd.get(componentClass)?.add(cb);
}

public onComponentDelete(componentClass: Function, cb: EntityCallback) {
this.cbComponentAdd.get(componentClass)?.delete(cb);
}

public addComponent(entity: Entity, component: Component) {
super.addComponent(entity, component);

this.cbComponentAdd.get(component.constructor)?.forEach(cb => cb(entity));
}

public removeComponent(entity: Entity, componentClass: Function) {
super.removeComponent(entity, componentClass);

this.cbComponentAdd.get(componentClass)?.forEach(cb => cb(entity));
}
}
6 changes: 3 additions & 3 deletions src/lib/ecs/System.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import ECS from ".";
import { Entity } from "./Entity";
import ECS from "src/lib/ecs/ExtendedECS";
import { Entity } from "src/lib/ecs/Entity";

export default abstract class System {
public ecs: ECS;

public abstract componentsRequired: Set<Function>;
public readonly abstract componentsRequired: Set<Function>;

constructor(ecs: ECS) {
this.ecs = ecs;
Expand Down
7 changes: 5 additions & 2 deletions src/lib/ecs/components/AIComponent.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { Component } from "src/lib/ecs/Component";

export default class AIComponent implements Component {
damage: number;
distance: number;
frequence: number; // seconds
lastAttackTime: number = 0;
damagePerSecond: number = 5;

constructor(distance: number) {
constructor(distance: number, damage: number, frequence: number = 1_000) {
this.distance = distance;
this.damage = damage;
this.frequence = frequence;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export default class AnimatedSpriteComponent implements Component {
sprite: TextureBitmap;
animationSpeed: number = 0.2;
timeSinceLastFrame: number = 0;
loop: boolean = false;

constructor(initialState: string, states: Record<string, TextureBitmap[]>) {
this.states = states;
Expand All @@ -16,21 +17,25 @@ export default class AnimatedSpriteComponent implements Component {
this.sprite = states[initialState][0];
}

// @TODO: improve
update(dt: number) {
const frames = this.states[this.currentState];
this.timeSinceLastFrame += dt;

if (!this.loop && this.currentFrame === frames.length -1) {
return
}

if (this.timeSinceLastFrame > this.animationSpeed) {
this.currentFrame = (this.currentFrame + 1) % frames.length;
this.sprite = frames[this.currentFrame];
this.timeSinceLastFrame = 0;
}
}

switchState(stateName: string) {
switchState(stateName: string, loop: boolean) {
if (stateName in this.states) {
this.currentState = stateName;
}
this.loop = loop;
}
}
5 changes: 5 additions & 0 deletions src/lib/ecs/components/WeaponComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,9 @@ export default class WeaponComponent implements Component {
damage: number = 5;
frequency: number = 1_000;
lastActionAt: number = +new Date();

constructor(damage: number = 15, frequency: number = 1_000) {
this.damage = damage;
this.frequency = frequency
}
}
27 changes: 8 additions & 19 deletions src/lib/ecs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import System from "./System";

export default class ECS {
protected entities = new Map<Entity, ComponentContainer>();
protected entitiesByQuery: Map<string, ComponentContainer[]> = new Map();
protected systems = new Map<System, Set<Entity>>();

protected nextEntityID = 0;
Expand Down Expand Up @@ -32,24 +31,6 @@ export default class ECS {
}
}

public query(componentClasses: Function[]): ComponentContainer[] {
const key = componentClasses.map(type => type.name).sort().join(',');

if (!this.entitiesByQuery.has(key)) {
const matchingEntities: ComponentContainer[] = [];

for (const components of this.entities.values()) {
if (components.all(componentClasses)) {
matchingEntities.push(components);
}
}

this.entitiesByQuery.set(key, matchingEntities);
}

return this.entitiesByQuery.get(key)!;
}

public addEntity(): Entity {
const entity = this.nextEntityID;

Expand Down Expand Up @@ -102,6 +83,14 @@ export default class ECS {
}
}

public getSystem<T extends System>(systemClass: { new (...args:any[]): T }): T | undefined {
for (const system of this.systems.keys()) {
if (system instanceof systemClass) {
return system;
}
}
}

private syncSystem(entity: Entity, system: System): void {
const components = this.entities.get(entity);

Expand Down
23 changes: 13 additions & 10 deletions src/lib/ecs/lib/PolarMap.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { angle, distance, normalizeAngle } from "src/lib/utils";
import PositionComponent from "../components/PositionComponent";
import CircleComponent from "../components/CircleComponent";
import { ComponentContainer } from "../Component";
import PositionComponent from "src/lib/ecs/components/PositionComponent";
import CircleComponent from "src/lib/ecs/components/CircleComponent";
import { ComponentContainer } from "src/lib/ecs/Component";

type Radius = number;
type Angle = number;
Expand All @@ -14,11 +14,10 @@ export interface PolarPosition {
}

export default class PolarMap {
polarEntities: PolarPosition[] = [];
public center?: ComponentContainer;
public entities?: ComponentContainer[];

constructor(center: ComponentContainer, entities: ComponentContainer[]) {
this.calculatePolarEntities(center, entities);
}
protected polarEntities: PolarPosition[] = [];

public select(distanceTo: number, angleFrom: number, angleTo: number) {

Expand All @@ -44,10 +43,14 @@ export default class PolarMap {
.sort((pe1, pe2) => pe2.distance - pe1.distance)
}

protected calculatePolarEntities(center: ComponentContainer, entities: ComponentContainer[]) {
const centerPosition = center.get(PositionComponent);
public calculatePolarEntities() {
const centerPosition = this.center?.get(PositionComponent);

if (!this.entities || !centerPosition) {
return
}

this.polarEntities = entities.map(container => {
this.polarEntities = this.entities.map(container => {
const pointCircle = container.get(CircleComponent);
const pointPosition = container.get(PositionComponent);
const a = angle(
Expand Down
10 changes: 7 additions & 3 deletions src/lib/ecs/lib/PositionMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ export default class PositionMap<T> {
rows: number;
cols: number;

constructor(cols: number, rows: number) {
this.cols = cols;
this.rows = rows;
constructor(levelMap: LevelMap) {
this.cols = levelMap[0].length;
this.rows = levelMap.length;

this.map = new Map<number, T>();
}
Expand All @@ -23,6 +23,10 @@ export default class PositionMap<T> {
return this.map.has(y * this.cols + x);
}

public reset(x: number, y: number) {
return this.map.delete(y * this.cols + x);
}

public clear() {
this.map.clear();
}
Expand Down
Loading

0 comments on commit 4590671

Please sign in to comment.