YAMADA TAISHI’s diary

ゲームについてとか私の日記とか。このブログのあらゆるコードは好きにどうぞ。利用規約があるものは記事内のGitHubのRepositoryのリンクで貼られていると思うので、そちらを参照ください。

【Unity】TilemapでRayが衝突した場所のTileを消す

こんにちは、やまだたいし( やまだ たいし@ソシャゲプログラマ (@OrotiYamatano) | Twitter )です。
Unity1WeekJamの時にTileMapを消す処理を書いた時に苦戦したので
今回はその時の知見を共有したいと思い記事化しました。

目次


前提


Unity2018.4.11f1で動作確認
UniRxだけど、分かる人はわかると思う。

使用結果


↓こんな感じで使いました。

f:id:OrotiYamatano:20191106003237g:plain

といっても、実装そのままだとコードが少し複雑なので皆様には、もう少し簡略化したものをお見せします。

f:id:OrotiYamatano:20191106235710g:plain

カーソルとかその他諸々を消しました。

Tileを消すプログラム全体


using Script.Util.input;
using UniRx;
using UniRx.Triggers;
using UnityEngine;
using UnityEngine.Tilemaps;
using Zenject;

namespace Script
{
    public class GetReyObject : MonoBehaviour
    {
        [Inject] 
        private InputService _inputService;

        private RaycastHit2D _hit;
        private Vector2 _hitPos;
        [SerializeField]
        private GameObject cursorObj;
        
        [SerializeField]
        Tilemap blockTilemap;

        private void Start()
        {
             //画面をクリックしたら呼ばれる
            _inputService.GetClickPos.Subscribe(val =>
            {
                //クリックするか、クリックを離すと反応
                if (val == Vector3.zero){    //クリック話したときはマウスの位置が0で来る
                    return;
                }

                var position = gameObject.transform.position;
                if (Camera.main == null) return;

                Vector3 diff =  (Camera.main.ScreenToWorldPoint(val) - position).normalized;
                _hitPos = diff;
                _hit = Physics2D.Raycast(position,diff/*方向*/, 1/*距離*/,LayerMask.GetMask("Block"));
            }).AddTo(gameObject);

             //毎フレーム呼ばれる
            this.UpdateAsObservable().Subscribe(_ =>
            {   
                Action();
            }).AddTo(gameObject);
        }
        
        private void Action(){
            if (_hit.collider == null) return;
            var tilePos = blockTilemap.WorldToCell(_hit.point+_hitPos);
            blockTilemap.SetTile(tilePos, null);    //消去
        }
    }
    
}

切り取って説明したいと思います。

Rayを飛ばす


            _inputService.GetClickPos.Subscribe(val =>
            {
                //クリックするか、クリックを離すと反応
                if (val == Vector3.zero){    //クリック話したときはマウスの位置が0で来る
                    return;
                }

                var position = gameObject.transform.position;
                if (Camera.main == null) return;

                Vector3 diff =  (Camera.main.ScreenToWorldPoint(val) - position).normalized;
                _hitPos = diff;
                _hit = Physics2D.Raycast(position,diff/*方向*/, 1/*距離*/,LayerMask.GetMask("Block"));
            }).AddTo(gameObject);

キャラクターの位置からクリックした方向までRayを飛ばします。 _hitPos という変数にどういう角度でRayを飛ばしたか格納しておきます。

Debug.DrawRay((Vector2)position,diff*10, Color.white,1);
をすると以下のようになります。

f:id:OrotiYamatano:20191107000438p:plain

消去処理


        private void Action(){
            if (_hit.collider == null) return;
            var tilePos = blockTilemap.WorldToCell(_hit.point+_hitPos);
            blockTilemap.SetTile(tilePos, null);    //消去
        }

今回Actionメソッドは毎フレーム呼ばれています。
Destroy(_hit.collider.gameObject); としたいところですが、それをするとTileマップ全体が消えてしまいます。
Tilemapのあたった箇所を Vecter3Int に変換し該当箇所のtileをnullに設定することでTileを消去できます。
_hitPos を足す理由としては_hit.pointだけだと、Tilemapの表面にあたってしまい、上手く変換できないためです。
_hitPos という変数を使わずにあたった箇所から一番近いTileを削除というロジックでも良いかも知れません。

以上です。

まとめ


とりあえず、まとめてみました。
一つのマップを拡縮させたり等は現状では厳しそうです。
今後もっと使い安くなっていってほしいです。