0
votes

The TileMap

namespace CBX.TileMapping.Unity
{
    using UnityEngine;

    /// <summary>
    /// Provides a component for tile mapping.
    /// </summary>
    public class TileMap : MonoBehaviour
    {
        /// <summary>
        /// Gets or sets the number of rows of tiles.
        /// </summary>
        public int Rows;

        /// <summary>
        /// Gets or sets the number of columns of tiles.
        /// </summary>
        public int Columns;

        /// <summary>
        /// Gets or sets the value of the tile width.
        /// </summary>
        public float TileWidth = 1;

        /// <summary>
        /// Gets or sets the value of the tile height.
        /// </summary>
        public float TileHeight = 1;

        /// <summary>
        /// Used by editor components or game logic to indicate a tile location.
        /// </summary>
        /// <remarks>This will be hidden from the inspector window. See <see cref="HideInInspector"/></remarks>
        [HideInInspector]
        public Vector3 MarkerPosition;

        /// <summary>
        /// Initializes a new instance of the <see cref="TileMap"/> class.
        /// </summary>
        public TileMap()
        {
            this.Columns = 20;
            this.Rows = 10;
        }

        /// <summary>
        /// When the game object is selected this will draw the grid
        /// </summary>
        /// <remarks>Only called when in the Unity editor.</remarks>
        private void OnDrawGizmosSelected()
        {
            // store map width, height and position
            var mapWidth = this.Columns * this.TileWidth;
            var mapHeight = this.Rows * this.TileHeight;
            var position = this.transform.position;

            // draw layer border
            Gizmos.color = Color.white;
            Gizmos.DrawLine(position, position + new Vector3(mapWidth, 0, 0));
            Gizmos.DrawLine(position, position + new Vector3(0, mapHeight, 0));
            Gizmos.DrawLine(position + new Vector3(mapWidth, 0, 0), position + new Vector3(mapWidth, mapHeight, 0));
            Gizmos.DrawLine(position + new Vector3(0, mapHeight, 0), position + new Vector3(mapWidth, mapHeight, 0));

            // draw tile cells
            Gizmos.color = Color.grey;
            for (float i = 1; i < this.Columns; i++)
            {
                Gizmos.DrawLine(position + new Vector3(i * this.TileWidth, 0, 0), position + new Vector3(i * this.TileWidth, mapHeight, 0));
            }

            for (float i = 1; i < this.Rows; i++)
            {
                Gizmos.DrawLine(position + new Vector3(0, i * this.TileHeight, 0), position + new Vector3(mapWidth, i * this.TileHeight, 0));
            }

            // Draw marker position
            Gizmos.color = Color.red;    
            Gizmos.DrawWireCube(this.MarkerPosition, new Vector3(this.TileWidth, this.TileHeight, 1) * 1.1f);
        }
    }
}

And the TileMapEditor

namespace CBX.Unity.Editors.Editor
{
    using System;
    using CBX.TileMapping.Unity;
    using UnityEditor;
    using UnityEngine;

    /// <summary>
    /// Provides a editor for the <see cref="TileMap"/> component
    /// </summary>
    [CustomEditor(typeof(TileMap))]
    public class TileMapEditor : Editor
    {
        /// <summary>
        /// Holds the location of the mouse hit location
        /// </summary>
        private Vector3 mouseHitPos;

        /// <summary>
        /// Lets the Editor handle an event in the scene view.
        /// </summary>
        /// 

        private void OnSceneGUI()
        {
            // if UpdateHitPosition return true we should update the scene views so that the marker will update in real time
            if (this.UpdateHitPosition())
            {
                SceneView.RepaintAll();
            }

            // Calculate the location of the marker based on the location of the mouse
            this.RecalculateMarkerPosition();

            // get a reference to the current event
            Event current = Event.current;

            // if the mouse is positioned over the layer allow drawing actions to occur
            if (this.IsMouseOnLayer())
            {
                // if mouse down or mouse drag event occurred
                if (current.type == EventType.MouseDown || current.type == EventType.MouseDrag)
                {
                    if (current.button == 1)
                    {
                        // if right mouse button is pressed then we erase blocks
                        this.Erase();
                        current.Use();
                    }
                    else if (current.button == 0)
                    {
                        // if left mouse button is pressed then we draw blocks
                        this.Draw();
                        current.Use();
                    }
                }
            }

            // draw a UI tip in scene view informing user how to draw & erase tiles
            Handles.BeginGUI();
            GUI.Label(new Rect(10, Screen.height - 90, 100, 100), "LMB: Draw");
            GUI.Label(new Rect(10, Screen.height - 105, 100, 100), "RMB: Erase");
            Handles.EndGUI();
        }

