ナノカ技術メモ

ただのエンジニア。何でも屋みたいな扱い受けてます。

Unityでパズドラ風パズル作ってみた

はじめに

最近いろんなゲームをやりつつ、戦略系などが好きなんだなーと実感しました。
その中でも単純なんだけど考えるようなものは暇つぶしには最適で、それがパズルでした。

パズルと言ったらパズドラということもあり(1週間くらいしかやったことないが...)
作ってみることにしました。

完成した動作が以下です。
レイアウト等はまだまだですが一応パズルできる...という感じです。
これにコンボやスコアを設定すれば完璧、作り上簡単にカスタマイズできるはずです。


実装内容

実装後に違うパズルに改造してしている途中なので確かこうだったなぁという復元になってますが、動かない場合はコメント等にておしらせください。

Line3Block

パズルのピースを用意します。
80x80のPrefabです。
f:id:nanokanato:20170616113854p:plain:w300

階層構造はBlock(CreateEmpty:空のGameObject)に
BackGround(UI.Image)とIcon(UI.Image)を用意します。
f:id:nanokanato:20170616113815p:plain:w300

Blockには以下のScriptを追加します。
主にピースの移動後の座標と種類を管理しています。

InspectorからBlockの色(BlockColors)とキャラアイコン(BlockCharas)を設定します。
BackGroundとIconにはBlockの下層に作った同じ名前のUI.Imageを設定します。
f:id:nanokanato:20170616114234p:plain:w300

設定したBlockの色とキャラアイコンの数だけピースの種類が増えます。

Line3Puzzle

BlockのPrefabを生成したり、ピースが揃っているか確認する部分です。

階層構造はCanvas以下にBoard(CreateEmpty:空のGameObject)を配置しているだけです。
Canvasは640x1136で、Boardは560x760です。
Camera等の設定は省きます。
f:id:nanokanato:20170616114904p:plain:w300

Canvasには以下のScriptを追加します。
Blockの生成と破棄、タップ操作の管理、ピースが揃ったかどうかの確認、アニメーションなどを行なっています。

InspectorのBoardにはCanvas下層のBoardを、BlockPrefabにはBlockを設定してください。
f:id:nanokanato:20170616115225p:plain:w300

ビルド

あとはビルドすることでCanvas > BoardにBlockが生成され、操作待ちになります。

処理の手順は以下になります。

  1. ピースの生成 → 2
  2. 揃っているか確認
    揃っていた場合 → 3
    揃っていない場合 → 6
  3. 揃っていたピースを消す → 4
  4. ピースを下に詰める
    空いている場所には上から新たなピースを落とす → 5
  5. 移動後に再確認 → 2
  6. 移動するピースの選択待ち → 7
  7. ピースを移動して入れ替える
    指が離されたら移動を確定 → 2

まとめ

パズルって単純なゲームなのに内部では色々なロジックが動いており面白いです。
結構出尽くしている感もありますがぜひオリジナルのパズルを作ってみてください。

UnityのAssetBundleを簡単にしてみた

はじめに

AssetBundleを使用するときにAssetBundleを読み込んでローカルに保存、Spriteの取得というのが毎回実装していると手間なので以下のように呼び出せるようにしました。
f:id:nanokanato:20170615115801p:plain:w300

AssetBundleMan


キャッシュ→StreamingAssets→ローカルの保存場所→サーバーの順にAssetBundleを探しに行きます。
一度取得したAssetBundleはキャッシュされます。

サーバーとローカルへの通信はWWWクラスを使用したWWWManを使っています。
madgenius.hateblo.jp

使用方法

Spriteを取得する場合
AssetBundleMan.GetAssetBundleSprite("AssetBundle名", "ファイル名.png", (Sprite sprite) => {
    image.sprite = sprite;
});

取得失敗の可能性もあるのでnullチェックしておくとベストです。
AssetBundleとSpriteの取得後に呼ばれて画像が反映されます。

GameObject,Prefabを取得する場合
AssetBundleMan.GetAssetBundleGameObject("AssetBundle名", "ファイル名.prefab", (GameObject prefab) => {
    GameObject gameObject = Instantiate (prefab, this.transform);
});

