React Nativeでイカゲーム！

<h1>（１）はじめに</h1>

普段はソーシャルワーカーとして障がいをお持ちの方の支援をしています。Advent Calendarに合わせてバージョンアップさせてきたReact Nativeでの3D表示について、今回で仕上げの投稿になります。前回は<a href="https://qiita.com/syeha/private/724bcc1e4826fecb7151">React NativeでSocket通信しよう</a>で3DキャラクターをSocket通信でマルチプレイができるコードを紹介しました。今回はイカゲームに出てくる、だるまさんが転んだの要素（イカゲームの流行に乗っただけ:joy:）を追加して完成にしたいと思います。<br>
大きな敵キャラクターが振り向いたときに、自身のキャラクターが動いているとアウトとなり初期位置に戻ります。githubで公開しましたので、ご興味のある方はどうぞ。

https://github.com/syehacom/expo3d

gl.pixelStorei() doesn't support this parameter yet! from TextureLoader が出ますのでnode_modulesにある/three/build/three.js 16527行目からの下記3行をコメントアウトしてください。
※github expo-threeのissue#196参照

```js
// _gl.pixelStorei(_gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultiplyAlpha);
// _gl.pixelStorei(_gl.UNPACK_ALIGNMENT, texture.unpackAlignment);
// _gl.pixelStorei(_gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, _gl.NONE);
```

![20211219_113006.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635004/2246da96-2140-fe4a-0381-23550eceb9c7.gif)

<h1>（２）Socket通信のサーバーに機能追加</h1>

まずは前回のnodejsコードに、一定間隔でtureを送るコードを追記します。Math.floor関数とMath.random関数を使い5～8（0～3 + 5）の整数を返し、それに1000ミリ秒乗算し、setTimeout関数の間隔を設定します。これでアプリ側に5秒から8秒間隔でランダムにtrueを送信することができます。このtrueをアプリが受信すると敵キャラクターが振り向きます。

```js
function enemy() {
  io.emit("Enemy", true);
}

(function loop() {
  let random = (Math.floor(Math.random() * 3) + 5) * 1000;
  setTimeout(() => {
    enemy();
    loop();
  }, random);
})();
```
:::note info 
Expoからnodejsアプリ（上記githubのserverフォルダ内）にアクセスする方法について、windowsであれば設定の「ネットワークとインターネット」からwifiのプロパティにあるIPv4アドレスを設定することでローカルに立てたサーバーにアクセスできます。
:::
```js
const socket = io("http://<IPv4アドレス>:3000");
```

<h1>（３）使った技術</h1>

使用するライブラリは前回と一緒です。

・react-native
・expo 42.0.1
・expo-gl 10.4.2
・expo-three 6.0.1
・gsap 3.6.0
・three 0.132.0　←　※バージョンによってはエラーになります
・expo-asset 8.3.3
・socket.io-client 4.4.0

参考にしたサイト
<a href="https://threejs.org/">Three.js公式</a>
<a href="https://docs.expo.dev/">Expo公式</a>
<a href="https://www.mixamo.com/">Mixamo公式</a>
<a href="https://socket.IO/">Socket.io公式</a>
<a href="https://www.blender.org/">blender公式</a>

<h1>（４）3Dモデル表示</h1>
追加したコードについて説明を記します。敵キャラクターはロイヤリティフリーのMixamoで作成します。振り返る動作をfbx形式でダウンロードしBlenderでスケールを拡大しgbl形式で保存します（testC.gbl）。アニメーションの変数walkCはMixamoで設定されているアニメーションになります。

![タイトルなし.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635004/b5b45dd5-37b4-4c95-df6b-710945d87d49.png)
まだ一部の機能しか使っていませんが、Blenderとても良いです！

