Skip to content
Merged

Pong #20

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions example/pong/src/collisions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Hitbox, Position } from "./components";
import { ecsLibrary } from "./index";

export function checkCollisions(entity: any) {
const entities = ecsLibrary.getZipper([Hitbox, Position]);

const { x, y } = entity.Position;
const { width, height } = entity.Hitbox;

for (const e of entities) {
if (e === undefined) continue;
const { x: ex, y: ey } = e.Position;
const { width: ew, height: eh } = e.Hitbox;

if (
e.Position != entity.Position &&
e.Hitbox != entity.Hitbox &&
x < ex + ew &&
x + width > ex &&
y < ey + eh &&
y + height > ey
) {
return true;
}
}

return false;
}
69 changes: 69 additions & 0 deletions example/pong/src/components.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import type { NfgCircle } from "@nanoforge/graphics-2d";
import type { NfgRectangle } from "@nanoforge/graphics-2d/src/components/shape/shapes/rectangle.shape";
import type { InputEnum } from "@nanoforge/input";

export class Velocity {
name = "Velocity";
x: number;
y: number;

constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}

export class Position {
name = "Position";
x: number;
y: number;

constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}

export class Hitbox {
name = "Hitbox";
height: number;
width: number;

constructor(width: number, height: number) {
this.height = height;
this.width = width;
}
}

export class CircleComponent {
name = "CircleComponent";
component: NfgCircle;

constructor(component: NfgCircle) {
this.component = component;
}
}

export class RectangleComponent {
name = "RectangleComponent";
component: NfgRectangle;

constructor(component: NfgRectangle) {
this.component = component;
}
}

export class Bounce {
name = "Bounce";
}

export class Controller {
name = "Controller";
up: InputEnum;
down: InputEnum;

constructor(up: InputEnum, down: InputEnum) {
this.up = up;
this.down = down;
}
}
154 changes: 98 additions & 56 deletions example/pong/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,76 +3,118 @@ import { type IRunOptions } from "@nanoforge/common";
import { NanoforgeFactory } from "@nanoforge/core";
import { ECSLibrary } from "@nanoforge/ecs";
import { Graphics2DLibrary } from "@nanoforge/graphics-2d";
import { InputLibrary } from "@nanoforge/input";
import { InputEnum } from "@nanoforge/input";

import {
Bounce,
CircleComponent,
Controller,
Hitbox,
Position,
RectangleComponent,
Velocity,
} from "./components";
import { bounce, controlPlayer, drawCircle, drawRectangle, move, moveRectangle } from "./systems";

export const ecsLibrary = new ECSLibrary();

export const app = NanoforgeFactory.createClient();

class Velocity {
x: number;
y: number;

constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}

class Position {
x: number;
y: number;

constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}

let lastFrame = 0;

function move() {
const zip = ecsLibrary.getZipper([Position, Velocity]);

for (let data = zip.getValue(); data != undefined; data = zip.next()) {
data["Position"].x += data["Velocity"].x;
data["Position"].y += data["Velocity"].y;
}
}

function logger() {
const zip = ecsLibrary.getZipper([Position]);

for (let data = zip.getValue(); data != undefined; data = zip.next()) {
console.log(data["Position"].x, data["Position"].y);
}
}

function framerate(rate: number) {
const frameDuration = 1000 / rate;
let currentFrame = performance.now();
let elapsedTime = currentFrame - lastFrame;

while (elapsedTime < frameDuration) {
currentFrame = performance.now();
elapsedTime = currentFrame - lastFrame;
}
lastFrame = performance.now();
}
export const graphics = new Graphics2DLibrary();
export const inputs = new InputLibrary();

