slight refactor in collision behavior
This commit is contained in:
parent
cd6a3a56b0
commit
06bb417720
@ -1,32 +1,30 @@
|
|||||||
/* The Modal (background) */
|
|
||||||
.modal {
|
.modal {
|
||||||
display: none; /* Hidden by default */
|
display: none;
|
||||||
position: fixed; /* Stay in place */
|
position: fixed;
|
||||||
z-index: 1; /* Sit on top */
|
z-index: 1;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
width: 100%; /* Full width */
|
width: 100%;
|
||||||
height: 100%; /* Full height */
|
height: 100%;
|
||||||
justify-content: center; /* Center horizontally */
|
justify-content: center;
|
||||||
align-items: center; /* Center vertically */
|
align-items: center;
|
||||||
background-color: rgba(0, 0, 0, 0.4); /* Black w/ opacity */
|
background-color: rgba(0, 0, 0, 0.4);
|
||||||
overflow: auto; /* Enable scroll if needed */
|
overflow: auto;
|
||||||
animation: fadeIn 0.25s; /* Fade in the background */
|
animation: fadeIn 0.25s;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Modal Content */
|
|
||||||
.modal-content {
|
.modal-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
background-color: #282828; /* Gruvbox background */
|
background-color: #282828;
|
||||||
color: #ebdbb2; /* Gruvbox foreground */
|
color: #ebdbb2;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
border: 1px solid #928374; /* Gruvbox grey */
|
border: 1px solid #928374;
|
||||||
width: 40%; /* Adjust as needed */
|
width: 40%;
|
||||||
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
|
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
|
||||||
animation: scaleUp 0.25s; /* Scale animation */
|
animation: scaleUp 0.25s;
|
||||||
border-radius: 8px; /* Rounded corners */
|
border-radius: 8px;
|
||||||
justify-content: center; /* Center horizontally */
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Animations */
|
/* Animations */
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { Game } from ".";
|
import { Game } from ".";
|
||||||
import { Miscellaneous, loadAssets } from "./config";
|
import { Miscellaneous, loadAssets } from "./config";
|
||||||
import { Player, FunctionBox } from "./entities";
|
import { Player, FunctionBox, Wall } from "./entities";
|
||||||
import { FacingDirection, Input, Render } from "./systems";
|
import { Grid, FacingDirection, Input, Render } from "./systems";
|
||||||
import { Grid } from "./systems/Grid";
|
|
||||||
|
|
||||||
export class TheAbstractionEngine {
|
export class TheAbstractionEngine {
|
||||||
private game: Game;
|
private game: Game;
|
||||||
@ -24,7 +23,6 @@ export class TheAbstractionEngine {
|
|||||||
const facingDirectionSystem = new FacingDirection(inputSystem);
|
const facingDirectionSystem = new FacingDirection(inputSystem);
|
||||||
|
|
||||||
[
|
[
|
||||||
new Render(this.ctx),
|
|
||||||
inputSystem,
|
inputSystem,
|
||||||
facingDirectionSystem,
|
facingDirectionSystem,
|
||||||
new Grid(
|
new Grid(
|
||||||
@ -34,6 +32,7 @@ export class TheAbstractionEngine {
|
|||||||
height: Miscellaneous.GRID_CELL_HEIGHT,
|
height: Miscellaneous.GRID_CELL_HEIGHT,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
new Render(this.ctx),
|
||||||
].forEach((system) => this.game.addSystem(system));
|
].forEach((system) => this.game.addSystem(system));
|
||||||
|
|
||||||
const player = new Player();
|
const player = new Player();
|
||||||
@ -43,6 +42,9 @@ export class TheAbstractionEngine {
|
|||||||
this.game.addEntity(box);
|
this.game.addEntity(box);
|
||||||
const box2 = new FunctionBox({ x: 4, y: 1 }, "λ x . (x)");
|
const box2 = new FunctionBox({ x: 4, y: 1 }, "λ x . (x)");
|
||||||
this.game.addEntity(box2);
|
this.game.addEntity(box2);
|
||||||
|
|
||||||
|
const wall = new Wall({ x: 5, y: 3 });
|
||||||
|
this.game.addEntity(wall);
|
||||||
}
|
}
|
||||||
|
|
||||||
public play() {
|
public play() {
|
||||||
|
7
src/engine/components/Colliding.ts
Normal file
7
src/engine/components/Colliding.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { Component, ComponentNames } from ".";
|
||||||
|
|
||||||
|
export class Colliding extends Component {
|
||||||
|
constructor() {
|
||||||
|
super(ComponentNames.Colliding);
|
||||||
|
}
|
||||||
|
}
|
@ -6,4 +6,6 @@ export namespace ComponentNames {
|
|||||||
export const Control = "Control";
|
export const Control = "Control";
|
||||||
export const Highlight = "Highlight";
|
export const Highlight = "Highlight";
|
||||||
export const Interactable = "Interactable";
|
export const Interactable = "Interactable";
|
||||||
|
export const Pushable = "Pushable";
|
||||||
|
export const Colliding = "Colliding";
|
||||||
}
|
}
|
||||||
|
@ -6,15 +6,12 @@ export class Grid extends Component {
|
|||||||
|
|
||||||
public gridPosition: Coord2D;
|
public gridPosition: Coord2D;
|
||||||
public movingDirection: Direction;
|
public movingDirection: Direction;
|
||||||
public pushable: boolean = false;
|
|
||||||
|
|
||||||
constructor(pushable: boolean = false, position: Coord2D = { x: 0, y: 0 }) {
|
constructor(position: Coord2D = { x: 0, y: 0 }) {
|
||||||
super(ComponentNames.Grid);
|
super(ComponentNames.Grid);
|
||||||
|
|
||||||
this.initialized = false;
|
this.initialized = false;
|
||||||
|
|
||||||
this.gridPosition = position;
|
this.gridPosition = position;
|
||||||
this.movingDirection = Direction.NONE;
|
this.movingDirection = Direction.NONE;
|
||||||
this.pushable = pushable;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,33 @@
|
|||||||
import { Component, ComponentNames } from ".";
|
import { Component, ComponentNames } from ".";
|
||||||
|
|
||||||
export class Highlight extends Component {
|
export class Highlight extends Component {
|
||||||
constructor() {
|
public isHighlighted: boolean;
|
||||||
|
private onHighlight: Function;
|
||||||
|
private onUnhighlight: Function;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
onHighlight: Function,
|
||||||
|
onUnhighlight: Function,
|
||||||
|
isHighlighted: boolean = false,
|
||||||
|
) {
|
||||||
super(ComponentNames.Highlight);
|
super(ComponentNames.Highlight);
|
||||||
|
|
||||||
|
this.isHighlighted = isHighlighted;
|
||||||
|
this.onHighlight = onHighlight;
|
||||||
|
this.onUnhighlight = onUnhighlight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public highlight() {
|
||||||
|
if (!this.isHighlighted) {
|
||||||
|
this.isHighlighted = true;
|
||||||
|
this.onHighlight();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public unhighlight() {
|
||||||
|
if (this.isHighlighted) {
|
||||||
|
this.isHighlighted = false;
|
||||||
|
this.onUnhighlight();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
11
src/engine/components/Pushable.ts
Normal file
11
src/engine/components/Pushable.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { Component, ComponentNames } from ".";
|
||||||
|
|
||||||
|
export class Pushable extends Component {
|
||||||
|
public pushable: boolean;
|
||||||
|
|
||||||
|
constructor(pushable = false) {
|
||||||
|
super(ComponentNames.Pushable);
|
||||||
|
|
||||||
|
this.pushable = pushable;
|
||||||
|
}
|
||||||
|
}
|
@ -7,3 +7,5 @@ export * from "./BoundingBox";
|
|||||||
export * from "./Control";
|
export * from "./Control";
|
||||||
export * from "./Highlight";
|
export * from "./Highlight";
|
||||||
export * from "./Interactable";
|
export * from "./Interactable";
|
||||||
|
export * from "./Pushable";
|
||||||
|
export * from "./Colliding";
|
||||||
|
@ -3,6 +3,7 @@ import { Direction } from "../interfaces/Direction";
|
|||||||
export enum Sprites {
|
export enum Sprites {
|
||||||
PLAYER,
|
PLAYER,
|
||||||
FUNCTION_BOX,
|
FUNCTION_BOX,
|
||||||
|
WALL,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SpriteSpec {
|
export interface SpriteSpec {
|
||||||
@ -46,3 +47,12 @@ const functionBoxSpriteSpec = {
|
|||||||
sheet: "/assets/function_block.png",
|
sheet: "/assets/function_block.png",
|
||||||
};
|
};
|
||||||
SPRITE_SPECS.set(Sprites.FUNCTION_BOX, functionBoxSpriteSpec);
|
SPRITE_SPECS.set(Sprites.FUNCTION_BOX, functionBoxSpriteSpec);
|
||||||
|
|
||||||
|
const wallSpriteSpec = {
|
||||||
|
msPerFrame: 200,
|
||||||
|
width: 64,
|
||||||
|
height: 64,
|
||||||
|
frames: 3,
|
||||||
|
sheet: "/assets/wall.png",
|
||||||
|
};
|
||||||
|
SPRITE_SPECS.set(Sprites.WALL, wallSpriteSpec);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
export namespace EntityNames {
|
export namespace EntityNames {
|
||||||
export const Player = "Player";
|
export const Player = "Player";
|
||||||
export const FunctionBox = "FunctionBox";
|
export const FunctionBox = "FunctionBox";
|
||||||
|
export const Wall = "Wall";
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,9 @@ import {
|
|||||||
BoundingBox,
|
BoundingBox,
|
||||||
ComponentNames,
|
ComponentNames,
|
||||||
Grid,
|
Grid,
|
||||||
|
Highlight,
|
||||||
Interactable,
|
Interactable,
|
||||||
|
Pushable,
|
||||||
Sprite,
|
Sprite,
|
||||||
} from "../components";
|
} from "../components";
|
||||||
import { Coord2D } from "../interfaces";
|
import { Coord2D } from "../interfaces";
|
||||||
@ -36,7 +38,9 @@ export class FunctionBox extends Entity {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.addComponent(new Grid(true, gridPosition));
|
this.addComponent(new Pushable());
|
||||||
|
|
||||||
|
this.addComponent(new Grid(gridPosition));
|
||||||
|
|
||||||
this.addComponent(
|
this.addComponent(
|
||||||
new Sprite(
|
new Sprite(
|
||||||
@ -51,25 +55,31 @@ export class FunctionBox extends Entity {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.hooks.set(ComponentNames.Highlight, {
|
this.addComponent(
|
||||||
add: () => {
|
new Highlight(
|
||||||
let modalOpen = false;
|
() => this.onHighlight(),
|
||||||
const interaction = () => {
|
() => this.onUnhighlight(),
|
||||||
if (modalOpen) {
|
),
|
||||||
modalOpen = false;
|
);
|
||||||
closeModal();
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
modalOpen = true;
|
|
||||||
openModal(this.code);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.addComponent(new Interactable(interaction));
|
private onUnhighlight() {
|
||||||
},
|
closeModal();
|
||||||
remove: () => {
|
this.removeComponent(ComponentNames.Interactable);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onHighlight() {
|
||||||
|
let modalOpen = false;
|
||||||
|
const interaction = () => {
|
||||||
|
if (modalOpen) {
|
||||||
|
modalOpen = false;
|
||||||
closeModal();
|
closeModal();
|
||||||
this.removeComponent(ComponentNames.Interactable);
|
return;
|
||||||
},
|
}
|
||||||
});
|
modalOpen = true;
|
||||||
|
openModal(this.code);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.addComponent(new Interactable(interaction));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
45
src/engine/entities/Wall.ts
Normal file
45
src/engine/entities/Wall.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { Entity, EntityNames } from ".";
|
||||||
|
import { BoundingBox, Colliding, Grid, Sprite } from "../components";
|
||||||
|
import { IMAGES, SPRITE_SPECS, SpriteSpec, Sprites } from "../config";
|
||||||
|
import { Coord2D } from "../interfaces";
|
||||||
|
|
||||||
|
export class Wall extends Entity {
|
||||||
|
private static spriteSpec: SpriteSpec = SPRITE_SPECS.get(
|
||||||
|
Sprites.WALL,
|
||||||
|
) as SpriteSpec;
|
||||||
|
|
||||||
|
constructor(gridPosition: Coord2D) {
|
||||||
|
super(EntityNames.Wall);
|
||||||
|
|
||||||
|
this.addComponent(new Grid(gridPosition));
|
||||||
|
|
||||||
|
this.addComponent(new Colliding());
|
||||||
|
|
||||||
|
this.addComponent(
|
||||||
|
new BoundingBox(
|
||||||
|
{
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
width: Wall.spriteSpec.width,
|
||||||
|
height: Wall.spriteSpec.height,
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.addComponent(
|
||||||
|
new Sprite(
|
||||||
|
IMAGES.get(Wall.spriteSpec.sheet)!,
|
||||||
|
{ x: 0, y: 0 },
|
||||||
|
{
|
||||||
|
width: Wall.spriteSpec.width,
|
||||||
|
height: Wall.spriteSpec.height,
|
||||||
|
},
|
||||||
|
Wall.spriteSpec.msPerFrame,
|
||||||
|
Wall.spriteSpec.frames,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -2,3 +2,4 @@ export * from "./Entity";
|
|||||||
export * from "./EntityNames";
|
export * from "./EntityNames";
|
||||||
export * from "./Player";
|
export * from "./Player";
|
||||||
export * from "./FunctionBox";
|
export * from "./FunctionBox";
|
||||||
|
export * from "./Wall";
|
||||||
|
@ -71,14 +71,20 @@ export class Grid extends System {
|
|||||||
|
|
||||||
highlightableEntities.forEach((id) => {
|
highlightableEntities.forEach((id) => {
|
||||||
const entity = game.getEntity(id)!;
|
const entity = game.getEntity(id)!;
|
||||||
if (!entity.hasComponent(ComponentNames.Highlight)) {
|
if (entity.hasComponent(ComponentNames.Highlight)) {
|
||||||
entity.addComponent(new Highlight());
|
const highlight = entity.getComponent<Highlight>(
|
||||||
|
ComponentNames.Highlight,
|
||||||
|
)!;
|
||||||
|
highlight.highlight();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
game.forEachEntityWithComponent(ComponentNames.Highlight, (entity) => {
|
game.forEachEntityWithComponent(ComponentNames.Highlight, (entity) => {
|
||||||
if (!highlightableEntities.has(entity.id)) {
|
if (!highlightableEntities.has(entity.id)) {
|
||||||
entity.removeComponent(ComponentNames.Highlight);
|
const highlight = entity.getComponent<Highlight>(
|
||||||
|
ComponentNames.Highlight,
|
||||||
|
)!;
|
||||||
|
highlight.unhighlight();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -97,21 +103,41 @@ export class Grid extends System {
|
|||||||
// continue until no more pushable entities are found
|
// continue until no more pushable entities are found
|
||||||
for (const entity of movingEntities) {
|
for (const entity of movingEntities) {
|
||||||
const grid = entity.getComponent<GridComponent>(ComponentNames.Grid)!;
|
const grid = entity.getComponent<GridComponent>(ComponentNames.Grid)!;
|
||||||
|
const { gridPosition, movingDirection } = grid;
|
||||||
|
|
||||||
|
grid.movingDirection = Direction.NONE;
|
||||||
|
entity.addComponent(grid); // default to not moving
|
||||||
|
|
||||||
let nextGridPosition = this.getNewGridPosition(
|
let nextGridPosition = this.getNewGridPosition(
|
||||||
grid.gridPosition,
|
gridPosition,
|
||||||
grid.movingDirection,
|
movingDirection,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const moving = new Set<string>();
|
||||||
|
moving.add(entity.id);
|
||||||
|
|
||||||
while (!this.isOutOfBounds(nextGridPosition)) {
|
while (!this.isOutOfBounds(nextGridPosition)) {
|
||||||
const { x, y } = nextGridPosition;
|
const { x, y } = nextGridPosition;
|
||||||
const entities = Array.from(this.grid[y][x]).map(
|
const entities = Array.from(this.grid[y][x]).map(
|
||||||
(id) => game.getEntity(id)!,
|
(id) => game.getEntity(id)!,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
entities.some((entity) =>
|
||||||
|
entity.hasComponent(ComponentNames.Colliding),
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
moving.clear();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
const pushableEntities = entities.filter((entity) => {
|
const pushableEntities = entities.filter((entity) => {
|
||||||
if (!entity.hasComponent(ComponentNames.Grid)) return false;
|
if (!entity.hasComponent(ComponentNames.Grid)) return false;
|
||||||
|
|
||||||
const { pushable, movingDirection } =
|
const { movingDirection } = entity.getComponent<GridComponent>(
|
||||||
entity.getComponent<GridComponent>(ComponentNames.Grid)!;
|
ComponentNames.Grid,
|
||||||
|
)!;
|
||||||
|
const pushable = entity.hasComponent(ComponentNames.Pushable);
|
||||||
return movingDirection === Direction.NONE && pushable;
|
return movingDirection === Direction.NONE && pushable;
|
||||||
});
|
});
|
||||||
if (pushableEntities.length === 0) {
|
if (pushableEntities.length === 0) {
|
||||||
@ -119,18 +145,21 @@ export class Grid extends System {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const pushableEntity of pushableEntities) {
|
for (const pushableEntity of pushableEntities) {
|
||||||
const pushableGrid = pushableEntity.getComponent<GridComponent>(
|
moving.add(pushableEntity.id);
|
||||||
ComponentNames.Grid,
|
|
||||||
)!;
|
|
||||||
pushableGrid.movingDirection = grid.movingDirection;
|
|
||||||
pushableEntity.addComponent(pushableEntity);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nextGridPosition = this.getNewGridPosition(
|
nextGridPosition = this.getNewGridPosition(
|
||||||
nextGridPosition,
|
nextGridPosition,
|
||||||
grid.movingDirection,
|
movingDirection,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const id of moving) {
|
||||||
|
const entity = game.getEntity(id)!;
|
||||||
|
const grid = entity.getComponent<GridComponent>(ComponentNames.Grid)!;
|
||||||
|
grid.movingDirection = movingDirection;
|
||||||
|
entity.addComponent(grid);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { System, SystemNames } from ".";
|
import { System, SystemNames } from ".";
|
||||||
import { BoundingBox, ComponentNames, Sprite } from "../components";
|
import { BoundingBox, ComponentNames, Highlight, Sprite } from "../components";
|
||||||
import { Game } from "..";
|
import { Game } from "..";
|
||||||
import { clamp } from "../utils";
|
import { clamp } from "../utils";
|
||||||
import { DrawArgs } from "../interfaces";
|
import { DrawArgs } from "../interfaces";
|
||||||
@ -43,8 +43,13 @@ export class Render extends System {
|
|||||||
center: boundingBox.center,
|
center: boundingBox.center,
|
||||||
dimension: boundingBox.dimension,
|
dimension: boundingBox.dimension,
|
||||||
rotation: boundingBox.rotation,
|
rotation: boundingBox.rotation,
|
||||||
tint: entity.hasComponent(ComponentNames.Highlight) ? "red" : undefined,
|
|
||||||
};
|
};
|
||||||
|
if (entity.hasComponent(ComponentNames.Highlight)) {
|
||||||
|
const highlight = entity.getComponent<Highlight>(
|
||||||
|
ComponentNames.Highlight,
|
||||||
|
);
|
||||||
|
drawArgs.tint = highlight.isHighlighted ? "red" : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
sprite.draw(this.ctx, drawArgs);
|
sprite.draw(this.ctx, drawArgs);
|
||||||
});
|
});
|
||||||
|
@ -3,3 +3,4 @@ export * from "./System";
|
|||||||
export * from "./Render";
|
export * from "./Render";
|
||||||
export * from "./Input";
|
export * from "./Input";
|
||||||
export * from "./FacingDirection";
|
export * from "./FacingDirection";
|
||||||
|
export * from "./Grid";
|
||||||
|
Loading…
Reference in New Issue
Block a user