Click here to Skip to main content
15,896,269 members
Articles / Desktop Programming / Windows Forms

2D Map Editor

Rate me:
Please Sign up or sign in to vote.
4.89/5 (21 votes)
8 Oct 2009CPOL9 min read 186.1K   6.8K   92  
Create and edit 2D maps using tiles
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.Collections.Generic;
using System.IO;

using MapEditorLib;

namespace MapEditor
{
	/// <summary>
	/// The tiled map control provides an interface for the
	/// EditableMap, and makes use of the XnaMapRenderer to
	/// render the actual map
	/// </summary>
	public partial class TiledMap : UserControl
	{
		#region Variables
		//The actual map
		private EditableMap m_Map;

		//A renderer for the map that uses either XNA or GDI
		private IMapRenderer m_renderer;

		//Control properties
		private Color m_gridColour;
		private Color m_overlayColour;
		private bool m_showGrid;
		private bool m_showSolids;
		private bool m_showNumbers;
		private Font m_font;

		//Nothing special about these
		private bool m_ctrlKey;
		private bool m_mouseDown;
		private int m_x,m_y;
		private int x_offset, y_offset;
		private bool m_lockEvents;
		private string m_tempPath;
		#endregion

		#region Properties
		public event TileSizeChangedEventHandler OnTileSizeChanged;

		[Browsable(false)]
		public bool CtrlKey
		{
			get { return m_ctrlKey; }
			set { m_ctrlKey = value; }
		}

		[Browsable(false)]
		public bool ShowSolids
		{
			get { return m_showSolids; }
			set
			{
				m_showSolids = value;
				m_renderer.SetSolidsVisible(value);
				this.mainGrid_panel.Invalidate();
			}
		}

		public Font MapFont
		{
			get { return m_font; }
			set { m_font = value; this.mainGrid_panel.Invalidate(); }
		}
		
		public bool ShowGrid
		{
			get { return m_showGrid; }
			set
			{
				m_showGrid = value;
				m_renderer.SetGridVisible(value);
				this.mainGrid_panel.Invalidate();
			}
		}

		public bool ShowNumbers
		{
			get { return m_showNumbers; }
			set { m_showNumbers = value; this.mainGrid_panel.Invalidate(); }
		}

		public Color GridColour
		{
			get { return m_gridColour; }
			set
			{
				m_gridColour = value;
				m_renderer.SetGridColour(m_gridColour.R, m_gridColour.G, m_gridColour.B);
				this.mainGrid_panel.Invalidate();
			}
		}

		public Color OverlayColour
		{
			get { return m_overlayColour; }
			set
			{
				m_overlayColour = value;
				m_renderer.SetOverlayColour(m_overlayColour.R, m_overlayColour.G, m_overlayColour.B);
				this.mainGrid_panel.Invalidate();
			}
		}
		
		public int TileSize
		{
			get { return m_Map.TileSize; }
			set 
			{ 
				if(value > 0)
					m_Map.TileSize = value;
				else
					m_Map.TileSize = 1;
				
				//Set the new size of the panel
				this.mainGrid_panel.Size = new Size(m_Map.RealWidth, m_Map.RealHeight);
			}
		}

		public System.Drawing.Point MapSize
		{
			get { return new System.Drawing.Point(m_Map.Width, m_Map.Height); }
			
			set
			{
				this.widthUpDown.Value = value.X;
				this.heightUpDown.Value = value.Y;
				setSize();
			}
		}
		#endregion
		
        public TiledMap()
		{
			InitializeComponent();
			
			//Default values
			m_gridColour = Color.Gray;
			m_overlayColour = Color.Red;
			m_showGrid = true;
			m_font = this.Font;
			m_mouseDown = false;
			m_x = 0;
			m_y = 0;
			m_lockEvents = false;

			//Setup the map
			m_Map = new EditableMap();
			refillLayerList();
			m_Map.OnMapChanged += new EditableMap.MapChangedEventHandler(this.redrawMap);

			//Set the correct sizes for our panel
			this.mainGrid_panel.Size = new Size(m_Map.RealWidth, m_Map.RealHeight);

			//Setup the renderer, and trust me, you want the XnaMapRenderer
			//m_renderer = new GdiMapRenderer(m_Map);
			m_renderer = new XnaMapRenderer(m_Map, this.mainGrid_panel.Handle, this.mainGrid_panel.Width, this.mainGrid_panel.Height);
			m_renderer.SetClearColour(this.BackColor.R, this.BackColor.G, this.BackColor.B);
			m_renderer.SetGridColour(m_gridColour.R, m_gridColour.G, m_gridColour.B);
			m_renderer.SetOverlayColour(m_overlayColour.R, m_overlayColour.G, m_overlayColour.B);
			m_renderer.SetGridVisible( m_showGrid);
			m_renderer.SetSolidsVisible(m_showSolids);
			this.mainGrid_panel.SetRenderMethod(m_renderer.RenderMap);
		}

