どうもこんにちは、ウマシバ(@UMASHIBA)と言います。
今回こちらの記事に触発されてThree.jsでラーメンタイマーを作成してみました。
その中でいくつかの機能をどのように実装したかを紹介してみようと思います。
作ったモノ🍜
3Dラーメンタイマー
Three.js使ってラーメンタイマー🍜作りました!
— UMASHIBA (@UMASHIBA) February 24, 2020
PWAに対応させたのでオフラインで使うことができます。
良かったら使ってみてください! pic.twitter.com/GHrhWlFie8
アプリURL
https://3d-ramen-timer.umashiba.dev/
GitHubコード
https://github.com/UMASHIBA1/3DRamenTimer
使用した技術
- Three.js
- Typescript
- webpack
- PWA
赤いリングの作成方法
考え方
Three.jsにはTorusGeometryという下の写真のようなチューブ状の円を表現するジオメトリがありました。
そのTorusGeometryは下の写真のようにチューブ状であった円を弧にすることができます。
その弧をいくつか組み合わせて下の写真のような小さな一重の円を作成し、
コード
コードは以下のような感じです。
弧のコード
ここでもともとチューブの円形であったTorusGeometryを弧の形にしています。
またここで色なども付けています。
import * as THREE from "three";
class RingFragment extends THREE.Mesh {
constructor(
fragmentIndex: number,
radius: number,
tube: number = 3,
arc: number = 0.63
) {
const thisAngleDiff = (2 / 9) * Math.PI * fragmentIndex;
const circleDiameter = 10;
const geometry = new THREE.TorusGeometry(radius, tube, 2, 100, arc);
const material = new THREE.MeshToonMaterial({ color: "#FF0000" });
super(geometry, material);
this.position.x = Math.sin(thisAngleDiff) * circleDiameter;
this.position.y = Math.cos(thisAngleDiff) * circleDiameter;
this.rotation.y = 0;
this.rotation.x = 0;
// 1.23は初期でのangleの傾きを修正するための値
this.rotation.z = -thisAngleDiff + 1.23;
}
}
export default RingFragment;
一重の円のコード
- 弧を作っているRingFragmentというクラスのインスタンスをいくつか作りThree.jsのGroup機能でまとめます。
- Groupのrotation.zをフレームごとに少し進めて円を回転させます(tickというメソッドが毎フレームごとに呼ばれるようになってます。)
import * as THREE from "three";
import RingFragment from "../component/RingFragment";
type RotateDirectionType = "right" | "left";
class OneRing {
private _group: THREE.Group;
private _rotateDirection: RotateDirectionType;
constructor(
rotateDirection: RotateDirectionType,
radius: number,
tube?: number,
arc?: number
) {
this._group = new THREE.Group();
this._rotateDirection = rotateDirection;
for (let i = 0; i < 9; i++) {
const ringFrag = new RingFragment(i, radius, tube, arc);
this._group.add(ringFrag);
}
this._group.rotation.z = Math.random();
}
public get ring() {
return this._group;
}
public tick() {
const rotateSpeed = 0.005;
if (this._rotateDirection === "left") {
this._group.rotation.z += rotateSpeed;
} else {
this._group.rotation.z -= rotateSpeed;
}
}
}
export default OneRing;
複数重ねの円
- 複数の大きさの一重の円のインスタンスを作成して、これまたThree.jsのGroupでまとめます。
- 1重の円のtickメソッドをこちらのtickメソッドに組み込んで回るようにして完成です。
import * as THREE from "three";
import OneRing from "./OneRing";
class MultiRing {
private _group: THREE.Group;
private _oneRings: OneRing[];
constructor(
outlineRadius: number = 50,
ringNum: number = 3,
ringSizeDiff: number = 10,
tube?: number,
arc?: number
) {
this._group = new THREE.Group();
this._oneRings = [];
for (
let [count, nowRadius] = [0, outlineRadius];
count < ringNum;
count++, nowRadius -= ringSizeDiff
) {
const rotateDirection = count % 2 ? "left" : "right";
const ring = new OneRing(rotateDirection, nowRadius, tube, arc);
this._oneRings.push(ring);
}
for (let i of this._oneRings) {
this._group.add(i.ring);
}
}
public addToScene(scene: THREE.Scene) {
scene.add(this._group);
}
public tick() {
for (let i of this._oneRings) {
i.tick();
}
}
}
export default MultiRing;
以上が赤いリングの作り方です。
タイマーの作り方
次はタイマーの実装方法について説明します。
タイマーは普通にsetIntervalで時間を計測しました。
手順としては
1. 1秒経った際にoneSecondFlagといったような「一秒経ったよ」フラグをtrueにする
2. 毎フレーム呼ばれるメソッドであるtickでoneSecondFlagがtrueの際のみ一秒をカウントする処理を実行
このような流れになります。
基本的に他のアニメーションなどもフラグを作成してtickで処理をするという流れで作りました。
コードにすると以下のようなイメージです。
class Second {
private _oneSecondFlag: boolean;
constructor() {
this._oneSecondFlag = false;
}
public startCount() {
setInterval(() => {
this._oneSecondFlag = true;
}, 1000);
}
public tick() {
if (this._oneSecondFlag) {
// 一秒経った際にする処理
this._oneSecondFlag = false;
}
}
}
今回私が作ったラーメンタイマーでは「一秒経った際にする処理」としてタイマーを回転させる処理を挟みました。
以上がタイマーの作り方です。
イージングの作成
今回作ったラーメンタイマーではほとんどのアニメーションにイージングを付けました。
そのコードは以下のようなものになりました。
const easing = (
nowLocation: number,
targetLocation: number,
easingSpeed: number = 0.09
) => {
return (targetLocation - nowLocation) * easingSpeed;
};
この関数を毎フレームごとにnowLocationを変えて呼びだす事でイージングを実装しました。
終わりに
以上が3Dラーメンタイマーの技術紹介になります。
Three.jsは今まで触ったことがなかったのですがこのラーメンタイマーを作ったら何となくわかるようになりました。
やっぱりどんなフレームワークや言語でもラーメンタイマーは良い題材なんですね!
最後まで読んでいただきありがとうございました。