使用 React Native 构建移动游戏概念:想法:开发:发布到 App Store:结论:

2025-06-10

使用 React Native 构建移动游戏

概念:

主意:

发展:

发布到 App Store:

结论:

替代文本

概念:

最简单的游戏往往最有趣。至少对我来说是这样。《Flappy Bird》很受欢迎,它只需要触摸屏幕就能让你的小鸟飞起来。那么,为什么我不能创造下一个《Flappy Bird》呢?作为一名专业的 Web 开发者,JavaScript 和 HTML/CSS 对我来说已经很熟悉了。这使得 React Native 成为我创建移动应用的理想框架。我过去开发过几个移动应用,但我能用这个框架创建一个完整的 2D 游戏吗?我决定一探究竟。

主意:

我不知道如何使用 React Native 开发移动游戏。我浏览了一些示例和文章,发现有人开发了一款方块从屏幕顶部落下的游戏。他们使用了一个名为 React Native Game Engine 的简单库和 Matter JS 来模拟物理效果。这太棒了!然后,游戏就开始有了转机。当时我儿子刚满三岁。他除了喜欢火车和汽车之外,还喜欢“太空”。这主要源于电影《玩具总动员》。我还希望我的游戏能够通过手机的移动来控制。我见过很多孩子在设备上玩游戏,他们经常通过移动手机来控制屏幕上的玩家。于是,我决定采用“摇晃穿梭机”这个游戏。游戏的概念很简单。太空物品会从视图顶部落向底部的穿梭机。用户可以通过倾斜设备来左右移动穿梭机以躲避这些障碍物。游戏中还会有星星和其他物品落下,让穿梭机看起来像是在太空中飞驰!分数越高,移动物品的数量就越多。这是我们第一阶段的前提。

发展:

这对我来说将是一次学习经历。我从未创建过游戏,更不用说使用 React Native 了。幸运的是,React Native 游戏引擎有大量的示例,甚至还有一个其他游戏项目的仓库。我使用 Expo 启动了应用程序。这省去了 React Native 项目的所有繁重工作和配置。项目创建完成后,就该设置 React Native 游戏引擎了。我不会深入讲解这个库的概念。我无法一一详述,而且它们的文档和示例远远超出了我在本文中所能解释的范围。RNGE 的核心是它以设定的间隔“滴答”一次。每次滴答代表屏幕上的一帧。因此,游戏引擎有两个核心组件:系统和实体。系统是每次滴答时都会调用的函数数组。实体是要放置在屏幕上的对象。实体通常包含一个渲染函数,它返回您在屏幕上看到的内容。这可以是任何东西,从图像到像 View 这样的原生元素。

render() {
const { showOverlay, entities, score, appState } = this.state;
return (
<GameEngine
style={styles.container}
ref="engine"
systems={[Physics, Tilt, Trajectory]}
entities={entities}
running={appState === 'active'}
>
<Score score={score} />
<StatusBar hidden />
<GameOver showOverlay={showOverlay} score={score} reloadApp={this.reloadApp} />
</GameEngine>
);
}
view raw game.js hosted with ❤ by GitHub
render() {
const { showOverlay, entities, score, appState } = this.state;
return (
<GameEngine
style={styles.container}
ref="engine"
systems={[Physics, Tilt, Trajectory]}
entities={entities}
running={appState === 'active'}
>
<Score score={score} />
<StatusBar hidden />
<GameOver showOverlay={showOverlay} score={score} reloadApp={this.reloadApp} />
</GameEngine>
);
}
view raw game.js hosted with ❤ by GitHub

上面是我的游戏组件的渲染函数。注意,它返回的内容不多。GameEngine 是从 React Native Game Engine 导入的。我们给它传入了一些 props,主要是我们的系统和实体。

系统:

我们有三个系统。首先是物理系统。这个比较简单。每次 tick 都会根据重力模拟,将时间传递给物质,以更新物体的下落。

const Physics = (entities, { time }) => {
const { engine } = entities.physics;
engine.world.gravity.y = 0.5;
Matter.Engine.update(engine, time.delta);
return entities;
};
view raw physics.js hosted with ❤ by GitHub
const Physics = (entities, { time }) => {
const { engine } = entities.physics;
engine.world.gravity.y = 0.5;
Matter.Engine.update(engine, time.delta);
return entities;
};
view raw physics.js hosted with ❤ by GitHub

接下来是倾斜。这会更新火箭的 x 位置,确保它不会移出屏幕的左侧或右侧,也不会超出视野范围。

const Tilt = state => {
const { rocket } = state;
const xTilt = rocket.body.tilt;
let xPos = rocket.body.position.x;
if (xPos >= width - 25 && xTilt > 0) {
xPos = width - 25;
} else if (xPos <= 25 && xTilt < 0) {
xPos = 25;
} else {
xPos += xTilt * 5;
}
Matter.Body.setPosition(rocket.body, {
x: xPos,
y: height - 200,
});
return state;
};
view raw tilt.js hosted with ❤ by GitHub
const Tilt = state => {
const { rocket } = state;
const xTilt = rocket.body.tilt;
let xPos = rocket.body.position.x;
if (xPos >= width - 25 && xTilt > 0) {
xPos = width - 25;
} else if (xPos <= 25 && xTilt < 0) {
xPos = 25;
} else {
xPos += xTilt * 5;
}
Matter.Body.setPosition(rocket.body, {
x: xPos,
y: height - 200,
});
return state;
};
view raw tilt.js hosted with ❤ by GitHub

最后,轨迹 (trajectory ) 设定了所有空间物体的路径。这会将它们保持在屏幕上或在顶部重新生成它们。

