首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >写了个小游戏,揭秘碰撞检测

写了个小游戏,揭秘碰撞检测

作者头像
Maic
发布2025-02-12 14:26:02
发布2025-02-12 14:26:02
31010
代码可运行
举报
文章被收录于专栏:Web技术学苑Web技术学苑
运行总次数:0
代码可运行

最近因为一个面试笔试题检测,写了一个H5小游戏,总结了一下关于两个物体的检测,如何判断两个物体的碰撞检测,希望能在业务上能带来一点帮助和思考

我用以下示例图解释了两个物体产生碰撞的条件

我们用一段伪代码来解释下这个过程

代码语言:javascript
代码运行次数:0
运行
复制
 
// 代表圆形物体
class FallingObject {
      constructor(canvas){
        ...
        this.x1 = canvas.width;
        this.y1 = 0;
        this.size = 10;
      }
      // 检测碰撞,character代表下面的一个物体
      checkCollision(character) {
         const bool1 = this.x1 < character.x2 + character.width;
         const bool2 = this.x1 + this.size > character.x2;
         const bool3 = this.y1 < character.y2 + character.height;
         const bool4 = this.y1+this.size > character.y2 + character.height;
         
         const is_collision = bool1 && bool2 && bool3 && bool4;
         if (is_collision) {
            return true
         }
         return false;
      }
}

checkCollision就是检测两个物体产生碰撞的方法,如果产生了碰撞就返回true,否则返回false,这是一段非常简单的判断碰撞逻辑,基本可以这个万能公式解决碰撞问题

如何生成障碍物

我们主要是使用了一个FallingObject的类,这个类代表了随机生成的障碍物

代码语言:javascript
代码运行次数:0
运行
复制
 
class FallingObject {
    private canvas: any;
    private ctx: any;
    private x: number;
    private y: number;
    private size: number;
    private speed: number;
    private blockImage: any;
    constructor(canvas: any) {
      this.canvas = canvas;
      this.ctx = canvas.getContext("2d");
      // 初始位置
      this.x = Math.random() * canvas.width;
      this.y = 0;
      this.size = Math.random() * 30 + 20; // 随机物体大小
      this.speed = Math.random() * 3 + 2; // 随机物体下落速度
      this.blockImage = new Image();
      this.blockImage.src = "/meme/SHIKOKU.png"; 
      this.blockImage.onload = () => {
          this.draw(); // 只有在图片加载完成后调用绘制
      };
    }

首先掉落物体这个类的基本属性就有

  • 初始化位置随机大小,以及下落的速度,以及canvas的上下文环境

我们再看下如何下落,首先是从初识位置下落,不断更新Y轴纵向的距离

代码语言:javascript
代码运行次数:0
运行
复制
 
...
// 更新Y掉落的位置
 update() {
      this.y += this.speed;
 }
//绘制掉落的物体
draw() {
    this.ctx.drawImage(this.blockImage, this.x, this.y, this.size, this.size);
}
// 检测碰撞
 checkCollision(character: {
      x: number;
      width: any;
      y: number;
      height: any;
    }) {
      if (
        this.x < character.x + character.width &&
        this.x + this.size > character.x &&
        this.y < character.y + character.height &&
        this.y + this.size > character.y
      ) {
        return true; // 发生碰撞
      }
      return false;
    }

角色类控制

由于我们需要一个角色躲避障碍物,因此我们得生成一个角色

代码语言:javascript
代码运行次数:0
运行
复制
 
   // 角色类
export class Character {
    private canvas: any;
    private ctx: any;
    private x: number;
    private y: number;
    private width: number;
    private height: number;
    private speed: number;
    private velocityY: number;
    private gravity: number;
    lives: number;
    private image: HTMLImageElement;
    constructor(x: number, y: number, canvas: any) {
      this.canvas = canvas;
      this.ctx = canvas.getContext("2d");
      this.x = x;
      this.y = y;
      this.width = 50;
      this.height = 50;
      this.speed = 5;
      this.velocityY = 0; // 重力的速度
      this.gravity = 0.5; // 重力大小
      this.lives = 3; // 生命数量
      this.image = new Image();
      this.image.src = "/role.png";
      this.image.onload = () => {
        this.draw(this.ctx);
      };
    }
    ...
  }

这个角色初始化的属性主要有

  • 自身的大小初始位置,以及生命次数,还有重力速度以及大小

角色主要是躲避,因此需更新x方向的位置即可

代码语言:javascript
代码运行次数:0
运行
复制
 

    ...
    move(left: boolean, right: boolean) {
        // 左移动
        if (left) {
            this.x -= this.speed;
            // 确保角色不会移出画布
          if (this.x < 0) this.x = 0;
          if (this.x > this.canvas.width - 50) this.x = this.canvas.width - 50;
            return;
      };
      // 右移动
        if (right) {
            this.x += this.speed;
           // 确保角色不会移出画布
          if (this.x < 0) this.x = 0;
          if (this.x > this.canvas.width - 50) this.x = this.canvas.width - 50;
            return;
        };
  
    }

角色还可以跳跃,左右移动

代码语言:javascript
代码运行次数:0
运行
复制
 
