仿制 Google Chrome 的恐龙小游戏
通过仿制 Google Chrome 的恐龙小游戏,我们可以掌握如下知识点:
- 灵活使用视口单位
- 掌握绝对定位
- JavaScript 来操作 CSS 变量
- requestAnimationFrame 函数的使用
- 无缝动画实现
页面结构

实现页面结构
通过上述的页面结构我们可以知道,此游戏中需要有如下的元素:
- 游戏世界
- 小恐龙
- 分数
- 游戏开始的信息提示
- 地面
- 仙人掌
然后构建对应的页面结构
<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);
}
完整代码下载
完整代码下载
相关文章:
仿制 Google Chrome 的恐龙小游戏
通过仿制 Google Chrome 的恐龙小游戏,我们可以掌握如下知识点: 灵活使用视口单位掌握绝对定位JavaScript 来操作 CSS 变量requestAnimationFrame 函数的使用无缝动画实现 页面结构 实现页面结构 通过上述的页面结构我们可以知道,此游戏中…...
Redis面试题(五)
文章目录 前言一、使用过 Redis 做异步队列么,你是怎么用的?有什么缺点?二、 什么是缓存穿透?如何避免?什么是缓存雪崩?何如避免?总结 前言 使用过 Redis 做异步队列么,你是怎么用的…...
组队竞赛(int溢出问题)
目录 一、题目 二、代码 (一)没有注意int溢出 (二)正确代码 1. long long sum0 2. #define int long long 3. 使用现成的sort函数 一、题目 二、代码 (一)没有注意int溢出 #include <iostream&g…...
Swift SwiftUI 隐藏键盘
如果仅支持 iOS 15 及更高版本,则可以通过聚焦和取消聚焦来激活和关闭文本字段的键盘。 在最简单的形式中,这是使用 FocusState 属性包装器和 focusable() 修饰符完成的-第一个存储一个布尔值,用于跟踪第二个当前是否被聚焦。 Code struct C…...
Python与数据分析--Pandas-1
目录 1.Pandas简介 2.Series的创建 1.通过数组列表来创建 2.通过传入标量创建 3.通过字典类型来创建 4.通过numpy来创建 3.Series的索引和应用 1. 通过index和values信息 2. 通过切片方法获取信息 4.DataFrame的创建 1.直接创建 2.矩阵方式创建 3.字典类型创建 5.…...
如何完美通过token获取用户信息(springboot)
1. 什么是Token? 身份验证令牌(Authentication Token):在身份验证过程中,“token” 可以表示一个包含用户身份信息的令牌。 例如 Token(JWT)是一种常见的身份验证令牌,它包含用户的…...
2023 “华为杯” 中国研究生数学建模竞赛(B题)深度剖析|数学建模完整代码+建模过程全解全析
华为杯数学建模B题 当大家面临着复杂的数学建模问题时,你是否曾经感到茫然无措?作为2021年美国大学生数学建模比赛的O奖得主,我为大家提供了一套优秀的解题思路,让你轻松应对各种难题。 让我们来看看研赛的B题呀~! 问…...
文件相关工具类
文章目录 1.MultipartFile文件转File2.读取文件(txt、json)3.下载网络文件4.压缩文件 1.MultipartFile文件转File public File transferToFile(MultipartFile multipartFile) { // 选择用缓冲区来实现这个转换即使用java 创建的临时文件 使用 MultipartFile.transferto()…...
18795-2012 茶叶标准样品制备技术条件
声明 本文是学习GB-T 18795-2012 茶叶标准样品制备技术条件. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本标准规定了各类茶叶(除再加工茶)标准样品的制备、包装、标签、标识、证书和有效期。 本标准适用于各类茶叶(除再加工茶)感官品质…...
C++11互斥锁的使用
是C11标准库中用于多线程同步的库,提供互斥锁(mutex)及其相关函数。 以下是一些基本的使用示例: 1.创建和销毁互斥锁 #include <mutex>std::mutex mtx;2.加锁 std::lock_guard<std::mutex> lock(mtx); // 加锁 // 或者 mtx.lock(); //…...
unity 桌面程序
using System; using System.Collections; using System.Collections.Generic; using System.Runtime.InteropServices; using UnityEngine; public class chuantou : MonoBehaviour { [DllImport(“user32.dll”)] public static extern int MessageBox(IntPtr hwnd,string t…...
echarts统一纵坐标y轴的刻度线,刻度线对齐。
要求: 纵坐标刻度线对齐;刻度间隔为5;去掉千位默认的逗号;刻度最小是0. 效果图: 代码: yAxis: [{type: "value",position: "left",name: "kW",offset: 100,nameTextStyle:…...
一个数据库版本兼容问题
mysql旧的版本号是:5.3.10 本机版本号是:8.0.22 报错:“com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: Could not create” 1.程序里做兼容: <dependency><groupId>mysql</groupId>…...
学习Nano编辑器:入门指南、安装步骤、基本操作和高级功能
文章目录 使用Nano编辑器入门指南引言1.1 关于Nano编辑器1.2 Nano的起源和特点 安装Nano2.1 在Debian/Ubuntu系统上安装Nano2.2 在CentOS/RHEL系统上安装Nano2.3 在其他Linux发行版上安装Nano 启动Nano3.1 命令行启动Nano3.2 打开文件 Nano的基本操作4.1 光标移动和选择文本4.2…...
在北京多有钱能称为富
背景 首先声明,此讨论仅限个人的观点,因为我本身不富嘛,所以想法应该非常局限。 举个栗子 富二代问我朋友,100~1000w之间,推荐一款车? 一开始听到这个问题的时候,有被唬住,觉得预…...
Chrome扩展程序开发随记
在Chrome浏览器向正被浏览的外网网页植入自定义JS脚本 为了实现如标题的目的,需要开发一个Chrome扩展程序。接下来内容是实现简要步骤: 一、新建文件夹,命名为项目名,如“MyPlugin”。 二、进入文件夹内,新建名为“…...
使用命令行快速创建Vite项目
一、构建项目 在终端中使用如下命令行: npm create vite 二、定义项目名称 三、选择项目类型 Vanilla是我们常用的JavaScript,Vue和React是常用前端框架,可以根据自己的需要进行选择 通过上下键进行选择,按下回车进行确认 创建…...
int *a, int **a, int a[], int *a[]的区别
int *a; ---定义一个指向整型变量的指针a int **a; ---定义一个指向整型变量指针的指针a int a[]; ---定义一个整型变量数组a int *a[]; ---定义一个指向整型变量指针的数组a...
leetcode100----双指针
283. 移动零 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。 请注意 ,必须在不复制数组的情况下原地对数组进行操作。 示例 1:输入: nums [0,1,0,3,12] 输出: [1,3,12,0,0] 示例 2:输入: nums …...
ORM基本操作
ORM基本操作 基本操作包括增删改查操作,即(CRUD操作) CRUD是指在做计算处理时的增加(Create)、读取查询(Read)、更新Update)和删除(Delete) ORM CRUD 核心-> 模型类管理器对象 每个继承自 models.Model 的模型类,都会有一个 objects 对象被同样继…...
Python|GIF 解析与构建(5):手搓截屏和帧率控制
目录 Python|GIF 解析与构建(5):手搓截屏和帧率控制 一、引言 二、技术实现:手搓截屏模块 2.1 核心原理 2.2 代码解析:ScreenshotData类 2.2.1 截图函数:capture_screen 三、技术实现&…...
接口测试中缓存处理策略
在接口测试中,缓存处理策略是一个关键环节,直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性,避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明: 一、缓存处理的核…...
云计算——弹性云计算器(ECS)
弹性云服务器:ECS 概述 云计算重构了ICT系统,云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台,包含如下主要概念。 ECS(Elastic Cloud Server):即弹性云服务器,是云计算…...
JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作
一、上下文切换 即使单核CPU也可以进行多线程执行代码,CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短,所以CPU会不断地切换线程执行,从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...
【论文阅读28】-CNN-BiLSTM-Attention-(2024)
本文把滑坡位移序列拆开、筛优质因子,再用 CNN-BiLSTM-Attention 来动态预测每个子序列,最后重构出总位移,预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵(S…...
Element Plus 表单(el-form)中关于正整数输入的校验规则
目录 1 单个正整数输入1.1 模板1.2 校验规则 2 两个正整数输入(联动)2.1 模板2.2 校验规则2.3 CSS 1 单个正整数输入 1.1 模板 <el-formref"formRef":model"formData":rules"formRules"label-width"150px"…...
如何在网页里填写 PDF 表格?
有时候,你可能希望用户能在你的网站上填写 PDF 表单。然而,这件事并不简单,因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件,但原生并不支持编辑或填写它们。更糟的是,如果你想收集表单数据ÿ…...
AI,如何重构理解、匹配与决策?
AI 时代,我们如何理解消费? 作者|王彬 封面|Unplash 人们通过信息理解世界。 曾几何时,PC 与移动互联网重塑了人们的购物路径:信息变得唾手可得,商品决策变得高度依赖内容。 但 AI 时代的来…...
服务器--宝塔命令
一、宝塔面板安装命令 ⚠️ 必须使用 root 用户 或 sudo 权限执行! sudo su - 1. CentOS 系统: yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh2. Ubuntu / Debian 系统…...
人工智能(大型语言模型 LLMs)对不同学科的影响以及由此产生的新学习方式
今天是关于AI如何在教学中增强学生的学习体验,我把重要信息标红了。人文学科的价值被低估了 ⬇️ 转型与必要性 人工智能正在深刻地改变教育,这并非炒作,而是已经发生的巨大变革。教育机构和教育者不能忽视它,试图简单地禁止学生使…...
