devvit-phaser - v0.2.0

Devvit-Phaser 🎮

npm version License: MIT Tests

A library to integrate Phaser.js with Reddit's Devvit platform for building interactive multiplayer games. This library provides a simple, yet powerful framework for creating multiplayer games with synchronized state between players.

Installation

npm install devvit-phaser

Architecture

Devvit-Phaser is built around a few core concepts:

  1. BasicGameServer - The foundation for all multiplayer Devvit games, handling communication between players, timers, and more.

  2. PhaserGameServer - Extends BasicGameServer with Phaser-specific functionality for game state synchronization.

  3. SyncedDataManager - Client-side component that extends Phaser's DataManager, providing synchronized state between all players.

  4. DataManagerServer - Server-side component that manages the persistent state in Redis and broadcasts changes to all connected players.

Usage

Creating a Game Server

import { PhaserGameServer } from 'devvit-phaser';
import { Devvit } from '@devvit/public-api';

// Create a game server with your game name
const gameServer = new PhaserGameServer('Asteroid Blaster');

// Set up custom handlers if needed
gameServer.onDataManagerSubscribe = async (subscriptions) => {
console.log('Player subscribed to data managers', subscriptions);
// Perform authorization checks, etc.
};

gameServer.onDataManagerMutate = async (mutation) => {
console.log('Player mutated data manager', mutation);
// Validate mutations if needed
};

// Override methods to handle game-specific logic
gameServer.onPlayerJoined = async () => {
await super.onPlayerJoined(); // Call the parent method to handle basic setup

// Create a player-specific data manager
const playerData = new DataManagerServer(this, { id: `player_${this.userId}` });

// Initialize with default values
await playerData.setAll({
score: 0,
health: 100,
position: { x: 0, y: 0 }
});

// Subscribe the player to their data
await this.subscribePlayerToDataManager(playerData);
};

// Build the game server (sets up Devvit integration)
gameServer.build();

Using Synchronized Data in Phaser

import { SyncedDataManager } from 'devvit-phaser';
import Phaser from 'phaser';

export class MainScene extends Phaser.Scene {
gameState: SyncedDataManager;
playerState: SyncedDataManager;
ship: Phaser.GameObjects.Sprite;

create() {
// Create a global game state all players can see
this.gameState = new SyncedDataManager('gameState', this);

// Create player-specific state (using the player's ID)
const screenId = window.me.get('screenId') as string;
this.playerState = new SyncedDataManager(`player_${screenId}`, this);

// Wait for initial data
this.playerState.events.once('ready', () => {
// Create the player's ship
this.ship = this.add.sprite(
this.playerState.get('position').x,
this.playerState.get('position').y,
'ship'
);

// Update UI
this.updateScoreDisplay(this.playerState.get('score'));
});

// Listen for changes in player data
this.playerState.events.on('changedata-position', (_, position) => {
// Update ship position when it changes on the server
this.ship.setPosition(position.x, position.y);
});

// Listen for changes in game state
this.gameState.events.on('changedata-asteroids', (_, asteroids) => {
// Update asteroid positions when they change
this.updateAsteroids(asteroids);
});

// Handle ship movement
this.input.keyboard.on('keydown-W', () => {
const position = this.playerState.get('position');
position.y -= 10;
// This will sync to other players automatically
this.playerState.set('position', position);
});
}

increaseScore(points: number) {
// Increment the score (synchronized to server and other players)
this.playerState.inc('score', points);
}

updateScoreDisplay(score: number) {
// Update UI based on score changes
}

updateAsteroids(asteroids: any[]) {
// Update asteroid display based on server data
}
}

Advanced Features

Timers and Scheduled Events

The PhaserGameServer includes reliable timer functions that persist even through server restarts:

// One-time event after 5 seconds
gameServer.setTimeout('respawn_player', 5000, { playerId: '12345' });

// Recurring event every 1 second
gameServer.setInterval('spawn_asteroid', 1000);

// Handle timer events
gameServer.onTimerEvent = async (event) => {
if (event.name === 'spawn_asteroid') {
const asteroidData = new DataManagerServer(this, { id: 'asteroids' });
const asteroids = await asteroidData.get('list') || [];

asteroids.push({
id: v4(),
x: Math.random() * 800,
y: 0,
size: Math.floor(Math.random() * 3) + 1
});

await asteroidData.set('list', asteroids);
}
};

Player Management

Track players joining and leaving:

gameServer.onPlayerJoined = async () => {
await super.onPlayerJoined();

// Get the list of active players
const playersData = new DataManagerServer(this, { id: 'players' });
const players = await playersData.get('active') || [];

// Add the new player
players.push({
id: this.userId,
name: this.userInfo.username,
joinedAt: Date.now()
});

await playersData.set('active', players);
await this.broadcast(this.postId, {
type: 'PLAYER_JOINED',
player: this.userInfo
});
};

License

MIT