おもちゃラボ

Unityで遊びを作ってます

Unityでテクスチャにお絵描きしよう

Unityではゲームの実行時にリアルタイムでテクスチャのピクセルを変更することが出来ます。この方法を使ってテクスチャにお絵かきをしてみたいと思います。

f:id:nn_hokuson:20161208195649p:plain

今回の記事の内容は次のとおりです。

平面の配置

まずは、シーンビューに絵を描くための平面を配置します。ヒエラルキービューから「Create」→「3D Object」→「Plane」を選択してください。

f:id:nn_hokuson:20161208192116p:plain

カメラは平面を見下ろすように配置します。ヒエラルキービューからMain Cameraを選択し、インスペクタからPositionを(0, 7, -8)に、Rotationを(50, 0, 0)に設定してください。

f:id:nn_hokuson:20161208192228p:plain

テクスチャの設定

次に、絵を描くためのテクスチャをプロジェクトにインポートします。ここでは次のファイルを使いました。

f:id:nn_hokuson:20161208192247p:plain

テクスチャに絵を描くためにはテクスチャ自体の設定が必要です。プロジェクトビューでテクスチャを選択して、インスペクタから「Texture Type」を「Advanced」に設定して、「Read/Write Enabled」にチェックを入れます。

f:id:nn_hokuson:20161208192449p:plain

このテクスチャを先ほど配置したPlaneにドラッグ&ドロップして、マテリアルをアタッチします。Planeのシェーダは紙の質感が見えやすいようにUnlit/Textureを選択しておきましょう。

f:id:nn_hokuson:20161208192926p:plain

テクスチャの上半分を塗りつぶす

最後に、お絵かきの肝となるスクリプトの作成をしましょう。プロジェクトビューで右クリックし、「Create」→「C# Script」でスクリプトを作成し「PaintController」にリネームしてください。

とりあえずテクスチャの上半分を真っ黒に塗りつぶすスクリプトを書いてみましょう。

using UnityEngine;
using System.Collections;

public class PaintController : MonoBehaviour {

	Texture2D drawTexture ;
	Color[] buffer;

	void Start () {
		Texture2D mainTexture = (Texture2D) GetComponent<Renderer> ().material.mainTexture;
		Color[] pixels = mainTexture.GetPixels();

		buffer = new Color[pixels.Length];
		pixels.CopyTo (buffer, 0);

		// 画面上半分を塗りつぶす
		for(int x = 0; x < mainTexture.width; x++){
			for(int y = 0; y < mainTexture.height; y++){
				if( y < mainTexture.height / 2 ){
					buffer.SetValue (Color.black, x + 256 * y);
				}
			}
		}

		drawTexture = new Texture2D (mainTexture.width, mainTexture.height, TextureFormat.RGBA32, false);
		drawTexture.filterMode = FilterMode.Point;
	}

	void Update () 
	{
		drawTexture.SetPixels (buffer);
		drawTexture.Apply();
		GetComponent<Renderer> ().material.mainTexture = drawTexture;
	}
}

 Startメソッド内では、現在表示しているテクスチャのピクセルをバッファ用の配列(buffer)にコピーしています。このバッファを更新することで画面上の見た目も更新されるように、Updateメソッドではbufferの配列をdrawTextureに設定したうえで、それをmainTextureにセットしています。

 作成したPaintControllerスクリプトをシーンビューの「Plane」にドラッグ&ドロップしてアタッチします。

f:id:nn_hokuson:20161208193222p:plain

実行結果は次のようになります。

f:id:nn_hokuson:20161208193554p:plain

マウスの軌跡を塗りつぶす

では、このスクリプトを書き換えてマウスの軌跡を黒く塗りつぶすように変更してみましょう。

using UnityEngine;
using System.Collections;

public class PixAccess : MonoBehaviour
 {
	Texture2D drawTexture ;
	Color[] buffer;

	void Start () {
		Texture2D mainTexture = (Texture2D) GetComponent<Renderer> ().material.mainTexture;
		Color[] pixels = mainTexture.GetPixels();

		buffer = new Color[pixels.Length];
		pixels.CopyTo (buffer, 0);

		drawTexture = new Texture2D (mainTexture.width, mainTexture.height, TextureFormat.RGBA32, false);
		drawTexture.filterMode = FilterMode.Point;
	}

	public void Draw(Vector2 p)
	{
		buffer.SetValue (Color.black, (int)p.x + 256 * (int)p.y);
	}

	void Update () 
	{
		if (Input.GetMouseButton (0)) 
		{
			Ray ray = Camera.main.ScreenPointToRay (Input.mousePosition);
			RaycastHit hit;
			if (Physics.Raycast (ray, out hit, 100.0f)) {
				Draw (hit.textureCoord * 256);
			}

			drawTexture.SetPixels (buffer);
			drawTexture.Apply ();
			GetComponent<Renderer> ().material.mainTexture = drawTexture;
		}
	}
}

 マウスの軌跡を塗りつぶすためには、マウスが乗っている平面の座標を知る必要があります。これにはUnityが提供してくれているRayの機能を使います。Rayを使えば、マウスから出たRayが平面と交差した点のuv座標を取得することができます。

