Skip to content

Commit

Permalink
chore: implement spite rendering
Browse files Browse the repository at this point in the history
  • Loading branch information
ufocoder committed May 13, 2024
1 parent 177bc3d commit f16b736
Show file tree
Hide file tree
Showing 10 changed files with 186 additions and 104 deletions.
1 change: 1 addition & 0 deletions src/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ interface Сharacter {

interface Enemy extends Сharacter {
sprite: string;
radius: number;
}

type LevelMap = number[][];
Expand Down
34 changes: 23 additions & 11 deletions src/levels/base.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
import { Color } from "src/managers/TextureManager";

const random = (from: number, to: number) => {
return from + Math.random() * (to - from);
};

const generateEnemies = (limit: number) => {
return new Array(limit).fill(0).map(() => ({
x: random(2, 4),
y: random(3, 8),
angle: 45,
health: 100,
sprite: "soldier",
radius: 0.4,
}))
}

const level: Level = {
world: {
colors: {
Expand Down Expand Up @@ -30,22 +45,19 @@ const level: Level = {
angle: 0,
health: 100,
},
enemies: [
enemies: generateEnemies(10)
/*
[
{
x: 4,
y: 3.5,
x: 3,
y: 4.5,
angle: 45,
health: 100,
sprite: "soldier",
radius: 0.4,
},
{
x: 4,
y: 7,
angle: 45,
health: 100,
sprite: "soldier",
},
],
]*/
,
exit: {
x: 4,
y: 5,
Expand Down
3 changes: 3 additions & 0 deletions src/lib/Canvas/BufferCanvas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ export default class BufferCanvas {
}

drawPixel({ x, y, color }: DrawPixelProps) {
if (color.a === 0) {
return;
}
const offset = 4 * (Math.floor(x) + Math.floor(y) * this.width);

this.buffer.data[offset] = color.r;
Expand Down
11 changes: 11 additions & 0 deletions src/lib/ecs/components/AnimatedSpriteComponent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Component } from "src/lib/ecs/Component";

type Sprite = Texture;

export default class SpriteComponent implements Component {
sprite: Sprite;

constructor(sprite: Sprite) {
this.sprite = sprite;
}
}
77 changes: 77 additions & 0 deletions src/lib/ecs/lib/PolarMap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { angle, distance, normalizeAngle } from "src/lib/utils";
import Entity from "../Entity";
import PositionComponent from "../components/PositionComponent";
import CircleComponent from "../components/CircleComponent";

type Radius = number;
type Angle = number;

export interface PolarPosition {
distance: Radius;
angleFrom: Angle;
angleTo: Angle;
entity: Entity;
}

export default class PolarMap {
polarEntities: PolarPosition[] = [];

constructor(center: Entity, entities: Entity[]) {
this.calculatePolarEntities(center, entities);
}

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

angleFrom = normalizeAngle(angleFrom);
angleTo = normalizeAngle(angleTo);

return this.polarEntities
.filter((polarEntity) => {
if (distanceTo <= polarEntity.distance) {
return false
}

const a1 = 0;
const a2 = normalizeAngle(polarEntity.angleTo - polarEntity.angleFrom);
const b1 = normalizeAngle(angleFrom - polarEntity.angleFrom);
const b2 = normalizeAngle(angleTo - polarEntity.angleFrom);

return (
a1 <= b1 && b1 <= a2 &&
a1 <= b2 && b2 <= a2
)
})
.sort((pe1, pe2) => pe2.distance - pe1.distance)
}

protected calculatePolarEntities(center: Entity, entities: Entity[]) {
const centerPosition = center.getComponent(PositionComponent);

this.polarEntities = entities.map(entity => {
const pointCircle = entity.getComponent(CircleComponent);
const pointPosition = entity.getComponent(PositionComponent);
const a = angle(
centerPosition.x,
centerPosition.y,
pointPosition.x,
pointPosition.y,
);

const d = distance(
centerPosition.x,
centerPosition.y,
pointPosition.x,
pointPosition.y,
);

const ta = Math.asin(pointCircle.radius / (d)) * (180 / Math.PI);

return {
distance: d,
angleFrom: normalizeAngle(a - ta),
angleTo: normalizeAngle(a + ta),
entity
}
}).filter(polarEntity => !isNaN(polarEntity.angleFrom));
}
}
140 changes: 53 additions & 87 deletions src/lib/ecs/systems/CameraSystem.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import Canvas from "src/lib/Canvas/BufferCanvas";
import System from "src/lib/ecs/System";
import Entity from "src/lib/ecs/Entity";
import {
degreeToRadians,
radiansToDegrees,
} from "src/lib/utils";
// import BoxComponent from "src/lib/ecs/components/BoxComponent";
import { degreeToRadians, distance, normalizeAngle } from "src/lib/utils";
import PositionComponent from "src/lib/ecs/components/PositionComponent";
import AngleComponent from "src/lib/ecs/components/AngleComponent";
import TextureComponent from "../components/TextureComponent";
Expand All @@ -14,6 +10,7 @@ import PositionMap from "../lib/PositionMap";
import QuerySystem from "../lib/QuerySystem";
import SpriteComponent from "../components/SpriteComponent";
import TextureManager from "src/managers/TextureManager";
import PolarMap, { PolarPosition } from "../lib/PolarMap";

export default class CameraSystem extends System {
requiredComponents = [CameraComponent, PositionComponent];
Expand Down Expand Up @@ -84,12 +81,16 @@ export default class CameraSystem extends System {
const cameraAngle = camera.getComponent(AngleComponent);
const cameraPosition = camera.getComponent(PositionComponent);

const spriteEntities: Entity[] = [];
const polarMap = new PolarMap(
camera,
this.querySystem.query([PositionComponent, SpriteComponent]),
);

const incrementAngle = cameraFov.fov / this.width;

let rayAngle = cameraAngle.angle - cameraFov.fov / 2;
let rayAngle = normalizeAngle(cameraAngle.angle - cameraFov.fov / 2);

for (let x = 0; x < this.width; x++) {
for (let screenX = 0; screenX < this.width; screenX++) {
const incrementRayX =
Math.cos(degreeToRadians(rayAngle)) / this.rayPrecision;
const incrementRayY =
Expand Down Expand Up @@ -122,9 +123,11 @@ export default class CameraSystem extends System {
isPropogating = false;
}

distanceRay = Math.sqrt(
Math.pow(cameraPosition.x - rayX, 2) +
Math.pow(cameraPosition.y - rayY, 2)
distanceRay = distance(
cameraPosition.x,
cameraPosition.y,
rayX,
rayY,
);

if (distanceRay >= this.rayMaxDistanceRay) {
Expand All @@ -135,23 +138,25 @@ export default class CameraSystem extends System {
distanceRay =
distanceRay * Math.cos(degreeToRadians(rayAngle - cameraAngle.angle));

if (wallEntity) {
const wallHeight = Math.floor(this.height / 2 / distanceRay);
const wallHeight = Math.floor(this.height / 2 / distanceRay);

this._drawHorizon(x, wallHeight);
this._drawFloor(x, wallHeight, rayAngle, camera);
this._drawWall(x, rayX, rayY, wallEntity, wallHeight);
if (wallEntity) {
this._drawHorizon(screenX, wallHeight);
this._drawFloor(screenX, wallHeight, rayAngle, camera);
this._drawWall(screenX, rayX, rayY, wallEntity, wallHeight);
} else {
this._drawHorizon(x, 0);
this._drawFloor(x, 0, rayAngle, camera);
this._drawHorizon(screenX, 0);
this._drawFloor(screenX, 0, rayAngle, camera);
}

rayAngle += incrementAngle;
}
polarMap
.select(distanceRay, rayAngle, rayAngle + incrementAngle)
.forEach(polarEntity => {
this._drawSpriteLine(screenX, rayAngle, polarEntity);
});

spriteEntities.forEach((sprite) => {
this._drawSprite(camera, sprite);
});
rayAngle += normalizeAngle(incrementAngle);
}
}

_drawHorizon(x: number, wallHeight: number) {
Expand Down Expand Up @@ -199,6 +204,31 @@ export default class CameraSystem extends System {
}
}

_drawSpriteLine(screenX: number, rayAngle: number, polarEntity: PolarPosition){
const spriteComponent = polarEntity.entity.getComponent(SpriteComponent).sprite;
const projectionHeight = Math.floor(this.height / 2 / polarEntity.distance);

const a1 = normalizeAngle(rayAngle - polarEntity.angleFrom);
const a2 = normalizeAngle(polarEntity.angleTo - polarEntity.angleFrom);
const xTexture = Math.floor(a1 / a2 * spriteComponent.width)

const yIncrementer = (projectionHeight * 2) / spriteComponent.height;

let y = this.height / 2 - projectionHeight;

for (let i = 0; i < spriteComponent.height; i++) {
if (y > -yIncrementer && y < this.height) {
this.canvas.drawVerticalLine({
x: screenX,
y1: y,
y2: Math.floor(y + yIncrementer),
color: spriteComponent.colors[i][xTexture],
});
}
y += yIncrementer;
}
}

_drawFloor(
x: number,
wallHeight: number,
Expand Down Expand Up @@ -238,68 +268,4 @@ export default class CameraSystem extends System {
this.canvas.drawPixel({ x, y, color });
}
}

_calculateEntityProjection(camera: Entity, entity: Entity) {
const cameraPosition = camera.getComponent(PositionComponent);
const cameraAngle = camera.getComponent(AngleComponent);
const cameraFov = camera.getComponent(CameraComponent);
const entityPosition = entity.getComponent(PositionComponent);

const dx = entityPosition.x - cameraPosition.x;
const dy = entityPosition.y - cameraPosition.y;

const entityAngleRadians = Math.atan2(dx, dy);
const entityAngle =
radiansToDegrees(entityAngleRadians) -
Math.floor(cameraAngle.angle - cameraFov.fov / 2);

const x = Math.floor((entityAngle * this.width) / cameraFov.fov);
const distance = Math.sqrt(dx ** 2 + dy ** 2);

const height = Math.floor(this.height / 2 / distance);
const width = Math.floor(this.width / 2 / distance);

return {
x,
distance,
height,
width,
};
}

_drawSprite(camera: Entity, sprite: Entity) {
const spriteProjection = this._calculateEntityProjection(camera, sprite);
const spriteTexture = sprite.getComponent(SpriteComponent).sprite;

const wallHeight = Math.floor(this.height / spriteProjection.distance);
const xIncrementer = spriteProjection.width / spriteTexture.width;
const yIncrementer = spriteProjection.height / spriteTexture.height;

let xProjection = spriteProjection.x - spriteProjection.width / 2;

for (let spriteX = 0; spriteX < spriteTexture.width; spriteX++) {
let yProjection =
this.height / 2 -
spriteProjection.height / 2 -
(spriteProjection.height / 2 - wallHeight / 2) / 2;

for (let spriteY = 0; spriteY < spriteTexture.height; spriteY++) {
const color = spriteTexture.colors[spriteY][spriteX];

if (color.a !== 0) {
this.canvas.drawRect({
x: xProjection,
y: yProjection,
width: xIncrementer,
height: yIncrementer,
color,
});
}

yProjection += yIncrementer;
}
xProjection += xIncrementer;
}
}

}
8 changes: 4 additions & 4 deletions src/lib/ecs/systems/MoveSystem.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { degreeToRadians } from "src/lib/utils";
import { degreeToRadians, normalizeAngle } from "src/lib/utils";
import Entity from "src/lib/ecs/Entity";
import System from "src/lib/ecs/System";
import AngleComponent from "src/lib/ecs/components/AngleComponent";
Expand All @@ -9,9 +9,10 @@ import CollisionComponent from "src/lib/ecs/components/CollisionComponent";
import QuerySystem from "../lib/QuerySystem";
import PositionMap from "../lib/PositionMap";
import CameraComponent from "../components/CameraComponent";
import CircleComponent from "../components/CircleComponent";

export default class MoveSystem extends System {
requiredComponents = [PositionComponent, AngleComponent, RotateComponent, MoveComponent];
requiredComponents = [CircleComponent, PositionComponent, AngleComponent, RotateComponent, MoveComponent];

protected positionMap: PositionMap<Entity>;
protected cols: number;
Expand Down Expand Up @@ -69,8 +70,7 @@ export default class MoveSystem extends System {
}

if (k) {
angleComponent.angle = angleComponent.angle + k * rotateComponent.rotationSpeed * dt;
angleComponent.angle %= 360;
angleComponent.angle = normalizeAngle(angleComponent.angle + k * rotateComponent.rotationSpeed * dt);
}
}

Expand Down
Loading

0 comments on commit f16b736

Please sign in to comment.