取得失敗の可能性もあるのでnullチェックしておくとベストです。
AssetBundleとGameObjectの取得後に呼ばれてPrefabから生成されます。

UnityのWWW通信をCallBackで受けとるクラス

簡単にメモ程度ですが、WWWでの通信を簡単に行うために作成しました。
以下の画像の通り、Coroutineを使わずにCallBackでProgress(進捗度)と通信結果を受け取ることができます。
f:id:nanokanato:20170608185914p:plain:w300

staticのクラスなのでどこからでも呼び出すことができます。
MonoBehaviourを通信開始時に取得してCoroutineの処理をしているのでその部分のみ指定のMonoBehaviourに差し替えて使用することもできます。
現状、タイムアウトの処理がないので今後実装するかもしれません。

Unityでアプリ容量削減。700MB→179MB

はじめに

先日、Unityで簡単なiOSアプリを作りリリースしたのですが、Archiveでipaを作成したら130MB近くあったのでびっくり...
ちなみにiOSではAppStoreに公開して100MBを超えているとWifiに接続しないとダウンロードできません。
簡単なゲームのアプリなのでWifiに繋いでまで入れるものではないし、このままではインストールの壁が高い...
なんとしても容量削減しないといけないのでそのときやったことを書きます。

ipaのサイズ:130MB
iPhoneでの容量:700MB
iOSではインストール時はipaに圧縮されており、インストール後はappとして展開されます。

検証結果

Textureサイズを適切なものに変更

・容量にはほぼ変化なしだがメモリ削減に効果あり。
docs.unity3d.com


UnityのProjectから画像素材を選択するとInspectorに下のような画面が表示されます。
そこで画像のサイズとクオリティを設定できます。

デフォルトでは2048という無駄に大きいサイズになっているはずなので画像素材のサイズより大きい適切なものに変更しましょう。(下では200x200pxの素材を256に変更)
f:id:nanokanato:20170605142542p:plain:w300

素材の容量を削減

・容量にやや効果ありだが、やりすぎると劣化します
  ・ipaのサイズ:130MB→124MB
  ・iPhoneでの容量:700MB→680MB
tinyjpg.com

今回の場合、容量の原因は主に画像素材でした。
なので画像素材自体を軽くしてみました。

素材によっては大幅に削減されます。
劣化した場合はそのまま使いました。

AssetBundle

iPhoneでの容量に大幅に効果あり
  ・ipaのサイズ:124MB→116MB
  ・iPhoneでの容量:680MB→179MB
docs.unity3d.com

今回の場合、連番画像などが多くなっており、それをResources.Load()で取得しておりました。
しかし、Resourcesフォルダの多用はビルド時に使用している使用していないに関係なく無圧縮でビルドされることがわかりました。
また、画像素材の他にもビルド時に生成されたものがあり容量的によくないようです。
AssetBundleはUnityで使う素材を圧縮したもので、アプリで使用する時だけ解凍されます。

Resourcesをやめ、フォルダ名をResourceに変更しました。(フォルダ名はなんでも良い)
Resourceフォルダを選択すると、下にAssetBundle名を登録できますので自由につけました。(デフォルトはNone)
※グループごとにフォルダを分けている場合でもフォルダ以下をAssetBundleにできますが、個別の方がいいでしょう。
f:id:nanokanato:20170605145759p:plain:w300

以下のコードをUnityのProjectの「Assets/Editor」に追加します。

using UnityEngine;
using System.Collections;
using UnityEditor;
using System.IO;

public class ExportAssetbundle  {

	[MenuItem("Export/AssetBundle/iOS")]
	static void iOS_Export() {
		Directory.CreateDirectory (Application.streamingAssetsPath);
		BuildPipeline.BuildAssetBundles(Application.streamingAssetsPath+"/iOS", BuildAssetBundleOptions.ChunkBasedCompression, BuildTarget.iOS);
	}

	[MenuItem("Export/AssetBundle/Android")]
	static void Android_Export() {
		Directory.CreateDirectory (Application.streamingAssetsPath);
		BuildPipeline.BuildAssetBundles(Application.streamingAssetsPath+"/Android", BuildAssetBundleOptions.ChunkBasedCompression, BuildTarget.Android);
	}
}