  ...
  jump() {
      if (this.y === this.canvas.height - this.height) {
        // 确保只有在地面上时可以跳跃
        this.velocityY = -15;
      }
    }
    // y轴更新位置
    update() {
      this.y += this.velocityY;
      if (this.y < this.canvas.height - this.height) {
        this.velocityY += this.gravity;
      } else {
        this.y = this.canvas.height - this.height; // 防止角色掉出画面
        this.velocityY = 0;
      }
    }
    // 绘制角色
    draw(ctx: any) {
      ctx.drawImage(this.image, this.x, this.y, this.width, this.height);
    }

如何驱动障碍物与角色动画

在浏览器驱动页面动画,有三种方式,一种是css3的动画帧@keyframes,一种是定时器,还有一种是requestAnimationFrame,我们使用requestAnimationFrame递归调用,驱动动画

核心代码大概思路就是:

  • requestAnimationFrame递归调用自己,驱动动画
  • 把障碍物对象装到一个数组中
  • 遍历障碍物数组,实时更新障碍物的位置,并与角色做碰撞检测,如果碰撞了,则减少生命次数,如果达到次数,则标记,游戏结束
代码语言:javascript
代码运行次数:0
运行
复制
 
  ...
  const is_pc_media = useMediaQuery("(min-width:1025px)");
  const character_obj = useRef<any>(null);

  // 按键状态
  const [left, setLeft] = useState(false);
  const [right, setRight] = useState(false);

  useEffect(() => {
    const canvas: any = canvasDom.current;
    const ctx = canvas.getContext("2d");
    canvas.width = is_pc_media ? 800 : window.innerWidth;
    canvas.height = 600;

    let character = new Character(
      canvas.width / 2 - 25,
      canvas.height - 50,
      canvas
    );
    character_obj.current = character;
    let fallingObjects: any = [];
    let score = 0;
    let gameOver = false;
    let paused = false;

    const gameInit = () => {
      if (gameOver) {
        console.log("game over");
        message.alert({
          type: "warning",
          msg: "game over, please reset again!",
        });
        return;
      }
      if (!paused) return;

      ctx.clearRect(0, 0, canvas.width, canvas.height);

      // 根据按键状态控制角色移动
      character.move(left, right);
      character.update();
      character.draw(ctx);

      // 随机添加和更新掉落物体
      if (Math.random() < 0.05) {
        fallingObjects.push(new FallingObject(canvas, symbolData));
      }

      fallingObjects.forEach((object: any, index: any) => {
        object.update();
        object.draw(ctx);
        // 检测每一个物体的碰撞
        if (object.checkCollision(character)) {
          character.lives -= 1;
          setLifeTime(character.lives);
          fallingObjects.splice(index, 1);
          if (character.lives <= 0) {
            gameOver = true;
          }
        }
      });

      score += 1;
      setSource(score);
      requestAnimationFrame(gameInit);
    };

    // 开始游戏
    startGame.current = () => {
      character = new Character(
        canvas.width / 2 - 25,
        canvas.height - 50,
        canvas
      );
      fallingObjects = [];
      score = 0;
      gameOver = false;
      setLeft(false);
      setRight(false);
      paused = true;
      gameInit();
    };

    // 暂停/开始游戏
    plauseGame.current = () => {
      paused = !paused;
      if (paused) {
        setStartText("pause");
        gameInit();
      } else {
        setStartText("start");
        stopGameStart();
      }
    };

    // 监听键盘事件
    const handleKeyDown = (e: KeyboardEvent) => {
      if (e.key === "ArrowLeft") {
        setLeft(true); // 持续按下左键时,角色会向左移动
      }
      if (e.key === "ArrowRight") {
        setRight(true); // 持续按下右键时,角色会向右移动
      }
      if ([" ", "ArrowUp"].includes(e.key)) character.jump();
    };

    const handleKeyUp = (e: KeyboardEvent) => {
      if (e.key === "ArrowLeft") {
        setLeft(false); // 松开左键时,停止向左移动
      }
      if (e.key === "ArrowRight") {
        setRight(false); // 松开右键时,停止向右移动
      }
    };

    window.addEventListener("keydown", handleKeyDown);
    window.addEventListener("keyup", handleKeyUp);

    return () => {
      window.removeEventListener("keydown", handleKeyDown);
      window.removeEventListener("keyup", handleKeyUp);
    };
  }, [is_pc_media, left, right]);
  

核心代码基本差不多了,具体预览地址预览地址[1],源码[2]

后续再拓展新增一些好玩的功能了

总结

  • 详细描述了两个物体碰撞检测的条件
  • 主要实现一下障碍物于角色的动画运动
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-12-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Web技术学苑 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 如何生成障碍物
  • 角色类控制
  • 如何驱动障碍物与角色动画
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档