GridBrushBase (または GridBrush など GridBrushBase の使いやすいサブクラス) から継承して新しいクラスを作成します。この新しい Brush クラスに必要なメソッドをすべてオーバーライドします。通常は以下のメソッドをオーバーライドします。
Paint は、ブラシがターゲットグリッドにアイテムを追加できるようにします。Erase は、ブラシがターゲットグリッドからアイテムを削除できるようにします。FloodFill は、ブラシがターゲットグリッドをアイテムで塗りつぶせるようにします。Rotate は、ブラシ内に設定されたアイテムを回転できるようにします。Flip は、ブラシ内に設定されたアイテムを反転できるようにします。
ScriptableObject.CreateInstance<(Your Brush Class>() を使用して新しいクラスのインスタンスを作成します。AssetDatabase.CreateAsset() を呼び出すと、エディター上でこの新規インスタンスをアセットに変換でき、繰り返し使用できるようになります。
ブラシ用にカスタムエディターを作成することもできます。これは、ScriptableObjects のカスタムエディターと同様に機能します。カスタムエディターを作成する場合にオーバーライドする主なメソッドは以下の通りです。
OnPaintInspectorGUI をオーバーライドすると、ブラシ選択中のペイントの追加動作を提供する Inspector ウィンドウをパレット上に表示できます。OnPaintSceneGUI をオーバーライドすると、SceneView 上でのペイントに追加動作を設定できます。validTargets をオーバーライドすると、ブラシが相互作用できるカスタムのターゲットリストを作成できます。このターゲットリストは Tile Palette ウィンドウにドロップダウンリストで表示されます。作成されたスクリプタブルブラシは Tile Palette ウィンドウの ブラシ ドロップダウンメニューに表示されます。デフォルトでは、スクリプタブルブラシのスクリプトのインスタンスはインスタンス化されると、プロジェクトの Library フォルダー内に保存されます。ブラシのプロパティに加えられた変更はそのインスタンス内に保存されます。複数のブラシのコピーに異なるプロパティを設定したい場合は、プロジェクト内にアセットとしてブラシをインスタンス化できます。これらのブラシアセットは、ブラシドロップダウンメニューのリストで個々に表示されます。
Scriptable Brush クラスには CustomGridBrush 属性を追加することができます。これにより、Tile Palette ウィンドウ内でのブラシの動作を設定できます。CustomGridBrush 属性には以下のプロパティがあります。
HideAssetInstances - true に設定すると、作成されたブラシアセットのすべてのコピーが Tile Palette ウィンドウ内で非表示になります。Tile Palette ウィンドウのブラシドロップドロップダウンメニューにデフォルトのインスタンスのみを表示したい場合に便利です。HideDefaultInstances - true に設定すると、デフォルトブラシのインスタンスが Tile Palette ウィンドウで非表示になります。作成したアセットのみを Tile Palette ウィンドウのブラシドロップダウンメニューに表示したい場合に便利です。DefaultBrush - true に設定すると、デフォルトブラシのインスタンスがプロジェクトのデフォルトのブラシに設定されます。これにより、このブラシのインスタンスは、プロジェクト起動時に毎回デフォルトで選択されるブラシとして設定されます。ノート:デフォルトブラシに設定するスクリプタブルブラシは 1 つのみにしてください。デフォルトブラシを複数設定すると、スクリプタブルブラシが正しく動作しない場合があります。DefaultName - ここに名前を設定すると、ブラシドロップダウンメニューにブラシのクラス名ではなく、設定した名前がブラシの名前として使用されます。スクリプタブルブラシクラスで特定のツールのみを使用したい場合は、互換性のある TilemapEditorTools 型のリストを使用してクラスに BrushTools 属性を追加します。これにより、スクリプタブルブラシは Tile Palette ツールバーの特定のツールでのみアクティブになります。
LineBrush を使用すると、開始点と終了点を指定することで、タイルマップ上に簡単にタイルの線を描画することができます。LineBrush のペイント方法をオーバーライドすると、Paint モード中の最初のマウスクリックで線の開始点を指定し、2 回目のマウスクリックで線を描画できるようになります。OnPaintSceneGUI メソッドをオーバーライドすると、最初のクリックと 2 回目のクリックの間に線のプレビューを表示できます。以下はブラシの作成に使用するスクリプトの例です。
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Tilemaps;
namespace UnityEditor.Tilemaps
{
[CustomGridBrush(true, false, false, "Line Brush")]
public class LineBrush : GridBrush {
public bool lineStartActive = false;
public Vector3Int lineStart = Vector3Int.zero;
public override void Paint(GridLayout grid, GameObject brushTarget, Vector3Int position)
{
if (lineStartActive)
{
Vector2Int startPos = new Vector2Int(lineStart.x, lineStart.y);
Vector2Int endPos = new Vector2Int(position.x, position.y);
if (startPos == endPos)
base.Paint(grid, brushTarget, position);
else
{
foreach (var point in GetPointsOnLine(startPos, endPos))
{
Vector3Int paintPos = new Vector3Int(point.x, point.y, position.z);
base.Paint(grid, brushTarget, paintPos);
}
}
lineStartActive = false;
}
else
{
lineStart = position;
lineStartActive = true;
}
}
[MenuItem("Assets/Create/Line Brush")]
public static void CreateBrush()
{
string path = EditorUtility.SaveFilePanelInProject("Save Line Brush", "New Line Brush", "Asset", "Save Line Brush", "Assets");
if (path == "")
return;
AssetDatabase.CreateAsset(ScriptableObject.CreateInstance<LineBrush>(), path);
}
// http://ericw.ca/notes/bresenhams-line-algorithm-in-csharp.html
public static IEnumerable<Vector2Int> GetPointsOnLine(Vector2Int p1, Vector2Int p2)
{
int x0 = p1.x;
int y0 = p1.y;
int x1 = p2.x;
int y1 = p2.y;
bool steep = Math.Abs(y1 - y0) > Math.Abs(x1 - x0);
if (steep)
{
int t;
t = x0; // swap x0 and y0
x0 = y0;
y0 = t;
t = x1; // swap x1 and y1
x1 = y1;
y1 = t;
}
if (x0 > x1)
{
int t;
t = x0; // swap x0 and x1
x0 = x1;
x1 = t;
t = y0; // swap y0 and y1
y0 = y1;
y1 = t;
}
int dx = x1 - x0;
int dy = Math.Abs(y1 - y0);
int error = dx / 2;
int ystep = (y0 < y1) ? 1 : -1;
int y = y0;
for (int x = x0; x <= x1; x++)
{
yield return new Vector2Int((steep ? y : x), (steep ? x : y));
error = error - dy;
if (error < 0)
{
y += ystep;
error += dx;
}
}
yield break;
}
}
[CustomEditor(typeof(LineBrush))]
public class LineBrushEditor : GridBrushEditor
{
private LineBrush lineBrush { get { return target as LineBrush; } }
public override void OnPaintSceneGUI(GridLayout grid, GameObject brushTarget, BoundsInt position, GridBrushBase.Tool tool, bool executing)
{
base.OnPaintSceneGUI(grid, brushTarget, position, tool, executing);
if (lineBrush.lineStartActive)
{
Tilemap tilemap = brushTarget.GetComponent<Tilemap>();
if (tilemap != null)
tilemap.ClearAllEditorPreviewTiles();
// Draw preview tiles for tilemap
Vector2Int startPos = new Vector2Int(lineBrush.lineStart.x, lineBrush.lineStart.y);
Vector2Int endPos = new Vector2Int(position.x, position.y);
if (startPos == endPos)
PaintPreview(grid, brushTarget, position.min);
else
{
foreach (var point in LineBrush.GetPointsOnLine(startPos, endPos))
{
Vector3Int paintPos = new Vector3Int(point.x, point.y, position.z);
PaintPreview(grid, brushTarget, paintPos);
}
}
if (Event.current.type == EventType.Repaint)
{
var min = lineBrush.lineStart;
var max = lineBrush.lineStart + position.size;
// Draws a box on the picked starting position
GL.PushMatrix();
GL.MultMatrix(GUI.matrix);
GL.Begin(GL.LINES);
Handles.color = Color.blue;
Handles.DrawLine(new Vector3(min.x, min.y, min.z), new Vector3(max.x, min.y, min.z));
Handles.DrawLine(new Vector3(max.x, min.y, min.z), new Vector3(max.x, max.y, min.z));
Handles.DrawLine(new Vector3(max.x, max.y, min.z), new Vector3(min.x, max.y, min.z));
Handles.DrawLine(new Vector3(min.x, max.y, min.z), new Vector3(min.x, min.y, min.z));
GL.End();
GL.PopMatrix();
}
}
}
}
}