使用 JavaScript、React、Canvas2D 和 CSS 进行游戏编程 - 第一部分






4.56/5 (4投票s)
如何使用 JavaScript、React、Canvas 和 CSS 创建简单的 2D 游戏
在本系列文章中,我想向您展示如何使用 JavaScript、React、Canvas 和 CSS 创建简单的 2D 游戏。我们将制作一个简单的《太空侵略者》克隆版,您可以在 https://phillikus.github.io/react_invaders/ 上查看最终效果。要查看完整代码,请访问 https://github.com/phillikus/react_invaders。
在本部分中,我们将设置项目,添加一个 TitleScreen 组件和一个处理用户输入的类。
要求
- NodeJS >= 6 (https://docs.npmjs.net.cn/getting-started/installing-node)
create-react-app
(https://github.com/facebookincubator/create-react-app)- HTML、JavaScript 和 CSS 的基础知识
安装
首先,我们需要设置项目。为此,请转到您的Projects文件夹并运行以下命令
create-react-app react_invaders
并用您喜欢的 JavaScript IDE(我喜欢使用 Sublime Text)打开该文件夹。您应该会看到如下的项目结构
现在(或任何您想测试我们进展的时候),我们可以在命令行中运行 npm start
在本地部署我们的应用程序。当您在浏览器中打开命令行中的链接时,您应该会看到一个 React 的欢迎页面。
接下来,我们将删除此欢迎页面,以便开始添加自己的内容。为此,请打开项目中的App.js文件,并将render()
方法中的代码替换为一个空的div
render() {
return (<div> </div>);
}
render()
方法是我们所有渲染逻辑的中心位置。它包含伪 HTML 代码(JSX),该代码将在浏览器中被翻译成真实的 HTML。
我们还可以删除logo.svg文件,并从App.js中删除import logo from './logo.svg';
。在浏览器中重新加载应用程序后,我们现在应该会看到一个空白页面。
绘制游戏画布
我们的整个应用程序将运行在一个 HTML canvas 中。要绘制一个空白的 canvas,我们必须定义其宽度、高度和纵横比。目前,我们将为此定义 3 个常量
const width = 800;
const height = window.innerHeight;
const ratio = window.devicePixelRatio || 1;
我们将把它们放在类定义的正上方。现在,我们可以使用这些常量来初始化我们App
类的状态。为此,我们必须在App
类中添加一个构造函数,并使用这些变量初始化状态
this.state = {
screen: {
width: width,
height: height,
ratio: ratio
}};
我将上述属性封装在一个新的screen
属性中。请记住,我们只能在构造函数中以这种方式初始化 React 组件的状态!
接下来,我们必须调整render
方法以使用这些值绘制一个canvas
render() {
return (
<div>
<canvas ref="canvas"
width={ this.state.screen.width * this.state.screen.ratio }
height={ this.state.screen.height * this.state.screen.ratio } />
</div>
);
}
最后,我们必须在现有的App.css文件中添加一些 CSS 来设置canvas
的颜色并正确对齐它
canvas {
display: block;
background-color: #000000;
margin-left: auto;
margin-right: auto;
}
就是这样!在浏览器中重新加载应用程序,您应该会看到一个具有定义宽度和高度的黑色空白canvas
。它应该位于屏幕的正中央。
添加一个 TitleScreen 组件
现在,是时候开始向我们新创建的canvas
添加一些内容了。为此,我们将添加一个新的 React 组件来显示标题屏幕。为了将我们的 React 组件保存在一个地方,我们首先在/src文件夹中添加一个新的ReactComponents文件夹。然后,我们可以向此文件夹添加一个新的TitleScreen.js文件
import React, { Component } from 'react';
export default class TitleScreen extends React.Component {
render() {
return (
<div>
<span className="centerScreen title">React Invaders</span>
<span className="centerScreen pressSpace">Press Enter to start the game!</span>
</div>
);
}
}
就是这样!我们只需要一个render
方法来返回我们的欢迎消息,以及一些 CSS 来正确格式化它。只需确保您继承自 React.Component。我使用了以下 CSS(在App.css中),请随意尝试
.centerScreen {
text-align: center;
display: block;
position: absolute;
z-index: 1;
width: 100%;
}
.title {
top: 20%;
color: green;
font-size: 80px;
}
.pressSpace {
top: 35%;
font-size: 20px;
color: #ffffff;
}
现在我们可以从我们的App.js文件中导入并使用这个类。为此,我们将首先为我们的新组件添加一个导入语句
import TitleScreen from './ReactComponents/TitleScreen';
最后,我们可以在render
方法中添加这个新组件
render() {
return (
<div>
<TitleScreen />
<canvas ref="canvas"
.....
重新加载页面,我们现在应该看到我们游戏的标题,然后是如何开始玩的说明。由于我们的游戏对任何用户输入都没有响应,按下Enter键将无效。
处理用户输入
现在我们已经有了第一个运行的组件,是时候考虑处理用户输入了。在 JavaScript 中,我们可以使用keyup
和keydown
事件来拦截用户输入。为了将用户输入的逻辑与我们的游戏逻辑分开,我们将向/src文件夹添加一个名为InputManager.js的新类。keyup
和keydown
事件提供了一个简单的整数来标识按下的按键。为了更好地理解这些按键码,我们将首先添加一个常量来为这些按键码赋予更好的名称
const KEY = {
LEFT: 37,
RIGHT: 39,
A: 65,
D: 68,
SPACE: 32,
ENTER: 13
};
您可以在 http://keycode.info/ 上查看按键码。
在我们的InputManager
类的构造函数中,我们将添加一个新的pressedKeys
属性,我们稍后将用它来查看用户按下了哪些键
constructor() {
this.pressedKeys = { left: 0, right: 0, space: 0, enter: 0 };
}
我们将使用左右键来移动玩家,空格键来射击,回车键来在菜单之间导航。
现在,我们将添加两个函数来在打开应用程序时绑定按键事件,并在之后解除绑定。它们将负责将这些事件绑定到一个新的handleKeys
方法,该方法又将负责设置上面提到的pressedKeys
属性
bindKeys() {
window.addEventListener('keyup', this.handleKeys.bind(this, false));
window.addEventListener('keydown', this.handleKeys.bind(this, true));
}
unbindKeys() {
window.removeEventListener('keyup', this.handleKeys);
window.removeEventListener('keydown', this.handleKeys);
}
handleKeys(value, e){
let keys = this.pressedKeys;
switch (e.keyCode) {
case KEY.LEFT:
case KEY.A:
keys.left = value;
break;
case KEY.RIGHT:
case KEY.D:
keys.right = value;
break;
case KEY.SPACE:
keys.space = value;
break;
case KEY.ENTER:
keys.enter = value;
break;
}
this.pressedKeys = keys;
}
对于keyup
事件,我们调用<handlekeys>
并传入false
值;对于keydown
事件,我们传入true
值。例如,当按下A
键时,handleKeys
将被调用,传入value==true
和e.keyCode == KEY.A
,因此keys.left
将被设置为true
;一旦按键释放,将触发keydown
事件,keys.left
将被重新设置为false
。
这样就完成了我们的InputManager
类,我们现在可以将其集成到我们的App.js类中。首先,我们必须为它添加一个import
语句
import InputManager from './InputManager';
然后,我们将在构造函数中初始化这个类的一个新实例,并将其分配给一个新的state
属性
constructor() {
super();
this.state = {
input: new InputManager(),
screen: {
width: width,
height: height,
ratio: ratio
}
};
}
为了调用我们的bindKeys
和unbindKeys
方法,我们将覆盖 React 提供的componentDidMount
和componentWillUnmount
方法。它们将在我们加载和卸载应用程序时被调用
componentDidMount() {
this.state.input.bindKeys();
}
componentWillUnmount() {
this.state.input.unbindKeys();
}
这就够了!如果您在浏览器开发者工具的handleKeys
中设置一个断点,您会看到每当您按下/释放某个键时,它都会被触发。
在本系列的下一部分中,我们将添加一个由玩家控制的ship
类、游戏状态管理、一个“游戏结束”屏幕和一个用于显示游戏控件的叠加层!
感谢您阅读本文 :) 如果您有任何问题、疑难或反馈,请在评论中告知我。