おもちゃラボ

Unityで遊びを作ってます

【Unity】複数点を通るスプライン曲線に沿って動かす

指定した点をとおるようにUnityちゃんを動かしたい場合、単に点を結ぶだけではカクカクした動きになってしまいます。

そんなときはベジェ曲線やスプライン曲線と呼ばれる曲線を使います。スプライン曲線を使えば、すべての点を通る滑らかな曲線を引くことが出来ます。

f:id:nn_hokuson:20190228204227j:plain

スプライン曲線に沿って動かす

通常のベジェ曲線やスプライン曲線を使うと、複数点を結ぶ場合に接続点での挙動がおかしくなってしまいます。そこで、ゲームでは次の「丸み不均一スプライン曲線」なるものがよく使われているようです。

f:id:nn_hokuson:20190228205129p:plain:w500

ここで引数のp1,p2は各点の座標、v1,v2は方向ベクトルを表します。次のサイトの内容(もとはGame Programming Gems4の内容らしいです)を参考にプログラムを作ってみます。

marupeke296.com

これを素直に実装すると次のようなメソッドになります。

Vector3 CalcSpline(Vector3 p1, Vector3 p2, Vector3 v1, Vector3 v2, float t)
{
	Matrix4x4 T = new Matrix4x4();
	Matrix4x4 H = new Matrix4x4();
	Matrix4x4 G = new Matrix4x4();

	T.m00 = t*t*t; T.m01 = t*t; T.m02 = t; T.m03 = 1;

	H.m00 = 2; H.m01 = -2; H.m02 = 1; H.m03 = 1;
	H.m10 =-3; H.m11 =  3; H.m12 =-2; H.m13 =-1;
	H.m20 = 0; H.m21 =  0; H.m22 = 1; H.m23 = 0;
	H.m30 = 1; H.m31 =  0; H.m32 = 0; H.m33 = 0;

	G.m00 = p1.x; G.m01 = p1.y; G.m02 = p1.z; G.m03 = 1;
	G.m10 = p2.x; G.m11 = p2.y; G.m12 = p2.z; G.m13 = 1;
	G.m20 = v1.x; G.m21 = v1.y; G.m22 = v1.z; G.m23 = 1;
	G.m30 = v2.x; G.m31 = v2.y; G.m32 = v2.z; G.m33 = 1;

	return (T * H * G).GetRow(0);
}

CalcSplineはスプライン曲線上の座標を返すメソッドです。tを0〜1まで動かすことで、p1〜p2までのスプライン曲線上の座標を得ることが出来ます。

この方法では、各点の座標と方向ベクトルさえわかれば良いので、簡単に複数点を通る滑らかなスプライン曲線を作ることが出来ます。

 
と、ここまでプログラムを作っておいてなんですが・・・
重大発表。
 
 
悪いことは言いません、
やめておきましょう!

 
UnityにはDOTweenという、超便利なアニメーションアセットがあり、もちろんスプライン曲線もサポートされています。ここは易きに流さましょう。笑


DOTweenでスプライン曲線に沿って動かす

DOTWeenでスプライン曲線に沿って動かすには、パス上の各ポイントの座標を配列で指定します。あとはDOTweenがいい感じにのスプライン曲線(CatmullRom)を作成してくれます。もちろんモデルを生成したスプライン曲線に沿って動かすのも簡単に出来ます。

DOTweenをAsset Storeからインポートできたら、プレイヤのコマを動かすスクリプトを作ります。プロジェクトウィンドウでC#のスクリプトを作ってください。名前は「Person.cs」にしておきましょう。

f:id:nn_hokuson:20190228203358p:plain:w80

ファイルが出来たら次のスクリプトを入力してください。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;

public class Person : MonoBehaviour
{
    void Start()
    {
		Vector3[] path = {
			new Vector3(1.35f, 0, 0),
			new Vector3(1.35f, 0, 2.0f),
			new Vector3(0.75f, 0, 2.5f),
		};

		transform.DOLocalPath(path, 2.0f, PathType.CatmullRom)
				.SetEase(Ease.Linear)
				.SetLookAt(0.01f, Vector3.forward)
				.SetOptions(false, AxisConstraint.Y);
    }
}

