Open mezz opened 3 years ago
Hi, do you have a visual example/s of what you would like to do? This would help us understand better your use-case. Thanks!
Sure thing, here's the pattern I'm working with. The pattern repeats in blocks of 7x7, and there are 7 unique tiles in the pattern.
Unique tiles:
One full repeating pattern:
I would like to be able to draw a tiled floor on my scene that follows the above pattern, but is an arbitrary shape like this example:
Thanks! I hope that is more clear.
It looks like you may have to write a custom Tile that does this for you.
You can look at the Random Tile as a base for your custom Tile (https://github.com/Unity-Technologies/2d-extras/blob/master/Runtime/Tiles/RandomTile/RandomTile.cs). The Random Tile sets a pseudo-random Sprite based on the position of the Tile. You could change the behaviour in GetTileData to pick from your repeating pattern as well as consider any rotations.
The following example is based off the images that you have posted, where the pattern will repeat every 7 steps in x, and each step in y offsets the pattern by 4:
public override void GetTileData(Vector3Int position, ITilemap tilemap, ref TileData tileData)
{
base.GetTileData(position, tilemap, ref tileData);
if ((m_Sprites != null) && (m_Sprites.Length > 0))
{
tileData.sprite = m_Sprites[(int) (4 * (position.y % 7) + position.x) % 7];
}
}
Thanks! I was able to create a pattern tile based on RandomTile. I had to add an arbitrary 10k offset to X and Y to ensure they didn't go negative for the remainder operator.
Is this the minimal code to support something like this? I am an experienced coder but new to Unity, and so I wasn't sure if I was duplicating some work here or if it's all necessary.
using UnityEngine;
using UnityEngine.Tilemaps;
using System;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace Tiles {
[Serializable]
public class PatternTile : Tile {
/// <summary>
/// The Sprites used for patterened output.
/// </summary>
[SerializeField] public Sprite[] sprites;
/// <summary>
/// The amount that each row should be offset from the previous row.
/// </summary>
[SerializeField] public uint rowOffset = 1;
/// <summary>
/// The amount that the pattern should be shifted in the X direction.
/// </summary>
[SerializeField] public int rotateX = 0;
/// <summary>
/// The amount that the pattern should be shifted in the Y direction.
/// </summary>
[SerializeField] public int rotateY = 0;
/// <summary>
/// Retrieves any tile rendering data from the scripted tile.
/// </summary>
/// <param name="position">Position of the Tile on the Tilemap.</param>
/// <param name="tilemap">The Tilemap the tile is present on.</param>
/// <param name="tileData">Data to render the tile.</param>
public override void GetTileData(Vector3Int position, ITilemap tilemap, ref TileData tileData) {
base.GetTileData(position, tilemap, ref tileData);
if (this.sprites != null) {
var spritesLength = this.sprites.Length;
if (spritesLength > 0) {
var x = (uint) (position.x + 10000 + this.rotateX);
var y = (uint) (position.y + 10000 + this.rotateY);
var index = (this.rowOffset * (y % spritesLength) + x) % spritesLength;
tileData.sprite = this.sprites[index];
}
}
}
}
#if UNITY_EDITOR
[CustomEditor(typeof(PatternTile))]
public class PatternTileEditor : Editor {
private SerializedProperty m_Color;
private SerializedProperty m_ColliderType;
private PatternTile tile => (this.target as PatternTile);
/// <summary>
/// OnEnable for PatternTile.
/// </summary>
public void OnEnable() {
this.m_Color = this.serializedObject.FindProperty("m_Color");
this.m_ColliderType = this.serializedObject.FindProperty("m_ColliderType");
}
/// <summary>
/// Draws an Inspector for the PatternTile.
/// </summary>
public override void OnInspectorGUI() {
this.serializedObject.Update();
EditorGUI.BeginChangeCheck();
var count = EditorGUILayout.DelayedIntField("Number of Sprites", this.tile.sprites?.Length ?? 0);
if (count < 0) {
count = 0;
}
if (this.tile.sprites == null || this.tile.sprites.Length != count) {
Array.Resize(ref this.tile.sprites, count);
}
if (count == 0) {
return;
}
var rowOffset = EditorGUILayout.DelayedIntField("Offset per row", (int) this.tile.rowOffset);
if (rowOffset < 0) {
rowOffset = 0;
}
this.tile.rowOffset = (uint) rowOffset;
this.tile.rotateX = EditorGUILayout.DelayedIntField("Rotate pattern X", this.tile.rotateX);
this.tile.rotateY = EditorGUILayout.DelayedIntField("Rotate pattern Y", this.tile.rotateY);
EditorGUILayout.LabelField("Place patterened sprites.");
EditorGUILayout.Space();
for (var i = 0; i < count; i++) {
this.tile.sprites[i] = (Sprite) EditorGUILayout.ObjectField("Sprite " + (i+1), this.tile.sprites[i], typeof(Sprite), false, null);
}
EditorGUILayout.Space();
EditorGUILayout.PropertyField(this.m_Color);
EditorGUILayout.PropertyField(this.m_ColliderType);
if (EditorGUI.EndChangeCheck()) {
EditorUtility.SetDirty(this.tile);
this.serializedObject.ApplyModifiedProperties();
}
}
}
#endif
}
I also had to add a menu item to create my asset instance. I didn't have access to the ETilesMenuItemOrder
enum so I had to just hack on a 1000
priority here. Not sure if there is a cleaner way to do it.
using UnityEditor;
using UnityEngine;
namespace Tiles.Menu {
public class PatternTileMenu {
internal static partial class AssetCreation {
[MenuItem("Assets/Create/2D/Tiles/Pattern Tile", priority = 1000)]
static void CreatePatternTile() {
ProjectWindowUtil.CreateAsset(ScriptableObject.CreateInstance<PatternTile>(), "New Pattern Tile.asset");
}
}
}
}
This looks good for your use-case! This can be used to generate other Tiles which have the pattern behaviour, with different Sprites or offsets.
The remainder for negative coordinates would need be handled a bit more, as the remainder is a negative value and arrays do not support negative indexing I believe. The arbitrary offset does work fine for this!
Thanks! Do you think something like this could be included directly in 2d-extras? I'd be happy to contribute.
Certainly, you are welcome to add this to the repository! You can make a pull request to this repository with your contribution.
If you would like this to be added to the package found in the Unity Package Manager, which contains a subset of the features found in the repository, we would need to get approval from other stakeholders within Unity.
Hello, I have a 7x7-tile repeating pattern that I'd like to be able to paint onto a scene one tile at a time. The basic idea is to check the (x,y) coordinate of the tile I'm painting, and choose from the tile pattern based on that. Ideally I'd also like to implement an offset per-scene so that I could "rotate" the pattern on the scene. I tried implementing a custom brush or a rule tile to do this but it gets complex quickly so I'm not sure if I'm on the right path. Do you have any recommendations for how to approach this?