		#region Public Functions
		public void SetSelectedTiles(Tile[,] tiles)
        {
            m_Map.SetSelectedTiles(tiles);
        }

		public void SaveMap(string path)
		{
			m_Map.Save(path);

			//Get the directory the user is saving in to
			string parentDir = path.Substring(0, path.LastIndexOf('\\'));
			m_Map.SaveTextures(parentDir);
		}

		public void LoadMap(string path)
		{
			m_tempPath = path;
			m_Map.Load(path, this.textureRequest);
			m_tempPath = String.Empty;

			//Sort ourselves out
			revivification();	
		}

		public void Clear()
		{
			m_Map.Reset();
			revivification();
		}

		public List<Tileset> GetTilesets()
		{
			return m_Map.TileSets;
		}
		#endregion

		#region private Functions
		private void revivification()
		{
			refillLayerList();
			
			this.widthUpDown.Value = m_Map.Width;
			this.heightUpDown.Value = m_Map.Height;
			this.tileSizeUpDown.Value = m_Map.TileSize;
			this.zvalueUpDown.Value = (Decimal)m_Map.WorkingLayer.Z_Value;
			this.layerVisChkBox.Checked = m_Map.WorkingLayer.Visible;
			this.mainGrid_panel.Size = new Size(m_Map.RealWidth, m_Map.RealHeight);

			this.mainGrid_panel.Invalidate();
		}

		private void setTiles(int posX, int posY)
		{
			m_Map.SetTiles(posX, posY);
		}

		private void setSize()
		{
			m_Map.SetSize((int)this.widthUpDown.Value, (int)this.heightUpDown.Value);
			this.mainGrid_panel.Size = new Size(m_Map.RealWidth, m_Map.RealHeight);
		}

		private void refillLayerList()
		{
			//Clear - refill the combobox with the new list
			this.layerSelectBox.SuspendLayout();
			this.layerSelectBox.Items.Clear();

			foreach (MapLayer layer in m_Map.MapLayers)
				this.layerSelectBox.Items.Add(layer);

			this.layerSelectBox.Items.Add("Add...");
			this.layerSelectBox.SelectedItem = m_Map.WorkingLayer;
			this.layerSelectBox.ResumeLayout();
		}

		private void redrawMap(object sender, EditableMap.MapChangedEventArgs e)
		{
			//Account for offset
			System.Drawing.Rectangle rect = e.Area;
			rect.Offset(-x_offset, -y_offset);
			this.mainGrid_panel.Invalidate(rect);
		}

