Feature/editor optimization (#251)
* refactor: 编辑器/运行时架构拆分与构建系统升级 * feat(core): 层级系统重构与UI变换矩阵修复 * refactor: 移除 ecs-components 聚合包并修复跨包组件查找问题 * fix(physics): 修复跨包组件类引用问题 * feat: 统一运行时架构与浏览器运行支持 * feat(asset): 实现浏览器运行时资产加载系统 * fix: 修复文档、CodeQL安全问题和CI类型检查错误 * fix: 修复文档、CodeQL安全问题和CI类型检查错误 * fix: 修复文档、CodeQL安全问题、CI类型检查和测试错误 * test: 补齐核心模块测试用例,修复CI构建配置 * fix: 修复测试用例中的类型错误和断言问题 * fix: 修复 turbo build:npm 任务的依赖顺序问题 * fix: 修复 CI 构建错误并优化构建性能
This commit is contained in:
226
packages/tilemap-editor/src/stores/TilemapEditorStore.ts
Normal file
226
packages/tilemap-editor/src/stores/TilemapEditorStore.ts
Normal file
@@ -0,0 +1,226 @@
|
||||
/**
|
||||
* Tilemap Editor State Store
|
||||
*/
|
||||
|
||||
import { create } from 'zustand';
|
||||
|
||||
export type TilemapToolType = 'brush' | 'eraser' | 'fill' | 'rectangle' | 'select';
|
||||
|
||||
export interface TileSelection {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
tiles: number[];
|
||||
}
|
||||
|
||||
export interface LayerState {
|
||||
id: string;
|
||||
name: string;
|
||||
visible: boolean;
|
||||
locked: boolean;
|
||||
opacity: number;
|
||||
}
|
||||
|
||||
export interface TilemapEditorState {
|
||||
// Current editing target
|
||||
entityId: string | null;
|
||||
|
||||
// Pending file to open (for file-based editing)
|
||||
pendingFilePath: string | null;
|
||||
// Current file being edited (for file-based editing)
|
||||
currentFilePath: string | null;
|
||||
|
||||
// Tileset
|
||||
tilesetImageUrl: string | null;
|
||||
tilesetColumns: number;
|
||||
tilesetRows: number;
|
||||
tileWidth: number;
|
||||
tileHeight: number;
|
||||
|
||||
// Selection
|
||||
selectedTiles: TileSelection | null;
|
||||
|
||||
// Tools
|
||||
currentTool: TilemapToolType;
|
||||
brushSize: number;
|
||||
|
||||
// View
|
||||
zoom: number;
|
||||
panX: number;
|
||||
panY: number;
|
||||
showGrid: boolean;
|
||||
showCollision: boolean;
|
||||
|
||||
// Layers
|
||||
currentLayer: number;
|
||||
layers: LayerState[];
|
||||
editingCollision: boolean;
|
||||
|
||||
// History
|
||||
undoStack: Uint32Array[];
|
||||
redoStack: Uint32Array[];
|
||||
|
||||
// Actions
|
||||
setEntityId: (id: string | null) => void;
|
||||
setPendingFilePath: (path: string | null) => void;
|
||||
setCurrentFilePath: (path: string | null) => void;
|
||||
clearPendingFile: () => void;
|
||||
setTileset: (url: string | null, columns: number, rows: number, tileWidth: number, tileHeight: number) => void;
|
||||
setSelectedTiles: (selection: TileSelection | null) => void;
|
||||
setCurrentTool: (tool: TilemapToolType) => void;
|
||||
setBrushSize: (size: number) => void;
|
||||
setZoom: (zoom: number) => void;
|
||||
setPan: (x: number, y: number) => void;
|
||||
setShowGrid: (show: boolean) => void;
|
||||
setShowCollision: (show: boolean) => void;
|
||||
setCurrentLayer: (layer: number) => void;
|
||||
setEditingCollision: (editing: boolean) => void;
|
||||
pushUndo: (data: Uint32Array) => void;
|
||||
undo: () => Uint32Array | null;
|
||||
redo: () => Uint32Array | null;
|
||||
reset: () => void;
|
||||
|
||||
// Layer management
|
||||
setLayers: (layers: LayerState[]) => void;
|
||||
toggleLayerVisibility: (index: number) => void;
|
||||
toggleLayerLocked: (index: number) => void;
|
||||
setLayerOpacity: (index: number, opacity: number) => void;
|
||||
renameLayer: (index: number, name: string) => void;
|
||||
}
|
||||
|
||||
const initialState = {
|
||||
entityId: null,
|
||||
pendingFilePath: null,
|
||||
currentFilePath: null,
|
||||
tilesetImageUrl: null,
|
||||
tilesetColumns: 0,
|
||||
tilesetRows: 0,
|
||||
tileWidth: 32,
|
||||
tileHeight: 32,
|
||||
selectedTiles: null,
|
||||
currentTool: 'brush' as TilemapToolType,
|
||||
brushSize: 1,
|
||||
zoom: 1,
|
||||
panX: 0,
|
||||
panY: 0,
|
||||
showGrid: true,
|
||||
showCollision: false,
|
||||
currentLayer: 0,
|
||||
layers: [] as LayerState[],
|
||||
editingCollision: false,
|
||||
undoStack: [] as Uint32Array[],
|
||||
redoStack: [] as Uint32Array[],
|
||||
};
|
||||
|
||||
export const useTilemapEditorStore = create<TilemapEditorState>((set, get) => ({
|
||||
...initialState,
|
||||
|
||||
setEntityId: (id) => set({ entityId: id }),
|
||||
|
||||
setPendingFilePath: (path) => set({ pendingFilePath: path }),
|
||||
|
||||
setCurrentFilePath: (path) => set({ currentFilePath: path }),
|
||||
|
||||
clearPendingFile: () => set({ pendingFilePath: null }),
|
||||
|
||||
setTileset: (url, columns, rows, tileWidth, tileHeight) => set({
|
||||
tilesetImageUrl: url,
|
||||
tilesetColumns: columns,
|
||||
tilesetRows: rows,
|
||||
tileWidth,
|
||||
tileHeight,
|
||||
selectedTiles: null,
|
||||
}),
|
||||
|
||||
setSelectedTiles: (selection) => set({ selectedTiles: selection }),
|
||||
|
||||
setCurrentTool: (tool) => set({ currentTool: tool }),
|
||||
|
||||
setBrushSize: (size) => set({ brushSize: Math.max(1, Math.min(10, size)) }),
|
||||
|
||||
setZoom: (zoom) => set({ zoom: Math.max(0.1, Math.min(10, zoom)) }),
|
||||
|
||||
setPan: (x, y) => set({ panX: x, panY: y }),
|
||||
|
||||
setShowGrid: (show) => set({ showGrid: show }),
|
||||
|
||||
setShowCollision: (show) => set({ showCollision: show }),
|
||||
|
||||
setCurrentLayer: (layer) => set({ currentLayer: layer }),
|
||||
|
||||
setEditingCollision: (editing) => set({ editingCollision: editing }),
|
||||
|
||||
pushUndo: (data) => {
|
||||
const { undoStack } = get();
|
||||
set({
|
||||
undoStack: [...undoStack.slice(-49), data],
|
||||
redoStack: [],
|
||||
});
|
||||
},
|
||||
|
||||
undo: () => {
|
||||
const { undoStack, redoStack } = get();
|
||||
if (undoStack.length === 0) return null;
|
||||
|
||||
const data = undoStack[undoStack.length - 1]!;
|
||||
set({
|
||||
undoStack: undoStack.slice(0, -1),
|
||||
redoStack: [...redoStack, data],
|
||||
});
|
||||
return undoStack.length > 1 ? undoStack[undoStack.length - 2]! : null;
|
||||
},
|
||||
|
||||
redo: () => {
|
||||
const { redoStack, undoStack } = get();
|
||||
if (redoStack.length === 0) return null;
|
||||
|
||||
const data = redoStack[redoStack.length - 1]!;
|
||||
set({
|
||||
redoStack: redoStack.slice(0, -1),
|
||||
undoStack: [...undoStack, data],
|
||||
});
|
||||
return data;
|
||||
},
|
||||
|
||||
reset: () => set(initialState),
|
||||
|
||||
// Layer management
|
||||
setLayers: (layers) => set({ layers }),
|
||||
|
||||
toggleLayerVisibility: (index) => {
|
||||
const { layers } = get();
|
||||
const layer = layers[index];
|
||||
if (!layer) return;
|
||||
const newLayers = [...layers];
|
||||
newLayers[index] = { ...layer, visible: !layer.visible };
|
||||
set({ layers: newLayers });
|
||||
},
|
||||
|
||||
toggleLayerLocked: (index) => {
|
||||
const { layers } = get();
|
||||
const layer = layers[index];
|
||||
if (!layer) return;
|
||||
const newLayers = [...layers];
|
||||
newLayers[index] = { ...layer, locked: !layer.locked };
|
||||
set({ layers: newLayers });
|
||||
},
|
||||
|
||||
setLayerOpacity: (index, opacity) => {
|
||||
const { layers } = get();
|
||||
const layer = layers[index];
|
||||
if (!layer) return;
|
||||
const newLayers = [...layers];
|
||||
newLayers[index] = { ...layer, opacity: Math.max(0, Math.min(1, opacity)) };
|
||||
set({ layers: newLayers });
|
||||
},
|
||||
|
||||
renameLayer: (index, name) => {
|
||||
const { layers } = get();
|
||||
const layer = layers[index];
|
||||
if (!layer) return;
|
||||
const newLayers = [...layers];
|
||||
newLayers[index] = { ...layer, name };
|
||||
set({ layers: newLayers });
|
||||
},
|
||||
}));
|
||||
Reference in New Issue
Block a user