追加するとUnityのメニューに「Export/AssetBundle」が追加されます。
f:id:nanokanato:20170605162212p:plain:w300

UnityのProjectのAssetsに「StreamingAssets/iOS」を追加してメニューのiOSを選択するとAssetBundleの作成が開始されます。
作成されたAssetBundleは「Assets/StreamingAssets/iOS」に追加されます。

AssetBundleの読み込みはAssetBundle.LoadFromFile()メソッドで読み込めます。
しかし、AssetBundleは1度しか読み込めません(読み込むとエラーが出ます)
なので自分は以下のようにstaticのメソッドで一度読み込んだらキャッシュから読み出すようにしています。
AssetBundleが読み込めない時は、上の手順では「Assets/StreamingAssets/iOS」にAssetBundleを作成したので問題はないはずですがLoadFromFile()内のパスを確認してください。

private static Dictionary<string, AssetBundle> assetBundleCache = new Dictionary<string, AssetBundle>();
private static AssetBundle readAssetBundleAssetBundle(string key) {
	AssetBundle assetBundle = null;
	if (!string.IsNullOrEmpty(key)) {
		if (assetBundleCache != null) {
			if (assetBundleCache.ContainsKey (key)) {
				assetBundle = assetBundleCache [key];
			}
		}
		if (assetBundle == null) {
			assetBundle = AssetBundle.LoadFromFile(Application.streamingAssetsPath+"/iOS/"+key);
			if (assetBundleCache.ContainsKey(key)) {
				assetBundleCache[key] = assetBundle;
			} else {
				assetBundleCache.Add(key, assetBundle);
			}
		}
	}
	return assetBundle;
}

読み込んだAssetBundleから素材を取り出すにはLoadAsset()を使用します。
上のreadAssetBundleAssetBundleメソッドを使ってAssetBundleを取得した後、LoadAssetでSpriteを取得しています。
Resouces.Loadでは「Resouces/Chara/chara_1.png」を読み込む場合、"Chara/chara_1.png"を引数としていましたが、
LoadAssetではフォルダ階層を無視し、拡張子が必要です。"chara_1.png"が引数になります。

AssetBundle assetBundle = readAssetBundleAssetBundle(AssetBundle名);
if (assetBundle != null) {
	sprite = assetBundle.LoadAsset<Sprite>(素材ファイル名);
}

AssetBundleで素材を読み込むのならAssetBundle化した素材たちはプロジェクトの外で管理しておきましょう。

ダウンロードで素材を取得

・ダウンロード時のサイズに効果あり
  ・ipaのサイズ:124MB→116MB
  ・iPhoneでの容量:680MB→179MB

上の3つの対策をしてiPhoneでの容量は大幅に減らせましたが、ipaのサイズが100MBを超えているため素材は初回にダウンロードすることにしました。
このアプリでは一応、設定値などを起動時に通信していたのでその通信にAssetBundleも含めたいと思います。
サーバーにはAssetBundleのパスと最終更新日を用意し、最終更新日がアプリ内の最終更新日付より新しくなったら再取得するようにしました。

作成したAssetBundleにはフォルダに登録したAssetBundle名のファイル以外に「.manifest」や「iOS」「iOS.manifest」などが作成されますが削除してビルドしても実機で動かす際には問題なかったので使用しませんでした。

AssetBundleの取得はWWWクラスで行います。
WWWクラスの引数assetBundleがあれば取得成功なので通信結果であるByte配列をローカルに保存します。
※引数assetBundleはAssetBundle.LoadFromFile()と同じく1度しか取得されないようです。
 取得したらキャッシュとしてどこかに保持しましょう。

using (WWW data_www = new WWW (サーバーに設置したAssetBundleのURL)) {
	yield return data_www;
	
	//通信結果を取得
	if (string.IsNullOrEmpty (data_www.error)) {
		//通信成功
		AssetBundle assetBundle = data_www.assetBundle;
		if (assetBundle != null) {
			//AssetBundleの取得成功、保存
			System.IO.File.Delete(AssetBundleを保存するローカルのパス);
			System.IO.File.WriteAllBytes(AssetBundleを保存するローカルのパス, data_www.bytes);
		}
	}
}

