(1)はじめに
普段はソーシャルワーカーとして障がいをお持ちの方の支援をしています。Advent Calendarに合わせてバージョンアップさせてきたReact Nativeでの3D表示について、今回で仕上げの投稿になります。前回はReact NativeでSocket通信しようで3DキャラクターをSocket通信でマルチプレイができるコードを紹介しました。今回はイカゲームに出てくる、だるまさんが転んだの要素(イカゲームの流行に乗っただけ)を追加して完成にしたいと思います。
大きな敵キャラクターが振り向いたときに、自身のキャラクターが動いているとアウトとなり初期位置に戻ります。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);
(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で設定されているアニメーションになります。
まだ一部の機能しか使っていませんが、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の皆さんの記事とても勉強になっています