lover-fish
  • Introduction
  • 搭建环境
  • 绘制背景
  • 绘制海葵
  • 绘制果实
  • 果实动画
  • 控制果实的数量
  • 增加果实的类型
  • 绘制鱼
  • 让鱼动起来
  • 让鱼跟着鼠标动起来
  • 吃果实之碰撞检测
  • 创建鱼宝宝
  • 给鱼添加动画
  • 加入计分模块
  • 大鱼身体特效
  • 游戏结束逻辑
  • 增加吃果实特效
  • 海葵动画
  • 漂浮物
Powered by GitBook
On this page

绘制背景

下载图片资源

将图片资源放到 asset/img 下面。

在项目里面我们可能用到 requestAnimationFrame ,关于兼容,大家请查看 目前 requestAnimationFrame 兼容性

为了语义化,规范化,大家可以参考以下规范。

写出一份优雅的代码,是每一位工程师的责任。

  • 如何规范 CSS 的命名和书写?(知乎)

  • 前端编码规范

  • web前端规范

  • 前端通用规范

  • 首先修改我们的 index.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>Luvdisc</title>
  <link rel="stylesheet" href="assets/css/main.css" />
</head>
<body>

<!--
  HTML 属性应当按照以下给出的顺序依次排列,确保代码的易读性。
  class
  id 、 name
  data-*
  src、for、 type、 href
  title、alt
  aria-*、 role
-->

  <section class="wrapper">
    <figure class="canvas-container">
      <canvas class="canvas-item" id="one" width="800" height="600"></canvas>
      <canvas class="canvas-item" id="two" width="800" height="600"></canvas>
    </figure>
  </section>
  <script async src="bundle.js"></script>
</body>
</html>
  • 添加 main.css

/*

CSS书写顺序

1.位置属性(position, top, right, z-index, display, float等)

2.大小(width, height, padding, margin)

3.文字系列(font, line-height, letter-spacing, color- text-align等)

4.背景(background, border等)

5.其他(animation, transition等)

*/

.wrapper{
  width: 800px;
  height: 600px;
  padding-top: 10px;
  margin: 0 auto;
}

.canvas-container{
  position: relative;

  width: 800px;
  height: 600px;
  margin: 0;
}

canvas.canvas-item{
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
}

canvas#one{
  z-index: 1;
}

canvas#two{
  z-index: 0;
}

首先我们封装获取一个获取 DOM 和 context 的函数

function getCanvasAndContextById(id: string): [HTMLCanvasElement, CanvasRenderingContext2D] {
  const dom = <HTMLCanvasElement>document.querySelector('#' + id);
  const ctx = dom.getContext('2d');
  return [dom, ctx];
}

为什么会知道是 HTMLCanvasElement ?

把鼠标移动到 document.querySelector 上面,按 F12,跳转到原生定义文件里面,搜索 canvas 就可以搜索得到,因为是 HTMLEelemnt 实例,所以必然有 Element 单词在里面。

而这个 CanvasRenderingContext2D ,直接查看 dom.getContext 的返回值类型即可知道。

这里的返回值我们用了元组的解构。

所以获取我们应该这样解构,并且放在了 window.onload 函数里面,报错 DOM 加载完成之后再执行代码。

window.onload = () => {
  const [cvs_one, ctx_one] = getCanvasAndContextById('one');
  const [cvs_two, ctx_two] = getCanvasAndContextById('two');
}

我们设置的 #one 的 z-index 层级比较高,所以我们会在 one 这一层上面绘制一些 UI、结构之类的逻辑,而 two 则是绘制背景。

  • 接下来我们要用到 requestAnimationFrame 来优化 Web 动画性能

大家可以看一下这一篇文章 requestAnimationFrame简介 。

我们知道游戏的运行,是需要浏览器不停的重新绘制的,所以说一定有一个循环,在不停的重绘。

let lastTime: number = Date.now(), // 记录上一次绘制的时间
    deltaTime: number = 0; // requestAnimationFrame 执行完成所用的时间 = 当前时间 - 上一次绘制的世界

function gameLoop() {
  const now = Date.now()
  deltaTime = now - lastTime;
  lastTime = now;

  console.log(deltaTime);
  requestAnimationFrame(gameLoop);

}

