当前位置:编程学习 > > 正文

reactnative零基础入门到项目实战(用React Native制作一个简单的游戏引擎)

时间:2022-01-26 01:58:28类别:编程学习

reactnative零基础入门到项目实战

用React Native制作一个简单的游戏引擎

简介

今天我们将学习如何使用React Native制作一个游戏。因为我们使用的是React Native,这个游戏将是跨平台的,这意味着你可以在Android、iOS和网络上玩同一个游戏。然而,今天我们将只关注移动设备。所以我们开始吧。

开始吧

要制作任何游戏,我们需要一个循环,在我们玩的时候更新我们的游戏。这个循环被优化以顺利运行游戏,为此我们将使用 React Native游戏引擎 。

首先让我们用以下命令创建一个新的React Native应用。

  • npx react-native init ReactNativeGame
    
  • 创建项目后,我们需要添加一个依赖项,以便添加游戏引擎。

  • npm i -S react-native-game-engine
    
  • 这个命令将把React Native游戏引擎添加到我们的项目中。

    那么,我们要做一个什么样的游戏呢?为了简单起见,让我们做一个蛇的游戏,它可以吃食物的碎片并增长身长。

    对React Native游戏引擎的简单介绍

    React Native Game Engine是一个轻量级的游戏引擎。它包括一个组件,允许我们将对象的数组添加为实体,这样我们就可以对它们进行操作。为了编写我们的游戏逻辑,我们使用了一个系统道具阵列,它允许我们操纵实体(游戏对象),检测触摸,以及许多其他令人敬畏的细节,帮助我们制作一个简单的、功能性的游戏。

    让我们在React Native中建立一个蛇形游戏

    要制作一个游戏,我们需要一个画布或容器,我们将在其中添加游戏对象。要制作一个画布,我们只需添加一个带有风格的视图组件,像这样。

  • // App.js     
    <View style={styles.canvas}>
    </View>
    
  • 我们可以像这样添加我们的样式。

  • const styles = StyleSheet.create({
      canvas: {
        flex: 1,
        backgroundColor: "#000000",
        alignItems: "center",
        justifyContent: "center",
      }
    });
    
  • 在画布中,我们将使用 GameEngine 组件和一些来自React Native Game Engine的样式。

  • import { GameEngine } from "react-native-game-engine";
    import React, { useRef } from "react";
    import Constants from "./Constants";
    
    
    export default function App() {
      const BoardSize = Constants.GRID_SIZE * Constants.CELL_SIZE;
      const engine = useRef(null);
      return (
        <View style={styles.canvas}>
          <GameEngine
                  ref={engine}
                  style={{
                    width: BoardSize,
                    height: BoardSize,
                    flex: null,
                    backgroundColor: "white",
                  }}
                />
        </View>
    );
    
  • 我们还使用 useRef() React Hook为游戏引擎添加了一个ref,以便日后使用。

    我们还在项目的根部创建了一个 Constants.js 文件来存储我们的常量值。

  • // Constants.js
    import { Dimensions } from "react-native";
    export default {
      MAX_WIDTH: Dimensions.get("screen").width,
      MAX_HEIGHT: Dimensions.get("screen").height,
      GRID_SIZE: 15,
      CELL_SIZE: 20
    };
    
  • 你会注意到我们正在做一个15乘15的网格,我们的蛇将在那里移动。

    这时我们的游戏引擎已经设置好了,以显示蛇和它的食物。我们需要将实体和道具添加到 GameEngine ,但在此之前,我们需要创建一个蛇和食物的组件,在设备上渲染。

    创建游戏实体

    让我们首先制作蛇。蛇分为两部分,头部和身体(或尾巴)。现在我们将制作蛇的头部,我们将在本教程的后面添加蛇的尾巴。

    为了制作蛇的头部,我们将在组件文件夹中制作一个 Head 组件。

    正如你所看到的,我们有三个组件: Head , Food ,和 Tail 。我们将在本教程中逐一查看这些文件的内容。

    在 Head 组件中,我们将创建一个带有一些样式的视图。

  • import React from "react";
    import { View } from "react-native";
    export default function Head({ position, size }) {
      return (
        <View
          style={{
            width: size,
            height: size,
            backgroundColor: "red",
            position: "absolute",
            left: position[0] * size,
            top: position[1] * size,
          }}
        ></View>
      );
    } 
    
  • 我们将传递一些道具来设置头部的大小和位置。

    我们使用 position: "absolute" 属性来轻松移动头部。

    这将呈现一个正方形,我们不打算使用更复杂的东西;一个正方形或长方形的形状代表蛇的身体,一个圆形的形状代表食物。

    现在让我们将这条蛇的头部添加到 GameEngine 。

    要添加任何实体,我们需要在 GameEngine 中的 entities 道具中传递一个对象。

  • //App.js
    import Head from "./components/Head";
    
    
     <GameEngine
            ref={engine}
            style={{
              width: BoardSize,
              height: BoardSize,
              flex: null,
              backgroundColor: "white",
            }}
            entities={{
              head: {
                position: [0, 0],
                size: Constants.CELL_SIZE,
                updateFrequency: 10,
                nextMove: 10,
                xspeed: 0,
                yspeed: 0,
                renderer: <Head />,
              }
            }} 
    />
    
  • 我们在 entities 道具中传递了一个对象,其关键是头。这些是它定义的属性。

    在添加完 Head 组件后,我们也来添加其他组件。

  • // commponets/Food/index.js
    import React from "react";
    import { View } from "react-native";
    export default function Food({ position, size }) {
      return (
        <View
          style={{
            width: size,
            height: size,
            backgroundColor: "green",
            position: "absolute",
            left: position[0] * size,
            top: position[1] * size,
            borderRadius: 50
          }}
        ></View>
      );
    }
    
  • Food 组件与 Head 组件类似,但我们改变了背景颜色和边框半径,使其成为一个圆形。

    现在创建一个 Tail 组件。这个可能很棘手。

  • // components/Tail/index.js
    
    import React from "react";
    import { View } from "react-native";
    import Constants from "../../Constants";
    export default function Tail({ elements, position, size }) {
      const tailList = elements.map((el, idx) => (
        <View
          key={idx}
          style={{
            width: size,
            height: size,
            position: "absolute",
            left: el[0] * size,
            top: el[1] * size,
            backgroundColor: "red",
          }}
        />
      ));
      return (
        <View
          style={{
            width: Constants.GRID_SIZE * size,
            height: Constants.GRID_SIZE * size,
          }}
        >
          {tailList}
        </View>
      );
    }
    
  • 当蛇吃了食物后,我们将在蛇身中添加一个元素,这样我们的蛇就会成长。这些元素将传入 Tail 组件,这将表明它必须变大。

    我们将循环浏览所有的元素来创建整个蛇身,附加上它,然后渲染。

    在制作完所有需要的组件后,让我们把这两个组件作为 GameEngine 。

  • // App.js
    
    import Food from "./components/Food";
    import Tail from "./components/Tail";
    
    
    // App.js
    const randomPositions = (min, max) => {
        return Math.floor(Math.random() * (max - min + 1) + min);
      };
    
    
    // App.js
    
    <GameEngine
            ref={engine}
            style={{
              width: BoardSize,
              height: BoardSize,
              flex: null,
              backgroundColor: "white",
            }}
            entities={{
              head: {
                position: [0, 0],
                size: Constants.CELL_SIZE,
                updateFrequency: 10,
                nextMove: 10,
                xspeed: 0,
                yspeed: 0,
                renderer: <Head />,
              },
              food: {
                position: [
                  randomPositions(0, Constants.GRID_SIZE - 1),
                  randomPositions(0, Constants.GRID_SIZE - 1),
                ],
                size: Constants.CELL_SIZE,
                renderer: <Food />,
              },
              tail: {
                size: Constants.CELL_SIZE,
                elements: [],
                renderer: <Tail />,
              },
            }}
    
          />
    
  • 为了保证食物位置的随机性,我们做了一个带有最小和最大参数的 randomPositions 函数。

    在 tail ,我们在初始状态下添加了一个空数组,所以当蛇吃到食物时,它将在 elements: 空间中存储每个尾巴的长度。

    在这一点上,我们已经成功创建了我们的游戏组件。现在是在游戏循环中添加游戏逻辑的时候了。

    游戏逻辑

    为了使游戏循环, GameEngine 组件有一个叫 systems 的道具,它接受一个数组的函数。

    为了保持一切结构化,我正在创建一个名为 systems 的文件夹,并插入一个名为 GameLoop.js 的文件。

    在这个文件中,我们正在导出一个带有某些参数的函数。

  • // GameLoop.js
    
    export default function (entities, { events, dispatch }) {
      ...
    
      return entities;
    }
    
  • 第一个参数是 entities ,它包含了我们传递给 GameEngine 组件的所有实体,所以我们可以操作它们。另一个参数是一个带有属性的对象,即 events 和 dispatch 。

    移动蛇头

    让我们编写代码,将蛇头向正确的方向移动。

    在 GameLoop.js 函数中,我们将更新头部的位置,因为这个函数在每一帧都会被调用。

  • // GameLoop.js
    export default function (entities, { events, dispatch }) {
      const head = entities.head;
      head.position[0] += head.xspeed;
      head.position[1] += head.yspeed;
    }
    
    
    
  • 我们使用 entities 参数访问头部,在每一帧中我们都要更新蛇头的位置。

    如果你现在玩游戏,什么也不会发生,因为我们把 xspeed 和 yspeed 设置为0。如果你把 xspeed 或 yspeed 设置为1,蛇的头部会移动得很快。

    为了减慢蛇的速度,我们将像这样使用 nextMove 和 updateFrequency 的值。

  • const head = entities.head;
    
    head.nextMove -= 1;
    if (head.nextMove === 0) {
      head.nextMove = head.updateFrequency;
    
      head.position[0] += head.xspeed;
      head.position[1] += head.yspeed;
    }
    
    
    
  • 我们通过在每一帧中减去1来更新 nextMove 的值为0。当值为0时, if 条件被设置为 true , nextMove 值被更新回初始值,从而移动蛇的头部。

    现在,蛇的速度应该比以前慢了。

    "游戏结束!"条件

    在这一点上,我们还没有添加 "游戏结束!"条件。第一个 "游戏结束!"条件是当蛇碰到墙时,游戏停止运行,并向用户显示一条信息,表明游戏已经结束。

    为了添加这个条件,我们使用这段代码。

  • if (head.nextMove === 0) {
      head.nextMove = head.updateFrequency;
      if (
            head.position[0] + head.xspeed < 0 ||
            head.position[0] + head.xspeed >= Constants.GRID_SIZE ||
            head.position[1] + head.yspeed < 0 ||
            head.position[1] + head.yspeed >= Constants.GRID_SIZE
          ) {
            dispatch("game-over");
          } else {
            head.position[0] += head.xspeed;
            head.position[1] += head.yspeed;
        }
    
  • 第二个 if 条件是检查蛇头是否触及墙壁。如果该条件为真,那么我们将使用 dispatch 函数来发送一个 "game-over" 事件。

    通过 else ,我们正在更新蛇的头部位置。

    现在让我们添加 "游戏结束!"的功能。

    每当我们派发一个 "game-over" 事件时,我们将停止游戏,并显示一个警告:"游戏结束!"让我们来实现它。

    为了监听 "game-over" 事件,我们需要将 onEvent 道具传递给 GameEngine 组件。为了停止游戏,我们需要添加一个 running 道具并传入 useState 。

    我们的 GameEngine 应该看起来像这样。

  • // App.js
    import React, { useRef, useState } from "react";
    import GameLoop from "./systems/GameLoop";
    
    ....
    ....
    
    const [isGameRunning, setIsGameRunning] = useState(true);
    
    ....
    ....
    
     <GameEngine
            ref={engine}
            style={{
              width: BoardSize,
              height: BoardSize,
              flex: null,
              backgroundColor: "white",
            }}
            entities={{
              head: {
                position: [0, 0],
                size: Constants.CELL_SIZE,
                updateFrequency: 10,
                nextMove: 10,
                xspeed: 0,
                yspeed: 0,
                renderer: <Head />,
              },
              food: {
                position: [
                  randomPositions(0, Constants.GRID_SIZE - 1),
                  randomPositions(0, Constants.GRID_SIZE - 1),
                ],
                size: Constants.CELL_SIZE,
                renderer: <Food />,
              },
              tail: {
                size: Constants.CELL_SIZE,
                elements: [],
                renderer: <Tail />,
              },
            }}
            systems={[GameLoop]}
            running={isGameRunning}
            onEvent={(e) => {
              switch (e) {
                case "game-over":
                  alert("Game over!");
                  setIsGameRunning(false);
                  return;
              }
            }}
          />
    
  • 在 GameEngine 中,我们已经添加了 systems 道具,并通过我们的 GameLoop 函数传入了一个数组,同时还有一个 running 道具和一个 isGameRunning 状态。最后,我们添加了 onEvent 道具,它接受一个带有事件参数的函数,这样我们就可以监听我们的事件。

    在这种情况下,我们在switch语句中监听 "game-over" 事件,所以当我们收到该事件时,我们显示 "Game over!" 警报,并将 isGameRunning 状态设置为 false ,以停止游戏。

    食用食物

    我们已经写好了 "游戏结束!"的逻辑,现在让我们来写一下让蛇吃食物的逻辑。

    当蛇吃了食物后,食物的位置应该随机变化。

    打开 GameLoop.js ,写下以下代码。

  • // GameLoop.js
    
    const randomPositions = (min, max) => {
      return Math.floor(Math.random() * (max - min + 1) + min);
    };
    
    export default function (entities, { events, dispatch }) {
      const head = entities.head;
      const food = entities.food;
    
      ....
      ....
      ....
      if (
            head.position[0] + head.xspeed < 0 ||
            head.position[0] + head.xspeed >= Constants.GRID_SIZE ||
            head.position[1] + head.yspeed < 0 ||
            head.position[1] + head.yspeed >= Constants.GRID_SIZE
          ) {
            dispatch("game-over");
          } else {
    
         head.position[0] += head.xspeed;
         head.position[1] += head.yspeed;
    
         if (
              head.position[0] == food.position[0] &&
              head.position[1] == food.position[1]
            ) {
    
              food.position = [
                randomPositions(0, Constants.GRID_SIZE - 1),
                randomPositions(0, Constants.GRID_SIZE - 1),
              ];
            }
      }
    
    
    
  • 我们添加了一个 if ,以检查蛇头和食物的位置是否相同(这将表明蛇已经 "吃 "了食物)。然后,我们使用 randomPositions 函数更新食物的位置,正如我们在上面的 App.js 。请注意,我们是通过 entities 参数来访问食物的。

    控制蛇

    现在让我们来添加蛇的控制。我们将使用按钮来控制蛇的移动位置。

    要做到这一点,我们需要在画布下面的屏幕上添加按钮。

  • // App.js
    
    import React, { useRef, useState } from "react";
    import { StyleSheet, Text, View } from "react-native";
    import { GameEngine } from "react-native-game-engine";
    import { TouchableOpacity } from "react-native-gesture-handler";
    import Food from "./components/Food";
    import Head from "./components/Head";
    import Tail from "./components/Tail";
    import Constants from "./Constants";
    import GameLoop from "./systems/GameLoop";
    export default function App() {
      const BoardSize = Constants.GRID_SIZE * Constants.CELL_SIZE;
      const engine = useRef(null);
      const [isGameRunning, setIsGameRunning] = useState(true);
      const randomPositions = (min, max) => {
        return Math.floor(Math.random() * (max - min + 1) + min);
      };
      const resetGame = () => {
        engine.current.swap({
          head: {
            position: [0, 0],
            size: Constants.CELL_SIZE,
            updateFrequency: 10,
            nextMove: 10,
            xspeed: 0,
            yspeed: 0,
            renderer: <Head />,
          },
          food: {
            position: [
              randomPositions(0, Constants.GRID_SIZE - 1),
              randomPositions(0, Constants.GRID_SIZE - 1),
            ],
            size: Constants.CELL_SIZE,
            updateFrequency: 10,
            nextMove: 10,
            xspeed: 0,
            yspeed: 0,
            renderer: <Food />,
          },
          tail: {
            size: Constants.CELL_SIZE,
            elements: [],
            renderer: <Tail />,
          },
        });
        setIsGameRunning(true);
      };
      return (
        <View style={styles.canvas}>
          <GameEngine
            ref={engine}
            style={{
              width: BoardSize,
              height: BoardSize,
              flex: null,
              backgroundColor: "white",
            }}
            entities={{
              head: {
                position: [0, 0],
                size: Constants.CELL_SIZE,
                updateFrequency: 10,
                nextMove: 10,
                xspeed: 0,
                yspeed: 0,
                renderer: <Head />,
              },
              food: {
                position: [
                  randomPositions(0, Constants.GRID_SIZE - 1),
                  randomPositions(0, Constants.GRID_SIZE - 1),
                ],
                size: Constants.CELL_SIZE,
                renderer: <Food />,
              },
              tail: {
                size: Constants.CELL_SIZE,
                elements: [],
                renderer: <Tail />,
              },
            }}
            systems={[GameLoop]}
            running={isGameRunning}
            onEvent={(e) => {
              switch (e) {
                case "game-over":
                  alert("Game over!");
                  setIsGameRunning(false);
                  return;
              }
            }}
          />
          <View style={styles.controlContainer}>
            <View style={styles.controllerRow}>
              <TouchableOpacity onPress={() => engine.current.dispatch("move-up")}>
                <View style={styles.controlBtn} />
              </TouchableOpacity>
            </View>
            <View style={styles.controllerRow}>
              <TouchableOpacity
                onPress={() => engine.current.dispatch("move-left")}
              >
                <View style={styles.controlBtn} />
              </TouchableOpacity>
              <View style={[styles.controlBtn, { backgroundColor: null }]} />
              <TouchableOpacity
                onPress={() => engine.current.dispatch("move-right")}
              >
                <View style={styles.controlBtn} />
              </TouchableOpacity>
            </View>
            <View style={styles.controllerRow}>
              <TouchableOpacity
                onPress={() => engine.current.dispatch("move-down")}
              >
                <View style={styles.controlBtn} />
              </TouchableOpacity>
            </View>
          </View>
          {!isGameRunning && (
            <TouchableOpacity onPress={resetGame}>
              <Text
                style={{
                  color: "white",
                  marginTop: 15,
                  fontSize: 22,
                  padding: 10,
                  backgroundColor: "grey",
                  borderRadius: 10
                }}
              >
                Start New Game
              </Text>
            </TouchableOpacity>
          )}
        </View>
      );
    }
    const styles = StyleSheet.create({
      canvas: {
        flex: 1,
        backgroundColor: "#000000",
        alignItems: "center",
        justifyContent: "center",
      },
      controlContainer: {
        marginTop: 10,
      },
      controllerRow: {
        flexDirection: "row",
        justifyContent: "center",
        alignItems: "center",
      },
      controlBtn: {
        backgroundColor: "yellow",
        width: 100,
        height: 100,
      },
    });
    
    
    
  • 除了控制之外,我们还添加了一个按钮,以便在前一个游戏结束时开始一个新的游戏。这个按钮只在游戏没有运行时出现。在点击该按钮时,我们通过使用游戏引擎的 swap 函数来重置游戏,传入实体的初始对象,并更新游戏的运行状态。

    现在说说控制。我们已经添加了可触摸物体,当按下这些物体时,就会派发将在游戏循环中处理的事件。

  • // GameLoop.js
    ....
    ....
     export default function (entities, { events, dispatch }) {
        const head = entities.head;
        const food = entities.food;
    
      if (events.length) {
        events.forEach((e) => {
          switch (e) {
            case "move-up":
              if (head.yspeed === 1) return;
              head.yspeed = -1;
              head.xspeed = 0;
              return;
            case "move-right":
              if (head.xspeed === -1) return;
              head.xspeed = 1;
              head.yspeed = 0;
              return;
            case "move-down":
              if (head.yspeed === -1) return;
              head.yspeed = 1;
              head.xspeed = 0;
              return;
            case "move-left":
              if (head.xspeed === 1) return;
              head.xspeed = -1;
              head.yspeed = 0;
              return;
          }
        });
      }
    
    ....
    ....
    });
    
    
    
  • 在上面的代码中,我们添加了一个 switch 语句来识别事件并更新蛇的方向。

    还在听我说吗?很好!唯一剩下的就是尾巴了。

    尾巴功能

    当蛇吃了食物后,我们希望它的尾巴能长出来。我们还想在蛇咬到自己的尾巴或身体时发出一个 "游戏结束!"的事件。

    让我们来添加尾巴逻辑。

  • // GameLoop.js
    
    const tail = entities.tail;
    
    ....
    ....
    
    ....
    
        else {
          tail.elements = [[head.position[0], head.position[1]], ...tail.elements];
          tail.elements.pop();
    
          head.position[0] += head.xspeed;
          head.position[1] += head.yspeed;
    
          tail.elements.forEach((el, idx) => {
            if (
              head.position[0] === el[0] &&
              head.position[1] === el[1] 
            )
              dispatch("game-over");
          });
          if (
            head.position[0] == food.position[0] &&
            head.position[1] == food.position[1]
          ) {
            tail.elements = [
              [head.position[0], head.position[1]],
              ...tail.elements,
            ];
    
            food.position = [
              randomPositions(0, Constants.GRID_SIZE - 1),
              randomPositions(0, Constants.GRID_SIZE - 1),
            ];
          }
        }
    
    
    
  • 为了使尾巴跟随蛇的头部,我们要更新尾巴的元素。我们通过将头部的位置添加到元素数组的开头,然后删除尾巴元素数组上的最后一个元素来实现这一目的。

    在这之后,我们写一个条件,如果蛇咬了自己的身体,我们就分派 "game-over" 事件。

    最后,每当蛇吃了食物,我们就用蛇头的当前位置来追加蛇尾的元素,以增加蛇尾的长度。

    下面是 GameLoop.js 的完整代码。

  • // GameLoop.js
    
    import Constants from "../Constants";
    const randomPositions = (min, max) => {
      return Math.floor(Math.random() * (max - min + 1) + min);
    };
      export default function (entities, { events, dispatch }) {
        const head = entities.head;
        const food = entities.food;
        const tail = entities.tail;
      if (events.length) {
        events.forEach((e) => {
          switch (e) {
            case "move-up":
              if (head.yspeed === 1) return;
              head.yspeed = -1;
              head.xspeed = 0;
              return;
            case "move-right":
              if (head.xspeed === -1) return;
              head.xspeed = 1;
              head.yspeed = 0;
              // ToastAndroid.show("move right", ToastAndroid.SHORT);
              return;
            case "move-down":
              if (head.yspeed === -1) return;
              // ToastAndroid.show("move down", ToastAndroid.SHORT);
              head.yspeed = 1;
              head.xspeed = 0;
              return;
            case "move-left":
              if (head.xspeed === 1) return;
              head.xspeed = -1;
              head.yspeed = 0;
              // ToastAndroid.show("move left", ToastAndroid.SHORT);
              return;
          }
        });
      }
      head.nextMove -= 1;
      if (head.nextMove === 0) {
        head.nextMove = head.updateFrequency;
        if (
          head.position[0] + head.xspeed < 0 ||
          head.position[0] + head.xspeed >= Constants.GRID_SIZE ||
          head.position[1] + head.yspeed < 0 ||
          head.position[1] + head.yspeed >= Constants.GRID_SIZE
        ) {
          dispatch("game-over");
        } else {
          tail.elements = [[head.position[0], head.position[1]], ...tail.elements];
          tail.elements.pop();
          head.position[0] += head.xspeed;
          head.position[1] += head.yspeed;
          tail.elements.forEach((el, idx) => {
            console.log({ el, idx });
            if (
              head.position[0] === el[0] &&
              head.position[1] === el[1] 
            )
              dispatch("game-over");
          });
          if (
            head.position[0] == food.position[0] &&
            head.position[1] == food.position[1]
          ) {
            tail.elements = [
              [head.position[0], head.position[1]],
              ...tail.elements,
            ];
    
            food.position = [
              randomPositions(0, Constants.GRID_SIZE - 1),
              randomPositions(0, Constants.GRID_SIZE - 1),
            ];
          }
        }
      }
      return entities;
    }
    
    
    
  • 结语

    现在你的第一个React Native游戏已经完成了你可以在自己的设备上运行这个游戏来玩。我希望你能学到一些新的东西,也希望你能与你的朋友分享。

    谢谢你的阅读,祝你有个愉快的一天。

    The post How to build a simple game in React Native appeared first onLogRocket Blog .

    以上就是用React Native构建一个简单的游戏的详细内容,更多关于React Native游戏的资料请关注开心学习网其它相关文章!

    标签:
    上一篇下一篇

    猜您喜欢

    热门推荐