DoPathには3つの引数を渡します。それぞれ次の値を指定できます。

引数
第一引数 通る点の座標配列
第二引数 スタートからゴールまでの時間
第三引数 直線補間か曲線補完か

ここでは3点の座標を通るようにVector3型のpositions配列で指定しています。また、第三引数にはスプライン曲線で補間するように、CatmullRomを指定しています。

また、DoPathメソッドには続けてSetLookAtやSetOptionを指定しています。それぞれ次のような値を指定できます。

メソッド 意味
SetLookAt どちらに顔を向けて走るか
SetEase イージング。パス走行のときは基本的にLinerでOK
SetOptions 挙動のオプション

SetOptionsだけ詳しく説明しておきます。第一引数をtrueにすると、ゴールに着いたときに自動的にスタート地点に戻るようになります。また、第二引数では動かしたくない値を指定することが出来ます。ここではAxisConstraint.Yを指定することで上下に移動するのを防いでいます。

実行してみる

実行結果は次のようになりました。コマが滑らかに動いていることがわかると思います。このようにDOTweenを使えば、ベジェ曲線やスプライン曲線を自分で実装しなくても簡単に曲線補間したパスを使うことが出来ます

f:id:nn_hokuson:20190228204528g:plain:w550

動かしたいモデルををパスに沿って動かすのも簡単に出来ました。DOTweenは他にも便利な機能満載なので、ぜひ調べてみてください。

おまけ

今回はVector3で通る点の座標を指定しましたが、GameObjectを使ってエディタで指定することも出来ます(こっちのほうが視覚的で分かりやすいですね)

その場合はスクリプトにGameObjectの配列を作って、LinqでVector3の配列に変換すると便利です。

import System.Linq;
・・・
public GameObject[] positions = new GameObject[5];
・・・
Vector3[] path = positions.Select(x => x.transform.position).ToArray();
transform.DOPath (path, 1.0f, PathType.CatmullRom);
・・・

Blender2.8でテクスチャを張る

Blender2.7xのときと比べて、Blender2.8ではテクスチャの貼り方〜表示方法が少し変わったので、この記事で紹介したいと思います。

f:id:nn_hokuson:20190225220355j:plain

ここではBlender2.8を使って、上図のようにサイコロの1面だけにテクスチャを貼ってみます。

使用するテクスチャ

使うテクスチャは何でも良いのですが、今回は次のテクスチャを使います。ダウンロードしてdice.pngという名前で保存してください。

f:id:nn_hokuson:20190225220508p:plain:w200

UV展開する

まずは「どこの面に、テクスチャのどの部分を貼るのか」を指定します。これをUV展開といいます。Bledner2.8でUV展開をするには、画面上のタブでUV Editingを選択します。

f:id:nn_hokuson:20190225215425j:plain

次にテクスチャを貼りたい面を、右のウインドウで選択します(Editモードにしてください)。今回はサイコロの上面を選択しました。この状態でUボタンを押して展開を選択してください。

f:id:nn_hokuson:20190225213502g:plain

すると、左のウインドウに今選択した面が展開されます(うっすらとですが、テクスチャのまわりに外枠が表示されている)。これを使ってテクスチャのどの部分を使うのかを指定していきます。

その前に貼りたいテクスチャを表示しておきましょう。画面上の「開く」からdice.pngを指定してください。

f:id:nn_hokuson:20190225215609j:plain:w450

テクスチャのどの部分を使うのかを指定していきます。今回は天面に1の目を表示したいので、Aキーでサイコロの上面の4頂点を選択して、gキーやsキーを使って左上の「1の目」にフィットさせます。

f:id:nn_hokuson:20190225214105g:plain:w450

これでサイコロの上面には、今指定した領域のテクスチャ(1の目)が使われるようになります。ウインドウ上の「Modelingタブ」をクリックしてモデリングモードに戻りましょう。