前回起動時にサーバー通信でByte配列を保存した後はローカルパスをWWWで通信することで取得できます。
サーバー通信後にローカルパスからも取得しようとすると同じAssetBundleを2回取得しようとしていることになるためエラーになります。

結論

・画像は256色にする
・Resourcesフォルダの多用はしない
・それでも重い時はAssetBundle化
ipaの容量が100MB超えたらサーバーからダウンロードさせる

AssetBundleはPrefabなども可能なのでサーバーがあればアプリを更新せずにレイアウトの変更も可能に!

UnityのuGUIで縦書きテキスト表示

はじめに

Unityで縦書き表示を使いたい時があると思う。
そういう時のためにメモ。

動作検証環境
MacOS:Sierra10.12.1
Unity:5.5.0

ソース

回転させたくない文字を「NonRotatableCharacters」に入れる。
例:「ー」「。」など...

回転だけだと位置がずれる、回転させなくても位置がおかしいものに関して
Pixelで修正したいものは「ShiftCharacters」に文字を入れ、「ShiftXPixels」「ShiftYPixels」で修正位置を記入する
例:「、」「。」など(Pixelは現在の位置から+-pxで指定)

using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Collections.Generic;
using System.Linq;

[RequireComponent(typeof(Text))]
public class RotateText : UIBehaviour, IMeshModifier
{
	private Text textComponent;
	private string text = null;
	private char[] characters;

	// 回転させない文字群
	[SerializeField] private List<char> NonRotatableCharacters;
	[SerializeField] static int ShiftChar = 0;
	[SerializeField] private char[] ShiftCharacters = new char[ShiftChar];
	[SerializeField] private float[] ShiftXPixels = new float[ShiftChar];
	[SerializeField] private float[] ShiftYPixels = new float[ShiftChar];

	void Update() {
		if (textComponent == null) {
			textComponent = this.GetComponent<Text>();
		} else {
			if (textComponent.text != "") {
				if (textComponent.text != text) {
					text = textComponent.text;
					var graphics = base.GetComponent<Graphic>();
					if (graphics != null) {
						graphics.SetVerticesDirty();
					}
				}
			}
		}
	}

	void OnValidate() {
		textComponent = this.GetComponent<Text>();
		if (textComponent.text != null && textComponent.text != "") {
			if (textComponent.text != text) {
				text = textComponent.text;
				var graphics = base.GetComponent<Graphic>();
				if (graphics != null) {
					graphics.SetVerticesDirty();
				}
			}
		}
	}

	public void ModifyMesh (Mesh mesh) {}
	public void ModifyMesh (VertexHelper verts)
	{
		if (!this.IsActive())
		{
			return;
		}

		List<UIVertex> vertexList = new List<UIVertex>();
		verts.GetUIVertexStream(vertexList);

		ModifyVertices(vertexList);

		verts.Clear();
		verts.AddUIVertexTriangleStream(vertexList);
	}

	void ModifyVertices(List<UIVertex> vertexList) {
		if (textComponent != null) {
			if (textComponent.text != null && textComponent.text != "") {
				characters = textComponent.text.ToCharArray ();
				if (characters.Length == 0) {
					return;
				}

				for (int i = 0, vertexListCount = vertexList.Count; i < vertexListCount; i += 6) {
					int index = i / 6;
					//文字の回転の制御
					if (!IsNonrotatableCharactor (characters [index])) {
						var center = Vector2.Lerp (vertexList [i].position, vertexList [i + 3].position, 0.5f);
						for (int r = 0; r < 6; r++) {
							var element = vertexList [i + r];
							var pos = element.position - (Vector3)center;
							var newPos = new Vector2 (
								            pos.x * Mathf.Cos (90 * Mathf.Deg2Rad) - pos.y * Mathf.Sin (90 * Mathf.Deg2Rad),
								            pos.x * Mathf.Sin (90 * Mathf.Deg2Rad) + pos.y * Mathf.Cos (90 * Mathf.Deg2Rad)
							            );
							element.position = (Vector3)(newPos + center);
							vertexList [i + r] = element;
						}
					}
					//文字の位置の制御
					float[] shiftPixel = GetPixelShiftCharactor (characters [index]);
					if (shiftPixel [0] != 0 || shiftPixel [1] != 0) {
						var center = Vector2.Lerp (vertexList [i].position, vertexList [i + 3].position, 0.5f);
						for (int r = 0; r < 6; r++) {
							var element = vertexList [i + r];
							Debug.Log ("before:" + element.position.x + "," + element.position.y);
							var pos = element.position - (Vector3)center;
							var newPos = new Vector2 (
								            pos.x + shiftPixel [0],
								            pos.y + shiftPixel [1]
							            );
							element.position = (Vector3)(newPos + center);
							Debug.Log ("after:" + element.position.x + "," + element.position.y);
							vertexList [i + r] = element;
						}
					}
				}
			}
		}
	}

