前言
本文内容包含以下几个部分:
- 游戏引擎的运行逻辑
- 创建 HTML 页面:包含一个居中显示的
canvas
元素 canvas
坐标系统:包括逻辑宽高与屏幕显示宽高的区别- 监听窗口的
resize
事件:调整canvas
的逻辑宽高 canvas
的两种上下文:2D 和 WebGL
代码托管在 Github
0. 游戏引擎的运行逻辑
先从动画片说起。
早期的动画片制作,先绘制出一张张图片(称为帧
),每一帧略有差别。然后,快速播放这些帧(例如一秒钟播放 24 帧),就形成了动画效果。
游戏引擎的运行逻辑与此类似,只是游戏的每一帧不是事先画好的图片,而是需要实时计算。另外,游戏需要达到 60 帧/秒
,才能流畅运行。
每一帧,引擎需要完成以下工作:
- 逻辑层:负责更新游戏状态,例如角色位置、动作等,然后生成一系列渲染指令
- 渲染层:负责绘制游戏画面,根据渲染指令,调用图形库 API 绘制到屏幕上
其中,第二步渲染层
是基于图形库的,例如 Windows 的 DirectX、Mac 的 Metal、跨平台的 OpenGL、移动平台的的 OpenGL ES、H5 的 WebGL 等。而逻辑层
可以独立于渲染层
,只需要针对不同的图形库,生成不同的渲染指令即可。
接下来,我们为游戏引擎创建一个基础的运行环境。这个环境是一个 HTML 页面,包含一个 canvas
元素、一段 CSS 代码和一段 JavaScript 代码。
1. 创建 HTML 页面
首先,我们创建一个 HTML 页面,其中包含一个 canvas
元素。这个是 Web 前端开发基础,各位掘金大佬比我熟悉,我就不多说了。
方便起见,将 CSS 和 JavaScript 代码都放在 HTML 文件中。CSS 代码因为很少,会一直放在 HTML 文件中。JavaScript 代码后续会移到单独的文件中,通过 script
标签引入。
该代码做了两件事:
- 创建一个居中显示的深蓝色背景的
canvas
- 输出一句
Hello NovaEngine!
HTML 文件:index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lesson_01</title>
<style>
html, body {
margin: 0;
padding: 0;
overflow: hidden;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
canvas {
display: block;
width: 50vw; /* CSS 宽度为屏幕宽度的一半 */
height: 50vh; /* CSS 高度为屏幕高度的一半 */
background-color: darkblue;
}
</style>
</head>
<body>
<canvas id="main-canvas"></canvas>
<script type="text/javascript">
console.log("Hello NovaEngine!");
</script>
</body>
</html>
2. Canvas 坐标系统
canvas
是 H5 游戏引擎渲染的核心,在开始设计一款游戏引擎之前,先熟悉下 canvas 的坐标系统。因为 canvas
是 HTML 元素,所以它的坐标系统与 HTML 类似,以左上角为原点,X 轴向右为正,Y 轴向下为正。
这与其他的游戏引擎略有差别,一般游戏引擎的坐标系统以左下角为原点,X 轴向右为正,Y 轴向上为正。这个影响不大,涉及到坐标计算的数学原理是相同的。
2.1 坐标原点与轴方向
- 坐标原点位于
canvas
的左上角(0, 0)
- X 轴从左到右,值逐渐增大
- Y 轴从上到下,值逐渐增大
2.2 屏幕显示宽高与逻辑宽高
canvas
的两种宽高:屏幕显示宽高和逻辑宽高。
- 屏幕显示宽高
- 在 Web 开发中使用,通过 CSS 控制
canvas
在网页中的显示大小 - 在上面代码中,
canvas
的屏幕显示宽高为50vw
和50vh
,即父元素的一半
- 在 Web 开发中使用,通过 CSS 控制
- 逻辑宽高
- 在
canvas
内部绘制时使用,通过canvas.width
和canvas.height
设置 - 在坐标
(canvas.width, 0)
绘制一张图片,那么这张图片就位于canvas
的右上角 - 在坐标
(canvas.width, canvas.height)
绘制一张图片,那么这张图片就位于canvas
的右下角
- 在
通常,我们要保证逻辑宽高比与屏幕显示宽高比一致,否则会造成图片拉伸变形。简单情况,就是设置逻辑宽高与屏幕显示宽高相同。
/************************************************************
* 设置 canvas 的逻辑宽高
************************************************************/
const canvas = document.getElementById('main-canvas');
const canvasRect = canvas.getBoundingClientRect();
canvas.width = canvasRect.width;
canvas.height = canvasRect.height;
3. 监听窗口的 resize
事件
与手机游戏不同,H5 游戏的窗口大小经常发生变化,因此我们需要监听窗口的 resize
事件,并重新设置 canvas
的逻辑宽高。
/************************************************************
* 窗口大小变化
************************************************************/
window.addEventListener('resize', () => {
/************************************************************
* 设置 canvas 的逻辑宽高
************************************************************/
const canvas = document.getElementById('main-canvas');
const canvasRect = canvas.getBoundingClientRect();
canvas.width = canvasRect.width;
canvas.height = canvasRect.height;
})
4. canvas
的两种上下文
在图形渲染层,canvas
的两种上下文:2D
和 WebGL
。
2D
上下文是 canvas
最常用的上下文,用于绘制 2D 图形。使用 CPU
进行计算,性能较低,但可以满足大部分需求。
WebGL
上下文是 canvas
的另一种上下文,基于 OpenGL ES 2.0 的标准。使用 GPU
进行计算,性能高,功能强大,适合绘制 3D 图形或高性能 2D 图形。
特性 | 2D 上下文 | WebGL 上下文 |
---|---|---|
方式 | canvas.getContext("2d") | canvas.getContext("webgl") |
用途 | 2D 图形和简单动画绘制 | 3D 图形和高性能渲染 |
性能 | 使用 CPU 绘制,性能较低 | 使用 GPU 硬件加速,性能优越 |
易用性 | 简单易用,适合初学者 | 复杂,需要学习着色器和渲染管线 |
编程模型 | 基于 Canvas API | 基于 OpenGL 的图形 API |
显然,WebGL
上下文更适合游戏引擎。但 WebGL
基于 OpenGL,学习曲线较高,所以在项目前期为了避免陷入图形学的细节,我选择使用 2D
上下文。
等引擎主体功能(即逻辑层)基本完成,再将渲染层切换到 WebGL
上下文。
下面,执行代码在 canvas
上绘制一个浅蓝色的背景,并绘制一个红色的矩形。
/************************************************************
* 绘制内容
************************************************************/
const context = canvas.getContext('2d');
context.fillStyle = 'lightblue';
context.fillRect(0, 0, canvas.width, canvas.height);
context.fillStyle = 'red';
context.fillRect(0, 0, canvas.width / 2, canvas.height / 2);
5. 总结
本篇文章先创建了一个基础的 HTML 页面,并绘制了一个浅蓝色的背景。接着介绍了 canvas
的坐标系统,最后介绍了两种绘制上下文,并绘制了一个浅蓝色的背景。
通过本篇文章,我们完成了以下内容:
- 介绍游戏引擎的运行逻辑
- 创建了一个基础的 HTML 页面和 CSS 文件。
- 设置了
canvas
的逻辑宽高与显示宽高,并理解了它们的区别。 - 学习了如何适配高 DPI 屏幕和绘制简单内容。
下一篇文章,我们将实现游戏逻辑层的帧循环。