🏀从0到1打造Web端投篮机游戏:Vue3+Three.js+GSAP实战

82 阅读4分钟

出发点

还记得小时候在游戏厅里玩的投篮机吗?那种投中篮球时的成就感和紧张刺激的倒计时,是很多人的童年回忆。今天,我将带你一起用现代前端技术栈,在浏览器中重现这份经典乐趣!

本文将详细介绍如何使用Vue3、Three.js和GSAP这三个强大的前端工具,打造一个完整的Web端投篮机游戏。无需复杂的游戏引擎,仅凭前端技术就能实现令人惊艳的3D交互体验。

ball.gif

技术选型解析

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游戏开发开辟了新的可能性。