feat(scene): battle grid overlay with color and trap VFX stacking
Add configurable square/hex grid in the scene editor (persisted and shown on control/presentation) and keep trap activation effects above trap icons. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -23,6 +23,7 @@ function scene(id: string): Scene {
|
||||
previewRotationDeg: 0,
|
||||
darkenScene: false,
|
||||
traps: [],
|
||||
grid: { enabled: false, type: 'square', sizeN: 0.06, color: '#ffffff' },
|
||||
media: { videos: [], audios: [] },
|
||||
settings: { autoplayVideo: false, autoplayAudio: true, loopVideo: true, loopAudio: true },
|
||||
connections: [],
|
||||
|
||||
@@ -28,6 +28,7 @@ function scene(id: string, title: string): Scene {
|
||||
previewRotationDeg: 0,
|
||||
darkenScene: false,
|
||||
traps: [],
|
||||
grid: { enabled: false, type: 'square', sizeN: 0.06, color: '#ffffff' },
|
||||
media: { videos: [], audios: [] },
|
||||
settings: { autoplayVideo: false, autoplayAudio: false, loopVideo: false, loopAudio: false },
|
||||
connections: [],
|
||||
|
||||
@@ -20,6 +20,7 @@ import type {
|
||||
Scene,
|
||||
SceneDarknessEvent,
|
||||
SceneDarknessState,
|
||||
SceneGrid,
|
||||
SceneId,
|
||||
SceneTrap,
|
||||
SceneTrapsEvent,
|
||||
@@ -703,6 +704,7 @@ export type ScenePatch = {
|
||||
previewRotationDeg?: 0 | 90 | 180 | 270;
|
||||
darkenScene?: boolean;
|
||||
traps?: SceneTrap[];
|
||||
grid?: SceneGrid;
|
||||
settings?: Partial<Scene['settings']>;
|
||||
media?: Partial<Scene['media']>;
|
||||
layout?: Partial<Scene['layout']>;
|
||||
|
||||
@@ -9,6 +9,7 @@ import type {
|
||||
SceneId,
|
||||
} from './ids';
|
||||
import type { MaterialLegend } from './materialLegend';
|
||||
import type { SceneGrid } from './sceneGrid';
|
||||
import type { SceneTrap } from './sceneTraps';
|
||||
|
||||
export const PROJECT_SCHEMA_VERSION = 9 as const;
|
||||
@@ -162,6 +163,8 @@ export type Scene = {
|
||||
darkenScene: boolean;
|
||||
/** Ловушки на карте (только для image-превью); расстановка в проекте. */
|
||||
traps: SceneTrap[];
|
||||
/** Боевая сетка поверх превью (под ловушками/эффектами). */
|
||||
grid: SceneGrid;
|
||||
media: SceneMediaRefs;
|
||||
settings: SceneSettings;
|
||||
connections: SceneId[];
|
||||
|
||||
@@ -5,6 +5,7 @@ export * from './materialLegend';
|
||||
export * from './materials';
|
||||
export * from './npcs';
|
||||
export * from './sceneDarkness';
|
||||
export * from './sceneGrid';
|
||||
export * from './sceneTraps';
|
||||
export * from './sceneView';
|
||||
export * from './videoPlayback';
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/** Настройки боевой сетки на превью сцены (сохраняется в проекте). */
|
||||
|
||||
export const SCENE_GRID_TYPES = ['square', 'hex'] as const;
|
||||
|
||||
export type SceneGridType = (typeof SCENE_GRID_TYPES)[number];
|
||||
|
||||
export type SceneGrid = {
|
||||
/** Наложить сетку на изображение сцены. */
|
||||
enabled: boolean;
|
||||
type: SceneGridType;
|
||||
/** Размер ячейки относительно min(w, h) картинки. */
|
||||
sizeN: number;
|
||||
/** Hex `#rrggbb`. */
|
||||
color: string;
|
||||
};
|
||||
|
||||
export const DEFAULT_SCENE_GRID_SIZE_N = 0.06;
|
||||
export const SCENE_GRID_SIZE_MIN = 0.02;
|
||||
export const SCENE_GRID_SIZE_MAX = 0.25;
|
||||
export const DEFAULT_SCENE_GRID_COLOR = '#ffffff';
|
||||
|
||||
export const DEFAULT_SCENE_GRID: SceneGrid = {
|
||||
enabled: false,
|
||||
type: 'square',
|
||||
sizeN: DEFAULT_SCENE_GRID_SIZE_N,
|
||||
color: DEFAULT_SCENE_GRID_COLOR,
|
||||
};
|
||||
|
||||
export function clampSceneGridSizeN(sizeN: number): number {
|
||||
if (!Number.isFinite(sizeN)) return DEFAULT_SCENE_GRID_SIZE_N;
|
||||
return Math.max(SCENE_GRID_SIZE_MIN, Math.min(SCENE_GRID_SIZE_MAX, sizeN));
|
||||
}
|
||||
|
||||
export function normalizeSceneGridColor(raw: unknown): string {
|
||||
if (typeof raw !== 'string') return DEFAULT_SCENE_GRID_COLOR;
|
||||
const s = raw.trim();
|
||||
if (/^#[0-9a-fA-F]{6}$/u.test(s)) return s.toLowerCase();
|
||||
if (/^#[0-9a-fA-F]{3}$/u.test(s)) {
|
||||
const r = s[1]!;
|
||||
const g = s[2]!;
|
||||
const b = s[3]!;
|
||||
return `#${r}${r}${g}${g}${b}${b}`.toLowerCase();
|
||||
}
|
||||
return DEFAULT_SCENE_GRID_COLOR;
|
||||
}
|
||||
|
||||
export function normalizeSceneGrid(raw: unknown): SceneGrid {
|
||||
if (!raw || typeof raw !== 'object') return { ...DEFAULT_SCENE_GRID };
|
||||
const obj = raw as Partial<SceneGrid>;
|
||||
const type: SceneGridType = obj.type === 'hex' ? 'hex' : 'square';
|
||||
return {
|
||||
enabled: Boolean(obj.enabled),
|
||||
type,
|
||||
sizeN: clampSceneGridSizeN(typeof obj.sizeN === 'number' ? obj.sizeN : DEFAULT_SCENE_GRID_SIZE_N),
|
||||
color: normalizeSceneGridColor(obj.color),
|
||||
};
|
||||
}
|
||||
|
||||
export function sceneGridTypeLabelRu(type: SceneGridType): string {
|
||||
return type === 'hex' ? 'Гексогональная' : 'Квадратная';
|
||||
}
|
||||
Reference in New Issue
Block a user