refactor: update api
This commit is contained in:
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -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,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";
|
||||
|
||||
Reference in New Issue
Block a user