这里有一个 deltaTime 来记录一下每一次requestAnimationFrame的时间

而我们的 DOM 实例和 Context 是需要在其他函数里面使用的,所以说,我们必须拿到外面来,但是又必须等 DOM 完成之后再获取。

所有我们小小的修改一下之前的代码

function getCanvasAndContextById(id: string): [HTMLCanvasElement, CanvasRenderingContext2D] {
  const dom = <HTMLCanvasElement>document.querySelector('#' + id);
  const ctx = dom.getContext('2d');
  return [dom, ctx];
}

let cvs_one: HTMLCanvasElement,
    cvs_two: HTMLCanvasElement,
    ctx_one: CanvasRenderingContext2D,
    ctx_two: CanvasRenderingContext2D;

let lastTime: number = Date.now(), // 记录上一次绘制的时间
    deltaTime: number = 0; // requestAnimationFrame 执行完成所用的时间 = 当前时间 - 上一次绘制的世界


window.onload = () => {
  init();
  gameLoop();

}

function init() {
  [cvs_one, ctx_one] = getCanvasAndContextById('one');
  [cvs_two, ctx_two] = getCanvasAndContextById('two');


}

function gameLoop() {
  const now = Date.now()
  deltaTime = now - lastTime;
  lastTime = now;

  console.log(deltaTime);
  requestAnimationFrame(gameLoop);
}
  • 接下来我们添加绘制背景的函数

因为背景是处于游戏中,所以我们要把它写到游戏循环里面。

const bgPic = new Image();

function init() {
  [cvs_one, ctx_one] = getCanvasAndContextById('one');
  [cvs_two, ctx_two] = getCanvasAndContextById('two');

  bgPic.src = 'assets/img/background.jpg';

  cvs_width = cvs_one.width;
  cvs_height = cvs_one.height;
}

function gameLoop() {
  const now = Date.now()
  deltaTime = now - lastTime;
  lastTime = now;

  console.log(deltaTime);

  drawBackbround()

  requestAnimationFrame(gameLoop);
}

function drawBackbround() {
  ctx_two.drawImage(bgPic, 0, 0, cvs_width, cvs_height)
}

现在你应该可以看到一个背景图片了。

这样的代码看起来非常的乱,现在我们该来想一想如何重构了。

首先我们理清逻辑,初始化 Init -> 游戏循环 ,目前来说就这么2个逻辑,所以,我们把这 2 个函数分成 2 个文件。

分别创建 init.ts 、game-loop.ts 文件

// init.ts
let cvs_one: HTMLCanvasElement,
    cvs_two: HTMLCanvasElement,
    ctx_one: CanvasRenderingContext2D,
    ctx_two: CanvasRenderingContext2D;

let cvs_width: number,
    cvs_height: number;

const bgPic = new Image();

function getCanvasAndContextById(id: string): [HTMLCanvasElement, CanvasRenderingContext2D] {
  const dom = <HTMLCanvasElement>document.querySelector('#' + id);
  const ctx = dom.getContext('2d');
  return [dom, ctx];
}

function init() {
  [cvs_one, ctx_one] = getCanvasAndContextById('one');
  [cvs_two, ctx_two] = getCanvasAndContextById('two');

  bgPic.src = 'assets/img/background.jpg';

  cvs_width = cvs_one.width;
  cvs_height = cvs_one.height;
}

export default init;

export { bgPic, cvs_width , cvs_height, ctx_two };
// game-loop.ts
import { bgPic, cvs_width , cvs_height, ctx_two } from "./init";


let lastTime: number = Date.now(), // 记录上一次绘制的时间
    deltaTime: number = 0; // requestAnimationFrame 执行完成所用的时间 = 当前时间 - 上一次绘制的世界

function gameLoop() {
  const now = Date.now()
  deltaTime = now - lastTime;
  lastTime = now;

  console.log(deltaTime);

  drawBackbround()

  requestAnimationFrame(gameLoop);
}


function drawBackbround() {
  ctx_two.drawImage(bgPic, 0, 0, cvs_width, cvs_height)
}

export default gameLoop;
// main.ts

import gameLoop from "./game-loop";
import init from "./init";


window.onload = () => {
  init();
  gameLoop();
}

重构搞定!

Previous搭建环境Next绘制海葵

Last updated 7 years ago