仿制 Google Chrome 的恐龙小游戏

news2024/11/24 17:46:05

通过仿制 Google Chrome 的恐龙小游戏,我们可以掌握如下知识点:

  1. 灵活使用视口单位
  2. 掌握绝对定位
  3. JavaScript 来操作 CSS 变量
  4. requestAnimationFrame 函数的使用
  5. 无缝动画实现

页面结构

在这里插入图片描述

实现页面结构

通过上述的页面结构我们可以知道,此游戏中需要有如下的元素:

  • 游戏世界
  • 小恐龙
  • 分数
  • 游戏开始的信息提示
  • 地面
  • 仙人掌

然后构建对应的页面结构

<div class="world">
  <div class="score">0</div>
  <div class="start-screen">按任意键开始</div>
  <img src="./image/ground.png" class="ground" />
  <img src="./image/ground.png" class="ground" />
  <img src="./image/dino-stationary.png" class="dino" />
</div>

使用绝对定位完成页面元素的布局

定义好元素后,我们可以编写对应的样式:

.world {
  position: relative;
  overflow: hidden;
  /* 这里我们先使用固定值来设置,随后使用JS来动态设置值 */
  width: 100%;
  height: 300px;
}

.score {
  position: absolute;
  top: 1vmin;
  right: 1vmin;
  font-size: 3vmin;
}

.start-screen {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-size: 3vmin;
}

.hine {
  display: none;
}

/* 使用CSS变量进行占位,然后使用JS来控制变量计算 */
.ground {
  --left: 0;
  position: absolute;
  width: 300%;
  bottom: 0;
  left: calc(var(--left) * 1%);
}

.dino {
  --bottom: 0;
  position: absolute;
  left: 0;
  height: 30%;
  bottom: calc(var(--bottom) * 1%);
}

.cactus {
  --left: 0;
  position: absolute;
  left: calc(var(--left) * 1%);
  height: 30%;
  bottom: 0;
}

使用 JS 来监听视口大小改变,从而修改游戏世界元素的宽高

为了能够很好的适配所有的设备,我们需要使用 JS 来监听视口的大小改变,从而动态修改页面的元素大小具体的步骤如下。

首先在游戏世界元素中添加如下属性:

<div class="world" data-world></div>

编写 JS 代码:

const WORLD_WIIDTH = 100;
const WORLD_HEIGHT = 30;

const worldElem = document.querySelector("[data-world]");

setPixelToWorldScale(); // 初始化游戏世界的大小
window.addEventListener("resize", setPixelToWorldScale);

function setPixelToWorldScale() {
  let worldToPixeScale = 0;

  /**
   * 判断视口的大小是否大于我们自定义的常量,这样做主要是保证我们的游戏世界的元素大小在视口的中央
   *
   * 当视口宽度大于高度时,判断结果为false,就取高度做计算
   * 当视口宽度小于高度时,判断结果为true,就取宽度做计算
   */
  if (window.innerWidth / window.innerHeight < WORLD_WIIDTH / WORLD_HEIGHT) {
    worldToPixeScale = window.innerWidth / WORLD_WIIDTH;
  } else {
    worldToPixeScale = window.innerHeight / WORLD_HEIGHT;
  }

  worldElem.style.width = `${WORLD_WIIDTH * worldToPixeScale}px`;
  worldElem.style.height = `${WORLD_HEIGHT * worldToPixeScale}px`;
}

使用 requestAnimationFrame 编写对应的更新动画函数

整体的页面布局好以后,我们就可以开始对恐龙、地面、仙人掌和分数进行动画的渲染。但是首先我们先用编写控制动画运行的函数。

我们要编写动画函数的话,可以使用requestAnimationFrame函数来帮我们实现。

window.requestAnimationFrame()函数它会告诉浏览器你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。具体的实现代码如下:

let lastTime; // 上一次动画执行时间
function update(time) {
  // 动画开始执行时,lastTime是为null,所以需要对齐赋值
  if (lastTime == null) {
    lastTime = time;
    window.requestAnimationFrame(update);
    return;
  }

  const delta = time - lastTime; // 计算上一次动画时间和本次动画时间差
  console.log(delta);
  lastTime = time;
  window.requestAnimationFrame(update);
}

实现地面动画

说明: 以下核心代码中会出现一些辅助函数,为了不让篇幅过长,所以这里就不在展示辅助函数代码,可以在下载完整的代码中进行查看。

实现地面移动的动画其实很简单,因为我们地面的样式是使用绝对定位,并且地面元素有两个,所以只要让两个地面元素交替向左移动就可以实现无缝的动画。

为了提示游戏的难度,游戏会随着时间的推移地面的移动速度会越来越快,具体核心代码如下:

