效果展示

项目简介
在基础交互之上,本次我们将挑战更复杂的游戏场景。目标是构建一款具备多关卡地图、角色移动、摄像机控制、小地图及碰撞检测的 3D 自由巡视闯关游戏。这不仅是视觉上的拓展,更是从基础交互到完整游戏化场景开发流程的一次实战演练。
环境准备
- OS:Windows 11
- Browser:Google Chrome
- Node:v24.14.0
- NPM:11.9.0
- Vue:3.5.25
- Vite:7.3.1
核心步骤
1. 项目初始化
确保 PlayCanvas 项目已创建并正确集成到 Vue 环境中。这一步是地基,如果环境配置有误,后续的脚本逻辑将无法加载。
2. 功能规划
我们需要实现以下核心能力:
- 基础交互:方向键控制角色移动,WASD 控制摄像机旋转。
- 视觉定制:每关地面颜色差异化,障碍物灰色,背景白色。
- 关卡系统:至少 3 张差异化地图,到达终点按回车进入下一关。
- 辅助功能:小地图实时显示角色、起点、终点及障碍物位置。
- 碰撞检测:防止穿模,处理角色与障碍物、边界的交互。
- 通关判定:到达终点触发提示,支持重置。
3. 脚本逻辑实现
这是游戏的'大脑'。我们使用 Script Setup 语法糖来管理状态和生命周期。这里有个细节要注意:键盘事件的处理需要双重保障,因为 PlayCanvas 内置监听有时会因为焦点问题失效。
<script setup>
import { ref, onMounted, onUnmounted, nextTick } from 'vue'
import * as pc from 'playcanvas'
// 游戏状态管理
const currentLevel = ref(1)
const isWon = ref(false)
let app = null
let player = null
let startPoint = null
let endPoint =
cameraEntity =
globalEnterHandler =
moveSpeed =
cameraRotateSpeed =
cameraYaw =
cameraPitch = -
cameraDistance =
minimapCanvas =
minimapCtx =
minimapScale =
obstacles = []
levelConfigs = [
{
: ,
: pc.(-, , -),
: pc.(, , ),
: ,
: [, , ],
: [, , ],
: [, , ]
},
{
: ,
: pc.(-, , ),
: pc.(, , ),
: ,
: [, , ],
: [, , ],
: [, , ]
},
{
: ,
: pc.(, , -),
: pc.(, , ),
: ,
: [, , ],
: [, , ],
: [, , ]
}
]
= () => {
(isWon.) {
.(, currentLevel.)
currentLevel. = currentLevel. % levelConfigs. +
isWon. =
()
.().()
}
}
() {
{
()
canvas = .()
(!canvas) {
.()
}
canvas.()
canvas.(, canvas.())
()
graphicsDevice = pc.(canvas, {
: ,
:
})
graphicsDevice. = .
app = pc.(canvas, {
: graphicsDevice,
: pc.(canvas),
: pc.(),
: ,
:
})
app.()
(!app.) app. = pc.(app.)
app.(pc.)
app.(pc.)
()
app.(, update)
app..(pc., goToNextLevel)
app..(pc., goToNextLevel)
app..(, goToNextLevel)
app..(, goToNextLevel)
globalEnterHandler = {
(e. === || e. === ) {
e.()
()
}
}
.(, globalEnterHandler)
.(, {
(app && app.) {
app.(., .)
app..(., .)
}
})
app.(., .)
} (error) {
.(, error)
}
}
() {
minimapCanvas = .()
minimapCtx = minimapCanvas.()
}
() {
(!minimapCtx || !player || !startPoint || !endPoint)
minimapCtx.(, , minimapCanvas., minimapCanvas.)
minimapCtx. =
minimapCtx.(, , minimapCanvas., minimapCanvas.)
centerX = minimapCanvas. /
centerY = minimapCanvas. /
minimapCtx. =
obstacles.( {
x = centerX + obs.. * minimapScale
y = centerY + obs.. * minimapScale
w = obs.. * minimapScale
h = obs.. * minimapScale
minimapCtx.(x - w / , y - h / , w, h)
})
minimapCtx. =
startX = centerX + startPoint.(). * minimapScale
startY = centerY + startPoint.(). * minimapScale
minimapCtx.()
minimapCtx.(startX, startY, , , . * )
minimapCtx.()
minimapCtx. =
endX = centerX + endPoint.(). * minimapScale
endY = centerY + endPoint.(). * minimapScale
minimapCtx.()
minimapCtx.(endX, endY, , , . * )
minimapCtx.()
minimapCtx. =
playerX = centerX + player.(). * minimapScale
playerY = centerY + player.(). * minimapScale
minimapCtx.()
minimapCtx.(playerX, playerY, , , . * )
minimapCtx.()
minimapCtx. =
minimapCtx. =
minimapCtx.()
minimapCtx.(playerX, playerY)
minimapCtx.(
playerX + .(player.(). * . / ) * ,
playerY + .(player.(). * . / ) *
)
minimapCtx.()
}
() {
(!player || obstacles. === )
playerRadius =
( i = ; i < obstacles.; i++) {
obs = obstacles[i]
obsMinX = obs.. - obs.. /
obsMaxX = obs.. + obs.. /
obsMinZ = obs.. - obs.. /
obsMaxZ = obs.. + obs.. /
playerMinX = nextPos. - playerRadius
playerMaxX = nextPos. + playerRadius
playerMinZ = nextPos. - playerRadius
playerMaxZ = nextPos. + playerRadius
xOverlap = playerMaxX > obsMinX && playerMinX < obsMaxX
zOverlap = playerMaxZ > obsMinZ && playerMinZ < obsMaxZ
(xOverlap && zOverlap)
}
currentConfig = levelConfigs[currentLevel. - ]
mapHalfSize = currentConfig. /
(.(nextPos.) > mapHalfSize - || .(nextPos.) > mapHalfSize - ) {
}
}
() {
obstacles = []
(app && app. && app... > ) {
children = [...app..]
children.( child.())
}
config = levelConfigs[currentLevel. - ] || levelConfigs[]
app.. = pc.(...config.)
ground = pc.()
ground.(, { : , : })
ground.(config., , config.)
ground.(, , )
ground.. = (...config.)
app..(ground)
( i = ; i < config.; i++) {
posX, posZ
validPos =
(!validPos) {
posX = (-config. / + , config. / - )
posZ = (-config. / + , config. / - )
distToStart = .(posX - config.., posZ - config..)
distToEnd = .(posX - config.., posZ - config..)
(distToStart > && distToEnd > ) validPos =
}
scaleX = (, )
scaleZ = (, )
obstacle = pc.()
obstacle.(, { : , : })
obstacle.(posX, scaleX / , posZ)
obstacle.(scaleX, scaleX, scaleZ)
obstacle.. = (...config.)
app..(obstacle)
obstacles.({
: { : posX, : posZ },
: { : scaleX, : scaleZ }
})
}
startPoint = pc.()
startPoint.(, { : , : })
startPoint.(config.)
startPoint.(, , )
startPoint.. = (, , )
app..(startPoint)
endPoint = pc.()
endPoint.(, { : , : })
endPoint.(config.)
endPoint.(, , )
endPoint.. = (, , )
app..(endPoint)
player = pc.()
player.(, { : , : })
player.(config.)
player.(, , )
player.. = (, , )
app..(player)
cameraEntity = pc.()
cameraEntity.(, {
: pc.(...config.),
: pc.,
: pc.
})
()
cameraEntity.(player.())
app..(cameraEntity)
()
}
() {
(!cameraEntity || !player)
yawRad = cameraYaw * . /
pitchRad = cameraPitch * . /
x = .(yawRad) * .(pitchRad) * cameraDistance
z = .(yawRad) * .(pitchRad) * cameraDistance
y = .(pitchRad) * cameraDistance +
playerPos = player.()
cameraEntity.(playerPos. + x, playerPos. + y, playerPos. + z)
cameraEntity.(playerPos)
}
() {
.() * (max - min) + min
}
() {
material = pc.()
material..(r, g, b)
material..(r * , g * , b * )
material.()
material
}
() {
(isWon. || !app || !player || !endPoint || !cameraEntity)
keyboard = app.
(keyboard.(pc.)) cameraYaw += cameraRotateSpeed * dt
(keyboard.(pc.)) cameraYaw -= cameraRotateSpeed * dt
(keyboard.(pc.)) {
cameraPitch += cameraRotateSpeed * dt
cameraPitch = .(cameraPitch, )
}
(keyboard.(pc.)) {
cameraPitch -= cameraRotateSpeed * dt
cameraPitch = .(cameraPitch, -)
}
()
moveStep = moveSpeed * dt
currentPos = player.()
newPos = pc.(currentPos., currentPos., currentPos.)
(keyboard.(pc.)) newPos. -= moveStep
(keyboard.(pc.)) newPos. += moveStep
(keyboard.(pc.)) newPos. -= moveStep
(keyboard.(pc.)) newPos. += moveStep
(!(newPos)) player.(newPos)
()
distanceToEnd = .(
player.(). - endPoint.().,
player.(). - endPoint.().
)
(distanceToEnd < ) {
isWon. =
.(, isWon.)
}
}
( () => {
()
})
( {
(app) {
app.(, update)
(app.) {
app..(pc., goToNextLevel)
app..(, goToNextLevel)
}
.(, {})
app.()
app =
}
(globalEnterHandler) {
.(, globalEnterHandler)
globalEnterHandler =
}
})
</script>


