Skip to content

Refactor 8 #26

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: refactor-7
Choose a base branch
from
Open
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
37 changes: 19 additions & 18 deletions src/components/App/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { MutableRefObject, useState } from "react";
import { MutableRefObject, useRef, useState } from "react";
import { GlobalContext } from "../../contexts";
import World from "../World";
import Player from "../Player";
Expand All @@ -11,30 +11,36 @@ import Lever from "../Lever";
import House from "../House";
import Fire from "../Fire";
import GameOver from "../GameOver";
import { GAME_STATES, MAX_HEALTH, MIN_HEALTH } from "../../constants";
import { EVENTS, GAME_STATES, MAX_HEALTH, MIN_HEALTH } from "../../constants";
import { AnyFunction, Events } from "../../types";
import { Collider } from "../../utils";
import "./style.css";
import { clampValue } from "../../utils/clampValue";
import "./style.css";

/*
* TODO:
* - Move component actions and state inside components
* - Use context to connect components
*/
export default function App() {
const [gameState, setGameState] = useState<GAME_STATES>(GAME_STATES.Game);
const [colliders, setColliders] = useState<MutableRefObject<Collider>[]>([]);
const [isCellarDoorOpen, setIsCellarDoorOpen] = useState(false);
const [isLeverUsed, setIsLeverUsed] = useState(false);
const [playerHealth, setPlayerHealth] = useState(MAX_HEALTH);
const [score, setScore] = useState(0);
const events = useRef<Events>({});
const setEvent = (event: EVENTS, cb: AnyFunction) => {
events.current = {
...events.current,
[event]: [...(events.current[event] || []), cb],
};
};
const callEvent = (event: EVENTS) => {
events.current[event]?.forEach((cb) => cb());
};

return (
<div className="App">
<GlobalContext.Provider
value={{
gameState,
setGameState,
callEvent,
setEvent,
playerHealth,
setPlayerHealth: (health: number) =>
setPlayerHealth(clampValue(health, MIN_HEALTH, MAX_HEALTH)),
Expand All @@ -47,15 +53,10 @@ export default function App() {
{gameState === GAME_STATES.GameOver && <GameOver />}
<World />
<Ui />
<Player top={332} left={428} onInteract={setIsLeverUsed} />
<Player top={332} left={428} />
<Npc left={1608} top={224} />
<CellarDoor isOpen={isCellarDoorOpen} left={528} top={272} />
<Lever
left={600}
top={264}
used={isLeverUsed}
onInteract={setIsCellarDoorOpen}
/>
<CellarDoor left={528} top={272} />
<Lever left={600} top={264} />
<House left={372} top={192} />
<Fire left={480} top={524} />
<Heart left={320} top={828} />
Expand Down
25 changes: 16 additions & 9 deletions src/components/CellarDoor/index.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
import { useRef, FC } from "react";
import { TILE_SETS } from "../../constants";
import { useRef, FC, useContext, useState, useEffect } from "react";
import { EVENTS, TILE_SETS } from "../../constants";
import { GlobalContext } from "../../contexts";
import { useSprite } from "../../hooks";
import "./style.css";

const WIDTH = 64;
const HEIGHT = 64;
const TILE_X = 992;

type CellarDoorProps = { top: number; left: number; isOpen?: boolean };
type CellarDoorProps = { top: number; left: number };

/*
* TODO:
* - util function for tile set, tiles and animation
* - track state internally
*/
const CellarDoor: FC<CellarDoorProps> = ({ isOpen = false, top, left }) => {
const CellarDoor: FC<CellarDoorProps> = ({ top, left }) => {
const { setEvent } = useContext(GlobalContext);
const [isOpen, setIsOpen] = useState(false);
const canvasRef = useRef<HTMLCanvasElement>(null);

useEffect(() => {
setEvent(EVENTS.LEVER_ON, () => {
setIsOpen(true);
});
setEvent(EVENTS.LEVER_OFF, () => {
setIsOpen(false);
});
}, [setEvent]);

useSprite({
canvasRef,
left,
Expand Down
8 changes: 4 additions & 4 deletions src/components/Coin/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,22 @@ const Coin: FC<CoinProps> = ({ left, top }) => {
const onCollision = (c: Collider) => {
setScore(POINTS);
setIsHidden(true);
collider.current.hide();
c.hide();

setTimeout(() => {
collider.current.show();
c.show();
setIsHidden(false);
}, TIMEOUT);
};
const collider = useRef<Collider>(
const colliderRef = useRef<Collider>(
new Collider(
new Rect(left, top, WIDTH, HEIGHT),
ColliderType.Bonus,
onCollision
)
);

useColliders(collider);
useColliders(colliderRef);

useAnimatedSprite({
canvasRef,
Expand Down
4 changes: 2 additions & 2 deletions src/components/Fire/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ type FireProps = { left: number; top: number };

const Fire: FC<FireProps> = ({ left, top }) => {
const canvasRef = useRef<HTMLCanvasElement>(null);
const collider = useRef<Collider>(
const colliderRef = useRef<Collider>(
new Collider(new Rect(left, top, WIDTH, HEIGHT), ColliderType.Damage)
);

useColliders(collider);
useColliders(colliderRef);

useAnimatedSprite({
canvasRef,
Expand Down
3 changes: 3 additions & 0 deletions src/components/Heart/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,19 @@ type HeartProps = { left: number; top: number };
const Heart: FC<HeartProps> = ({ left, top }) => {
const [position, setPosition] = useState<Vector>(new Vector(left, top));
const canvasRef = useRef<HTMLCanvasElement>(null);

const updatePosition = (c: Collider) => {
const newPosition = getRandomPosition(WIDTH, HEIGHT);
setPosition(newPosition);
c.rect.moveTo(newPosition.x, newPosition.y);
};

const collider = new Collider(
new Rect(position.x, position.y, WIDTH, HEIGHT),
ColliderType.Health,
updatePosition
);

const colliderRef = useRef<Collider>(collider);

useColliders(colliderRef);
Expand Down
35 changes: 26 additions & 9 deletions src/components/Lever/index.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,52 @@
import { useRef, FC } from "react";
import { TILE_SIZE, TILE_SETS } from "../../constants";
import { useSprite } from "../../hooks";
import { useRef, FC, useState, useContext } from "react";
import { TILE_SIZE, TILE_SETS, EVENTS } from "../../constants";
import { GlobalContext } from "../../contexts";
import { useChangeEffect, useColliders, useSprite } from "../../hooks";
import { Collider, ColliderType, Rect } from "../../utils";
import "./style.css";

const WIDTH = TILE_SIZE;
const HEIGHT = TILE_SIZE;
const INTERACTION_RANGE = TILE_SIZE / 2;
const TILE_X = 64;

type LeverProps = {
left: number;
top: number;
used: boolean;
onInteract: (value: boolean | ((prev: boolean) => boolean)) => void;
};

const Lever: FC<LeverProps> = ({ left, top, used, onInteract }) => {
const Lever: FC<LeverProps> = ({ left, top }) => {
const [isOn, setIsOn] = useState(false);
const { callEvent } = useContext(GlobalContext);
const canvasRef = useRef<HTMLCanvasElement>(null);

useChangeEffect(
() => callEvent(isOn ? EVENTS.LEVER_ON : EVENTS.LEVER_OFF),
[isOn]
);

const onCollision = () => setIsOn((v) => !v);
const colliderRef = useRef<Collider>(
new Collider(
new Rect(left, top, WIDTH, HEIGHT, INTERACTION_RANGE, INTERACTION_RANGE),
ColliderType.Object,
onCollision
)
);

useColliders(colliderRef);

useSprite({
canvasRef,
left,
top,
tileSet: TILE_SETS.Objects,
width: WIDTH,
height: HEIGHT,
tileX: used ? TILE_X + WIDTH : TILE_X,
tileX: isOn ? TILE_X + WIDTH : TILE_X,
tileY: 288,
});

onInteract(used);

return (
<canvas
ref={canvasRef}
Expand Down
30 changes: 15 additions & 15 deletions src/components/Player/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,9 @@ import "./style.css";
type PlayerProps = {
top: number;
left: number;
onInteract: (isOpen: boolean | ((wasOpen: boolean) => boolean)) => void;
};

const Player: FC<PlayerProps> = ({ onInteract, top, left }) => {
const Player: FC<PlayerProps> = ({ top, left }) => {
const canvasRef = useRef<HTMLCanvasElement>(null);
const playerRect = useRef<Rect>(new Rect(left, top, WIDTH, HEIGHT));
const invulnerable = useRef<boolean>(false);
Expand All @@ -43,12 +42,20 @@ const Player: FC<PlayerProps> = ({ onInteract, top, left }) => {

moveTo(new Vector(left, top), canvasRef.current);

const checkCollisions = () => {
const checkCollisions = (isInteraction: boolean) => {
colliders.forEach((collider) => {
if (!collider.current.rect.overlaps(playerRect.current)) {
return;
}

if (isInteraction) {
if (collider.current.is(ColliderType.Object)) {
collider.current.onCollision();
}

return;
}

if (
collider.current.is(ColliderType.Health) &&
playerHealth < MAX_HEALTH
Expand Down Expand Up @@ -92,15 +99,16 @@ const Player: FC<PlayerProps> = ({ onInteract, top, left }) => {
return;
}

checkCollisions();
const isInteraction = Input.Interact.includes(event.key);
checkCollisions(isInteraction);

if (playerHealth <= MIN_HEALTH) {
setGameState(GAME_STATES.GameOver);
return;
}

if (Input.Interact.includes(event.key)) {
onInteract((wasOpen) => !wasOpen);
if (isInteraction) {
return;
}

direction.current = getInputVector(event.key);
Expand All @@ -118,15 +126,7 @@ const Player: FC<PlayerProps> = ({ onInteract, top, left }) => {
}
};
};
}, [
onInteract,
setPlayerHealth,
playerHealth,
setGameState,
top,
left,
colliders,
]);
}, [setPlayerHealth, playerHealth, setGameState, top, left, colliders]);

return (
<>
Expand Down
2 changes: 1 addition & 1 deletion src/components/Ui/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
position: absolute;
width: 100%;
height: 100vh;
top: calc((1536px - 100vh) / 2 + 24px);
top: calc((1536px - 100vh) / 2);
left: calc((2048px - 100vw) / 2);
}
.score {
Expand Down
5 changes: 5 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ export enum TILE_SETS {
World = "assets/overworld.png",
}

export enum EVENTS {
LEVER_ON = "LEVER_ON",
LEVER_OFF = "LEVER_OFF",
}

export const TILE_SIZE = 32;
export const WORLD_WIDTH = 2048;
export const WORLD_HEIGHT = 1536;
Expand Down
7 changes: 6 additions & 1 deletion src/contexts/global.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createContext, MutableRefObject } from "react";
import { GAME_STATES, MAX_HEALTH } from "../constants";
import { EVENTS, GAME_STATES, MAX_HEALTH } from "../constants";
import { AnyFunction } from "../types";
import { Collider, noop } from "../utils";

export type GlobalContextType = {
Expand All @@ -15,6 +16,8 @@ export type GlobalContextType = {
) => void;
readonly score: number;
setScore: (value: number) => void;
callEvent: (event: EVENTS) => void;
setEvent: (event: EVENTS, cb: AnyFunction) => void;
};

export const GlobalContext = createContext<GlobalContextType>({
Expand All @@ -26,4 +29,6 @@ export const GlobalContext = createContext<GlobalContextType>({
setColliders: noop,
score: 0,
setScore: noop,
setEvent: noop,
callEvent: noop,
});
1 change: 1 addition & 0 deletions src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./useSprite";
export * from "./useAnimatedSprite";
export * from "./useColliders";
export * from "./useChangeEffect";
14 changes: 14 additions & 0 deletions src/hooks/useChangeEffect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { useEffect, useRef } from "react";
import { AnyFunction } from "../types";

export const useChangeEffect = (fn: AnyFunction, inputs: unknown[]) => {
const didMount = useRef(false);
useEffect(() => {
if (didMount.current) {
fn();
return;
}

didMount.current = true;
}, [inputs, fn]);
};
4 changes: 4 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { EVENTS } from "./constants";

export type AnyFunction = () => void;
export type Events = Partial<Record<EVENTS, AnyFunction[]>>;
4 changes: 3 additions & 1 deletion src/utils/collider.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { Rect, noop } from "./";
import { AnyFunction } from "../types";

export enum ColliderType {
Health,
Bonus,
Damage,
Object,
}

export class Collider {
public readonly rect: Rect;
public readonly type: ColliderType;
public readonly onCollision: () => void;
public readonly onCollision: AnyFunction;
private ignoreCollisions = false;

constructor(
Expand Down
Loading