f:id:nn_hokuson:20161208194745p:plain

得られる座標は 0 < u < 1、0 < v < 1なので、これをテクスチャのピクセル数に変換して、該当のピクセルを黒く塗りつぶしています。実行結果は次のとおりです。

f:id:nn_hokuson:20161208194805g:plain

ブラシの太さを太くする

最後にブラシの太さが1pxでは見にくいので、もう少しブラシを太くするスクリプトを紹介します。今回はDrawメソッドの中身だけを書き換えています。

	public void Draw(Vector2 p)
	{
		for (int x = 0; x < 256; x++){
			for (int y = 0; y < 256; y++){
				if ((p - new Vector2 (x, y)).magnitude < 5){
					buffer.SetValue (Color.black, x + 256 * y);
				}
			}
		}
	}

 先ほどのスクリプトではマウスが乗っている1ピクセルだけを書き換えていました。今回のスクリプトでは毎フレーム、テクスチャ上のすべてのピクセルをチェックして、マウスが乗っている座標からの距離が8以下なら黒く塗りつぶします。

f:id:nn_hokuson:20161208194433p:plain

実行結果は次のようになります。

f:id:nn_hokuson:20161208195013g:plain

まとめ

今回はテクスチャにお絵かきをする方法を紹介しました。直接テクスチャに書くのではなく、マスクに描画してシェーダで合成するともっときれいな表現が可能になる・・・気がします。

基本的なUnityの使い方は「Unity5の教科書」で解説しているので是非参考にしてみてくださいね!

C#のヒアドキュメントで変数を展開する

Rubyには、文字列を見た目通りのフォーマットに整えてくれるヒアドキュメントという書き方があります。C#では見た目通りの入力ができる文字列のことを逐次的リテラル文字列と呼びます。

逐次的リテラル文字列とは

書き方は簡単で文字列の先頭に@(アットマーク)をつけるだけです。逐次的リテラル文字列はよくパスの指定などで使われます。通常バックスラッシュはエスケープシークエンスとみなされるため、パスを書く場合は次のように、バックスラッシュを2個重ねる必要がありました。

".¥¥hoge¥¥hoge¥¥hoge.txt"

逐次的リテラル文字列を使えば、見た目通りの文字列が得られるので次のように書くことができます。

@".¥hoge¥hoge¥hoge.txt"

また、複数行にわたって次のように文字列を書くこともできます。

@"1    2     3
  tama
       joro
             kuro
"

もちろん出力結果も見た目通りになります。

逐次的リテラル文字列の中で変数を使う

逐次的リテラル文字列の中で変数の値を表示したい時にはどうすれば良いでしょうか。例えば次のように普通に変数名を書いてしまうと、当然変数名がそのまま表示されてしまいます。

int point = 80;
Console.WriteLine(@"your score is point. GREAT!");

出力
your score is point. GREAT!

逐次的リテラル文字列の中で変数の値を展開したい時には、stringクラスのFormatメソッドを使えばOKです。

int point = 80;
Console.WriteLine(string.Format(@"your score is {0}. GREAT!"), point);

出力
your score is 80. GREAT!

逐次的リテラル文字列の時点では「your score is {0}. GREAT!」と出力され、Formatメソッドによってpoint変数が代入される、という仕組みです。ちょっとトリッキーですね。

C# 6以降なら文字列挿入が使える

そこでC# 6では、文字列挿入(string interpolation)という文法(シンタックスシュガー)が導入されました。これは文字列の中で直接変数を展開する構文で、$(ドルマーク)に続けて次のように書くことで{}内の変数を自動的に展開してくれます。

int point = 80;
Console.WriteLine($"your score is {point}. GREAT!");

出力
your score is 80. GREAT!

逐次的リテラル文字列と文字列挿入の合わせ技で、次のように「$@」と書くこともできます。

Console.WriteLine($@"your score is {point}.GREAT!");

このあたり、便利になったと見るのか、カオスになったとみるのか・・・

【Unity入門】60分で作るシューティングゲーム 最終回

前回までの記事で、シューティングゲームを動かすための基本的なプログラムは完成しました。

nn-hokuson.hatenablog.com

最終回は見た目などの細かい部分の手直しをしていきましょう。

第1回 ロケットを動かそう
第2回 弾を発射しよう
第3回 隕石を落下させよう
第4回 当たり判定をしよう
第5回 ゲームオーバを判定しよう
第6回 お化粧をしよう

背景をスクロールしよう

