65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.56/5 (4投票s)

2017 年 12 月 22 日

CPOL

5分钟阅读

viewsIcon

14295

如何使用 JavaScript、React、Canvas 和 CSS 创建简单的 2D 游戏

在本系列文章中,我想向您展示如何使用 JavaScript、React、Canvas 和 CSS 创建简单的 2D 游戏。我们将制作一个简单的《太空侵略者》克隆版,您可以在 https://phillikus.github.io/react_invaders/ 上查看最终效果。要查看完整代码,请访问 https://github.com/phillikus/react_invaders

在本部分中,我们将设置项目,添加一个 TitleScreen 组件和一个处理用户输入的类。

要求

安装

首先,我们需要设置项目。为此,请转到您的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 中,我们可以使用keyupkeydown事件来拦截用户输入。为了将用户输入的逻辑与我们的游戏逻辑分开,我们将向/src文件夹添加一个名为InputManager.js的新类。keyupkeydown事件提供了一个简单的整数来标识按下的按键。为了更好地理解这些按键码,我们将首先添加一个常量来为这些按键码赋予更好的名称

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==truee.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        
      }
    };
  }

为了调用我们的bindKeysunbindKeys方法,我们将覆盖 React 提供的componentDidMountcomponentWillUnmount方法。它们将在我们加载和卸载应用程序时被调用

componentDidMount() {
  this.state.input.bindKeys();  
}

componentWillUnmount() {
  this.state.input.unbindKeys();
}

这就够了!如果您在浏览器开发者工具的handleKeys中设置一个断点,您会看到每当您按下/释放某个键时,它都会被触发。

本系列的下一部分中,我们将添加一个由玩家控制的ship类、游戏状态管理、一个“游戏结束”屏幕和一个用于显示游戏控件的叠加层!

感谢您阅读本文 :) 如果您有任何问题、疑难或反馈,请在评论中告知我。

© . All rights reserved.