f:id:nn_hokuson:20190225215844j:plain:w600

テクスチャを使うマテリアルを作る

Blender2.8からはEEVEEレンダラーが採用され、これまで使われていたBlenderレンダラーが廃止になりました。それに伴ってテクスチャの貼り方が少しだけ変わっています。

サイコロに使うマテリアルの設定をします。マテリアルタブを選択して、ベースカラーから「画像テクスチャ」を選択します。すぐ下にある「開くボタン」を押してdice.pngを選択しましょう。

f:id:nn_hokuson:20190225220017j:plain:w350

サイコロに使うマテリアルの設定が出来ましたが、これだけではまだ表示されません。右上のシェーディングから「LookDev」を選択してください。これでBlenderエディタでもテクスチャが表示されます。

f:id:nn_hokuson:20190225220244j:plain

さいごに

ここではBlender2.8を使ったテクスチャの貼り方を紹介しました。Blender2.8になってテクスチャ以外にも多数の変更が入っています。変更点をまとめた記事も合わせて御覧ください!

nn-hokuson.hatenablog.com

Arduino IDEを使ってAVRにスケッチを書き込む

f:id:nn_hokuson:20190220200250j:plain

Arduinoは回路の実験をするには便利なんですが、後々まで置いておきたいものに組み込むには、ちょっと大げさです。

組み込んで置いてても、いざ使おうとすると、ピン足が抜けててあれどこにさすんだっけ?みたいな。長期保存することを考えると、AVRにスケッチを書き込んでを半田付けしちゃうのが一番です。

ATTiny85なら秋月で100円程度なので、組み込む心理障壁もやや低め。ということで、前置きが長くなりましたが、ここではarduino で書いたスケッチをAVRに書き込む方法を紹介します。

ArduinoはAVRライターにしない!

既存のArduinoをAVRライターとして使う方法もあるっちゃぁ、あるのですけど、少しややこしい上に、 Arduino からAVRに書き込むタイミングで次のようなエラーが出て、どうしようもなくなることがあります。

avrdude: Device signature = 0x000000
avrdude: Yikes! Invalid device signature.
Double check connections and try again, or use -F to override
this check.
avrdude done. Thank you.

AVRのヒューズビットの問題のようですが、これを解決するためにヒューズリセッタを作って・・・なんてやってると終わらない。

AVRライター(USBASP V2.0)は買っても250円程度なので、エラーにハマる手間賃と思って、こっちを買うことをお勧めします。これとArduinoのスケッチさえあれば、確実にAVRにスケッチを書き込めます。

AVRライターとAVRの接続方法

AVRライターとAVRを接続します。ライター側のVCC、GND、MOSI、MISO、RST をそれぞれ、対応するピンに接続するだけです。

上記AVRライターの出力ピン配置は次のようになっています。

f:id:nn_hokuson:20190219202009p:plain:w300

Attiny85のピン配置は次のようになっているので、これをつなぎます。ほかのAVR(ATTiny2313など)でも同様です。ピン配置はデータシートを参照下さい。

f:id:nn_hokuson:20190219202356p:plain:w300

(番外編)アダプタを作る

ブレッドボードなどを使って接続しても良いのですが、いちいち組み立てるのが面倒なので、うちではTiny85用のアダプタを作りました。

f:id:nn_hokuson:20190219203241j:plain:w400

一応、マスクデータを置いておきますね。ご自由にお使いください(MOSIの配線だけはジャンプワイヤでつないでください)

f:id:nn_hokuson:20190219203615p:plain:w130

プリント基板を自作する方法は次の記事で説明しているので、参考にしてみてくださいね!

nn-hokuson.hatenablog.com

AVRにArduinoのスケッチを書き込む設定をする

次にArduino のスケッチをAVRに書き込むための設定をしていきます。Arduino IDEからAVRに書き込むにはATTinyCoreというミドルウェアを使用します。