背景が青色1色なのは地味で寂しいです。
背景をスクロールを使って星が流れるようにしましょう。背景をスクロールするには次の2つを繰り返します。

  1. 少しずつ背景を移動
  2. 一定量スクロールしたら元の場所に戻す

背景スクロールについてはこちらの記事でも説明しているので合わせてどうぞ!
nn-hokuson.hatenablog.com

まずは、背景のスプライトを配置します。
プロジェクトビューから「background」をシーンビューに配置して、インスペクタからPositionを(0, 4.8, 0) に設定して下さい。

また、ロケットや隕石、爆発エフェクトが背景よりも手前に表示されるように、背景のスプライトの「Order in Layer」を「-1」に設定します。

f:id:nn_hokuson:20161130195819p:plain:w500

次に、プロジェクトビューで右クリックし「Create」→「C# Script」を選択します。
作成したファイル名を「BackgroundController」に変更して次のプログラムを入力してください。

using UnityEngine;
using System.Collections;

public class BackgroundController : MonoBehaviour {

	void Update () {
		transform.Translate (0, -0.03f, 0);
		if (transform.position.y < -4.9f) {
			transform.position = new Vector3 (0, 4.9f, 0);
		}
	}
}

このプログラムでは、毎フレーム背景を下方向に0.02ずつ移動しています。
スクロールしていき、背景画像の座標が一定値以上小さくなったときに、元の位置に戻しています。

作成したスクロールするスクリプトを背景画像にアタッチします。
プロジェクトビューの「BackgroundController」をヒエラルキービューの「background」にドラッグ&ドロップしてください。

f:id:nn_hokuson:20161130200000p:plain:w500

実行して背景がスクロールすることを確かめておきましょう。

f:id:nn_hokuson:20161130200216g:plain

UIの設定を見直そう

ゲームを実行した状態でUnityエディタを拡大縮小してみてください。
右上に表示しているスコアの文字サイズは変化していません。

f:id:nn_hokuson:20170310204226g:plain

このように、UIに関しては画面サイズによらずに決まったピクセル数で描画されてしまいます。
様々な解像度のデバイスで同じような見た目にするためには、CanvasにアタッチされているCanvas Scalerコンポーネントを修正します。

ヒエラルキービューから「Canvas」を選択した状態で、インスペクタの「Canvas Scalerコンポーネント→UI Scale Mode」を「Scale With Screen Size」に設定し、「Screen Match Mode」を「Expand」に設定します。

f:id:nn_hokuson:20161130200448p:plain

上記の設定をしたことで、フォントがものすごく小さく表示されていると思います。
ヒエラルキービューから「Canvas/Score」を選択し、Rect TransformのPositionを(-200, -60, 0) に、WidthとHeightを(400, 120) に修正し、フォントサイズを「80」に設定します。

f:id:nn_hokuson:20161130201222p:plain:w500

同様にGameOverのラベルもサイズを再調整しておきましょう。
ヒエラルキービューから「Canvas/GameOver」を選択し、Rect TransformのWidthとHeightを(450, 120) に、フォントサイズを「80」に設定します。

f:id:nn_hokuson:20161130201457p:plain:w500

Trail Rendererを使って弾にエフェクトをつけよう

最後に、発射した弾が尾を引くようなエフェクトを付け加えましょう。
尾を引くようなエフェクトには「Trail Renderer」を使用します。

まずは、トレイルレンダラー用のマテリアルを作ります。
プロジェクトビューで右クリック→「Material」を選択し「trail_mat」という名前で保存して下さい。

f:id:nn_hokuson:20161130202910p:plain:w400

このtrail_matを選択した状態で、インスペクタからシェーダを「Particle/Additive(soft)」に変更し、Textureの欄にプロジェクトビューの「trail」をセットします。

f:id:nn_hokuson:20161130203204p:plain:w500

続いて、弾に「Trail Rendererコンポーネント」をアタッチします。プロジェクトビューで「bulletPrefab」を選択した状態で、インスペクタから「Add Component」→「Effects」→「Trail Renderer」を選択してください。

f:id:nn_hokuson:20161130202638p:plain:w500

「Trail Rendererコンポーネント」の「Materials/Element0」の欄に、いま作成した「trail_mat」をドラッグ&ドロップします。また、「Time」を「0.3」に、「Start Width」を「0.2」に、「End Width」を「0.1」に設定してください。

f:id:nn_hokuson:20161130203748p:plain:w500

これで完成です!実行してみましょう!

f:id:nn_hokuson:20161130205512g:plain

6回に渡ってシューティングゲームの作り方を紹介してきましたが、ようやく完成までたどり着きました〜!!
この記事があなたのゲームづくりのお役に立てれば幸いです。

ココまで読んでいただき、ありがとうございました!!

今回作成したプロジェクトは↓においておきます。
www.dropbox.com