	bool IsNonrotatableCharactor(char character) {
		return NonRotatableCharacters.Any(x => x == character);
	}

	float[] GetPixelShiftCharactor(char character) {
		int index = System.Array.IndexOf(ShiftCharacters,character);
		float[] pixel = new float[2];
		if (0 <= index && index < ShiftXPixels.Length && index < ShiftYPixels.Length) {
			pixel[0] = ShiftXPixels[index];
			pixel[1] = ShiftYPixels[index];
		}
		return pixel;
	}
}

UnityAdsを実装してみた

はじめに

動作検証環境
MacOS:Sierra10.12.1
Unity:5.5.0
Xcode:8.1
iPhone6:iOS8.0

広告についてよく知らない、どんな種類があるの?導入する際に気をつけることは?などに関して自分の整理のために以下の記事を書きましたので興味があればご覧下さい。
madgenius.hateblo.jp

では、UnityAdsについて...
UnityAdsはUnityが提供する動画広告の仕組み、またその機能のことを指す。
Unityのゲームをプレイ中に動画視聴でゲーム内通貨プレゼントなどというものを見たことないだろうか?主にそういう実装で使用されているためユーザーへの不快感は少ない。

項目 概要
種類 動画(30秒)
収益 CPI型(Install数×広告主の設定価格)
不快感 少なめ
実装コスト 少なめ
提供 Unity

30秒の動画を視聴させユーザーが興味を持ちストアからダウンロードすればInstall数に応じた広告主の設定価格が収益として手に入る仕組みだ。
Unityが提供しているためUnityでの動作の安定性はもちろん実装コストも最低限までに短縮されている。
実装コストとユーザーへの不快感は少ないため実装することに対してのデメリットはない。
CPI型なのが収益へ繋がるか不安という方もいるだろうがUnity製アプリの広告のみが出るため主に高クオリティで動画なので音声やプレイ画面から興味を持つユーザーも多いので心配はないそうだ。
また、クリックが収益になるバナー広告などとも共存は可能。

UnityAdsダッシュボードの設定

UnityAdsのページからUnityのデベロッパーアカウントを使ってログインができる。
また、新規作成もできるので持ってない人でもここで作成可能。
f:id:nanokanato:20170417121402p:plain:w300

アカウント認証後に広告を出すアプリ情報やユーザー情報などを聞かれる場合がありますがあとから変更や削除可能です。またアプリ情報は最新版であればUnityから自動生成ができます。
f:id:nanokanato:20170417121431p:plain:w300

ログインが完了するとAdminのページになる。左は日本版サイト、右は海外用サイトだ。
広告の導入や収益の確認はコインが重なっている方を、広告を配布してユーザー獲得する場合は人型のボタンを押してください。
ここでは広告の出し方に関しては記載しません。
f:id:nanokanato:20170417121448p:plain:w300

広告を掲載しているアプリのダッシュボードです。
画像はログイン時にアプリ情報の入力があったためすでにプロジェクトが追加されています。

  1. 新しいプロジェクトのボタンからいつでも追加できます。

f:id:nanokanato:20170417121523p:plain:w300

