...

Editor拡張マニアクス2014

by user

on
Category: Documents
187

views

Report

Comments

Transcript

Editor拡張マニアクス2014
Editor拡張マニアクス2014
チュートリアル
ハンズオン
安藤 圭吾!
フィールドエンジニア!
@kyusyukeigo
県人会議
Unity部
ドキュメント
執筆
この資料について
• 発表した内容を全て文字にしたものです!
• Unite Japan後の見直し用としてどうぞ
今日の流れ
• Editor拡張を作る側の目線が多めです!
• 本当にタメになるのか
なものまで紹介します!
• でもそういう話大好き!
• 使う側の人も楽しめるようにはしてるつもりです!
!
• 私が事前に作ったアセットを中心に紹介&解説 ( link )!
• Unity4.5やUnity5についても少しだけ紹介!
• あくまでEditor拡張の部分として
謝辞
• Facebookグループ「日本Androidの会 Unity部」!
• ここで今回のネタを募集しました!
• 協力してくださった皆様ありがとうございました!
Unity部
unity package preview
unity packageにプレビュー画像をつける
• プレビュー画像!
• 普段はアセットストアにある、
パッケージをインポートする時
に使用される部分!
• なのでEditor APIでプレビューを
付けることは考えられていない
unity packageにプレビュー画像をつける
• パッケージは配布目的が多い!
• アセットストアに出ていない(出せない)SDKの配布目的!
• 広告とかバックエンドサービスとか色々
unity packageにプレビュー画像をつける
• パッケージは解凍出来る!
• 解凍: tar -zxpf unitychan.unitypackage -C unitychan!
• 圧縮: tar -zcpf ../unitychan.unitypackage .!
!
• プレビュー画像!
• .icon.png!
• 128 x 128
GlobalGameManager
GlobalGameManager
• Project Settingsと同じ扱い方をしたい!
• でもそれは難しい!
• 「Project Settings」フォルダとのやりとりが複雑!
• Editor上でアクセスする時にどうする?!
• ビルドする時にどうする?
GlobalGameManager
• Resourcesフォルダ内に作成!
• ScriptableObjectを作成する!
• Editor上でもビルド後でも簡単
にアクセスできる!
• あとはどこまで自動化出来るか
GlobalGameManager
• ScriptテンプレートからGameManagerの作成
• JS, C# , Booと同じテンプレート!
• Assets/ScriptTemplatesフォルダ!
• ここに作成すること!
• フォーマットは?!
• Unity.app/Contents/Resources/
ScriptTemplatesを参照
GlobalGameManager
• 自動化 : マネージャースクリプトの存在チェック
[InitializeOnLoad]
public class Creator
{
static Creator ()
{
foreach (var monoScript in MonoImporter.GetAllRuntimeMonoScripts())
{
var type = monoScript.GetClass();
!
if (type == null)
{
continue;
}
if (type.IsSubclassOf(typeof (GlobalGameManagerObject)) == false)
)
{
continue;
}
!
!
if (ExistGlobalGameManager(type) == false)
{
GlobalGameManager(type);
}
if (ExistGameManagerInspector(type) == false)
{
GlobalGameManager
• 自動化 : マネージャースクリプトの存在チェック!
• スクリプトに何らかの変化があったらコンパイルされる!
• コンパイル後にはInitializeOnLoadが呼び出される!
• その時にMonoImporter.GetAllRuntimeMonoScripts()!
• Assembly-CSharp.dll にあるMonoScriptを全て取得!
• これでマネージャークラスが存在するかチェックする
GlobalGameManager
• 自動化 : CustomEditor スクリプトの作成
private static void GlobalGameManagerInspector(Type type)
{
#if UNITY_EDITOR
var script = new StringBuilder();
script.AppendLine("using UnityEditor;");
script.AppendLine("using UnityEngine;");
script.AppendLine("using System.Collections;");
script.AppendLine("");
script.AppendLine("[CustomEditor(typeof(#NAME#))]");
script.AppendLine("public class #NAME#Inspector : GlobalGameManagerInspector");
script.AppendLine("{");
script.AppendLine("
[MenuItem(\"Edit/Project Settings/#NAME#\")]");
script.AppendLine("
private static void ShowManagerSettings()");
script.AppendLine("
{");
script.AppendLine("
Show<#NAME#>();");
script.AppendLine("
}");
script.Append("}");
!
}
var content = Regex.Replace(script.ToString(), "#NAME#", type.Name);
var path = GetGlobalGameManagerInspectorPath(type);
File.WriteAllText(path, content);
AssetDatabase.ImportAsset(path);
#endif
GlobalGameManager
• 自動化 : CustomEditor スクリプトの作成!
• スクリプトはテキストベースのアセット!
• File.WriteAllText(path, contents) で書き込む!
• 作成した後のインポート処理が走らない!
• AssetDatabase.ImportAsset( path ) を使用する!
• ImportしないとLoadAssetAtPathでNullになる!
• AssetDatabase.CreateAssetなら自動でインポート処理が走る
MultiSpriteEditor
MultiSpriteEditor
• 複数のスプライトのスライスを一度に行う!
• スプライトエディターは複数のスライスが出来ない!
!
• 既にスプライトのサイズなんて仕様で決まってる!
• とにかく一度にスライスしたい
MultiSpriteEditor
• ウィンドウはScriptableWizard!
• Automaticは無しでGridのみ!
• offsetとpaddingも対応!
• たぶんうまく動く
MultiSpriteEditor
• 何かを作成する時はScriptableWizard!
• 条件を満たさない時は作成できない
MultiSpriteEditor
• 自前でスライスする
public Rect[] GenerateGridSpriteRectangles(Texture2D texture)
{
var rects = new List<Rect>();
for (var y = (int)offset.y; y + (int)pixelSize.y <= texture.height; y += (int)pixelSize.y)
{
for (var x = (int)offset.x; x + (int)pixelSize.x <= texture.width; x += (int)pixelSize.x)
{
!
var rect = new Rect(x, texture.height - y - pixelSize.y, pixelSize.x, pixelSize.y);
!
if (IsAlphaTexture(texture, rect) == false)
{
rects.Add(rect);
}
!
x += (int)padding.x;
}
!
y += (int)padding.y;
}
!
return rects.ToArray();
}
!
MultiSpriteEditor
• 入力した値でRectを作成する!
• これが実際に切り分ける部分!
• 切り分ける時に何も考えないでやるとアルファのみのスプライ
トが作成される!
• 「このRect内はアルファのみ」というのをどう判断するか?!
• Texture2D.GetPixelsを使用する!
• でもGetPixelはTextureのisReadableが有効じゃないといけない!
• ではどうするか
MultiSpriteEditor
• isReadableを強制的に有効にして、すぐ戻す
public void DoSlicing()
{
var _textures = m_selectedTextures;
for (var i = 0; i < _textures.Length; i++)
{
var tex = _textures[i];
var path = AssetDatabase.GetAssetPath(tex);
var importer = (TextureImporter)AssetImporter.GetAtPath(path);
var isReadable = importer.isReadable;
importer.isReadable = true;
EditorUtility.SetDirty(importer);
AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate);
var rects = GenerateGridSpriteRectangles(tex);
EditorUtility.DisplayProgressBar(tex.name, string.Format("{0}/{1}", i, _textures.Length),
(float)i / (float)_textures.Length);
!
importer.isReadable = isReadable;
!
importer.spritesheet = rects.Select((t, j) => new SpriteMetaData
{
alignment = (int)pivot,
MultiSpriteEditor
• isReadableを強制的に有効にして、すぐ戻す!
• スライスするときだけisReadableを有効にする!
• AssetDatabase.ImportAsset で ImportAssetOptions.ForceUpdate!
• 強制的に再インポートを行うもの!
• スライスが終わったらisReadableを元の設定に戻す!
• インポート処理が走り多少時間はかかる!
• でも普段のスプライトエディターでポチポチやるよりは良い
Overwriter
Scroll View
Scroll View
• GUIエレメントが多い場合はスクロールビューを使って
EditorWindow内に全てGUIエレメントを表示する事が多い!
• 例えば3000∼10000個のエレメントを同時に表示させるとどうな
るか!
• GUILayoutで表示させると重い。めっちゃ重い。!
• スクロールして見えない部分があるとしても描画はされている!
• よく、ビューワー系がこの現象に陥る!
• 私のプロジェクトでは3000のテクスチャがあるんだ!
Scroll View
• 見えないところは描画しない!
GUILayout.Space
• GUILayout.Spaceで穴埋め!
• かなり速度が違う
GUILayout.Space
Sync
Overwriter
Camera
Sync Camera
• シーンビューのカメラとゲームのカメラを同期させる
[InitializeOnLoad]
public class SyncCamera
{
!
static int selected;
static Rect windowRect = new Rect (10, 20, 100, 24);
!
static SyncCamera ()
{
SceneView.onSceneGUIDelegate += (sceneView) => {
!
var cameras = Object.FindObjectsOfType<Camera> ();
!
Handles.BeginGUI ();
!
GUI.skin = EditorGUIUtility.GetBuiltinSkin (EditorSkin.Inspector);
!
int windowID = EditorGUIUtility.GetControlID (FocusType.Passive, windowRect);
!
windowRect = GUILayout.Window (windowID, windowRect, (id) => {
!
string[] displayNames = new string[]{"None",""} ;
ArrayUtility.AddRange(ref displayNames,cameras.Select<Camera, string> (c => c.name).ToArray());
selected = EditorGUILayout.Popup (selected, displayNames);
Sync Camera
• シーンビューのカメラとゲームのカメラを同期させる!
• 非推奨のSceneViewクラスを使用する!
• onSceneGUIDelegateというデリゲートを使う!
!
• 同期の方法は簡単!
• シーンビューのカメラを取得して位置と角度を同期してる
Overwriter
Overwriter
• あるある話!
• 同じ名前のアセットをインポートする時、別のものとして判断!
• 名前の末端に数字がついてしまう!
• なので!
• 元のアセットを削除してインポート
_人人人人人人人人_
Missing
> <
 ̄Y^Y^Y^Y^Y^Y^Y ̄
Overwriter
• あるある話!
• Missingを起こさないために!
• インポート設定や参照を維持するためにエクスプローラー上
で上書きを行う
• こうすることで設定が維持
される
Overwriter
• あるある話!
• エクスプローラー上での操作に慣れた人たちはフォルダ名の変
更もやってしまう
• そのフォルダ以下のアセッ
トが別物としてインポート
される
_人人人人人人人人_
> 大量のMissing <
 ̄Y^Y^Y^Y^Y^Y^Y ̄
Overwriter
• 現状Unityでファイルの上書きはサポートしていない!
• でも何とかしたい
public class AssetPostprocessor : UnityEditor.AssetPostprocessor
{
static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] mov
{
var overwriter = Preference.GetOverwriter();
foreach (var assetPath in importedAssets)
{
var extension = Path.GetExtension(assetPath);
if (!overwriter.all && !overwriter.extensions
.Where(ex => ex.enabled)
.Select(ex => ex.extension)
.Contains(extension)) continue;
!
const string pattern = "\\s[\\d]+\\.(.*)$";
!
if (!Regex.IsMatch(assetPath, pattern)) continue;
!
var _assetPath = Regex.Replace(assetPath, pattern, ".$1");
File.Copy(assetPath, _assetPath, true);
!
Overwriter
• Unityの管理下で操作を行うと全部リネームされてしまう!
• 「Hoge」が「Hoge 1」「Hoge 2」「Hoge 3」というように!
• これはどうしようもない!
• なので!
• リネームされたものをUnity管理外のFile.Copyを使って元のア
セットへ上書きコピーする!
• そのあと強制的にインポート!
• こうすることでインポート設定や参照はそのままになる
Prefab
Overwriter
Extension
Prefab Extension
• プレハブを少しだけ扱いやすくするための拡張
// こっちはプレハブ
public GameObject player;
!
// こっちはゲームオブジェクト
public GameObject enemy;
Prefab Extension
• コードだけ見るとどっちなのかがわからない!
• シーン上のゲームオブジェクトなの?!
• アセットのプレハブなの?!
• 今回はPrefabクラス作って解決してみた
public class Example : MonoBehaviour
{
public Prefab prefab;
}
[System.Serializable]
public class Prefab
{
public GameObject gameObject;
}
Prefab Extension
• Prefabクラスを作るのは簡単だけど問題はインスペクター!
• 見た目をゲームオブジェクトと同じに!
• プレハブだけを受け付けるようにしなければいけない
そのまま
見た目変更
Prefab Extension
• 見た目を変えるのにPropertyDrawerを使う
[CustomPropertyDrawer(typeof(Prefab))]
public class PrefabDrawer : PropertyDrawer
{
public override void OnGUI (Rect position, SerializedProperty property, GUIContent label)
{
Object obj = null;
!
var gameObjectProperty = property.FindPropertyRelative ("gameObject");
!
if (gameObjectProperty != null) {
obj = gameObjectProperty.objectReferenceValue;
}
!
EditorGUI.BeginChangeCheck ();
!
var prefab = EditorGUI.ObjectField (position, label, obj, typeof(GameObject), true) as Gam
!
if (EditorGUI.EndChangeCheck ()) {
!
Prefab Extension
• 見た目を変えるのにPropertyDrawerを使う!
• 特定のフィールドをカスタマイズするだけなら断然こっち!
• PropertyDrawerの説明は割愛。ドキュメント見てね ( link )!
• 見た目のGUIはEditorGUI.ObjectField!
• Prefabかどうかの判断!
• PrefabUtility.GetPrefabType!
• プレハブならSerializedPropery.objectReferenceValueに格納
Prefab Extension
• 複数のプレハブをまとめてApplyする
public static void ApplyPrefabState (this GameObject prefabInstance, bool force = true)
{
var prefab = PrefabUtility.GetPrefabParent (prefabInstance) as GameObject;
!
if (prefab == null) {
Debug.LogException (new System.NullReferenceException ());
return;
}
!
bool updatePrefab = true;
!
if (force == false) {
updatePrefab = EditorUtility.DisplayDialog ("Apply", "Do you want to change the Prefab?", "Yes", "No");
}
!
if (updatePrefab) {
PrefabUtility.ReplacePrefab (prefabInstance, prefab, ReplacePrefabOptions.ConnectToPrefab);
}
}
Prefab Extension
• 複数のプレハブをまとめてApplyする!
• Unityではインスペクター上にゲームオブジェクトからPrefabへ
更新を反映させるためのボタンがある!
• 複数の更新には対応していない。めんどい!
• 自分で更新をする仕組みを作ってみる。どうするか
Prefab Extension
• 複数のプレハブをまとめてApplyする!
• プレハブのApplyの仕組み!
• PrefabUtility.ReplacePrefabしてるだけ!
• なのであとはメニューから実行できるようにしてあげるだけ
[MenuItem("PrefabExtension/Apply Prefab State")]
static void ApplyPrefabState ()
{
Selection.gameObjects.ApplyPrefabState (false);
}
!
[MenuItem("PrefabExtension/Apply Prefab State", true)]
static bool ValidateApplyPrefabState ()
{
return Selection.gameObjects.All (prefabInstance => prefabInstance.IsPrefabInstance ());
}
Reference
Overwriter
Viewer
Reference Viewer
• アセットの参照関係を見るためのビューワー!
• 「Find References In Scene」の Project 版!
!
• このプレハブはどこで使われてる?!
• このプレハブには何が使われてる?!
• このシーン内には何のアセットが使われてる?
Reference Viewer
• ひたすら参照チェック!
• 自分で書いたスクリプトやコンポーネントを取得してチェック!
• 結構単純作業!
• ただし、特定のアセットは異なる処理を書かなければいけない!
• プレハブ!
• シーンファイル!
• アニメーションコントローラー!
• マテリアル
Reference Viewer
• プレハブの参照チェック!
• AssetDatabase.LoadAssetAtPathで取得したプレハブ
(GameObject)でGetComponentは動かない!
• 1度シーン上に生成したゲームオブジェクトでチェックを行う
var prefab = AssetDatabase.LoadAssetAtPath(assetPath, typeof(GameObject));!
var go = (GameObject)PrefabUtility.InstantiatePrefab(prefab);!
!
∼∼∼ 何か処理 ∼∼∼!
!
Object.DestroyImmediate(go);!
Reference Viewer
• シーンの参照チェック!
• シーン上の全てのゲームオブジェクトの参照チェック!
• EditorApplication.OpenSceneでシーンを開く!
• Object.FindObjectsOfType(typeof(GameObject))!
• 全ゲームオブジェクトを取得
Reference Viewer
• アニメーションコントローラーの参照チェック!
• AnimationControllerをいじるAPIはUnityEditorInternalにある!
• Unity5.0からはUnityEditor.Animationsに移動!
!
• 以下を考慮して参照チェックしなければいけない!
• レイヤー!
• ステートマシン!
• ブレンドツリー
Reference Viewer
• マテリアルの参照チェック!
• マテリアルは大きく2つの参照チェックが必要!
• シェーダー / テクスチャ!
• テクスチャの取得!
• プロパティ名からテクスチャ取得!
• Material.GetTexture ( propertyName );!
• シェーダーのプロパティ名を知る!
• ShaderUtil.GetPropertyName ( shader , index );
ReorderableList
Overwriter
ReorderableList
• 配列の順番を楽に変更できる仕組み!
• これはリフレクションを使っているためかなり非推奨なもの!
• でも便利!
• Unity5からUnityEditorInternalに入ってる!
• 少しだけ扱いやすくなる!
!
• リフレクションを使うが、かなり便利なものはある!
• ようこそ闇の世界へ
Overwriter
Sprite Field
Sprite Field
• SpriteのためのObjectField!
• ObjectFieldはTexture系に対応しているがSpriteはしていない!
• EditorGUILayout.ObjectField(null,typeof(Sprite),false)
理想
現実
Sprite Field
• GUIを作る!
• GUIを作るのに必要なものはいくつかある!
• GUIStyle.Draw ー> GUIの描画部分!
• コントロールID!
• 様々なものにコントロールIDが割り振られている!
• コントロールIDを正確に扱うことによってGUI操作が可能
Sprite Field
• コントロールID!
!
var id = GUIUtility.GetControlID(FocusType.Keyboard, rect);
!
• この場合はキーボード操作を受け付けるコントロールIDを取得
している!
• コントロールIDと描画部分の紐付け!
EditorStyles.objectFieldThumb.Draw(rect,
content,
id,
false);
!
• これにより、フォーカスが当たった時の描画が行われる
Sprite Field
• コントロールID!
• キーボードのコントロールIDで描画するとTabキーで移動する!
• 移動によって現在フォーカスされているコントロールID!
• EditorGUIUtility.keyboardControl!
!
• キーボードのコントロールIDとキー入力を組み合わせる!
• そのGUIに対してのみのアクションが出来る!
• これがテキストフィールドやオブジェクトフィールドの仕組み
AssetBundles
Overwriter
AssetBundles
• Unity5で新しくなるアセットバンドルの仕組み!
• 今できること!
• アセットのラベルみたいにバンドル名を付けることが出来る!
• BuildPipeline.BuildAssetBundlesでサクッと作れる!
• 依存関係勝手に解決してくれる!
• マニフェストファイルが作られる!
• この中にいろんな情報が詰まってる!
AssetBundles
• 自動的にバンドル名を付ける
. static void OnPostprocessAllAssets (string[] importedAssets, string[] deletedAssets, string[] movedAssets, st
{
string[] results = Directory.GetDirectories ("Assets", "AssetBundles", SearchOption.AllDirectories).Where (
foreach (var assetBundlesFolder in results) {
foreach (var assetPath in AssetDatabase.FindAssets("", new []{assetBundlesFolder}).Select<string,string>
if (Directory.Exists (assetPath))
continue;
!
var assetBundleName = Path.GetDirectoryName (assetPath).Replace (assetBundlesFolder, "");
if (string.IsNullOrEmpty (assetBundleName))
assetBundleName = "___root___";
assetBundleName = Regex.Replace (assetBundleName, "^/", "");
AssetImporter.GetAtPath (assetPath).assetBundleName = assetBundleName;
}
}
AssetDatabase.RemoveUnusedAssetBundleNames ();
}
AssetBundles
• 自動的にバンドル名を付ける!
• AssetBundlesフォルダ以下のアセットに自動で付ける!
• インポート時にバンドル名を付ける!
• スラッシュ(” / ”)で階層になる
AssetBundles
• アセットバンドルの設定!
• ビルドしたいものにチェックを付ける!
• 手動でアセットバンドルのビルド出来る!
• プレイヤーをビルドした後にも自動ビルド出来る!
• UnityEditor.Callbacks.PostProcessBuildを使用する!
• プレイヤーのビルド後に呼び出される
Fly UP