// 地面相关JS
const SPEED = 0.05; // 地面移动量(移动量越大速度越快)
const grounds = document.querySelectorAll("[data-ground]");

// 重置地面位置
export function setupGround() {
  setCustomProperty(grounds[0], "--left", 0);
  setCustomProperty(grounds[1], "--left", 300);
}

/**
 * 修改每帧地面动画
 * @param {number} delta 每帧动画的时间差
 * @param {number} speedScale 难度系数
 */
export function updateGround(delta, speedScale) {
  grounds.forEach((ground) => {
    incrementCustomProperty(ground, "--left", delta * speedScale * SPEED * -1); // 向左移动地面

    // 当地面元素左移到-300像素时,需要把对应的地面元素重置到第二个地面元素后
    if (getCustomProperty(ground, "--left") <= -300) {
      // 因为有两个地面元素,所以长度为两个地面元素的长度
      incrementCustomProperty(ground, "--left", 600);
    }
  });
}
// 核心游戏JS

// 动画执行函数
function update(time) {
  if (lastTime == null) {
    lastTime = time;
    window.requestAnimationFrame(update);
    return;
  }

  const delta = time - lastTime;
  updateSpeedScale(delta);
  updateGround(delta, speedScale);

  lastTime = time;
  window.requestAnimationFrame(update);
}

// 修改游戏难度系数(随着时间的推移难度系数值越大,地面的移动速度越快)
function updateSpeedScale(delta) {
  speedScale += delta * SPEED_SCALE_INCREASE;
}

实现小恐龙相关动画

实现小恐龙关键帧替换

在这个游戏中我们的小恐龙是由两个关键帧交替实现动画效果的,所以我们需要在一帧动画期间交替替换小恐龙的两个关键帧,从而绘制小恐龙跑步的动画,具体核心代码如下:

/**
 * 修改小恐龙的动画
 * @param {number} delta 每帧动画的时间差
 * @param {number} speedScale 难度系数
 */
export function updateDino(delta, speedScale) {
  handleRun(delta, speedScale);
}

/**
 * 小恐龙运动动画
 * @param {number} delta 每帧动画的时间差
 * @param {number} speedScale 难度系数
 * @returns
 */
function handleRun(delta, speedScale) {
  // 判断小恐龙是否跳起,跳起的话关键帧只能固定一个
  if (isJumping) {
    dinoElem.src = `./imgs/dino-stationary.png`;
    return;
  }

  /**
   * 因为一帧动画可以执行很多次,所以我们需要对每帧执行完成后交替更换小恐龙的关键帧图片
   * FRAME_TIME用于每帧小恐龙交替拆分的关键值
   */
  if (currentFrameTime >= FRAME_TIME) {
    dinoFrame = (dinoFrame + 1) % DINO_FRAME_COUNT;
    dinoElem.src = `./imgs/dino-run-${dinoFrame}.png`;
    currentFrameTime -= FRAME_TIME;
  }

  currentFrameTime += delta * speedScale;
}

实现小恐龙跳起动画

小恐龙的跳起主要是在 Y 轴上进行上下运动,并且为了达到最好的动画效果,我们会声明两个变量用于控制跳起动画的效果。具体核心代码如下:

/**
 * 跳起动画
 * @param {number} delta 每帧动画的时间差,
 * @returns 小恐龙跳起跳起的高度
 */
function handleJump(delta) {
  if (!isJumping) return;

  incrementCustomProperty(dinoElem, "--bottom", yVelocity * delta);

  // 接触到地面后重置相关参数
  if (getCustomProperty(dinoElem, "--bottom") <= 0) {
    setCustomProperty(dinoElem, "--bottom", 0);
    isJumping = false;
  }

  yVelocity -= GRAVITY * delta;
}

// 监听小恐龙跳起事件
function onJump(e) {
  if (e.code !== "Space" || isJumping) return;

  yVelocity = JUMP_SPEED;
  isJumping = true;
}

实现仙人掌动画

仙人掌是在一定时间间隔内在游戏世界中创建出来,并且动画移动效果跟地面一样。所以我们在实现此功能的时候最核心的业务就是在随机间隔内生成对应的仙人掌,并执行相应的动画。具体的核心代码如下:

// 创建仙人掌
function createCactus() {
  const cactus = document.createElement("img");

  cactus.dataset.cactus = true;
  cactus.src = "./imgs/cactus.png";
  cactus.classList.add("cactus");

  setCustomProperty(cactus, "--left", 100);
  worldElem.append(cactus);
}