プロジェクトの追加画面の入力内容です。プロジェクトをサイトから追加する場合に使用します。
しかし、最新版のUnityでは自動生成されるため不要なので飛ばして結構です。
画面下の13歳以下を対象にしているかのチェックを入れるとおそらく過激な表現(戦争,暴力など?)の動画などは表示されなくなります。もし、過激な表現の動画が表示OKでもアメリカ合衆国13歳以下の児童を対象にしている場合はチェックをつけましょう。
f:id:nanokanato:20170417121545p:plain:w300

以上で完了です。これだけ?って感じですね。
Unity5.1以前の場合はAsset StoreよりUnityAdsのAssetの追加と、作成したプロジェクトごとのIDなどが必要になるようですが....それより新しいバージョンだとサイトでプロジェクトを作成するだけになります。
最新版でAssetを追加するとデフォルトで入っているため競合しエラーが発生します。

UnityAdsサンプル用のプロジェクトの作成

UnityAdsを有効にするためにUnityを起動してのプロジェクトを開きましょう。
プロジェクト名は先ほどサイトで追加したAdsTestです。

ProjectのタブはSceneとScriptのみです。
UnityAdsの実装処理を書くためのUnityAdsScript.csを用意しました。
f:id:nanokanato:20170417121615p:plain:w300

Awakeメソッドの中身は公式の実装方法にはなかったですが追加することで動作するようになりました。
GameIDの部分は置き換えますが次のUnityAdsの有効化で説明します。

using UnityEngine;
using UnityEngine.Advertisements;

public class UnityAdsScript : MonoBehaviour
{
	public UnityEngine.UI.Text CountText;
	private int count = 0;

	void Awake()
	{   
		// まずはAwake()内で、初期化をします。先ほどのゲームIDを入力。
		Advertisement.Initialize ("【GameID】");
	}

	public void ShowRewardedAd()
	{
		if (Advertisement.IsReady("rewardedVideo"))
		{
			var options = new ShowOptions { resultCallback = HandleShowResult };
			Advertisement.Show("rewardedVideo", options);
		}
	}

	private void HandleShowResult(ShowResult result)
	{
		switch (result)
		{
		case ShowResult.Finished:
			Debug.Log ("The ad was successfully shown.");
			//
			// YOUR CODE TO REWARD THE GAMER
			// Give coins etc.
			count++;
			CountText.text = "視聴回数:" + count;
			break;
		case ShowResult.Skipped:
			Debug.Log("The ad was skipped before reaching the end.");
			break;
		case ShowResult.Failed:
			Debug.LogError("The ad failed to be shown.");
			break;
		}
	}
}

HierarchyはCanvasを使用し、Textを2つとButtonを配置。Buttonを押すと動画広告を表示にします。
どこでも大丈夫ですがCanvasにUnityAdsScript.csを追加して、CountTextとHierarchyのCountTextを紐付けます。これによって動画広告が視聴完了して閉じられた時にCountTextに表示している視聴回数をカウントします。
Buttonが押された時UnityAdsScript.csのShowRewardedAd()が呼ばれるように設定します。
f:id:nanokanato:20170417121644p:plain:w300

Sceneが完成しました。
Buttonの文字は「動画広告を表示」に変更。
CountTextは「視聴回数:0」に変更。
DetailTextは「↑これがポイント付与要素になる」に変更しました。
f:id:nanokanato:20170417121711p:plain:w300

UnityAdsの有効化

UnityのWindowからServicesを選択してServicesのタブを表示します。
f:id:nanokanato:20170417121758p:plain:w300

最初にログインを求められることがありますがUnityAdsのサイトのアカウントでログインしてください。
ログインするとサイトにプロジェクトが自動で追加されます。その際設定が必要なIDなども自動で入力してくれています。
サイトで行った13歳以下対象のチェックやプロジェクト名、プロジェクトIDの変更などはSERVICESのところを押すと変更可能です。サイトで作成したプロジェクトを使う場合はプロジェクトIDを変更してください。
今回はAdsを有効にするためAdsを選択しましょう。
Adsの項目があるので
f:id:nanokanato:20170417121814p:plain:w300

