Offgrid placement

Registered: 2014-04-07
Posts: 28


It would be nice to paint prefabs off-grid. A simple prop placing tool would be nice. Dragging from the project window sucks when you already have a pretty palette window from Rotorz.

I know it wouldn't work with the chunk system, but that's fine as most offgrid props would probably have a bit of interaction to them.

I know that each chunk saves tiledata in the GameObject name. These wouldn't be part of that.

Keeping < and > for basic rotations would also be nice.

Quickly placing something like barrels or pots is difficult.

The fancy line tools and rectangle wouldn't be required, or even easily possible. I'm just trying to find a faster way to populate a level than constantly dragging from the project window. 8o

EDIT: PS I love Rotorz!

Last edited by TrentSterling (2014-04-07 17:41:05)

Lea Hayes
Rotorz Limited
From: United Kingdom
Registered: 2014-03-04
Posts: 638

Response 1

Thank you for your suggestions!

I hope to add a "Plop" tool in the future which would allow tiles to be painted without being "attached" to the active tile system. I hadn't thought about making tile alignment completely arbitrary though, nice suggestion!

Here is a quick implementation which I have hacked together for you which seems to work. Obviously this can only work for tiles which are painted from prefabs:

Tiles painted with Plop Tool

Simply place the following script into your project and it will be automatically detected:


using UnityEngine;
using UnityEditor;

using Rotorz.Tile;
using Rotorz.Tile.Editor;

namespace Custom {

	public class PlopTool : PaintTool, IBrushContext {

