refactor: update api

This commit is contained in:
2026-04-06 11:31:51 +08:00
parent 0e7aaea7da
commit fa104d10f2
12 changed files with 165 additions and 229 deletions
-147
View File
@@ -1,147 +0,0 @@
import Phaser from 'phaser';
import { effect, type Signal } from '@preact/signals-core';
import type { MutableSignal, Region, Part } from 'boardgame-core';
type DisposeFn = () => void;
export function bindSignal<T, K extends keyof T>(
signal: MutableSignal<T>,
getter: (state: T) => T[K],
setter: (value: T[K]) => void,
): DisposeFn {
return effect(() => {
const val = getter(signal.value);
setter(val);
});
}
export function bindGameObjectProperty<T>(
signal: Signal<T>,
target: Phaser.GameObjects.GameObject,
prop: string,
): DisposeFn {
return effect(() => {
(target as unknown as Record<string, unknown>)[prop] = signal.value;
});
}
export interface BindRegionOptions<TPart extends Part> {
cellSize: { x: number; y: number };
offset?: { x: number; y: number };
factory: (part: TPart, position: Phaser.Math.Vector2) => Phaser.GameObjects.GameObject;
update?: (part: TPart, obj: Phaser.GameObjects.GameObject) => void;
}
export function bindRegion<TState, TMeta>(
state: MutableSignal<TState>,
partsGetter: (state: TState) => Record<string, Part<TMeta>>,
regionGetter: (state: TState) => Region,
options: BindRegionOptions<Part<TMeta>>,
container: Phaser.GameObjects.Container,
): { cleanup: () => void; objects: Map<string, Phaser.GameObjects.GameObject> } {
const objects = new Map<string, Phaser.GameObjects.GameObject>();
const offset = options.offset ?? { x: 0, y: 0 };
const dispose = effect(function(this: { dispose: () => void }) {
const currentState = state.value;
const parts = partsGetter(currentState);
const region = regionGetter(currentState);
const currentIds = new Set(region.childIds);
// 移除不在 region 中的对象
for (const [id, obj] of objects) {
if (!currentIds.has(id)) {
obj.destroy();
objects.delete(id);
}
}
// 同步 region 中的 parts
for (const childId of region.childIds) {
const part = parts[childId];
if (!part) continue;
// 支持动态维度:取前两个维度作为 x, y
// position[0] = row (y轴), position[1] = col (x轴)
const pos = new Phaser.Math.Vector2(
(part.position[1] ?? 0) * options.cellSize.x + offset.x,
(part.position[0] ?? 0) * options.cellSize.y + offset.y,
);
let obj = objects.get(childId);
if (!obj) {
obj = options.factory(part, pos);
objects.set(childId, obj);
container.add(obj);
} else {
// 更新位置
if ('setPosition' in obj && typeof obj.setPosition === 'function') {
(obj as any).setPosition(pos.x, pos.y);
}
// 调用自定义更新函数
if (options.update) {
options.update(part, obj);
}
}
}
});
return {
cleanup: () => {
dispose();
for (const [, obj] of objects) obj.destroy();
objects.clear();
},
objects,
};
}
export interface BindCollectionOptions<T extends { id: string }> {
factory: (item: T) => Phaser.GameObjects.GameObject;
update?: (item: T, obj: Phaser.GameObjects.GameObject) => void;
}
export function bindCollection<T extends { id: string }>(
collection: Signal<Record<string, MutableSignal<T>>>,
options: BindCollectionOptions<T>,
container: Phaser.GameObjects.Container,
): { cleanup: () => void; objects: Map<string, Phaser.GameObjects.GameObject> } {
const objects = new Map<string, Phaser.GameObjects.GameObject>();
const effects: DisposeFn[] = [];
function syncCollection() {
const entries = Object.entries(collection.value);
const currentIds = new Set(entries.map(([id]) => id));
for (const [id, obj] of objects) {
if (!currentIds.has(id)) {
obj.destroy();
objects.delete(id);
}
}
for (const [id, signal] of entries) {
let obj = objects.get(id);
if (!obj) {
obj = options.factory(signal.value);
objects.set(id, obj);
container.add(obj);
} else if (options.update) {
options.update(signal.value, obj);
}
}
}
const e = effect(syncCollection);
effects.push(e);
return {
cleanup: () => {
for (const e of effects) e();
for (const [, obj] of objects) obj.destroy();
objects.clear();
},
objects,
};
}
+8 -3
View File
@@ -1,5 +1,5 @@
import Phaser from 'phaser';
import { effect, type ReadonlySignal } from '@preact/signals-core';
import { effect } from '@preact/signals-core';
type GO = Phaser.GameObjects.GameObject;
@@ -11,7 +11,7 @@ export interface Spawner<TData, TObject extends GO = GO> {
/** 创建新对象 */
onSpawn(t: TData): TObject | null;
/** 销毁旧对象 */
onDespawn(obj: TObject): void;
onDespawn(obj: TObject, t: TData): void;
/** 更新已有对象 */
onUpdate(t: TData, obj: TObject): void;
}
@@ -20,6 +20,7 @@ export function spawnEffect<TData, TObject extends GO = GO>(
spawner: Spawner<TData, TObject>,
): () => void {
const objects = new Map<string, TObject>();
const spawnData = new Map<string, TData>();
return effect(() => {
const current = new Set<string>();
@@ -32,17 +33,21 @@ export function spawnEffect<TData, TObject extends GO = GO>(
if (!existing) {
const obj = spawner.onSpawn(t);
if (obj) {
spawnData.set(key, t);
objects.set(key, obj);
}
} else {
if(spawnData.get(key) === t) continue;
spawner.onUpdate(t, existing);
spawnData.set(key, t);
}
}
for (const [key, obj] of objects) {
if (!current.has(key)) {
spawner.onDespawn(obj);
spawner.onDespawn(obj, spawnData.get(key)!);
objects.delete(key);
spawnData.delete(key);
}
}
});
+1 -1
View File
@@ -1,5 +1,5 @@
import Phaser from 'phaser';
import { signal, useSignal, useSignalEffect, type Signal } from '@preact/signals';
import { signal, useSignal, useSignalEffect } from '@preact/signals';
import { createContext, h } from 'preact';
import { useContext } from 'preact/hooks';
import {ReadonlySignal} from "@preact/signals-core";