```js
 // 敵のキャラクターを設置
    let mixerC;
    let clockC = new Clock();
      const assetC = Asset.fromModule(require("./assets/testC.glb"));
      await assetC.downloadAsync();
      loader.load(
        assetC.uri || "",
        (gltf) => {
          const modelC = gltf.scene;
          modelC.position.set(0, 0, -40); // 配置される座標 (x,y,z)
          modelC.rotation.y = Math.PI - 0.7;
          const animations = gltf.animations;
          //Animation Mixerインスタンスを生成
          mixerC = new AnimationMixer(modelC);
          // 設定した一つ目のアニメーションを設定
          let animation = animations[0];
          // アニメーションを変数walkにセット
          setWalkC(mixerC.clipAction(animation));
          // test.glbを3D空間に追加;
          scene.add(modelC);
          setModelsC(modelC);
        },
        (xhr) => {
          console.log("ロード中");
        },
        (error) => {
          console.error("読み込めませんでした");
        }
     ;
```
敵キャラクターが振り向くタイミングについて、先ほど追記したサーバーからの送信されるtrueをsocket.onで受け取ります。

```js
// App.js
  const [daruma, setDaruma] = useState(false); // サーバーから受信したかどうかをセットする変数 
  const [stare, setStare] = useState(false); // 自身のキャラクターが動いたらアウトになるタイミングの変数

  useEffect(() => {
    // サーバーのアドレス
    const socket = io("http://<IPv4アドレス>:3000");
    // サーバーからランダムな値を受け取り変数darumaにセット
    socket.on("Enemy", (data) => {
      // 敵キャラクターを読み込む前にtrueとなっているためfalseにセットしなおす
      setDaruma(false);
      // サーバーから受け取ったtrueをセット
      setDaruma(data);
    });
    return () => socket.disconnect();
  }, [modelsC]);
```
自身のキャラクターの挙動を実行する関数walkに、変数stareがtrueであれば初期位置に戻るを追加します。

```js
const move = (props) => {
    walkA.paused = false; // キャラクターのポーズを解除
    walkA.play(); // // アニメーションである変数walkを再生
    setAction({ z: props.y, x: props.x }); // Position.jsから受け取った座標を変数actionにセット
    walk(); // walk関数を実行
    // 関数moveが実行されているときにdarumaがtrueなら初期座標に戻る
    if (stare) {
      TweenMax.set(modelsA.position, {
        x: 0,
        y: 0,
        z: 25, // 中心より手前に初期座標を設定
      });
      TweenMax.set(cameras.position, {
        x: 0,
        y: 2,
        z: 32,
      });
      walkA.paused = true;
      send({
        x: modelsA.position.x,
        y: modelsA.rotation.y,
        z: modelsA.position.z,
        w: walkA.paused,
      });
    }
  };
```
最後に変数darumaが変化する度にuseEffectで反応させます。敵キャラクターが読み込まれていないとエラーになるので条件式if(modelsC !== null)を設定します。またsetTimeを使って振り向くアニメーションを実行、停止する間隔を2500ミリ秒、アニメーションが実行されて500ミリ秒後に変数stareをtrueにし、自身のキャラクターが動いているとアウトになるタイミングを遅れて開始させます。

```js
useEffect(() => {
//darumaがtrueであれば下記を実行する
    if (daruma) {
      if (modelsC !== null) {
       // アニメーション開始
        walkC.play();
        setTimeout(() => {
          setStare(true);
        }, 500);
      }
      setTimeout(() => {
        // 2500ミリ秒後にリセット
        if (modelsC !== null) {
          setDaruma(false);
          setStare(false);
          // アニメーション停止
          walkC.stop();
        }
      }, 2500);
    }
  //変数darumaが変化する度に反応する
  }, [daruma]);
```
<h1>（５）終わりに</h1>

React Nativeでの3D表示について、Advent Calendarに合わせて2週間バージョンアップさせてきました。貴重な機会を利用させていただき感謝します。ほんの少しでもReact Nativeのコミュニティに貢献できたならば嬉しいです。React Native Advent Calendarの皆さんの記事とても勉強になっています:hugging: