Feat/map-environment #6

Merged
math-pixel merged 116 commits from feat/map-environment into develop 2026-05-29 00:00:51 +00:00
4 changed files with 67 additions and 1 deletions
Showing only changes of commit bfe184dea4 - Show all commits
+8
View File
@@ -11,6 +11,7 @@ import {
Redo2,
RotateCw,
Save,
ScanSearch,
Undo2,
Unlock,
X,
@@ -37,6 +38,7 @@ interface EditorControlsProps {
redoCount: number;
onUndo: () => void;
onRedo: () => void;
onResetCamera: () => void;
onExportJson: () => void;
onSaveToServer?: (() => void | Promise<void>) | undefined;
onPlayerMode?: (() => void) | undefined;
@@ -101,6 +103,7 @@ export function EditorControls({
redoCount,
onUndo,
onRedo,
onResetCamera,
onExportJson,
onSaveToServer,
onPlayerMode,
@@ -268,6 +271,11 @@ export function EditorControls({
</button>
)}
<button className="editor-action-button" onClick={onResetCamera}>
<ScanSearch size={16} aria-hidden="true" />
Reset camera
</button>
<label className="editor-checkbox-row">
<input
type="checkbox"
@@ -3,11 +3,15 @@ import { OrbitControls } from "@react-three/drei";
import { useThree } from "@react-three/fiber";
import gsap from "gsap";
import * as THREE from "three";
import type { OrbitControls as OrbitControlsImpl } from "three-stdlib";
import { EditorMap } from "@/components/editor/scene/EditorMap";
import { FlyController } from "@/controls/editor/FlyController";
import type { CinematicDefinition } from "@/types/cinematics/cinematics";
import type { MapNode, TransformMode, SceneData } from "@/types/editor/editor";
const EDITOR_CAMERA_HOME_POSITION = new THREE.Vector3(0, 50, 100);
const EDITOR_CAMERA_HOME_TARGET = new THREE.Vector3(0, 0, 0);
export interface EditorCinematicPreviewRequest {
id: string;
cinematic: CinematicDefinition;
@@ -28,6 +32,7 @@ interface EditorSceneProps {
onNodeTransform: (nodeIndex: number, transform: MapNode) => void;
onUndo: () => void;
onRedo: () => void;
resetCameraRequest: number;
isPlayerMode?: boolean;
cinematicPreviewRequest?: EditorCinematicPreviewRequest | null;
onCinematicPreviewComplete?: (() => void) | undefined;
@@ -48,11 +53,55 @@ export function EditorScene({
onNodeTransform,
onUndo,
onRedo,
resetCameraRequest,
isPlayerMode = false,
cinematicPreviewRequest = null,
onCinematicPreviewComplete,
}: EditorSceneProps): React.JSX.Element {
const isCinematicPreviewing = cinematicPreviewRequest !== null;
const camera = useThree((state) => state.camera);
const orbitControlsRef = useRef<OrbitControlsImpl | null>(null);
const previousSelectedNodeIndexRef = useRef<number | null>(null);
useEffect(() => {
if (selectedNodeIndex === previousSelectedNodeIndexRef.current) return;
previousSelectedNodeIndexRef.current = selectedNodeIndex;
if (selectedNodeIndex === null || isPlayerMode || isCinematicPreviewing) {
return;
}
const selectedNode = sceneData.mapNodes[selectedNodeIndex];
if (!selectedNode) return;
const controls = orbitControlsRef.current;
const target = new THREE.Vector3(...selectedNode.position);
const currentTarget = controls?.target ?? EDITOR_CAMERA_HOME_TARGET;
const cameraOffset = camera.position.clone().sub(currentTarget);
camera.position.copy(target).add(cameraOffset);
camera.lookAt(target);
controls?.target.copy(target);
controls?.update();
}, [
camera,
isCinematicPreviewing,
isPlayerMode,
sceneData,
selectedNodeIndex,
]);
useEffect(() => {
if (resetCameraRequest === 0 || isPlayerMode || isCinematicPreviewing) {
return;
}
const controls = orbitControlsRef.current;
camera.position.copy(EDITOR_CAMERA_HOME_POSITION);
camera.lookAt(EDITOR_CAMERA_HOME_TARGET);
controls?.target.copy(EDITOR_CAMERA_HOME_TARGET);
controls?.update();
}, [camera, isCinematicPreviewing, isPlayerMode, resetCameraRequest]);
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
@@ -109,6 +158,7 @@ export function EditorScene({
<FlyController disabled={isCinematicPreviewing} />
) : (
<OrbitControls
ref={orbitControlsRef}
enabled={!isCinematicPreviewing}
enableDamping
dampingFactor={0.05}
+2 -1
View File
@@ -1371,7 +1371,8 @@ canvas {
transform 160ms ease;
}
.editor-action-button + .editor-action-button {
.editor-action-button + .editor-action-button,
.editor-player-button + .editor-action-button {
margin-top: 8px;
}
+7
View File
@@ -142,6 +142,7 @@ export function EditorPage(): React.JSX.Element {
const [isPlayerMode, setIsPlayerMode] = useState(false);
const [isSelectionLocked, setIsSelectionLocked] = useState(false);
const [lockTerrainSelection, setLockTerrainSelection] = useState(true);
const [resetCameraRequest, setResetCameraRequest] = useState(0);
const [sceneLoadingState, setSceneLoadingState] = useState<SceneLoadingState>(
{
...INITIAL_SCENE_LOADING_STATE,
@@ -257,6 +258,10 @@ export function EditorPage(): React.JSX.Element {
setIsPlayerMode((prev) => !prev);
}, []);
const handleResetCamera = useCallback(() => {
setResetCameraRequest((request) => request + 1);
}, []);
const handlePreviewCinematic = useCallback(
(cinematic: CinematicDefinition) => {
setCinematicPreviewRequest({
@@ -375,6 +380,7 @@ export function EditorPage(): React.JSX.Element {
onNodeTransform={handleNodeTransform}
onUndo={handleUndo}
onRedo={handleRedo}
resetCameraRequest={resetCameraRequest}
isPlayerMode={isPlayerMode}
cinematicPreviewRequest={cinematicPreviewRequest}
onCinematicPreviewComplete={handleCinematicPreviewComplete}
@@ -405,6 +411,7 @@ export function EditorPage(): React.JSX.Element {
redoCount={redoCount}
onUndo={handleUndo}
onRedo={handleRedo}
onResetCamera={handleResetCamera}
onExportJson={handleExportJson}
onSaveToServer={import.meta.env.DEV ? handleSaveToServer : undefined}
onPlayerMode={handlePlayerMode}