仿制 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 对象被同样继…...
渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止
<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet: https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...
解决本地部署 SmolVLM2 大语言模型运行 flash-attn 报错
出现的问题 安装 flash-attn 会一直卡在 build 那一步或者运行报错 解决办法 是因为你安装的 flash-attn 版本没有对应上,所以报错,到 https://github.com/Dao-AILab/flash-attention/releases 下载对应版本,cu、torch、cp 的版本一定要对…...
AI书签管理工具开发全记录(十九):嵌入资源处理
1.前言 📝 在上一篇文章中,我们完成了书签的导入导出功能。本篇文章我们研究如何处理嵌入资源,方便后续将资源打包到一个可执行文件中。 2.embed介绍 🎯 Go 1.16 引入了革命性的 embed 包,彻底改变了静态资源管理的…...
【Java学习笔记】BigInteger 和 BigDecimal 类
BigInteger 和 BigDecimal 类 二者共有的常见方法 方法功能add加subtract减multiply乘divide除 注意点:传参类型必须是类对象 一、BigInteger 1. 作用:适合保存比较大的整型数 2. 使用说明 创建BigInteger对象 传入字符串 3. 代码示例 import j…...
sipsak:SIP瑞士军刀!全参数详细教程!Kali Linux教程!
简介 sipsak 是一个面向会话初始协议 (SIP) 应用程序开发人员和管理员的小型命令行工具。它可以用于对 SIP 应用程序和设备进行一些简单的测试。 sipsak 是一款 SIP 压力和诊断实用程序。它通过 sip-uri 向服务器发送 SIP 请求,并检查收到的响应。它以以下模式之一…...
招商蛇口 | 执笔CID,启幕低密生活新境
作为中国城市生长的力量,招商蛇口以“美好生活承载者”为使命,深耕全球111座城市,以央企担当匠造时代理想人居。从深圳湾的开拓基因到西安高新CID的战略落子,招商蛇口始终与城市发展同频共振,以建筑诠释对土地与生活的…...
如何应对敏捷转型中的团队阻力
应对敏捷转型中的团队阻力需要明确沟通敏捷转型目的、提升团队参与感、提供充分的培训与支持、逐步推进敏捷实践、建立清晰的奖励和反馈机制。其中,明确沟通敏捷转型目的尤为关键,团队成员只有清晰理解转型背后的原因和利益,才能降低对变化的…...
离线语音识别方案分析
随着人工智能技术的不断发展,语音识别技术也得到了广泛的应用,从智能家居到车载系统,语音识别正在改变我们与设备的交互方式。尤其是离线语音识别,由于其在没有网络连接的情况下仍然能提供稳定、准确的语音处理能力,广…...
Pydantic + Function Calling的结合
1、Pydantic Pydantic 是一个 Python 库,用于数据验证和设置管理,通过 Python 类型注解强制执行数据类型。它广泛用于 API 开发(如 FastAPI)、配置管理和数据解析,核心功能包括: 数据验证:通过…...
DAY 26 函数专题1
函数定义与参数知识点回顾:1. 函数的定义2. 变量作用域:局部变量和全局变量3. 函数的参数类型:位置参数、默认参数、不定参数4. 传递参数的手段:关键词参数5 题目1:计算圆的面积 任务: 编写一…...
