diff --git a/client/src/main.ts b/client/src/main.ts new file mode 100644 index 0000000..caa9dcd --- /dev/null +++ b/client/src/main.ts @@ -0,0 +1,15 @@ +import Phaser from 'phaser'; +import { BootScene } from './scenes/BootScene.js'; +import { GameScene } from './scenes/GameScene.js'; + +const config: Phaser.Types.Core.GameConfig = { + type: Phaser.AUTO, + parent: 'game', + width: 800, + height: 600, + backgroundColor: '#1a1a2e', + pixelArt: true, + scene: [BootScene, GameScene], +}; + +new Phaser.Game(config); diff --git a/client/src/network/SocketClient.ts b/client/src/network/SocketClient.ts new file mode 100644 index 0000000..c4627a6 --- /dev/null +++ b/client/src/network/SocketClient.ts @@ -0,0 +1,51 @@ +import { io, Socket } from 'socket.io-client'; +import type { ServerEvents, ClientEvents, WorldState, StateUpdate, PlayerJoined, PlayerLeft, PlayerInput } from '@dflike/shared'; + +type TypedSocket = Socket; + +export class SocketClient { + private socket: TypedSocket; + private _playerId: string | null = null; + private _entityId: number | null = null; + + // Event callbacks + onWorldState: ((data: WorldState) => void) | null = null; + onStateUpdate: ((data: StateUpdate) => void) | null = null; + onPlayerJoined: ((data: PlayerJoined) => void) | null = null; + onPlayerLeft: ((data: PlayerLeft) => void) | null = null; + + constructor(url: string) { + this.socket = io(url); + + this.socket.on('world-state', (data) => { + this.onWorldState?.(data); + }); + + this.socket.on('state-update', (data) => { + this.onStateUpdate?.(data); + }); + + this.socket.on('player-joined', (data) => { + if (!this._playerId) { + this._playerId = data.playerId; + this._entityId = data.entityId; + } + this.onPlayerJoined?.(data); + }); + + this.socket.on('player-left', (data) => { + this.onPlayerLeft?.(data); + }); + } + + get playerId(): string | null { return this._playerId; } + get entityId(): number | null { return this._entityId; } + + sendInput(input: PlayerInput): void { + this.socket.emit('player-input', input); + } + + disconnect(): void { + this.socket.disconnect(); + } +} diff --git a/client/src/scenes/BootScene.ts b/client/src/scenes/BootScene.ts new file mode 100644 index 0000000..2f6541c --- /dev/null +++ b/client/src/scenes/BootScene.ts @@ -0,0 +1,31 @@ +import Phaser from 'phaser'; +import { SocketClient } from '../network/SocketClient.js'; +import type { WorldState } from '@dflike/shared'; + +export class BootScene extends Phaser.Scene { + private client!: SocketClient; + + constructor() { + super({ key: 'BootScene' }); + } + + create(): void { + const statusText = this.add.text( + this.cameras.main.centerX, + this.cameras.main.centerY, + 'Connecting to server...', + { fontSize: '24px', color: '#ffffff' } + ).setOrigin(0.5); + + const serverUrl = 'http://localhost:3001'; + this.client = new SocketClient(serverUrl); + + this.client.onWorldState = (worldState: WorldState) => { + statusText.setText('Connected! Loading game...'); + this.scene.start('GameScene', { + client: this.client, + worldState, + }); + }; + } +} diff --git a/client/src/scenes/GameScene.ts b/client/src/scenes/GameScene.ts new file mode 100644 index 0000000..ab8f34b --- /dev/null +++ b/client/src/scenes/GameScene.ts @@ -0,0 +1,12 @@ +import Phaser from 'phaser'; + +export class GameScene extends Phaser.Scene { + constructor() { + super({ key: 'GameScene' }); + } + + init(_data: unknown): void {} + create(): void { + this.add.text(100, 100, 'Game scene loaded', { color: '#ffffff' }); + } +}