デフォルトでは上のスイッチがOFFになってますのでONにしてください。ONで下の項目も表示されます。
GameIDの部分は自動で生成されます。こちらもサイトで作成したプロジェクトを使う場合、置き換えてください。GameIDをUnityAdsScript.csのAwakeメソッドでの初期化に使いますので置き換えてください。
f:id:nanokanato:20170417121829p:plain:w300

動作確認

UnityEditorでもエラーになったりはしないが非対応のような専用画面が表示される。Closeを押すことで動画視聴完了のコールバックが呼ばれ画面を閉じることができる。
f:id:nanokanato:20170417121928p:plain:w300

iOSの実機ではこんな感じ
動画再生中
f:id:nanokanato:20170417121947p:plain:w300
動画再生終了
f:id:nanokanato:20170417122020p:plain:w300

コールバックが無事に呼ばれたので視聴回数が1回になった。
f:id:nanokanato:20170417122039p:plain:w300

Unity for MacでVisualStudioを使用する

UnityをWindowsでやっていた人が、Macでやり始めると思うこと?にMonoDevelopってなんだ?ってのがあるかと思い調べてみた。
MacだとVisualStudioではなくMonoDevelopなので使い勝手がいいほうのエディターに変えたいという人もいるだろう。

Visual Studioをインストール

まず、MacにはVisualStudioはない....(完)
だが、VisualStudioCodeというEditorがあるのでそちらを使うことにする。

サイトのトップよりダウンロードができるのでインストールする。
f:id:nanokanato:20170417115610p:plain:w300

単品で使った場合、WindowsのVisualStadioとはレイアウトなどが違うが、使い勝手や機能は大体同じのようだ。

Unityと連携させるために...

Unityで普通にEditorを変更させようとしたがVisualStudioCodeが起動してそこからMonoDevelopが起動してしまう。

調べているとこんなものが…
VisualStadioCodeをUnityに追加するためのScriptらしい。
VSCode - Unity Visual Studio Code Integration

これをAssets以下に追加する
f:id:nanokanato:20170417115716p:plain:w300

Editorを変更

UnityのツールバーからUnity > Preferencesを選択。
f:id:nanokanato:20170417115749p:plain:w300

PreferencesのWindowが開く
VSCode.csを追加したのでVSCodeという欄が増えている
f:id:nanokanato:20170417115818p:plain:w300

External Toolsを選択してEditor AttachingをCodeに変更
もしくはBrowse...からVisualStudioCodeを選択する。
f:id:nanokanato:20170417115836p:plain:w300

これで連携が完了です。EditorがVisualStudioCodeになりました。

インテリセンス(自動補完)を利用する

VisualStudioCodeをEditorに設定し無事Scriptを編集できたのですが、MonoDevelopにはあるインテリセンスがなかったので設定する。

左側のタブから拡張機能( f:id:nanokanato:20170417115947p:plain:w12 )を選択。OmniSharpで検索してインストールしてください。
f:id:nanokanato:20170417120024p:plain:w300

使い心地

Editorとしては最高だと思います。

  • 拡張機能でインストールすることで多言語対応
  • Windows,Mac,LinaxなどのOSで利用でき、OS間の操作感の違いを吸収
  • Microsoftや利用者の更新頻度が高く、機能拡張が増えている。
  • Methodやif,forなどで折りたたむことができる
  • 左右でエディターの分割ができる。(コピペや他プロジェクトとの比較が楽)
  • 上書き保存していないScriptに応じて左上にバッジ表示(自動保存もあり)
  • インテリセンスは機能拡張から追加できるので独自のものも可能?

など...

f:id:nanokanato:20170417120050p:plain:w300

しかし、Unityで使うには以下の点よりまだ早いようです。

  • Projectに常にVSCode.csを追加しないといけない
  • Debug時にBuild構成ファイルやタスクランナーの構成が必要
  • ブレークポイントの設定がし辛い?
  • Unityでの利用者が少ないので何か起こると困るしかない

それでも多くのOSと多言語に対応しているMicrosoftのフリーエディタですのでどこかのすごい人が解決していそうです。
解決策があればぜひ教えていただきたいです。