React-Native声音和动画教程

761 阅读10分钟

在这个React-Native声音和动画教程中,你将学习如何为你的移动应用添加动画和声音效果的技巧。我们还将讨论诸如用React-Native AsyncStorage持久化数据的话题。

为了展示你如何做这些事情,我们将使用我们在本教程系列的前4集中一直在构建的移动游戏。

快速回顾一下。在我们的React-Native教程系列的前几集里,我们建立了我们的React-Native游戏的核心:你终于可以收集积分,看到它们,甚至输掉。

现在,让我们用音乐、反应式原生动画和声音效果来为我们的游戏增添乐趣,然后通过保存高分来结束吧

react native tutorial animation sound

为我们的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,easeelastic - 要探索它们,请务必查看文档。然而,我们现在还不需要它们,因为它真的会扼杀现在的性能。

我们的序列将看起来像这样。

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,但对于像这样的演示项目,我们可以轻松地使用它。如果你的目标是生产,请务必查看其他解决方案,如RealmSQLite

首先,我们应该在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.