// 生成仙人掌并执行相应动画
export function updateCactus(delta, speedScale) {
  document.querySelectorAll("[data-cactus]").forEach((cactus) => {
    incrementCustomProperty(cactus, "--left", delta * speedScale * SPEED * -1);
    if (getCustomProperty(cactus, "--left") <= -100) {
      cactus.remove();
    }
  });

  // 判断是否要生成下一个仙人掌
  if (nextCactusTime <= 0) {
    createCactus();

    // 生成下一个仙人掌的时间间隔
    nextCactusTime =
      randomNumberBetween(CACTUS_INTERVAL_MIN, CACTUS_INTERVAL_MAX) /
      speedScale;
  }

  nextCactusTime -= delta;
}

// 重置仙人掌
export function setupCactus() {
  nextCactusTime = CACTUS_INTERVAL_MIN;

  // 移除是有仙人掌
  document.querySelectorAll("[data-cactus]").forEach((cactus) => {
    cactus.remove();
  });
}

游戏结束评定

游戏的结束判断就是小恐龙是否碰到仙人掌,所以我们首先需要添加获取小恐龙和仙人掌的方法,具体函数如下:

// 获取仙人掌
export function getCactusRects() {
  return [...document.querySelectorAll("[data-cactus]")].map((cactus) => {
    return cactus.getBoundingClientRect();
  });
}

// 获取小恐龙
export function getDinoRect() {
  return dinoElem.getBoundingClientRect();
}

// 判断是否游戏结束
function checkLose() {
  const dinoRect = getDinoRect();
  return getCactusRects().some((rect) => isCollision(rect, dinoRect));
}

// 通过判断小恐龙和仙人掌是否碰撞
function isCollision(rect1, rect2) {
  return (
    rect1.left < rect2.right &&
    rect1.top < rect2.bottom &&
    rect1.right > rect2.left &&
    rect1.bottom > rect2.top
  );
}

完整代码下载

完整代码下载

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1037962.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【多态】虚函数表存储在哪个区域?

A:栈 B:堆 C:代码段&#xff08;常量区&#xff09; D:数据段&#xff08;静态区&#xff09; 答案 &#xff1a; 代码段&#xff08;常量区&#xff09; 验证如下&#xff1a; class Person { public:virtual void BuyTicket() { cout << "Person::BuyTicket()&q…

【Hash表】判断有没有重复元素-力扣 217

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kuan 的首页,持续学…

认识HTTP和HTTPS协议

HTTPS 是什么 HTTPS 也是一个应用层协议. 是在 HTTP 协议的基础上引入了一个加密层. 为什么要引入加密层呢&#xff1f; HTTP 协议内容都是按照文本的方式明文传输的. 这就导致在传输过程中出现一些被篡改的情况. HTTPS就是在HTTP的基础上进行了加密&#xff0c;进一步的保…

群体遗传学-选择消除分析

一、选择消除分析 所谓选择性清除&#xff1a;当一个有利突变发生后&#xff0c;这个突变基因的适合度越高&#xff0c;就越容易被选择固定。当这个基因被快速固定之后&#xff0c;与此基因座连锁的染色体区域&#xff0c;由于搭车效应也被固定下来&#xff0c;大片紧密连锁的染…

【跟小嘉学习区块链】二、Hyperledger Fabric 架构详解

系列文章目录 【跟小嘉学习区块链】一、区块链基础知识与关键技术解析 【跟小嘉学习区块链】一、区块链基础知识与关键技术解析 文章目录 系列文章目录[TOC](文章目录) 前言一、Hyperledger 社区1.1、Hyperledger(面向企业的分布式账本)1.2、Hyperledger社区组织结构 二、Hype…

UDS 28服务

28服务主要是用来控制报文接收和发送。 具体的服务控制格式&#xff1a; controlType 通信控制类型 tips&#xff1a;Bit7 用于是否抑制积极响应。 communication 报文类型 例子

Mysql 数据类型、运算符

数据类型 数据类型的选择不是越大越好&#xff0c;因为我们业务层一般都是在内存上工作的&#xff0c;效率以及速度是比较快的&#xff0c;但是我们的数据库涉及磁盘的IO操作磁盘的IO操作相对来说是要慢很多的&#xff0c;所以我们在定义表结构的时候每一个字段的数据类型还是比…

API网关是如何提升API接口安全管控能力的

API安全的重要性 近几年&#xff0c;越来越多的企业开始数字化转型之路。数字化转型的核心是将企业的服务、资产和能力打包成服务&#xff08;服务的形式通常为API&#xff0c;API又称接口&#xff0c;下文中提到的API和接口意思相同&#xff09;&#xff0c;从而让资源之间形…

计算机组成原理课程设计

操作控制和顺序控制 操作控制就是由各种微命令来构成的顺序控制就是由P测试和后续微地址构成的 这就构成了整个微指令的三个部分 访存指令就是实现对主存中的数据进行访问或存储 一、 操作控制字段是由各种微命令来构成的&#xff0c;这些微命令怎么来设计&#xff1f; 一个萝卜…