export const main = async (options: IRunOptions) => {
app.useGraphics(new Graphics2DLibrary());
app.useGraphics(graphics);
app.useComponentSystem(ecsLibrary);
app.useAssetManager(new AssetManagerLibrary());
app.useInput(inputs);

await app.init(options);

const ball = ecsLibrary.createEntity();
ecsLibrary.addComponent(ball, new Velocity(0.04, 0));
ecsLibrary.addComponent(ball, new Position(0.5, 0));
ecsLibrary.addComponent(ball, new Bounce());
ecsLibrary.addComponent(
ball,
new CircleComponent(
await graphics.factory.createCircle({
radius: 0.1,
color: { r: 1, g: 0, b: 0, a: 1 },
}),
),
);

const bg = ecsLibrary.createEntity();
ecsLibrary.addComponent(
bg,
new RectangleComponent(
await graphics.factory.createRectangle({
min: { x: -2, y: -1 },
max: { x: 2, y: 1 },
color: { r: 0, g: 0, b: 0, a: 0 },
}),
),
);

const topWall = ecsLibrary.createEntity();
ecsLibrary.addComponent(
topWall,
new RectangleComponent(
await graphics.factory.createRectangle({
color: { r: 0, g: 0, b: 0, a: 1 },
}),
),
);
ecsLibrary.addComponent(topWall, new Position(-1.8, 0.91));
ecsLibrary.addComponent(topWall, new Hitbox(3.6, 0.1));

const botWall = ecsLibrary.createEntity();
ecsLibrary.addComponent(
botWall,
new RectangleComponent(
await graphics.factory.createRectangle({
color: { r: 0, g: 0, b: 0, a: 1 },
}),
),
);
ecsLibrary.addComponent(botWall, new Position(-1.8, -1));
ecsLibrary.addComponent(botWall, new Hitbox(3.6, 0.1));

const player1 = ecsLibrary.createEntity();
ecsLibrary.addComponent(player1, new Position(-1.8, -0.3));
ecsLibrary.addComponent(player1, new Velocity(0, 0.1));
ecsLibrary.addComponent(player1, new Hitbox(0.1, 0.5));
ecsLibrary.addComponent(player1, new Controller(InputEnum.KeyW, InputEnum.KeyS));
ecsLibrary.addComponent(
player1,
new RectangleComponent(
await graphics.factory.createRectangle({
color: { r: 0, g: 0, b: 1, a: 1 },
}),
),
);

const player2 = ecsLibrary.createEntity();
ecsLibrary.addComponent(player2, new Position(1.7, -0.3));
ecsLibrary.addComponent(player2, new Velocity(0, 0.1));
ecsLibrary.addComponent(player2, new Hitbox(0.1, 0.5));
ecsLibrary.addComponent(player2, new Controller(InputEnum.ArrowUp, InputEnum.ArrowDown));
ecsLibrary.addComponent(
player2,
new RectangleComponent(
await graphics.factory.createRectangle({
color: { r: 0, g: 0, b: 1, a: 1 },
}),
),
);

ecsLibrary.addComponent(ball, new Velocity(2, 2));
ecsLibrary.addComponent(ball, new Position(50, 50));
ecsLibrary.addSystem(move);
ecsLibrary.addSystem(logger);
ecsLibrary.addSystem(() => framerate(30));
ecsLibrary.addSystem(controlPlayer);
ecsLibrary.addSystem(moveRectangle);
ecsLibrary.addSystem(drawRectangle);
ecsLibrary.addSystem(drawCircle);
ecsLibrary.addSystem(bounce);

app.run();
};
81 changes: 81 additions & 0 deletions example/pong/src/systems.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { checkCollisions } from "./collisions";
import {
Bounce,
CircleComponent,
Controller,
Hitbox,
Position,
RectangleComponent,
Velocity,
} from "./components";
import { ecsLibrary, graphics, inputs } from "./index";

export function move() {
const entities = ecsLibrary.getZipper([Bounce, Position, Velocity]);

entities.forEach((entity) => {
entity.Position.x += entity.Velocity.x;
entity.Position.y += entity.Velocity.y;
});
}

export function bounce() {
const entities = ecsLibrary.getZipper([Bounce, Position, Velocity]);

entities.forEach((entity) => {
if (entity.Position.x >= 1.6 || entity.Position.x <= -1.6) {
entity.Velocity.x = -entity.Velocity.x;
}
if (entity.Position.y >= 1 || entity.Position.y <= -1) {
entity.Velocity.y = -entity.Velocity.y;
}
});
}

export function controlPlayer() {
const entities = ecsLibrary.getZipper([Controller, Position, Hitbox, Velocity]);

entities.forEach((entity) => {
if (inputs.isKeyPressed(entity.Controller.up) && !checkCollisions(entity)) {
entity.Position.y += entity.Velocity.y;
} else {
entity.Position.y -= entity.Velocity.y;
}
if (inputs.isKeyPressed(entity.Controller.down) && !checkCollisions(entity)) {
entity.Position.y -= entity.Velocity.y;
} else {
entity.Position.y += entity.Velocity.y;
}
});
}

export function drawCircle() {
const entities = ecsLibrary.getZipper([CircleComponent, Position]);

entities.forEach((entity) => {
const pos = entity.Position;
entity.CircleComponent.component.setPosition(pos);
graphics.getWindow().draw(entity.CircleComponent.component);
});
}

export function moveRectangle() {
const entities = ecsLibrary.getZipper([RectangleComponent, Position, Hitbox]);

entities.forEach((entity) => {
const pos = entity.Position;
entity.RectangleComponent.component.setMin({ x: pos.x, y: pos.y });
entity.RectangleComponent.component.setMax({
x: pos.x + entity.Hitbox.width,
y: pos.y + entity.Hitbox.height,
});
});
}

export function drawRectangle() {
const entities = ecsLibrary.getZipper([RectangleComponent]);

entities.forEach((entity) => {
graphics.getWindow().draw(entity.RectangleComponent.component);
});
}