Files

233 lines
7.6 KiB
Plaintext

// 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();
};