Decentralandでsceneを構築する

Decentraland SDK version5.0 を用いてsceneを作ります。
ステージとか丸い物体のパーツは素材としてもともと存在していて、その素材を特定の位置に配置させたり丸い物体（以降、車輪と呼びます）をぐるぐる回転させたりするのをコーディングで作成します。

完成イメージはこんな感じです。
![無題の動画 ‐ Clipchampで作成.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/919904/28353644-d54c-801e-fba1-4a2156be35c0.gif)


※decentralandのチュートリアルの日本語訳です。公式を読みたい場合は[こちら](https://decentraland.org/blog/tutorials/intro-to-sdk-v5/)から


#  空のsceneを作成する
まずはDecentraland SDK をインストール
```
npm install -g decentraland
```

空のフォルダを作成し、そのディレクトリに移動し、以下のコマンドを実行する
```
dcl init
```


`game.ts`を開いて、ファイル内のコードを全て削除する。

scene内で使用するアセットを[このリンク](https://github.com/decentraland-scenes/Hypno-wheels/releases/download/Blogpost/Scene-assets.zip)からダウンロードし、`/materials`と`/models`を該当ディレクトリのルートディレクトリに配置する。


# EntityとComponentを追加する

### ステージEntityの作成
`game.ts`に以下のコードを追加する
```
let stage = new Entity()
stage.addComponent(new GLTFShape("models/Theatre.gltf"))
stage.addComponent(new Transform({
 position: new Vector3(8, 0, 8),
 rotation: Quaternion.Euler(0, 90, 0)
}))
engine.addEntity(stage)
```

上記のコードでは、`stage`という名前のEntityを作成している。
で、Entityに各Componentを追加していく。

`GLTFShape`コンポーネントは.gltf 3Dモデルをロードしてくれる。
この3Dモデルには、このsceneにある全ての固定物（車輪以外）が含まれている

`Transform`コンポーネントは、Entityの位置や回転、スケールを定義する。

最後に、engineにstageを追加する。


### 車輪Entityの作成
通り抜けができないような設定をしていく。
```
let CylinderWCollisions = new CylinderShape()
CylinderWCollisions.withCollisions = true
```

`CylinderShape`コンポーネントは人が通り抜けできないようにcolliderとして振舞う。

次に、車輪Entityの作成

```
// Create the first wheel entity
let wheel1 = new Entity()
wheel1.addComponent(CylinderWCollisions)
wheel1.addComponent(new Transform({
  position: new Vector3(6, 2, 11.9),
  rotation: Quaternion.Euler(90, 0, 0),
  scale: new Vector3(1, 0.05, 1)
}))
engine.addEntity(wheel1)

// Create a second wheel entity
let wheel2 = new Entity()
wheel2.addComponent(CylinderWCollisions)
wheel2.addComponent(new Transform({
  position: new Vector3(10, 2, 11.9),
  rotation: Quaternion.Euler(90, 0, 0),
  scale: new Vector3(1, 0.05, 1)
}))
engine.addEntity(wheel2)
```

Quaternionの概念は難しいが、Elulerという関数を使うことで通常の0-360の角度を使えるようになるっぽい

ルードディレクトリで以下を実行するとsceneのプレビューが見れます
```
dcl start
```

# Material
車輪にTextureを与える
```
// Create texture
const spiralTexture = new Texture("materials/hypno-wheel.png")

// Create material
let spiralMaterial = new Material()
spiralMaterial.albedoTexture = spiralTexture

// Add material to wheels
wheel1.addComponent(spiralMaterial)
wheel2.addComponent(spiralMaterial)
```

# コンポーネントのカスタマイズ
```
@Component('wheelSpin')
export class WheelSpin {
 active: boolean = false
 speed: number = 30
 direction: Vector3 = Vector3.Up()
}
```

- 車輪が回転しているかどうかを確認するためのbool値
- 回転速度を表す数値
- スピンの方向を表すベクトル

一度カスタムコンポーネントを定義するとscene内のどこでも使えます。
```
wheel1.addComponent(new WheelSpin())
wheel2.addComponent(new WheelSpin())
```

# コンポーネントグループ
コンポーネントグループとは、特定の条件に一致するscene内の全てのEntityを追跡する配列。
```
const wheels = engine.getComponentGroup(WheelSpin)
```
WheelSpinコンポーネントを持つ全てのEntityを配列で表してくれます。
wheelsは後ほど使います。


# クリックで特定の動きをさせる
車輪をクリックすると、ぐるぐる回り出すという動きを付ける
```
wheel1.addComponent(
 new OnPointerDown(e => {
   let spin = wheel1.getComponent(WheelSpin)
   if (!spin.active){
     spin.active = true
   } else {
     spin.speed += 20
   }
   //log("speed: ", spin.speed)
 })
)

wheel2.addComponent(
 new OnPointerDown(e => {
   let spin = wheel2.getComponent(WheelSpin)
   if (!spin.active){
     spin.active = true
   } else {
     spin.speed += 30
   }
   //log("speed: ", spin.speed)
 })
)

export class RotatorSystem implements ISystem {
 update(dt: number) {
   for (let wheel of wheels.entities) {
     let spin = wheel.getComponent(WheelSpin)
     let transform = wheel.getComponent(Transform)
     if (spin.active){
       transform.rotate(spin.direction, spin.speed * dt)
     }
   }
 }
}
```


これで車輪がぐるぐる回るようになりました。
次は、車輪の動きを変えたり、トリガーボタンを設置する実装を付け加えたい。