        /// <summary>
        /// When the <see cref="GameObject"/> is selected set the current tool to the view tool.
        /// </summary>
        private void OnEnable()
        {
            Tools.current = Tool.View;
            Tools.viewTool = ViewTool.FPS;
        }

        /// <summary>
        /// Draws a block at the pre-calculated mouse hit position
        /// </summary>
        private void Draw()
        {
            // get reference to the TileMap component
            var map = (TileMap)this.target;

            // Calculate the position of the mouse over the tile layer
            var tilePos = this.GetTilePositionFromMouseLocation();

            // Given the tile position check to see if a tile has already been created at that location
            var cube = GameObject.Find(string.Format("Tile_{0}_{1}", tilePos.x, tilePos.y));

            // if there is already a tile present and it is not a child of the game object we can just exit.
            if (cube != null && cube.transform.parent != map.transform)
            {
                return;
            }

            // if no game object was found we will create a cube
            if (cube == null)
            {
                cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
            }

            // set the cubes position on the tile map
            var tilePositionInLocalSpace = new Vector3((tilePos.x * map.TileWidth) + (map.TileWidth / 2), (tilePos.y * map.TileHeight) + (map.TileHeight / 2));
            cube.transform.position = map.transform.position + tilePositionInLocalSpace;

            // we scale the cube to the tile size defined by the TileMap.TileWidth and TileMap.TileHeight fields 
            cube.transform.localScale = new Vector3(map.TileWidth, map.TileHeight, 1);

            // set the cubes parent to the game object for organizational purposes
            cube.transform.parent = map.transform;

            // give the cube a name that represents it's location within the tile map
            cube.name = string.Format("Tile_{0}_{1}", tilePos.x, tilePos.y);

            // give the cube a tag
            cube.tag = "GridObject";

            TerrainObjects(cube);
        }

        public void TerrainObjects(GameObject gameobject)
        {
            Terrain terrain = Terrain.activeTerrain;
            var cube = GameObject.Find(string.Format("Terrain_{0}_{1}", gameobject.transform.position.x, gameobject.transform.position.y));
            if (cube != null)
                return;

            if (cube == null)
            {
                cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
            }

            cube.transform.position = new Vector3(terrain.transform.position.x + gameobject.transform.position.x, 0, terrain.transform.position.z + gameobject.transform.position.y);
            cube.transform.localScale = new Vector3(gameobject.transform.localScale.x, gameobject.transform.localScale.y, 1);

            cube.name = string.Format("Terrain_{0}_{1}", gameobject.transform.position.x, gameobject.transform.position.y);
        }

        /// <summary>
        /// Erases a block at the pre-calculated mouse hit position
        /// </summary>
        private void Erase()
        {
            // get reference to the TileMap component
            var map = (TileMap)this.target;

            // Calculate the position of the mouse over the tile layer
            var tilePos = this.GetTilePositionFromMouseLocation();

            // Given the tile position check to see if a tile has already been created at that location
            var cube = GameObject.Find(string.Format("Tile_{0}_{1}", tilePos.x, tilePos.y));

            // if a game object was found with the same name and it is a child we just destroy it immediately
            if (cube != null && cube.transform.parent == map.transform)
            {
                UnityEngine.Object.DestroyImmediate(cube);
            }
        }

        /// <summary>
        /// Calculates the location in tile coordinates (Column/Row) of the mouse position
        /// </summary>
        /// <returns>Returns a <see cref="Vector2"/> type representing the Column and Row where the mouse of positioned over.</returns>
        private Vector2 GetTilePositionFromMouseLocation()
        {
            // get reference to the tile map component
            var map = (TileMap)this.target;

            // calculate column and row location from mouse hit location
            var pos = new Vector3(this.mouseHitPos.x / map.TileWidth, this.mouseHitPos.y / map.TileHeight, map.transform.position.z);

            // round the numbers to the nearest whole number using 5 decimal place precision
            pos = new Vector3((int)Math.Round(pos.x, 5, MidpointRounding.ToEven), (int)Math.Round(pos.y, 5, MidpointRounding.ToEven), 0);

            // do a check to ensure that the row and column are with the bounds of the tile map
            var col = (int)pos.x;
            var row = (int)pos.y;
            if (row < 0)
            {
                row = 0;
            }

            if (row > map.Rows - 1)
            {
                row = map.Rows - 1;
            }

            if (col < 0)
            {
                col = 0;
            }

            if (col > map.Columns - 1)
            {
                col = map.Columns - 1;
            }

            // return the column and row values
            return new Vector2(col, row);
        }