Arduinoのメニューから環境設定を開き、メニューバーからArduino→Preferencesと進み、追加のボードマネージャのURLに http://drazzy.com/package_drazzy.com_index.json と入力してください。

f:id:nn_hokuson:20190219204319j:plain:w500

メニューバーから「ツール」→ 「ボード」→「ボードマネージャー」を選択して、ATTinyCore を選択してインストールしてください。

f:id:nn_hokuson:20190219204827j:plain:w500

次に書き込み設定を行います。メニューバーから「ツール」を選択して次のように設定してください。

ボード ATTinyCore/ATtiny85
Clock 8MHz(internal)
Chip ATtiny85
書き込み装置 USBasp

f:id:nn_hokuson:20190219205325j:plain:w300

Arduinoのスケッチを作成する

ここでは簡単にLチカのプログラムを作って・・・・と言いたいところですが、LEDを光らそうにもAVRのピン番号がわかりませんね。

AVRとArduinoのピン対応は次のサイトにまとまっているので、使用しているAVRを探してみてください。

github.com

上のサイトでArduinoとATTiny85の対応を確認したところ、次のようになっていました。

f:id:nn_hokuson:20190219211505p:plain

水色で書かれたARDUINO PINというのがピン番号です。「ディジタルピン/ アナログピン」の順番で並んでいます。ここでは3番ピンを使ってLチカするプログラムを作ってみましょう。

Arduino IDEに次のプログラムを作ってください。

void setup() {
     pinMode(3, OUTPUT);
}

void loop() {
    digitalWrite(3, HIGH);
    delay(100);
    digitalWrite(3, LOW);
    delay(100);
}

プログラムが作れたら、Arduinoの書き込みボタンを押してAVRに書き込みましょう。

f:id:nn_hokuson:20190220200455p:plain:w300

動作確認

AVRの書き込み装置から外して、3番ピン(Tiny85左側の上から2番目)にLEDと抵抗を接続してください。回路図は下のような感じになります。

f:id:nn_hokuson:20190220221839j:plain:w300

電源を入れると点滅することを確認しましょう!

f:id:nn_hokuson:20190220200927g:plain

(おまけ)プログラムのサイズを小さくする

Tiny85などの小さいAVRは、プログラムを配置するためのRAM容量も小さくなってしまいます。Arduinoに使われているATmega328はFlashが32KBなのに対して、Tiny85では8KBと1/4のサイズしかありません

コンパイル後のプログラムサイズはArduino IDEに表示されるので確認してみましょう。

最大8192バイトのフラッシュメモリのうち、スケッチが908バイト(11%)を使っています。
最大512バイトのRAMのうち、グローバル変数が9バイト(1%)を使っていて、ローカル変数で503バイト使うことができます。

Lチカだけで1KBちかく消費しています。なんとかプログラムのサイズを削りたい・・・そんなときの定石があります。digitalReadとdigitalWriteメソッド、あとpinModeメソットをそれぞれ次のプログラムに置き換えてみましょう。

置換前 置換後
digitalWrite(3, HIGH) PORTB |= _BV(3)
digitalWrite(3, LOW) PORTB &= ~_BV(3)
pinMode(3, OUTPUT) DDRB |= _BV(3)

置換後のプログラムは次のようになります。

void setup() {
     DDRB  |=  _BV(3);
}

void loop() {
    PORTB |=  _BV(3);
    delay(100);
    PORTB &= ~_BV(3);
    delay(100);
}

digitalWriteなどのメソッドは汎用性を考慮して、かなり大きなプログラムになっています。ここではマイコンらしくピンのオンオフだけをするように変更しました。コンパイルして容量を確認してみてください。

digitalWrite関数を使わないようにするだけで、プログラムサイズを500バイト程度減らせました。全部で8KBしかないことを考えると節約するに越したことはありません。

最大8192バイトのフラッシュメモリのうち、スケッチが490バイト(5%)を使っています。
最大512バイトのRAMのうち、グローバル変数が9バイト(1%)を使っていて、ローカル変数で503バイト使うことができます。