		private void addLayer()
		{
			bool validName;
			bool validZ;
			NewLayerDialog diag = new NewLayerDialog();
			DialogResult res;
			do
			{
				res = diag.ShowDialog();

				//If the user cancels, just break out
				if (res == DialogResult.Cancel)
					break;

				validName = true;
				validZ = true;
				if (m_Map.LayerNameInUse(diag.LayerName))
				{
					MessageBox.Show("There is already a layer with the same name", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
					validName = false;
				}
				else if (m_Map.LayerZInUse(diag.Z_Value))
				{
					DialogResult dr = MessageBox.Show("There is already a layer with the same Z-Value.\nWould you like to change it?", "Warning", MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
					if (dr == DialogResult.Yes)
						validZ = false;
				}

			} while (!(validName & validZ));

			if (res == DialogResult.OK)
			{
				m_Map.AddLayer(diag.LayerName, diag.Z_Value, diag.Background);
				refillLayerList();
			}

			//Make sure we're the current layer is still selected
			this.layerSelectBox.SelectedItem = m_Map.WorkingLayer;
		}	
	
		private Image textureRequest(string Name)
		{
			string parentDir = m_tempPath.Substring(0, m_tempPath.LastIndexOf('\\'));

			//Load up the image
			Bitmap img = null;
			
			string fullPath = parentDir + "\\MapData\\" + Name;
			if (File.Exists(fullPath))
			{
				img = new Bitmap(fullPath);
			}
			else
			{
				MessageBox.Show("Unable to find texture: " + Name, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
			}
						
			return img;
		}
		#endregion

		#region Controls Events
		void tileClick(object sender, MouseEventArgs e)
		{
			m_mouseDown = true;
					
			//Find the tile the use clicked
			int x = (int)Math.Floor((double) ((e.X+x_offset) / m_Map.TileSize));
			int y = (int)Math.Floor((double) ((e.Y+y_offset) / m_Map.TileSize));

			if(0 <= x && x < m_Map.Width && 0 <= y && y < m_Map.Height)
			{
                if (m_ctrlKey)
                {
					//Set tile to solid
					m_Map.ToggleSolid(x, y);
					this.mainGrid_panel.Invalidate();
				}
				else
                {
					//Get and set a tile, right click will make a new tile
                    if (e.Button == MouseButtons.Left)
                        setTiles(x, y);
                    else if (e.Button == MouseButtons.Right)
                    {
                        m_Map.ClearTile(x,y);
                    }
				}
				
				//Store the tile we just clicked
				m_x = x;
				m_y = y;
			}
		}

		void mainGrid_panelMouseMove(object sender, MouseEventArgs e)
		{
			//Get the tile we clicked
			int x = (int)Math.Floor((double) ((e.X+x_offset) / m_Map.TileSize));
			int y = (int)Math.Floor((double) ((e.Y+y_offset) / m_Map.TileSize));

			if(0 <= x && x < m_Map.Width && 0 <= y && y < m_Map.Height)
			{
				if(m_mouseDown && (x != m_x || y != m_y))
				{
                    if (m_ctrlKey)
                    {
						//Set tile to solid
						m_Map.ToggleSolid(x, y);
					}
					else
					{
						//Set 
						if(e.Button == MouseButtons.Left)
							setTiles(x,y);
                        else if (e.Button == MouseButtons.Right)
                        {
							m_Map.ClearTile(x,y);
                        }
					}

					//Store the tile we just rolled on over
					m_x = x;
					m_y = y;
				}
				
				//Update the status bar
				this.tileStatusLabel.Text = x.ToString() + "," + y.ToString();
				this.tileInfoStatusLabel.Text = m_Map.GetTilesetName(x, y);
			}
		}

		void mainGrid_panelMouseUp(object sender, MouseEventArgs e)
		{
			m_mouseDown = false;
		}

        private void comboBox_SelectedIndexChanged(object sender, EventArgs e)
        {
			if (m_lockEvents)
				return;

			//Lock the events so that setting values here doesn't 
			// trigger everything to reset itself
			m_lockEvents = true;

            //If the type is a string, then the user clicked 'Add...'
            if (this.layerSelectBox.SelectedItem.GetType() == typeof(string))
            {
				addLayer();
            }
            else
            {           
				try
				{
					m_Map.WorkingLayer = this.layerSelectBox.SelectedItem as MapLayer;
				}
				catch (System.ArgumentException)
				{
					//We hope never to get here...
					MessageBox.Show("This layer is no longer available.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
					refillLayerList();
				}
            }
			
			this.zvalueUpDown.Value = (Decimal)m_Map.WorkingLayer.Z_Value;
			this.layerVisChkBox.Checked = m_Map.WorkingLayer.Visible;

			m_lockEvents = false;
        }
       
		private void layerVisChkBox_CheckedChanged(object sender, EventArgs e)
		{
			if (!m_lockEvents)
			{
				m_Map.SetLayerVisible(this.layerVisChkBox.Checked);
			}
		}
		
		private void zvalueUpDown_ValueChanged(object sender, EventArgs e)
		{
			if (!m_lockEvents)
			{
				m_Map.SetLayerZValue((float)this.zvalueUpDown.Value);
			}
		}
		
		private void setsize_buttonClick(object sender, EventArgs e)
		{
			setSize();
		}

		private void tileSizeUpDown_ValueChanged(object sender, EventArgs e)
		{
			m_Map.TileSize = (int)this.tileSizeUpDown.Value;
			this.mainGrid_panel.Size = new Size(m_Map.RealWidth, m_Map.RealHeight);

			if (OnTileSizeChanged != null)
			{
				TileSizeChangedEventArgs eventArgs = new TileSizeChangedEventArgs(m_Map.TileSize);
				OnTileSizeChanged(this, eventArgs);
			}
		}
		
		private void mainGrid_panel_SizeChanged(object sender, EventArgs e)
		{
			//Setup the scroll bars

			//Horizontal
			if (m_Map.RealWidth > this.mainGrid_panel.Width) {
				this.hScrollBar1.Maximum = m_Map.RealWidth - this.mainGrid_panel.Width + (this.hScrollBar1.LargeChange - 1);
				this.hScrollBar1.Enabled = true;
			}
			else {
				x_offset = 0;
				this.hScrollBar1.Enabled = false;
			}

			//Vertical
			if (m_Map.RealHeight > this.mainGrid_panel.Height) {
				this.vScrollBar1.Maximum = m_Map.RealHeight - this.mainGrid_panel.Height + (this.vScrollBar1.LargeChange - 1);
				this.vScrollBar1.Enabled = true;
			}
			else {
				y_offset = 0;
				this.vScrollBar1.Enabled = false;
			}

			if (m_renderer != null)
			{
				m_renderer.SetOffset(x_offset, y_offset);
				m_renderer.ResetGraphicsDevice(this.mainGrid_panel.Width, this.mainGrid_panel.Height);
			}
		}

		private void hScrollBar1_ValueChanged(object sender, EventArgs e)
		{
			x_offset = this.hScrollBar1.Value;
			m_renderer.SetOffset(x_offset, y_offset);
			this.mainGrid_panel.Invalidate();
		}

		private void vScrollBar1_ValueChanged(object sender, EventArgs e)
		{
			y_offset = this.vScrollBar1.Value;
			m_renderer.SetOffset(x_offset, y_offset);
			this.mainGrid_panel.Invalidate();
		}
		#endregion

	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
England England
*blip*

Comments and Discussions