7
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

React Nativeでイカゲーム!

Last updated at Posted at 2021-12-19

(1)はじめに

普段はソーシャルワーカーとして障がいをお持ちの方の支援をしています。Advent Calendarに合わせてバージョンアップさせてきたReact Nativeでの3D表示について、今回で仕上げの投稿になります。前回はReact NativeでSocket通信しようで3DキャラクターをSocket通信でマルチプレイができるコードを紹介しました。今回はイカゲームに出てくる、だるまさんが転んだの要素(イカゲームの流行に乗っただけ:joy:)を追加して完成にしたいと思います。

大きな敵キャラクターが振り向いたときに、自身のキャラクターが動いているとアウトとなり初期位置に戻ります。githubで公開しましたので、ご興味のある方はどうぞ。

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

// _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

(2)Socket通信のサーバーに機能追加

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

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

(function loop() {
  let random = (Math.floor(Math.random() * 3) + 5) * 1000;
  setTimeout(() => {
    enemy();
    loop();
  }, random);
})();

Expoからnodejsアプリ(上記githubのserverフォルダ内)にアクセスする方法について、windowsであれば設定の「ネットワークとインターネット」からwifiのプロパティにあるIPv4アドレスを設定することでローカルに立てたサーバーにアクセスできます。

const socket = io("http://<IPv4アドレス>:3000");

(3)使った技術

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

・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

参考にしたサイト
Three.js公式
Expo公式
Mixamo公式
Socket.io公式
blender公式

(4)3Dモデル表示

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

タイトルなし.png
まだ一部の機能しか使っていませんが、Blenderとても良いです!

 // 敵のキャラクターを設置
    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で受け取ります。

// 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であれば初期位置に戻るを追加します。

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にし、自身のキャラクターが動いているとアウトになるタイミングを遅れて開始させます。

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]);

(5)終わりに

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

7
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?