diff --git a/package-lock.json b/package-lock.json index 998f2ea..08f5000 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "@eslint/eslintrc": "3.2.0", "@eslint/js": "9.17.0", "@types/gtag.js": "0.0.20", + "@types/react": "19.0.2", "@typescript-eslint/eslint-plugin": "8.18.1", "@typescript-eslint/parser": "8.18.1", "eslint": "9.17.0", @@ -1380,6 +1381,16 @@ "undici-types": "~6.20.0" } }, + "node_modules/@types/react": { + "version": "19.0.2", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.2.tgz", + "integrity": "sha512-USU8ZI/xyKJwFTpjSVIrSeHBVAGagkHQKPNbxeWwql/vDmnTIBgx+TJnhFnj1NXgz8XfprU0egV2dROLGpsBEg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.18.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.18.1.tgz", @@ -2138,6 +2149,13 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "devOptional": true, + "license": "MIT" + }, "node_modules/dargs": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/dargs/-/dargs-8.1.0.tgz", diff --git a/package.json b/package.json index ff19177..5b120c0 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "@eslint/eslintrc": "3.2.0", "@eslint/js": "9.17.0", "@types/gtag.js": "0.0.20", + "@types/react": "19.0.2", "@typescript-eslint/eslint-plugin": "8.18.1", "@typescript-eslint/parser": "8.18.1", "eslint": "9.17.0", diff --git a/src/components/Title.tsx b/src/components/Title.tsx new file mode 100644 index 0000000..15f0574 --- /dev/null +++ b/src/components/Title.tsx @@ -0,0 +1,64 @@ +import { createRef, Text, useScene } from 'phaser-jsx'; + +import { Audio } from '../constants'; + +interface Props { + onClick: () => void; +} + +export function Title(props: Props) { + const scene = useScene(); + const ref = createRef(); + + return ( + { + // title tween like retro arcade + scene.add.tween({ + targets: ref.current!, + duration: 800, + ease: (value: number) => value > 0.8, + alpha: 0, + repeat: -1, + yoyo: true, + }); + }} + onPointerOver={() => { + ref.current!.setColor('#9c88ff'); + scene.input.setDefaultCursor('pointer'); + }} + onPointerOut={() => { + ref.current!.setColor('#8c7ae6'); + scene.input.setDefaultCursor('default'); + }} + onPointerDown={() => { + scene.sound.play(Audio.Whoosh, { volume: 1.3 }); + scene.add.tween({ + targets: ref.current!, + ease: Phaser.Math.Easing.Bounce.InOut, + y: -1000, + onComplete: () => { + if (!scene.sound.get(Audio.ThemeSong)) { + scene.sound.play(Audio.ThemeSong, { loop: true, volume: 0.5 }); + } + props.onClick(); + }, + }); + }} + ref={ref} + /> + ); +} diff --git a/src/components/index.ts b/src/components/index.ts new file mode 100644 index 0000000..304b1af --- /dev/null +++ b/src/components/index.ts @@ -0,0 +1 @@ +export * from './Title'; diff --git a/src/scenes/Main.ts b/src/scenes/Main.tsx similarity index 88% rename from src/scenes/Main.ts rename to src/scenes/Main.tsx index 4702bf1..d8b2a27 100644 --- a/src/scenes/Main.ts +++ b/src/scenes/Main.tsx @@ -1,5 +1,7 @@ import Phaser from 'phaser'; +import { render } from 'phaser-jsx'; +import { Title } from '../components'; import { Audio, Image, Scene } from '../constants'; import { createCard } from '../utils/createCard'; @@ -67,57 +69,7 @@ export class Main extends Phaser.Scene { ) .setOrigin(0); - const titleText = this.add - .text( - this.sys.game.scale.width / 2, - this.sys.game.scale.height / 2, - 'Memory Card Game\nClick to Play', - { - align: 'center', - strokeThickness: 4, - fontSize: 40, - fontStyle: 'bold', - color: '#8c7ae6', - }, - ) - .setOrigin(0.5) - .setDepth(3) - .setInteractive(); - // title tween like retro arcade - this.add.tween({ - targets: titleText, - duration: 800, - ease: (value: number) => value > 0.8, - alpha: 0, - repeat: -1, - yoyo: true, - }); - - // Text Events - titleText.on(Phaser.Input.Events.POINTER_OVER, () => { - titleText.setColor('#9c88ff'); - this.input.setDefaultCursor('pointer'); - }); - - titleText.on(Phaser.Input.Events.POINTER_OUT, () => { - titleText.setColor('#8c7ae6'); - this.input.setDefaultCursor('default'); - }); - - titleText.on(Phaser.Input.Events.POINTER_DOWN, () => { - this.sound.play(Audio.Whoosh, { volume: 1.3 }); - this.add.tween({ - targets: titleText, - ease: Phaser.Math.Easing.Bounce.InOut, - y: -1000, - onComplete: () => { - if (!this.sound.get(Audio.ThemeSong)) { - this.sound.play(Audio.ThemeSong, { loop: true, volume: 0.5 }); - } - this.startGame(); - }, - }); - }); + render(, this); } restartGame() {