2020-09-01に投稿

A-FrameでVR選手ロッカーを作ってみた話

①NextJSでaframe-reactを動かす

aframe-react

ReactJSでAFrameを動かすときはこれ。
aframe-react

NextJSは一部SSR(サーバサイドレンダリング)が走るので、クライアントで動作する状態になってからロードする必要がある。
参考: aframe-next-static

windowというグローバル変数が使えるようになればクライアント側で動作する状態なので、レンダリング完了フラグで管理する。

if (typeof window !== "undefined") {
      require("aframe");
      setRendered(true);
    }

②aframe-reactで書いてみる

aframeは配置するものが多いと意外とソースが長くなるので、
1個のLockerを構成する単位でコンポーネント化しました。
選手の情報をAPIから取得して、奇数と偶数の場合で振り分けています。

if (!rendered || !teamMembers) {
    return <></>;
  }
return (
    <Scene 
        //device-orientation-permission-ui="enabled: true"
    >
      {teamMembers.map((member,idx)=>(
        <>
        {idx%2 === 0
          ?<RightLocker member={member} x={3 - idx*0.35} y={0} z={1.7+idx*0.35} rot_x={0} rot_y={0} rot_z={0}/>
          :<LeftLocker member={member} x={-idx*0.35} y={0} z={idx*0.35} rot_x={0} rot_y={0} rot_z={0}/>
        }
        </>
      ))}
      <Entity primitive="a-sky" material="color: #555" />
      <Entity camera look-controls wasd-controls position="3 1 2"/>
    </Scene>
  );

a-skyは背景、cameraはカメラの初期位置を定義しています。

これがRightLockerのソースです。
プログラム的に難しいことはないのですが、
座標や光源の調整などが結構面倒でした。
(..planeって光透過すんの?)

import { Entity } from "aframe-react";
import { Player } from "../../model/typedef";

type diff={
    x:number,
    y:number,
    z:number,
    rot_x:number,
    rot_y:number,
    rot_z:number,
    member:Player
}

const RightLocker:React.FC<diff> = ({x,y,z,rot_x,rot_y,rot_z,member}) => {
  return (
    <>
    <Entity
      geometry={{ primitive: "plane", width:1, height:2}}
      material={{ src: "/images/wood.jpg",alphaTest:0.1 }}
      position={{ x: x+0.7, y: y, z: z-4.3}}
      rotation={{ x: rot_x, y: -135+rot_y, z: rot_z }}
    />
    <Entity
    primitive="a-image"
    height="2"
      //geometry={{ primitive: "plane", width:1, height:2}}
      material={{ src: "/images/wood.jpg", transparent:false}}
      position={{ x: x+0.7, y: y, z: z-5 }}
      rotation={{ x: rot_x, y: rot_y-45, z: rot_z }}
    />
    <Entity
        primitive="a-image"
        height="2"
      //geometry={{ primitive: "plane", width:1, height:2}}
      material={{ src: "/images/wood.jpg"}}
      position={{ x: x, y: y, z: z-4.3 }}
      rotation={{ x: rot_x, y: rot_y-45, z: rot_z }}
    />
    <Entity
      geometry={{ primitive: "box", width:0.5, height:0.3}}
      material={{ color:'gray' }}
      position={{ x: x+0.5, y: y-1, z: z-4.5 }}
      rotation={{ x: rot_x, y: rot_y-45, z: rot_z }}
    />
    <Entity
        primitive="a-image"
        height="1.5"
        width="0.8"
      //geometry={{ primitive: "plane", width:1, height:2}}
      material={{ src: member.ogp_image}}
      position={{ x: x+0.7, y: y, z: z-4.3}}
      rotation={{ x: rot_x, y: -135+rot_y, z: rot_z }}
    />
    <Entity
        primitive="a-light" 
        light={{type:"spot"}}
        material={{color:'yellow'}}
        position={{ x: x+0.3, y: y+1.3, z: z-4.5 }}
        rotation={{ x: rot_x-90, y: rot_y-10, z: rot_z-10 }}
    />
    </>
    );
};
export default RightLocker;

DEMO

まだ本サイトにはデプロイしていませんが、team/[チームID]/vr_lockerで各チームのVRロッカーを見ることができます。
- キャップ野球情報局・VRロッカー

ツイッターでシェア
みんなに共有、忘れないようにメモ

ckoshien

個人開発5年目。普段はフロントエンドエンジニア。 ReactJS/NextJS/NodeJS/ReactNative/Java

Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。

また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!

有料記事を販売できるようになりました!

こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?

コメント