// 3D 摄像机控制器示例:演示 eve.CameraController 的多种视角行为。 // - follow 自动追踪"核心玩家"(第三人称跟随) // - orbit 自动盘旋 // - topdown 俯视 // - firstperson 第一人称 // - cinematic 过场(一组命名视角之间平滑自动切换) // 运行:make run/win32-debug GAME=examples/camera-controllers persist players = [] persist markers = [] persist ctrl = null persist cam = null persist mode = "follow" persist spin = true persist uiReady = false persist animT = 0.0 persist statusWas = "" persist spinWas = false persist fpYaw = 0.0 persist fpPitch = 0.0 local MODES = ["follow", "orbit", "topdown", "firstperson", "cinematic"]; function makeSphere(r, g, b, metallic, roughness) { local ent = eve.Renderable3D(); ent.setMesh(gfx.newMeshSphere(32, 16)); ent.setTint(r, g, b, 1.0); ent.setMetallic(metallic); ent.setRoughness(roughness); ent.setCastShadow(true); ent.setReceiveShadow(true); return ent; } function buildScene() { // 地面 local ground = makeSphere(0.35, 0.4, 0.45, 0.05, 0.9); ground.setScale(1.0, 0.15, 1.0); ground.setPosition(0.0, -1.6, 0.0); markers.push(ground); // 场景标记柱(orbit / cinematic 的观察对象) local markerColors = [[0.9, 0.35, 0.3], [0.3, 0.7, 0.9], [0.9, 0.75, 0.3]]; for (local i = 0; i < 3; i++) { local a = i * (6.2832 / 3.0); local m = makeSphere(markerColors[i][0], markerColors[i][1], markerColors[i][2], 0.1, 0.4); m.setScale(0.8, 2.4, 0.8); m.setPosition(cos(a) * 6.0, 0.6, sin(a) * 6.0); markers.push(m); } // "核心玩家":一个会移动的球,供 follow / firstperson 追踪 for (local i = 0; i < 5; i++) { local p = makeSphere(0.25 + i * 0.1, 0.9, 0.4, 0.3, 0.5); p.setScale(0.55, 0.55, 0.55); players.push(p); } } function setupCameraController() { cam = eve.Camera3D(); cam.setEye(0.0, 3.0, 8.0); cam.setTarget(0.0, 1.0, 0.0); cam.setFov(50.0); cam.setAmbient(0.3, 0.32, 0.34); gfx.setDirectionalLight(-0.4, 0.9, 0.35, 1.9, 1.6, 1.3); ctrl = eve.CameraController(); ctrl.setCamera(cam); ctrl.setMode("follow"); ctrl.setTarget(0.0, 1.0, 0.0); ctrl.setOffset(0.0, 2.6, 6.0); ctrl.setSmooth(6.0); ctrl.setPositionSmooth(7.0); ctrl.setTargetSmooth(10.0); ctrl.setComposition(0.08, 0.04); ctrl.setDeadZone(0.15); ctrl.setFov(50.0); // 静态遮挡探针:相机会在盒体前缩进,解除遮挡后平滑恢复。 ctrl.setCollisionEnabled(true); ctrl.setCollisionRadius(0.3); ctrl.setCollisionRecovery(4.0); ctrl.addCollisionBox(-1.2, -1.6, 1.5, 1.2, 2.5, 2.0); // Rig 是可复用的参数快照;Director 按 priority / enabled 选择。 ctrl.addRig("follow", "follow", 10); ctrl.setMode("orbit"); ctrl.setRadius(10.0); ctrl.setElevation(30.0); ctrl.addRig("orbit", "orbit", 10); ctrl.setMode("topdown"); ctrl.setRadius(12.0); ctrl.addRig("topdown", "topdown", 10); ctrl.setMode("firstperson"); ctrl.setFov(65.0); ctrl.addRig("firstperson", "firstperson", 10); ctrl.setMode("cinematic"); ctrl.setFov(48.0); ctrl.addRig("cinematic", "cinematic", 10); // cinematic 用的一组命名视角 ctrl.addView("front", 0.0, 3.0, 12.0, 0.0, 1.0, 0.0); ctrl.addView("side", 14.0, 3.0, 0.0, 0.0, 1.0, 0.0); ctrl.addView("high", 8.0, 9.0, 8.0, 0.0, 1.0, 0.0); ctrl.addView("close", 0.0, 2.0, 4.0, 0.0, 1.0, 0.0); // Timeline 切换 Rig 并产生可被全局 Event 或 controller 消费的 marker。 ctrl.setEventSink(platform_event); ctrl.addTimelineCut(0.0, "cinematic", 0.0); ctrl.addTimelineEvent(1.0, "camera.beat", "front"); ctrl.addTimelineCut(3.0, "orbit", 0.8); ctrl.addTimelineEvent(3.0, "camera.beat", "orbit"); ctrl.addTimelineCut(6.0, "cinematic", 0.8); ctrl.snap(); } function buildPanel() { ui.setTheme("dark"); ui.setNavKeyboard(true); ui.beginBuild(); ui.beginWindow("CAMERA CONTROLLERS", "root"); ui.text("Choose a camera behavior", "eyebrow"); ui.text(mode, "mode"); ui.button("follow (1)", "m_follow"); ui.button("orbit (2)", "m_orbit"); ui.button("topdown (3)", "m_topdown"); ui.button("firstperson (4)", "m_firstperson"); ui.button("cinematic (5)", "m_cinematic"); ui.text("Spin enabled (space)", "spinlab"); ui.text("Ready.", "status"); ui.end(); ui.mountBuildAs("cam"); ui.select("cam"); ui.setHostOverlay(true); ui.setHostPos(900.0, 30.0, 280.0, 380.0); uiReady = true; } function setMode(m) { mode = m; ctrl.activateRig(m, 0.35); if (m == "cinematic") { ctrl.seekTimeline(0.0, false); ctrl.playTimeline(true); } else { ctrl.pauseTimeline(); ctrl.stopSequence(); ctrl.setTarget(0.0, 1.0, 0.0); if (m == "orbit") { ctrl.setRadius(10.0); ctrl.setElevation(30.0); ctrl.setOrbitSpeed(20.0); } else if (m == "topdown") { ctrl.setRadius(12.0); } } if (uiReady) { ui.select("cam"); ui.setText("mode", m); ui.setText("status", "Mode: " + m); } } eve_init = function() { gfx.setBackgroundColor(0.05, 0.06, 0.08, 1.0); buildScene(); setupCameraController(); if (!uiReady) buildPanel(); }; eve_update = function(dt) { animT += dt; // 让"核心玩家"沿圆环移动,便于观察自动追踪 / 盘旋 local px = cos(animT * 0.6) * 3.0; local pz = sin(animT * 0.6) * 3.0; local py = 0.7 + sin(animT * 1.3) * 0.6; for (local i = 0; i < players.len(); i++) { local lead = i * 0.5; players[i].setPosition(cos(animT * 0.6 + lead) * 3.0, py, sin(animT * 0.6 + lead) * 3.0); } // follow / firstperson 追踪移动的"核心玩家" if (mode == "follow" || mode == "firstperson") { ctrl.setTarget(px, py, pz); } // 空格切换自动盘旋(orbit) local space = keyboard.isDown("Space"); local spinPressed = space && !spinWas; spinWas = space; if (spinPressed) { spin = !spin; ctrl.setOrbitSpeed(spin ? 20.0 : 0.0); } // firstperson:方向键调整朝向 if (mode == "firstperson") { if (keyboard.isDown("left")) fpYaw = fpYaw - 90.0 * dt; if (keyboard.isDown("right")) fpYaw = fpYaw + 90.0 * dt; if (keyboard.isDown("up")) fpPitch = fpPitch + 90.0 * dt; if (keyboard.isDown("down")) fpPitch = fpPitch - 90.0 * dt; ctrl.setYaw(fpYaw); ctrl.setPitch(fpPitch); } ctrl.update(dt); local marker = ctrl.consumeTimelineEvent(); if (marker != "" && uiReady) { ui.select("cam"); ui.setText("status", marker + ": " + ctrl.getTimelineEventData()); } // UI 交互 local clicked = ui.consumeClick(); while (clicked != "") { ui.select("cam"); if (clicked == "cam/m_follow") setMode("follow"); else if (clicked == "cam/m_orbit") setMode("orbit"); else if (clicked == "cam/m_topdown") setMode("topdown"); else if (clicked == "cam/m_firstperson") setMode("firstperson"); else if (clicked == "cam/m_cinematic") setMode("cinematic"); clicked = ui.consumeClick(); } // 数字键 1-5 直接切换模式 local keyModes = [["1", "follow"], ["2", "orbit"], ["3", "topdown"], ["4", "firstperson"], ["5", "cinematic"]]; foreach (pair in keyModes) { if (keyboard.isDown(pair[0])) { setMode(pair[1]); } } }; eve_render = function() { gfx.clear(); gfx.render3D(); ui.beginFrameAndRender(); };