用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,}} entities={{ head: { position: [0,0],size: Constants.CELL_SIZE,updateFrequency: 10,nextMove: 10,xspeed: 0,yspeed: 0,renderer: <Head />,} }} />
我們在 entities 道具中傳遞了一個物件,其關鍵是頭。這些是它定義的屬性。
position
是一組座標,用於放置蛇頭。size
是設定蛇頭大小的值。xspeed
和yspeed
是決定蛇的運動和方向的值,可以是1、0或-1。注意,當 xspeed 被設定為1或-1時,那麼 yspeed 的值必須為0,反之亦然- 最後,
renderer
,負責渲染該元件 updateFrequency
和nextMove
將在後面討論。
在新增完 Head 元件後,我們也來新增其他元件。
// commponets/Food/index.js import React from "react"; import { View } from "react-native"; export default function Food({ position,backgroundColor: "green",borderRadius: 50 }} ></View> ); }
Food 元件與 Head 元件類似,但我們改變了背景顏色和邊框半徑,使其成為一個圓形。
現在建立一個 Tail 元件。這個可能很棘手。
// components/Tail/index.js import React from "react"; import { View } from "react-native"; imphttp://www.cppcns.comort Constants from "../../Constants"; export default function Tail({ elements,position,size }) { const tailList = elements.map((el,idx) => ( <View key={idx} style={{ width: size,left: el[0] * size,top: el[1] * size,}} /> )); 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,},food: { position: [ randomPositions(0,Constants.GRID_SIZE - 1),randomPositions(0,],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,dispatch }) {
const head = entities.head;
程式設計客棧 head.position[0] http://www.cppcns.com+= 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,}} 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" 事WUMKz件,所以當我們收到該事件時,我們顯示 "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,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,]; } }
我們添加了一個 if ,以檢查蛇頭和食物的位置是否相同(這將表明蛇已經 "吃 "了食物)。然後,我們使用 randomPositions 函式更新食物的位置,正如我們在上面的 App.js 。請注意,我們是通過 entities 引數來訪問食物的。
控制蛇
現在讓我們來新增蛇的控制。我們將使用按鈕來控制蛇的移動位置。
要做到這一點,我們需要在畫布下面的螢幕上新增按鈕。
// App.js import React,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,food: { position: [ randomPositions(0,tail: { size: Constants.CELL_SIZE,}); setIsGameRunning(true); }; return ( <View style={styles.canvas}> <GameEngine ref={engine} style={{ width: BoardSize,}} 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,controlContainer: { marginTop: 10,controllerRow: { flexDirection: "row",controlBtn: { backgroundColor: "yellow",width: 100,height: 100,});
除了控制之外,我們還添加了一個按鈕,以便在前一個遊戲結束時開始一個新的遊戲。這個按鈕只在遊戲沒有執行時出現。在點選該按鈕時,我們通過使用遊戲引擎的 swap 函式來重置遊戲,傳入實體的初始物件,並更新遊戲的執行狀態。
現在說說控制。我們已經添加了可觸控物體,當按下這些物體時,就會派發將在遊戲迴圈中處理的事件。
// GameLoop.js .... .... export default function (entities,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],...tail.elements,]; food.position = [ randomPositions(0,]; } }
為了使尾巴跟隨蛇的頭部,我們要更新尾巴的元素。我們通過將頭部的位置新增到元素陣列的開頭,然後刪除尾巴元素陣列上的最後一個元素來實現這一目的。
在這之後,我們寫一個條件,如果蛇咬了自己的身體,我們就分派 "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,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],...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],]; } } } return entities; }
結語
現在你的第一個React Native遊戲已經完成了你可以在自己的裝置上執行這個遊戲來玩。我希望你能學到一些新的東西,也希望你能與你的朋友分享。
謝謝你的閱讀,祝你有個愉快的一天。
The post How to build a simple game in React Native appeared first onLogRocket Blog .
以上就是用React Native構建一個簡單的遊戲的詳細內容,更多關於React Native遊戲的資料請關注我們其它相關文章!