        /// <summary>
        /// Returns true or false depending if the mouse is positioned over the tile map.
        /// </summary>
        /// <returns>Will return true if the mouse is positioned over the tile map.</returns>
        private bool IsMouseOnLayer()
        {
            // get reference to the tile map component
            var map = (TileMap)this.target;

            // return true or false depending if the mouse is positioned over the map
            return this.mouseHitPos.x > 0 && this.mouseHitPos.x < (map.Columns * map.TileWidth) &&
                   this.mouseHitPos.y > 0 && this.mouseHitPos.y < (map.Rows * map.TileHeight);
        }

        /// <summary>
        /// Recalculates the position of the marker based on the location of the mouse pointer.
        /// </summary>
        private void RecalculateMarkerPosition()
        {
            // get reference to the tile map component
            var map = (TileMap)this.target;

            // store the tile location (Column/Row) based on the current location of the mouse pointer
            var tilepos = this.GetTilePositionFromMouseLocation();

            // store the tile position in world space
            var pos = new Vector3(tilepos.x * map.TileWidth, tilepos.y * map.TileHeight, 0);

            // set the TileMap.MarkerPosition value
            map.MarkerPosition = map.transform.position + new Vector3(pos.x + (map.TileWidth / 2), pos.y + (map.TileHeight / 2), 0);
        }

        /// <summary>
        /// Calculates the position of the mouse over the tile map in local space coordinates.
        /// </summary>
        /// <returns>Returns true if the mouse is over the tile map.</returns>
        private bool UpdateHitPosition()
        {
            // get reference to the tile map component
            var map = (TileMap)this.target;

            // build a plane object that 
            var p = new Plane(map.transform.TransformDirection(Vector3.forward), map.transform.position);

            // build a ray type from the current mouse position
            var ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);

            // stores the hit location
            var hit = new Vector3();

            // stores the distance to the hit location
            float dist;

            // cast a ray to determine what location it intersects with the plane
            if (p.Raycast(ray, out dist))
            {
                // the ray hits the plane so we calculate the hit location in world space
                hit = ray.origin + (ray.direction.normalized * dist);
            }

            // convert the hit location from world space to local space
            var value = map.transform.InverseTransformPoint(hit);

            // if the value is different then the current mouse hit location set the 
            // new mouse hit location and return true indicating a successful hit test
            if (value != this.mouseHitPos)
            {
                this.mouseHitPos = value;
                return true;
            }

            // return false if the hit test failed
            return false;
        }
    }
}

This is the new method i added: I want to translate the gameobject information like position,scale,rotation and create a copy on the terrain.

public void TerrainObjects(GameObject gameobject)
        {
            Terrain terrain = Terrain.activeTerrain;
            var cube = GameObject.Find(string.Format("Terrain_{0}_{1}", gameobject.transform.position.x, gameobject.transform.position.y));
            if (cube != null)
                return;

            if (cube == null)
            {
                cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
            }

The TileMap create a grid at some position. I can move the grid around the space and if i move the grid the TileMap script is attached to a GameObject if i move this GameObject away from the terrain much away it will create the new cubes in the method TerrainObjects near the position of the GameObject where the TileMap is attached to.

But i want the cubes i create in TerrainObjects to be always on the terrain are and somehow to translate the position of the cubes on the grid(In TileMapEditor) to the position on the Terrain.

In the script TileMapEditor inside the Draw() at the bottom i'm calling the method TerrainObjects.

TerrainObjects(cube);

Same idea/concept should be when I Erase() a cube/s from the grid.

The first screenshot is when the grid is near the terrain so the new cubes are on the terrain: Grid near terrain

In the second screenshot i moved the grid far away from the terrain and the new cubes created near the grid not on the terrain: I drawed new cubes and you can see where is the grid and where the new cubes are they are near the grid not the terrain.

Grid far from terrain

1
You said the want the object to always be on the terrain so what's happening now?Programmer
Now if i move the grid for example very far from the terrain it will put the new cubes under the grid where it is not on the terrain. I will add a screenshot what i mean.Daniel Lip
Why are you moving the object away from the terrain if you want it to stay on? (I don't think I quite understand the question)Samy Bencherif

1 Answers

0
votes

When you're placing your cube on your terrain, you're adding the x, y and z values of the gameobject you've attached the GridEditor script to. I've a feeling that might be your issue.