出发点
还记得小时候在游戏厅里玩的投篮机吗?那种投中篮球时的成就感和紧张刺激的倒计时,是很多人的童年回忆。今天,我将带你一起用现代前端技术栈,在浏览器中重现这份经典乐趣!
本文将详细介绍如何使用Vue3、Three.js和GSAP这三个强大的前端工具,打造一个完整的Web端投篮机游戏。无需复杂的游戏引擎,仅凭前端技术就能实现令人惊艳的3D交互体验。
技术选型解析
Vue3 - 现代化的响应式框架
Vue3的Composition API为我们管理游戏状态提供了极大的便利。响应式系统让UI与游戏数据的同步变得简单直观,而优秀的性能表现确保了游戏的流畅运行。
Three.js - 强大的3D图形库
作为WebGL的友好封装,Three.js让我们能够在浏览器中轻松创建3D场景、篮球、篮筐等游戏元素,无需深入WebGL的复杂细节。
GSAP - 专业的动画库
GSAP提供了精准的动画控制能力,无论是篮球的抛物线运动、得分动画还是场景过渡,都能轻松实现流畅自然的动画效果。
游戏核心功能设计
1. 篮球交互系统
游戏的核心交互非常简单直观:
- 拾取:鼠标左键点击篮球
- 投掷:松开鼠标左键发射
2. 关卡与计时系统
游戏采用经典的关卡制设计:
- 时间限制:每关30秒倒计时
- 过关条件:相同等级下第一关得分大于等于8分过关;第二关大于等于12分过关;第二关大于等于15分过关
- 难度设置:在代码中修改篮球离筐距离范围、篮筐的移动速度来修改难易程度
3. 音效与反馈系统
良好的反馈是游戏体验的关键:
- 背景音乐:营造游戏厅氛围
- 进球音效:给予玩家即时正反馈
- 得分动画:醒目的+1动画增强成就感
技术实现亮点
3D场景构建
使用Three.js创建逼真的游戏场景:
- 篮球、篮筐、篮板等模型的创建与材质设置
- 光照系统营造合适的视觉效果
- 摄像机角度选择最佳的游戏视角
export const initScene = (container) => {
const scene = new THREE.Scene();
// 确保创建并返回 characterGroup
const characterGroup = new THREE.Group();
scene.add(characterGroup);
const camera = new THREE.PerspectiveCamera(
60,
container.clientWidth / container.clientHeight,
0.01,
1000
);
camera.position.set(3.25, 0.95, 0);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(container.clientWidth, container.clientHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
container.appendChild(renderer.domElement);
// 加载HDR环境贴图
const hdrEquirect = new RGBELoader()
.setPath('/images/')
.load('fireplace_2k.hdr', function(texture) {
texture.mapping = THREE.EquirectangularReflectionMapping;
scene.environment = texture;
// scene.background = texture;
});
const controls = initOrbitControls(camera, renderer);
controls.enableRotate = false;
controls.enableZoom = false;
// 添加灯光
const directLight = new THREE.DirectionalLight(0xffffff, 3);
directLight.position.set(5, 9.6, -0.799);
scene.add(directLight);
// 确保返回所有必要的对象
return {
scene,
camera,
renderer,
controls,
};
};
物理运动模拟
虽然我们没有使用专门的物理引擎,但通过数学计算实现了合理的篮球运动轨迹:
- 抛物线运动计算
- 碰撞检测(篮球与篮筐、篮板)
- 旋转效果模拟真实篮球的旋转
处理地面碰撞(y轴方向)
const handleGroundCollision = (ball: any, groundY: number) => {
const velocity = ball.userData.velocity;
ball.position.y = groundY;
// 只有垂直速度足够大时才反弹
if (Math.abs(velocity.y) > 0.5) {
velocity.y = -velocity.y * bounceDamping;
velocity.x *= 0.7; // 水平方向也衰减
velocity.z *= 0.7;
console.log('球弹跳,速度:', velocity.y);
} else {
// 速度很小,停止运动
velocity.set(0, 0, 0);
ball.userData.hasLanded = true;
console.log('球落地,准备重置');
// 获取篮球的初始位置
const originalPos = originalPositions.get(ball);
if (originalPos) {
// 计算回初始位置的方向
const directionToStart = new THREE.Vector3()
.subVectors(originalPos, ball.position)
.normalize();
// 给篮球一个x轴方向的力,使其滚回初始位置
velocity.x = directionToStart.x * 2; // 调整这个值可以控制滚动速度
velocity.z = directionToStart.z * 2;
console.log('给篮球施加回滚力,方向:', directionToStart, '速度:', velocity);
// 标记篮球正在回滚
ball.userData.isRollingBack = true;
}
// 延迟一段时间后如果还没回到位置,强制重置
// setTimeout(() => {
// if (ball.userData.isRollingBack) {
// resetBallToOriginalPosition(ball);
// }
// }, 200);
if (ball.userData.isRollingBack) {
resetBallToOriginalPosition(ball);
}
}
};
投掷篮球
const throwBall = (ball: any) => {
if (!emptyPosition) return;
const ballPos = ball.position.clone();
const targetPos = emptyPosition.clone();
console.log('投掷起点:', ballPos, '目标点:', targetPos);
// 计算到篮筐的完整距离(包括高度差)
const deltaX = targetPos.x - ballPos.x;
const deltaY = targetPos.y - ballPos.y;
const deltaZ = targetPos.z - ballPos.z;
const horizontalDistance = Math.sqrt(deltaX * deltaX + deltaZ * deltaZ);
const totalDistance = Math.sqrt(horizontalDistance * horizontalDistance + deltaY * deltaY);
console.log('水平距离:', horizontalDistance, '垂直高度差:', deltaY, '总距离:', totalDistance);
// 更精确的抛物线参数计算
const optimalAngle = Math.PI / 4; // 45度角通常是最优投掷角度
// 根据距离和角度计算初始速度
const g = Math.abs(gravity); // 重力绝对值
const throwSpeed = Math.sqrt((horizontalDistance * horizontalDistance * g) /
(2 * Math.cos(optimalAngle) * Math.cos(optimalAngle) *
(horizontalDistance * Math.tan(optimalAngle) - deltaY)));
// 限制速度在合理范围内
const clampedSpeed = Math.min(Math.max(throwSpeed, 8), 15);
console.log('计算速度:', throwSpeed, '最终速度:', clampedSpeed);
// 计算水平方向单位向量
const horizontalDirection = new THREE.Vector3(deltaX, 0, deltaZ).normalize();
// 设置初始速度向量
ball.userData.velocity.set(
horizontalDirection.x * clampedSpeed * Math.cos(optimalAngle),
clampedSpeed * Math.sin(optimalAngle),
horizontalDirection.z * clampedSpeed * Math.cos(optimalAngle)
);
ball.userData.isThrown = true;
ball.userData.isInBasket = false;
ball.userData.hasLanded = false;
ball.userData.scored = false;
ball.userData.forcedFall = false; // 重置强制下落标记
console.log('投掷参数 - 速度:', ball.userData.velocity, '角度:', optimalAngle);
};
动画系统集成
GSAP负责处理各种动画效果:
- 篮球的投掷动画
- 得分数字的弹出效果
- 进球后篮网的动画
总结
通过这个项目,我们证明了现代前端技术完全有能力创建引人入胜的3D游戏体验。Vue3、Three.js和GSAP的黄金组合,为Web游戏开发开辟了新的可能性。