		static PlopTool() {

		#region Tool Information

		public override string Label {
			get { return "Plop"; }

		public override string Name {
			get { return "custom.tool.plop"; }

		public override Texture2D IconNormal {
			get { return null; }

		public override Texture2D IconActive{
			get { return null; }


		public override void OnTool(ToolEvent e, IToolContext context) {
			switch (e.type) {
				case EventType.MouseDown:
					OnPaint(e, context);

		private Vector3 _lastKnownMouseLocalPoint;

		public override void OnDrawGizmos(TileSystem system) {
			// We need to populate properties of `IBrushContext` for preview generation.
			Brush = (PreviousToolEvent != null && PreviousToolEvent.rightButton)
				? ToolUtility.SelectedBrushSecondary
				: ToolUtility.SelectedBrush;
			TileSystem = system;

			// We can only proceed for an oriented brush.
			var orientedBrush = Brush as OrientedBrush;
			if (orientedBrush == null || orientedBrush.disableImmediatePreview)

			// Pretend to paint tile so that we can see its data beforehand!
			var previewTile = ImmediatePreviewUtility.GetPreviewTileData(this, Brush, ToolUtility.Rotation);

			var orientation = orientedBrush.FindClosestOrientation(previewTile.orientationMask);
			if (orientation == null || orientation.variations.Length == 0)

			var variationGO = orientation.variations[0] as GameObject;
			if (variationGO != null) {
				var variationTransform = variationGO.transform;
				var matrix = orientedBrush.GetTransformMatrix(system, 0, 0, previewTile.Rotation, variationTransform);

				// Offset preview against mouse position.
				matrix = system.transform.worldToLocalMatrix * matrix;
				matrix = Matrix4x4.TRS(_lastKnownMouseLocalPoint, Quaternion.identity, * matrix;
				matrix = system.transform.localToWorldMatrix * matrix;

				ImmediatePreviewUtility.DrawNow(ImmediatePreviewUtility.PreviewMaterial, variationTransform, matrix, orientedBrush as IMaterialMappings);

		public override void OnSceneGUI(ToolEvent e, IToolContext context) {
			var system = context.TileSystem;

			// Preserve current state of handles.
			Matrix4x4 originalMatrix = Handles.matrix;
			Color restoreHandleColor = Handles.color;

			// Place handles within local space of tile system.
			var systemTransform = system.transform;
			Handles.matrix = systemTransform.localToWorldMatrix * Matrix4x4.Scale(system.tileSize);

			_lastKnownMouseLocalPoint = e.worldPoint;
			_lastKnownMouseLocalPoint.x -= system.tileSize.x / 2f;
			_lastKnownMouseLocalPoint.y += system.tileSize.y / 2f;

			// Restore former state of handles.
			Handles.matrix = originalMatrix;
			Handles.color = restoreHandleColor;

		private TileSystem CreateTemporaryTileSystem(TileSystem original) {
			var go = EditorUtility.CreateGameObjectWithHideFlags("{{Plop Tool}} Temp System", HideFlags.HideAndDontSave);
			var system = go.AddComponent<TileSystem>();
			system.CreateSystem(1, 1, 1, 1, 1, 1, 1);
			system.tileSize = original.tileSize;
			system.TilesFacing = original.TilesFacing;
			return system;

		private void OnPaint(ToolEvent e, IToolContext context) {
			var brush = e.leftButton
				? ToolUtility.SelectedBrush
				: ToolUtility.SelectedBrushSecondary;
			// We can't erase, let's just bail!
			if (brush == null)

			var activeSystem = context.TileSystem;
			var activeSystemTransform = activeSystem.transform;

			var tempSystem = CreateTemporaryTileSystem(activeSystem);
			var tempSystemTransform = tempSystem.transform;

			try {
				// Align temporary tile system to mouse pointer.
				tempSystemTransform.parent = activeSystemTransform;
				tempSystemTransform.localPosition = e.worldPoint;
				tempSystemTransform.localRotation = Quaternion.identity;
				tempSystemTransform.localScale =;

				var pos = tempSystemTransform.localPosition;
				pos.x -= activeSystem.tileSize.x / 2f;
				pos.y += activeSystem.tileSize.y / 2f;
				tempSystemTransform.localPosition = pos;

				// Paint tile!
				brush.PaintWithSimpleRotation(tempSystem, 0, 0, ToolUtility.Rotation, ToolUtility.RandomizeVariations ? Brush.RANDOM_VARIATION : 0);
				var tile = tempSystem.GetTile(0, 0);
				if (tile != null && tile.gameObject != null) {
					// Disconnect game object from data structure of temporary system.
					tile.gameObject.transform.parent = null;
					tile.gameObject = null;
			finally {
				// We must cleanup before we finish!

		#region IBrushContext Members

		public Brush Brush { get; private set; }

		public int Column {
			get { return 0; }

		public int Row {
			get { return 0; }

		public TileSystem TileSystem { get; private set; }




One little caveat with this tool... it will add a tile system to the scene palette. Just ignore this, it should not be saved to your scene file since it has been flagged with HideFlags.HideAndDontSave.

Be warned, the implementation of this tool will probably break when Rotorz Tile System 2.3.0 is released since the tool classes have been revamped. I will try to remember to post an updated version; just poke me if I forget :)

I hope that this helps; let me know if you have any problems and I will do my best to assist!

Last edited by Lea Hayes (2014-04-07 20:31:00)

Lea Hayes
Rotorz Limited
From: United Kingdom
Registered: 2014-03-04
Posts: 638

Response 2

I have just updated the script so that it includes support for tile systems with upwards facing tiles. The temporary tile system is now created/destroyed each time a tile is painted since the profiler seems to suggest that this has very little impact on performance.

Registered: 2014-04-07
Posts: 28

Response 3

Wow, thank you so much! This looks perfect!

Lea Hayes
Rotorz Limited
From: United Kingdom
Registered: 2014-03-04
Posts: 638

Response 4

The plop tool will be included in the 2.3.0 release of Rotorz Tile System and support has also been added to the existing picker and cycle tools.

Registered: 2014-04-07
Posts: 28

Response 5

Lea Hayes wrote:

The plop tool will be included in the 2.3.0 release of Rotorz Tile System...

This is excellent news!