カミナシ エンジニアブログ

株式会社カミナシのエンジニアが色々書くブログです

React3Dチュートリアル

f:id:kaminashi-developer:20210404173530p:plain

カミナシの浦岡です。

この記事ではreact-three-fiberというライブラリを使って、reactで3Dアプリケーションを作成してみます。

github.com

このライブラリを使うことでアプリの開発者からすると、 通常のreactアプリ(DOMにレンダリングするもの)を作るのと同じ実装方法で、3Dレンダリングするアプリケーションを作成することができます。

とは言え、 3Dの基本的な用語を知らないと思うように実装を進められないと思います。今回簡単なアプリの作成を通して3Dの基本に入門しましょう。

これから作る物

Reactの公式チュートリアル三目並べゲーム (tic-tac-toe) 」を3D化することをゴールにします。

gyazo.com

最終完成形

さっそく、完成形は以下になります。

codesandbox.io

Reactのチュートリアルと同じく、大きく以下3つのコンポーネントで構成しています。

  1. Game(ゲーム全体)
  2. Board(盤面)
  3. Square(正方形のマス目)

以下、1つずつ見てみましょう。

1. Gameコンポーネント

Gameコンポーネントは主に「三目並べゲーム」のロジックですが、Canvasタグの部分が3Dレンダリングのための実装になります。

      <Canvas camera={{ fov: 45, position: [0, 8, 12] }}>
        <CameraControls />
        <ambientLight />
        <pointLight position={[0, 10, 10]} />
        <Board
          squares={history[stepNumber].squares}
          handleClick={handleClick}
        />
      </Canvas>
カメラ
<Canvas camera={{ fov: 45, position: [0, 8, 12] }}><Canvas>

Canvasタグではカメラの設定を行うことができ、positionに三次元の座標を指定します。 中心座標を少し上から覗きこむような配置にしています。

gyazo.com

CameraControls
<CameraControls />

CameraControlsは、マウスのドラック操作などに合わせてカメラの位置を動かします。

今回扱う3Dオブジェクトはただの板なので、グリグリしても何も面白くありませんが 3Dキャラクターなどを「いろいろな角度から見たい」って時に重宝します。

ライティング
<ambientLight />
<pointLight position={[0, 10, 10]} />

もしライトを配置しないと、光のない世界になってしまうので、下の画像のようにただ黒い状態になります。

ライトはいくつか用意されていて、それによって色合いや影のつき方などが変わるので 合わせて選ぶ必要があります。ThreeJSのドキュメントを参照してください

2. Boadコンポーネント

Boadコンポーネントは、9つのSquareをマス状に配置するために座標を指定します。

import React from 'react'
import { BoardProps } from '../types'

import Square from './Square'

const positions: [x: number, y: number, z: number][] = [
  [-3, 3, 0],
  [0, 3, 0],
  [3, 3, 0],

  [-3, 0, 0],
  [0, 0, 0],
  [3, 0, 0],

  [-3, -3, 0],
  [0, -3, 0],
  [3, -3, 0],
]

const Board: React.FC<BoardProps> = (props) => {
  return (
    <>
      {props.squares.map((square, index) => {
        return (
          <Square key={index} position={positions[index]} value={square} handleClick={() => props.handleClick(index)} />
        )
      })}
    </>
  )
}

export default Board

3. Squareコンポーネント

Squareは1つずつ、meshとして定義します。

    <mesh
        {...props}
        ref={mesh}
        onClick={(_) => props.handleClick()}
        onPointerOver={(_) => {
          setHover(true);
        }}
        onPointerOut={(_) => {
          setHover(false);
        }}
      >
        <boxBufferGeometry args={[3, 3, 0]} />
        <meshStandardMaterial color={getColor(props.value, hovered)} />
      </mesh>
mesh

meshタグの子要素にある、Geometry(3D形状)とMaterial(素材)を基に、3Dオブジェクトが表示されるようになります。

<boxBufferGeometry args={[3, 3, 0]} />
<meshStandardMaterial color={getColor(props.value, hovered)} 
マウスイベント

マス目を選択できるようにするために、クリックとホバーイベントを検知するようにします。

ThreeJSでこれらのイベントを実装する場合、レイキャストを使って当たり判定から行う必要があるのですが、react-three-fiberでは、その処理がラップされていて、 meshタグの属性のonClick、onPointerOver、onPointerOutでイベント時の処理を実装できます。

◯×表示

マス目が選択状態になった場合、◯or×をマス目に表示します。 textGeometryで立体的な文字としてOXが表示されます。 (そもそも題材が3D向きではなかったなぁ💦)

    {props.value !== null && (
        <Suspense fallback={null}>
          <Text
            size={1}
            text={props.value}
            color="gray"
            position={props.position}
          />
        </Suspense>
      )}

まとめ

Reactのチュートリアルをベースにレンダリング部分だけ置き換える形で3D化することができました!

今回触れませんでしたが、EffectComposerを利用して見た目を凝ることもできます。3Dの世界奥が深いので興味持っていただいた方がいれば、ぜひ試してみてください!

gyazo.com