-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from DVDAGames/feature/easter-egg
Feature/easter egg
- Loading branch information
Showing
8 changed files
with
221 additions
and
7 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import { useMouse } from "@uidotdev/usehooks"; | ||
|
||
import Ship from "./ship"; | ||
|
||
export interface GameProps { | ||
isPlayable?: boolean; | ||
quit?: () => void; | ||
} | ||
|
||
export function Game({ isPlayable = false, quit }: GameProps): React.ReactElement { | ||
const [mouse, ref] = useMouse(); | ||
|
||
if (!isPlayable) { | ||
return <></>; | ||
} | ||
|
||
const classes = ["fixed", "top-0", "left-0", "w-full", "h-full", "justify-center", "items-center"]; | ||
|
||
const shipPosition = { | ||
x: mouse.elementPositionX, | ||
y: mouse.elementPositionY, | ||
}; | ||
|
||
// calculate rotation angle of the ship to point the nose of the ship towards mouse x and y | ||
const rotation = Math.atan2(shipPosition.y - mouse.y, shipPosition.x - mouse.x) * (180 / Math.PI); | ||
|
||
// get distance between ship and mouse | ||
const dx = mouse.x - shipPosition.x; | ||
const dy = mouse.y - shipPosition.y; | ||
|
||
const distance = Math.sqrt(dx * dx + dy * dy); | ||
|
||
const position = { | ||
x: shipPosition.x, | ||
y: shipPosition.y, | ||
}; | ||
|
||
if (Math.abs(distance) >= 32) { | ||
position.x = shipPosition.x + dx; | ||
position.y = shipPosition.y + dy; | ||
} | ||
|
||
return ( | ||
<section className={classes.join(" ")}> | ||
<Ship shipRef={ref} rotate={rotation - 90} x={position.x} y={position.y} onClickShip={quit} /> | ||
</section> | ||
); | ||
} | ||
|
||
export default Game; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
"use client"; | ||
|
||
import { useState } from "react"; | ||
import { useKonami } from "react-konami-code"; | ||
|
||
import Game from "./game"; | ||
|
||
export function EasterEgg(): React.ReactElement { | ||
const [isPlayable, setIsPlayable] = useState(false); | ||
|
||
const togglePlayable = () => { | ||
console.log("KONAMI!"); | ||
setIsPlayable((prev) => !prev); | ||
}; | ||
|
||
useKonami(togglePlayable); | ||
|
||
return <Game isPlayable={isPlayable} quit={togglePlayable} />; | ||
} | ||
|
||
export default EasterEgg; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
"use client"; | ||
|
||
import { useEffect, useRef } from "react"; | ||
import { useAnimate, AnimationPlaybackControls } from "framer-motion"; | ||
|
||
export interface ShipProps { | ||
x: number; | ||
y: number; | ||
rotate: number; | ||
shipRef: React.MutableRefObject<Element>; | ||
onClickShip?: () => void; | ||
} | ||
|
||
export function Ship({ shipRef, x, y, rotate, onClickShip }: ShipProps): React.ReactElement { | ||
// TODO: make ship fire bullets at the mouse position and trigger fire sound effect | ||
// TODO: garbage collect bullets that go off screen | ||
// TODO: make mouse take damage when hit by bullets | ||
// TODO: Game over screen after 5 hits | ||
const [scope, animate] = useAnimate(); | ||
const animationPromises: React.MutableRefObject<AnimationPlaybackControls[]> = useRef([]); | ||
|
||
const quit = () => { | ||
animationPromises?.current?.forEach((promise) => { | ||
promise?.stop(); | ||
promise?.cancel(); | ||
}); | ||
|
||
onClickShip?.(); | ||
}; | ||
|
||
useEffect(() => { | ||
const animation = async () => { | ||
if ( | ||
typeof scope.current !== "undefined" && | ||
scope.current !== null && | ||
typeof shipRef.current !== "undefined" && | ||
shipRef.current !== null | ||
) { | ||
animationPromises.current.push(animate(scope.current, { rotate }, { duration: 0.5, ease: "circOut" })); | ||
animationPromises.current.push(animate(scope.current, { x, y }, { duration: 5, ease: "circOut", delay: 0.25 })); | ||
|
||
await Promise.all(animationPromises.current); | ||
} | ||
}; | ||
|
||
if ( | ||
typeof scope.current !== "undefined" && | ||
scope.current !== null && | ||
typeof shipRef.current !== "undefined" && | ||
shipRef.current !== null | ||
) { | ||
animation?.() | ||
.then(() => { | ||
animationPromises.current = []; | ||
}) | ||
.catch((error) => { | ||
console.error(error); | ||
}); | ||
} | ||
}, [rotate, x, y]); | ||
|
||
return ( | ||
<button onClick={quit} ref={scope} title="Click to dismiss" className="h-[32px] w-[32px] appearance-none"> | ||
<img | ||
ref={shipRef as React.MutableRefObject<HTMLImageElement>} | ||
src="/assets/images/pulsar-ship.gif" | ||
alt="" | ||
width="32" | ||
height="32" | ||
/> | ||
</button> | ||
); | ||
} | ||
|
||
export default Ship; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters