在这个React-Native声音和动画教程中,你将学习如何为你的移动应用添加动画和声音效果的技巧。我们还将讨论诸如用React-Native AsyncStorage持久化数据的话题。
为了展示你如何做这些事情,我们将使用我们在本教程系列的前4集中一直在构建的移动游戏。
- 第一部分:开始使用React Native--介绍、关键概念和设置我们的开发者环境
- 第二部分:建立我们的主屏幕--分割index.js和style.js,创建应用程序的标题,等等。
- 第三部分:创建主要的游戏逻辑和网格--创建多个屏幕,用道具类型进行类型检查,生成我们的柔性网格
- 第四部分:底栏和负责任的布局--同时,使我们的游戏可以暂停,并添加一种输的方式
- 第五部分:你正在读它!
- 第六部分:用Expo进行React-Native测试,用Jest进行单元测试
快速回顾一下。在我们的React-Native教程系列的前几集里,我们建立了我们的React-Native游戏的核心:你终于可以收集积分,看到它们,甚至输掉。
现在,让我们用音乐、反应式原生动画和声音效果来为我们的游戏增添乐趣,然后通过保存高分来结束吧

为我们的React-Native游戏添加声音
你可能已经注意到了,我们在assets里有一个/music 和/sfx 目录,但我们直到现在才完全接触到它们。它们不是我的,所以让我们把功劳归于创造者:声音效果可以在这里找到,而我们将使用的音乐是由Komiku制作。
我们将使用世博会的内置音频API来处理音乐。我们将首先在Home/index.js ,添加主菜单主题。
首先,从ExpoKit中导入音频API。
import { Audio } from 'expo';
然后导入音乐,在componentWillMount() 中开始播放。
async componentWillMount() {
this.backgroundMusic = new Audio.Sound();
try {
await this.backgroundMusic.loadAsync(
require("../../assets/music/Komiku_Mushrooms.mp3")
);
await this.backgroundMusic.setIsLoopingAsync(true);
await this.backgroundMusic.playAsync();
// Your sound is playing!
} catch (error) {
// An error occurred!
}
这将加载音乐,将其设置为一个循环,并开始异步播放。
如果发生错误,你可以在catch 部分进行处理--也许是通知用户,console.log() 或调用你的崩溃分析工具。你可以在相关的Expo文档中阅读更多关于音频API如何在后台工作的信息。
在onPlayPress ,只需在导航前添加一行。
this.backgroundMusic.stopAsync();
如果你在路由到另一个屏幕时不停止音乐,音乐也会在下一个屏幕继续播放。
说到其他屏幕,让我们也为游戏屏幕添加一些背景音乐,步骤相同,但文件../../assets/music/Komiku_BattleOfPogs.mp3 。
用SFX调剂事情
除了音乐之外,音效也是使游戏变得有趣的重要部分。我们将在主菜单上设置一个音效(点击按钮),在游戏屏幕上设置六个音效(点击按钮,点击瓷砖--正确/错误,暂停/退出,输)。
让我们从主菜单的SFX开始,从那里开始,你将能够自己把其余的添加到游戏屏幕上(我希望)。
我们只需要几行代码来定义一个buttonFX对象,它是Audio.Sound() 的一个实例,并在同一个try-catch块中加载声音文件作为背景音乐。
async componentWillMount() {
this.backgroundMusic = new Audio.Sound();
this.buttonFX = new Audio.Sound();
try {
await this.backgroundMusic.loadAsync(
require("../../assets/music/Komiku_Mushrooms.mp3")
);
await this.buttonFX.loadAsync(
require("../../assets/sfx/button.wav")
);
...
你只需要一行代码来播放声音效果。在onPlayPress 事件处理程序的顶部,添加以下内容。
onPlayPress = () => {
this.buttonFX.replayAsync();
...
注意我是如何使用replayAsync ,而不是playAsync --这是因为我们可能会多次使用这个声音效果,如果你使用playAsync ,并多次运行它,它将只播放第一次的声音。它以后会派上用场,对继续游戏画面也很有用。
这很容易,因为一、二、三!现在,自己做游戏屏幕上的六个声音效果。
- 点击按钮
../../assets/sfx/button.wav- 按下退出按钮时播放
- 敲击瓷砖--正确
../../assets/sfx/tile_tap.wav- 在
onTilePress/good tile块中播放
- 敲击瓷砖--错误
../../assets/sfx/tile_wrong.wav- 在
onTilePress/wrong tile块中播放。
- 暂停 - 在
../../assets/sfx/pause_in.wav- 在
onBottomBarPress/case "INGAME"块中播放。
- 暂停 - 退出
../../assets/sfx/pause_out.wav- 在
onBottomBarPress/case "PAUSED"块中播放
- 丢失
../../assets/sfx/lose.wav- 在该区间的
if (this.state.timeLeft <= 0)块中播放。 - 也可以通过以下方式停止背景音乐的播放
this.backgroundMusic.stopAsync(); - 不要忘记在再次启动游戏时开始播放背景音乐。你可以通过在
onBottomBarPress/case "LOST"块中添加this.backgroundMusic.replayAsync();来做到这一点。
我们的游戏已经很有趣了,但它仍然缺乏当我们触摸错误的瓷砖时的摇晃动画--因此我们没有得到任何即时的明显反馈。
React-Native动画的入门(有例子)。
动画是一个庞大的话题,因此我们在这篇文章中只能涵盖冰山一角。然而,苹果公司有一个非常好的WWDC视频,关于用动画进行设计,而《人机界面指南》也是一个很好的资源。
我们可以在我们的应用程序中使用大量的动画(例如,当用户点击按钮时,动画显示按钮的大小),但我们在本教程中只涉及一个:当玩家触摸到错误的瓷砖时,网格的晃动。
这个React Native动画例子将有几个好处:它是某种惩罚(需要一些时间来完成),正如我已经提到的,当按下错误的瓷砖时,它是即时的反馈,而且它也看起来很酷。
现在有几个针对React-Native的动画框架,比如react-native-animatable,但我们现在将使用内置的Animated API。如果你对它还不熟悉,一定要看看文档。
给我们的游戏添加React-Native动画
首先,让我们在状态中初始化一个动画值,我们以后可以在网格容器的样式中使用。
state = {
...
shakeAnimation: new Animated.Value(0)
};
对于包含网格生成器的<View> (其中有大量的三元运算符),只需将<View> 改为<Animated.View> 。(别忘了也要改变关闭标签!)然后在内联风格中,添加left: shakeAnimation ,使其看起来像这样。
<Animated.View
style={{
height: height / 2.5,
width: height / 2.5,
flexDirection: "row",
left: shakeAnimation
}
>
{gameState === "INGAME" ?
...
现在让我们保存并重新加载游戏。在玩的时候,你不应该注意到任何区别。如果你注意到了,你就做错了--确保你完全按照每一个步骤进行。
现在,进入onTilePress() 处理程序,在// wrong tile 部分,你可以开始为网格制作动画。在文档中,你会看到在React Native中开始制作动画的基本推荐函数是Animated.timing() 。
你可以通过这个方法将一个值变成另一个值的动画,然而,要摇动一些东西,你将需要多个相连的动画,在一个序列中互相播放。例如,把它从0修改到50,然后是-50,再回到0,会产生类似摇晃的效果。
如果你再看一下文档,你会发现Animated.sequence([]) ,它正是这样做的:它在每个动画之后播放一个序列。你可以在一个数组中传入无穷无尽的动画(或Animated.timing()s),当你在这个序列上运行.play() ,动画就会开始执行。
你也可以用Easing 来缓解动画。你可以使用back,bounce,ease 和elastic - 要探索它们,请务必查看文档。然而,我们现在还不需要它们,因为它真的会扼杀现在的性能。
我们的序列将看起来像这样。
Animated.sequence([
Animated.timing(this.state.shakeAnimation, {
toValue: 50,
duration: 100
}),
Animated.timing(this.state.shakeAnimation, {
toValue: -50,
duration: 100
}),
Animated.timing(this.state.shakeAnimation, {
toValue: 50,
duration: 100
}),
Animated.timing(this.state.shakeAnimation, {
toValue: -50,
duration: 100
}),
Animated.timing(this.state.shakeAnimation, {
toValue: 0,
duration: 100
})
]).start();
这将把状态中的shakeAnimation 改为50、-50、50、-50,然后是0。因此,我们将摇动网格,然后重置到其原始位置。如果你保存文件,重新加载应用程序,并点击错误的瓷砖,你会听到声音效果的播放,并看到网格的晃动。
将动画从JavaScript线程转移到UI线程
动画是每一个流体UI的重要组成部分,在渲染动画时考虑到性能效率是每个开发者都需要努力的事情。
默认情况下,动画API在JavaScript线程上运行,会阻塞其他渲染和代码执行。这也意味着,如果它被阻塞,动画将跳帧。正因为如此,我们希望将动画驱动从JS线程转移到UI线程--好消息是,在本地驱动的帮助下,只需一行代码就可以完成。
要了解更多关于Animation API如何在后台工作,什么是 "动画驱动",以及为什么使用它们会更有效,请务必查看这篇博文,但让我们继续前进吧。
为了在我们的应用程序中使用本地驱动程序,我们只需要在我们的动画中添加一个属性:useNativeDriver: true 。
之前。
Animated.timing(this.state.shakeAnimation, {
toValue: 0,
duration: 100
})
之后。
Animated.timing(this.state.shakeAnimation, {
toValue: 0,
duration: 100,
useNativeDriver: true
})
嘣,你就完成了,做得很好!
现在,让我们来完成保存高分的工作。
保存数据 - 储存高分
在React-Native中,你会得到一个简单的、未加密的、异步的、持久的键值存储系统:AsyncStorage。
建议不要在面向生产时使用AsyncStorage,但对于像这样的演示项目,我们可以轻松地使用它。如果你的目标是生产,请务必查看其他解决方案,如Realm或SQLite。
首先,我们应该在utils 下创建一个新的文件,名为storage.js 或类似的文件。我们将用AsyncStorage API处理我们需要做的两个操作--存储和检索数据。
该API有两个内置的方法:AsyncStorage.setItem() 用于存储,AsyncStorage.getItem() 用于检索数据。你可以在上面链接的文档中阅读更多关于它们如何工作的信息。目前,上面的片段将能够满足我们的需求。
import { AsyncStorage } from "react-native";
export const storeData = async (key, value) => {
try {
await AsyncStorage.setItem(`@ColorBlinder:${key}`, String(value));
} catch (error) {
console.log(error);
};
export const retrieveData = async key => {
try {
const value = await AsyncStorage.getItem(`@ColorBlinder:${key}`);
if (value !== null) {
return value;
} catch (error) {
console.log(error);
};
通过添加这个,我们将有两个asyncAsynchrony,在软件编程中,指的是在主要程序流之外发生的事件和处理它们的方法。外部事件,如信号或程序提示的活动,与程序执行同时发生,而不会导致程序阻塞和等待结果,就是这一类的例子。异步输入/输出是一种......函数,可用于存储和持久化数据,从AsyncStorage 。让我们导入我们的新方法,并添加两个我们要持久化到游戏屏幕状态的键。
import {
generateRGB,
mutateRGB,
storeData,
retrieveData
} from "../../utilities";
...
state = {
points: 0,
bestPoints: 0, // < new
timeLeft: 15,
bestTime: 0, // < new
...
并在底栏中显示这些值,旁边是它们相应的图标。
<View style={styles.bestContainer}>
<Image
source={require("../../assets/icons/trophy.png")}
style={styles.bestIcon}
/>
<Text style={styles.bestLabel}>{this.state.bestPoints}</Text>
</View>
. . .
<View style={styles.bestContainer}>
<Image
source={require("../../assets/icons/clock.png")}
style={styles.bestIcon}
/>
<Text style={styles.bestLabel}>{this.state.bestTime}</Text>
</View>
现在,让我们先保存最好的积分--我们可以在以后担心储存最佳时间的问题。在计时器中,我们有一个if 语句来检查我们是否已经输了--这就是我们要更新最佳点的时间,所以我们就检查一下你的实际分数是否比我们的最佳还好,如果是,就更新最佳。
if (this.state.timeLeft <= 0) {
this.loseFX.replayAsync();
this.backgroundMusic.stopAsync();
if (this.state.points > this.state.bestPoints) {
this.setState(state => ({ bestPoints: state.points }));
storeData('highScore', this.state.points);
this.setState(me{ gameState: "LOST" });
} else {
...
而在初始化屏幕时,在async componentWillMount() ,确保读入初始高分,并将其存储在状态中,以便我们以后可以显示它。
retrieveData('highScore').then(val => this.setState({ bestPoints: val || 0 }));
现在,你在游戏屏幕上存储和检索高分 - 但在主屏幕上也有一个高分标签!你可以用和现在一样的行来检索数据,并自己在标签中显示。
在休息之前,我们只需要做最后一件事:存储玩家能达到的最高时间。要做到这一点,你可以使用我们已经用过的函数来存储数据(但用不同的键!),然而,我们需要一个有点不同的技术来检查我们是否需要更新存储。
this.interval = setInterval(async () => {
if (this.state.gameState === "INGAME") {
if (this.state.timeLeft > this.state.bestTime) {
this.setState(state => ({ bestTime: state.timeLeft }));
storeData('bestTime', this.state.timeLeft);
. . .
这将检查我们当前的timeLeft是否比我们取得的最好成绩要大。在componentWillMount ,不要忘记检索和存储最佳时间和高分。
retrieveData('highScore').then(val => this.setState({ bestPoints: val || 0 }));
retrieveData('bestTime').then(val => this.setState({ bestTime: val || 0 }));
现在一切都准备好了。游戏开始有了好的外观和感觉,核心功能也已经开始运行良好--所以从现在开始,我们不需要太多的工作来完成这个项目。
接下来是我们的React-Native教程
在这个系列的下一集里,我们将通过在从iPhone SE到Xs的设备上测试,以及最后但并非最不重要的,在Android上测试,来研究如何使我们的游戏具有响应性。我们还将研究如何利用ESLint改善开发者的体验,并利用Jest增加测试。
如果你仍然感到有点不知所措,不要担心,移动开发可能是一个巨大的挑战,即使你已经熟悉React--所以不要在结束前迷失自己。让自己休息一下,以后再来看看下一集吧
如果你想查看截至目前已经完成的代码--请查看项目的GitHub repo。
The postReact-Native Sound & Animation Tutorialappeared first onRisingStack Engineering.