const Trajectory = entities => {
const obstacles = Object.values(entities).filter(
item => item.body && item.body.label === 'obstacle'
);
obstacles.forEach(item => {
if (item.body.position.x > width || item.body.position.x < 0) {
Matter.Body.set(item.body, {
trajectory: randomInt(-5, 5) / 10,
});
Matter.Body.setPosition(item.body, {
x: randomInt(0, width - 30),
y: 0,
});
}
Matter.Body.setPosition(item.body, {
x: item.body.position.x + item.body.trajectory,
y: item.body.position.y,
});
});
return entities;
};
view raw trajectory.js hosted with ❤ by GitHub
const Trajectory = entities => {
const obstacles = Object.values(entities).filter(
item => item.body && item.body.label === 'obstacle'
);
obstacles.forEach(item => {
if (item.body.position.x > width || item.body.position.x < 0) {
Matter.Body.set(item.body, {
trajectory: randomInt(-5, 5) / 10,
});
Matter.Body.setPosition(item.body, {
x: randomInt(0, width - 30),
y: 0,
});
}
Matter.Body.setPosition(item.body, {
x: item.body.position.x + item.body.trajectory,
y: item.body.position.y,
});
});
return entities;
};
view raw trajectory.js hosted with ❤ by GitHub

实体:

实体分为两部分。主体部分由 Matter 创建,渲染器部分是我们自定义的组件,用于返回显示在屏幕上的图像。以下是实体的示例。

getSatellite = () => {
const body = Matter.Bodies.rectangle(randomInt(1, width - 50), randomInt(0, -200), 75, 45, {
frictionAir: 0.05,
label: 'obstacle',
trajectory: randomInt(-5, 5) / 10,
});
const satellite = { body, size: [75, 50], renderer: Satellite };
return { obstacle: satellite, body };
};
view raw satellite.js hosted with ❤ by GitHub
getSatellite = () => {
const body = Matter.Bodies.rectangle(randomInt(1, width - 50), randomInt(0, -200), 75, 45, {
frictionAir: 0.05,
label: 'obstacle',
trajectory: randomInt(-5, 5) / 10,
});
const satellite = { body, size: [75, 50], renderer: Satellite };
return { obstacle: satellite, body };
};
view raw satellite.js hosted with ❤ by GitHub

它的渲染器...

const Satellite = ({ body, size }) => {
const { position } = body;
const sizeWidth = size[0];
const sizeHeight = size[1];
const x = position.x - sizeWidth / 2;
const { y } = position;
return (
<Image
source={satellite}
style={[
styles.satellite,
{
left: x,
top: y,
width: sizeWidth,
height: sizeHeight,
},
]}
/>
);
};
const Satellite = ({ body, size }) => {
const { position } = body;
const sizeWidth = size[0];
const sizeHeight = size[1];
const x = position.x - sizeWidth / 2;
const { y } = position;
return (
<Image
source={satellite}
style={[
styles.satellite,
{
left: x,
top: y,
width: sizeWidth,
height: sizeHeight,
},
]}
/>
);
};

其余渲染功能:

渲染函数中的其他组件作为游戏引擎的子组件传入。主要是得分组件,用于隐藏状态栏,以及当火箭被障碍物击中时弹出的叠加层。当火箭被击中时,我们会向用户显示他们的得分和一个“重启”按钮。重启后,我们会重置组件的状态,并在游戏引擎中将其替换为重新创建的实体。

reloadApp = () => {
const { engine } = this.state.entities.physics;
Matter.Engine.clear(engine);
this.setState(this.initState, () => {
this.refs.engine.swap(newState.entities);
this.incrementScore();
});
};
view raw reload-app.js hosted with ❤ by GitHub
reloadApp = () => {
const { engine } = this.state.entities.physics;
Matter.Engine.clear(engine);
this.setState(this.initState, () => {
this.refs.engine.swap(newState.entities);
this.incrementScore();
});
};
view raw reload-app.js hosted with ❤ by GitHub

发布到 App Store:

在撰写本文时,该应用刚刚发布到 Apple 的 App Store。Expo 使这变得非常简单。他们的文档会引导您完成独立应用的构建以及上传到商店的过程。同样,我不会讲太多细节,因为 expo 的文档解释得更清楚。第一步是构建您的独立应用。这假设您已经拥有 Apple 开发者许可证。构建完成后,您将在终端中获得一个链接来下载捆绑包。现在,您已准备好上传到应用商店进行审核。有多种方法可以做到这一点,我发现最简单的方法是使用 Apple 的 Transporter 应用。上传应用后,您将能够在 App Store Connect 上看到构建版本。这就是等待的开始。他们需要时间来处理新的上传。完成后,您将能够通过 TestFlight 进行测试。我强烈建议这样做。我通过 TestFlight 发现了使用 Expo 时无法发现的错误。现在您需要屏幕截图和应用预览视频。确保它们尽可能高质量且具有吸引力。这是用户在浏览应用时看到的界面。这个过程也相当麻烦。苹果对分辨率的要求非常严格,当你提交的应用出现错误时,通常不会告诉你具体是什么问题。最终,你的应用提交会准备就绪,你的应用就可以开始销售了。点击发布按钮,然后就可以期待你的应用了🚀。注意:你的应用需要等待一段时间才能真正出现在 App Store 中。

结论:

Shaky Shuttle 主页

鏂囩珷鏉ユ簮锛�https://dev.to/ryanvanbelkum/building-a-mobile-game-using-react-native-3320
PREV
设计模式:工厂概述定义实现示例代码 - 创建书籍
NEXT
细粒度思考:SolidJS 为何如此高效?