タイルマップ上の特定のタイルの位置にオブジェクトを生成し、元のタイルを消すエディタ拡張。
おまけで、自作CharacterController2Dと連動する移動床のコードも。
サンプル動画
全体的な流れ。
ちゃんと変換元タイルが削除されているのが確認出来る。
(音なし)
変換元タイル画像
- ライセンス: CC0
- コードの都合上、ファイル名はそのままで使用してください。
使用方法
- 「変換元タイル画像」をProjectウィンドウに、ドラッグ&ドロップしてインポート。
- Pixel Per Unit: 32に設定してApply
- 「インポートしたスプライト」を、メインで使っているTile Paletteに、ドラッグ&ドロップ。
- 保存フォルダを選びタイルを作成。
- タイルマップ上の移動床を出したい位置に「変換元タイル」を書き込む。
(移動範囲の中心点になる) - ↓のコードをコピペし、各スクリプト&移動床のプレハブを作成しておく。
- 対象のTilemapを右クリック -> 「Convert Moving Platform」
- Tilemap下に移動床が出現するので、インスペクターからサイズや移動距離等を調整。
コード
ConvertMovingPlatformEditor
Tilemapコンポーネントを右クリックした時に、メニューが追加されるエディタ拡張。
- Editorフォルダ下に、新規スクリプト「ConvertMovingPlatformEditor」を作成し、コードを全文コピペ。
(Editorフォルダがない場合は新規作成)
using UnityEngine;
using UnityEngine.Tilemaps;
using UnityEditor;
public class ConvertMovingPlatformEditor : EditorWindow
{
//判定用のスプライト名。
static readonly string HorizontalMovingPlatformName = "horizontal-moving-platform";
static readonly string VerticalMovingPlatformName = "vertical-moving-platform";
//移動床の出現位置がタイルとズレてしまうのを補正。
static readonly Vector3 SpawnOffset = new Vector3(0.5f, 0.5f, 0);
[MenuItem("CONTEXT/Tilemap/Convert Moving Platform")]
static void ConvertMovingPlatform(MenuCommand command)
{
Tilemap tilemap = (Tilemap)command.context;
TileBase tile;
Vector3Int checkPosition = Vector3Int.zero;
MovingPlatform spawnedMovingPlatform;
GameObject movingPlatformPrefab = AssetDatabase.LoadAssetAtPath<GameObject>("Assets/Prefabs/MovingPlatform.prefab");
for (int i = tilemap.origin.x; i < tilemap.size.x; i++) {
for (int j = tilemap.origin.y; j < tilemap.size.y; j++) {
checkPosition.Set(i, j, 0);
tile = tilemap.GetTile(checkPosition);
//GetTileした位置に何もない場合はnullになるので弾く。
if (tile != null) {
//タイル名は元のスプライトのアセット名になる。
if (tile.name == HorizontalMovingPlatformName) {
spawnedMovingPlatform = Instantiate(movingPlatformPrefab, tilemap.transform).GetComponent<MovingPlatform>();
spawnedMovingPlatform.direction = MovingPlatform.Direction.Horizontal;
spawnedMovingPlatform.tf.position = tilemap.CellToWorld(checkPosition) + SpawnOffset;
//エディタのVerが2019以下の場合、削除用メソッドがないので、空のタイルで上書き。
tilemap.SetTile(checkPosition, new Tile());
//エディタのVerが2020以上なら、多分こっちでも消せる。
// tilemap.DeleteCells(checkPosition, Vector3Int.one);
} else if (tile.name == VerticalMovingPlatformName) {
spawnedMovingPlatform = Instantiate(movingPlatformPrefab, tilemap.transform).GetComponent<MovingPlatform>();
spawnedMovingPlatform.direction = MovingPlatform.Direction.Vertical;
spawnedMovingPlatform.tf.position = tilemap.CellToWorld(checkPosition) + SpawnOffset;
//エディタのVerが2019以下の場合、削除用メソッドがないので、空のタイルで上書き。
tilemap.SetTile(checkPosition, new Tile());
//エディタのVerが2020以上なら多分こっちでも消せる。
// tilemap.DeleteCells(checkPosition, Vector3Int.one);
}
}
}
}
}
}
MovingPlatform
移動床プレハブのメインスクリプト。
- 移動床プレハブの元となる空オブジェクト「MovingPlatform」を作成。
- Rigidbody2Dコンポーネントをアタッチ。
- タグとレイヤーをGroundに設定。
(無い場合は追加しつつ)
- 子の位置に、新規オブジェクト「PlatformsS」「PlatformsM」「PlatformsL」を作成。
(Sサイズから順で) - 「PlatformsS」を右クリック -> 2D Object -> Spriteを作成し、「Platform」と命名。
- 移動床のスプライトを設定。
- Box Collider 2D等で当たり判定を設定。
- Duplicateで複製しまくって、各Platformsサイズ毎に任意の個数設置。
(S: 1, M: 3, L: 5等々) - 新規スクリプト「MovingPlatform」を作成し、コードを全文コピペ。
- 「MovingPlatform」に「MovingPlatform」スクリプトをアタッチ。
- ProjectウィンドウのAssets/Prefabsの位置に、ドラッグ&ドロップしてプレハブ化。
(Prefabsフォルダを作っていない場合は新規作成)
using UnityEngine;
public class MovingPlatform : MonoBehaviour
{
public GameObject go;
public Transform tf;
[SerializeField]
Rigidbody2D rb2d;
public enum Direction {
Horizontal,
Vertical,
}
public Direction direction;
//個数はplatformGosの要素数と一致させておく。
public enum SizeType {
S,
M,
L,
}
//一応デフォルトサイズはMサイズにしてある。
public SizeType sizeType = SizeType.M;
[Header("移動距離 (設置位置を基準として移動)")]
[Range(1.0f, 100.0f)]
[SerializeField]
float movingDistance = 10.0f;
[Header("移動速度 (秒速)")]
[Range(1.0f, 100.0f)]
[SerializeField]
float speed = 10.0f;
//Time.fixedDeltaTimeを定数化した物。
static readonly float FixedDeltaTime = 0.02f;
//サイズ別の足場(小さい物順)。コンポーネントのアタッチ時に自動取得されるようにしたが、上手くいかなかった場合は手動で紐付けしてください。
[SerializeField]
GameObject[] platformGos;
Vector3 leftmostOrBottommostPosition;
Vector3 rightmostOrTopmostPosition;
Vector3 nextPosition;
Vector3 velocity;
//プレイヤーとの衝突ノーマルが、(真上を中心として)この角度以下だと上に乗られたと見なす。
static readonly float PlatformNormalAngleLimit = 30.0f;
static readonly string PlayerTag = "Player";
ContactPoint2D[] playerContacts = new ContactPoint2D[3];
ContactFilter2D playerContactFilter;
int contactCount;
//コレによってアタッチ時にモロモロ設定される。
void Reset()
{
go = gameObject;
tf = transform;
rb2d = GetComponent<Rigidbody2D>();
platformGos = new GameObject[tf.childCount];
for (int i = 0; i < tf.childCount; i++) {
platformGos[i] = tf.GetChild(i).gameObject;
platformGos[i].SetActive(false);
}
//出現位置が分かるように、デフォルトで足場を表示する場合。
// platformGos[(int)sizeType].SetActive(true);
}
void Awake()
{
for (int i = 0; i < platformGos.Length; i++) {
if (platformGos[i].activeSelf)
platformGos[i].SetActive(false);
}
platformGos[(int)sizeType].SetActive(true);
if (direction == Direction.Horizontal) {
leftmostOrBottommostPosition = tf.position + Vector3.left * movingDistance;
rightmostOrTopmostPosition = tf.position + Vector3.right * movingDistance;
} else {
leftmostOrBottommostPosition = tf.position + Vector3.up * movingDistance;
rightmostOrTopmostPosition = tf.position + Vector3.down * movingDistance;
}
playerContactFilter.SetLayerMask(1 << LayerMask.NameToLayer(PlayerTag));
}
//足場の移動とプレイヤーの移動を連動させるサンプル。
void FixedUpdate()
{
nextPosition = Vector3.Lerp(leftmostOrBottommostPosition, rightmostOrTopmostPosition, Mathf.PingPong(Time.time * speed * FixedDeltaTime, 1.0f));
velocity = nextPosition - tf.position;
//より精度を高くするなら、足場の上面にだけトリガーモードの当たり判定を設置し、Collider2D.IsTouchingLayersを使ってチェックするべし。
contactCount = rb2d.GetContacts(playerContactFilter, playerContacts);
for (int i = 0; i < contactCount; i++) {
if (Vector2.Angle(Vector2.down, playerContacts[i].normal) <= PlatformNormalAngleLimit) {
//画面上に1体しか存在しないプレイヤーの場合には、都度GetComponentするより、シングルトンで保持していた方が軽いが割愛。
playerContacts[i].rigidbody.GetComponent<Player>().platformVelocity = velocity;
break;
}
}
rb2d.position = nextPosition;
}
}
是非、ご活用ください。