全新贝锐蒲公英客户端6.0:如何实现快速部署、高效异地组网?

贝锐蒲公英客户端6.0版本进行了全新的升级&#xff0c;此次升级对原有企业版、个人版和个人管理端进行了深度整合&#xff0c;不同身份的用户现在可以统一登录&#xff0c;大大简化了异地组网的流程&#xff0c;同时提升了效率。那么贝锐蒲公英客户端6.0&#xff0c;做了哪些深…

Cortex-M3/M4之SVC和PendSV异常

一、SVC异常 SVC(系统服务调用&#xff0c;亦简称系统调用)用于产生系统函数的调用请求。例如&#xff0c;操作系统不让用户程序直接访问硬件&#xff0c;而是通过提供一些系统服务函数&#xff0c;用户程序使用 SVC 发出对系统服务函数的呼叫请求&#xff0c;以这种方法调用它…

更新至2022年上市公司ESG评级评分数据合集(含华证、盟浪、wind、彭博、润灵环球、商道融绿、和讯网、富时罗素数据)

更新至2022年ESG评级评分数据合集&#xff08;含华证、盟浪、wind、彭博、润灵环球、商道融绿、和讯网、富时罗素及世界各国ESG数据&#xff09; 1、来源&#xff1a;整理自wind和csmar 2、具体时间&#xff1a; 华证&#xff1a;2009-2022年、盟浪&#xff1a;2018-2022年、…

Python实现猎人猎物优化算法(HPO)优化LightGBM分类模型(LGBMClassifier算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 猎人猎物优化搜索算法(Hunter–prey optimizer, HPO)是由Naruei& Keynia于2022年提出的一种最新的…

【教学类-35-02】学号+姓名+班级(小2班)学号字帖(A4横版2份)

图片展示: 背景需求: 突然接到通知&#xff0c;明天下午临时去带小2班。 小班刚入园的孩子&#xff0c;能给他们提供什么样的可操作的学具呢&#xff1f; 思来想去&#xff0c;还是让生成一份学号字帖&#xff0c;让幼儿熟悉自己的学号&#xff0c;让我也熟悉幼儿的名字和学…

苹果手表 Series 6 拆解

步骤 1 苹果手表 Series 6 拆解 Series 6&#xff08;右&#xff09;与具有一年历史的姐妹&#xff08;左&#xff09;的外部比较仅显示出细微的差异&#xff0c;但这就是拆卸的目的。我们已经知道这些细节&#xff1a; LTPO OLED Retina 显示屏针对常亮功能进行了优化——这次…

Nginx浏览器缓存

浏览器缓存 配置浏览器缓存可以加速静态资源的访问&#xff0c;浏览器对用户访问的资源进⾏存储&#xff0c;下次访问&#xff0c;不⽤再去向服务器寻求资料&#xff0c;直接本地显示&#xff0c;加速访问体验&#xff0c;节省⽹络资源&#xff0c;提⾼效率。Nginx通过 expires…

基于YOLOv8模型的头盔行人检测系统(PyTorch+Pyside6+YOLOv8模型)

摘要&#xff1a;基于YOLOv8模型的头盔行人检测系统可用于日常生活中检测与定位头盔与行人目标&#xff0c;利用深度学习算法可实现图片、视频、摄像头等方式的目标检测&#xff0c;另外本系统还支持图片、视频等格式的结果可视化与结果导出。本系统采用YOLOv8目标检测算法训练…

安全测试之w3af 安装

背景&#xff1a; 学习安全测试时&#xff0c;离不开一些安全扫描工具&#xff0c;在类目众多的工具中&#xff0c;w3af是个绕不开的集成工具。由于没有linux环境&#xff0c;故在windows下利用wsl进行部署。亦可通过其它虚拟机安装亦可。 借鉴&#xff1a;Win10下安装w3af_Da…

kali linux多版本java共存并自由切换 update-alternatives

Kali Linux通过apt和dpkg安装的Java不是一样的。 它们安装的Java版本和管理方式可能不同。 1. **apt 安装 Java&#xff1a;** 当您使用apt包管理器在Kali Linux上安装Java时&#xff0c;您实际上是安装了由Kali Linux官方仓库提供的Java版本。 这个版本通常是经过Kali Linux团…

万字总结HTML超文本标记语言

一、前言:什么是网页? 网站是指在因特网上根据一定的规则,使用 HTML 等制作的用于展示特定内容相关的网页集合。网页是网站中的一“页”,通常是 HTML 格式的文件,它要通过浏览器来阅读。 网页是构成网站的基本元素,它通常由图片、链接、文字、声音、视频等元素组成。通常…