2d-extras icon indicating copy to clipboard operation
2d-extras copied to clipboard

Is there a way to get information about neighbor tiles (for rule tiles)?

Open timminkov opened this issue 7 years ago • 29 comments

What I have: ruletile1

What I want (notice the grey inner tiles on the edges): ruletile2

I want to add another rule to have more specific inner tiles, but as far as I can tell you can't really get information about the neighbor tile's neighbors. Is there any way to do what I'm looking for in Unity?

timminkov avatar Aug 08 '18 03:08 timminkov

Managed to somewhat figure it out, but I doubt it'll be performant:

First we grab the neighbor tiles' neighbors:

private void SetMatchingNeighboringTiles(ITilemap tilemap, Vector3Int position, TileBase baseTile, int xIndex)
		{
			if (baseTile == null) {
				return;
			}

            neighboringNeighbors[xIndex] = new TileBase[NeighborCount];
			int index = 0;
			for (int y = 1; y >= -1; y--)
			{
				for (int x = -1; x <= 1; x++)
				{
					if (x != 0 || y != 0)
					{
						Vector3Int tilePosition = new Vector3Int(position.x + x, position.y + y, position.z);
						neighboringNeighbors[xIndex][index] = tilemap.GetTile(tilePosition);
						index++;
					}
				}
			}
		}

Then we check against them during rule checks:

		public virtual bool RuleMatch(int neighbor, TileBase tile, int index)
		{
			switch (neighbor)
			{
				case TilingRule.Neighbor.This: return tile == m_Self;
				case TilingRule.Neighbor.NotThis: return tile != m_Self;
				case TilingRule.Neighbor.SkipATile: 
					TileBase[] baseTileArray = neighboringNeighbors[index];
					TileBase baseTile = baseTileArray[index];
					return baseTile != m_Self;
			}
			return true;
		}

timminkov avatar Aug 08 '18 05:08 timminkov

This doesn't work well on corners, as you can see here: image

The only real way to do it is to build out a UI to select neighboring sprites to use as rules, which I feel like would take forever. Anyone have a better idea?

timminkov avatar Aug 08 '18 05:08 timminkov

This seems to be a problem with the rule setting, as far as I know it doesn't even need to change the code. For the lack of corners, add the rules in the picture to fill it. tile

johnsoncodehk avatar Aug 08 '18 09:08 johnsoncodehk

That won't work - The tiles I'm talking about are surrounded on all sides.

timminkov avatar Aug 08 '18 10:08 timminkov

What do you want to do is the dark part of this picture? tile

johnsoncodehk avatar Aug 08 '18 10:08 johnsoncodehk

Yes - Using a single rule tile for the entire set.

timminkov avatar Aug 08 '18 10:08 timminkov

I made a light part, the same as the dark part. Maybe you don't need to be so complicated, but you can refer to it. tile2

johnsoncodehk avatar Aug 08 '18 10:08 johnsoncodehk

That rule tile doesn't have any of your dark tiles on it.

timminkov avatar Aug 08 '18 10:08 timminkov

Sorry, I misunderstood what you mean. You must currently implement it using two RuleTile.

johnsoncodehk avatar Aug 08 '18 11:08 johnsoncodehk

Just a suggestion, if draw the gray edge on the outer tile, the whole thing will be very simple.

johnsoncodehk avatar Aug 09 '18 03:08 johnsoncodehk

Hi, did johnsoncodehk's suggestions help?

ChuanXin-Unity avatar Aug 16 '18 02:08 ChuanXin-Unity

No, they didn't.

timminkov avatar Aug 16 '18 02:08 timminkov

It sounds like you need a RuleTile which takes into consideration tiles which are two cells away or if a neighboring tile is an edge.

ChuanXin-Unity avatar Aug 16 '18 02:08 ChuanXin-Unity

Yes - Is there any way to do that through the API (specifically the edge part). The two cells away is something I wrote but doesn't really work very well for some edge cases. I can live with two rule tiles, but it's simply not possible for the two tiles to interact with one another (in a way where it knows facts about the other tile's rules).

For example, here's what it looks like with two rule tiles: image

The issue is that the outer side is assuming it's a single row, when it should be thinking it's just the edge.

timminkov avatar Aug 16 '18 09:08 timminkov

If I change the following line: case TilingRule.Neighbor.This: return tile == m_Self; to: case TilingRule.Neighbor.This: return tile == m_Self || tile != null;

I get behavior that I can somewhat work around (using two rule tiles). It'd still be really nice to be able to encapsulate all of this inside a single rule tile.

timminkov avatar Aug 16 '18 09:08 timminkov

Hey you can use Override Rule Tile to fix the problem with this problem

Yes - Is there any way to do that through the API (specifically the edge part). The two cells away is something I wrote but doesn't really work very well for some edge cases. I can live with two rule tiles, but it's simply not possible for the two tiles to interact with one another (in a way where it knows facts about the other tile's rules).

For example, here's what it looks like with two rule tiles: image

The issue is that the outer side is assuming it's a single row, when it should be thinking it's just the edge.

alijaya avatar Jan 25 '19 00:01 alijaya

Has there been any update to this?

I'm creating a rule where the tile's sprite changes depending on what the neighbours tile positions have on another tilemap so I'm in need of getting the neighboring tile position inside of RuleMatch.

Tencryn avatar May 15 '19 21:05 Tencryn

I also have this issue and was hoping there'd be a solution out there. I'm surprised to see there isn't. It seems like a pretty common thing to do. My current workflow involves two rule tiles to get this effect, which is very tedious as it requires me to "repaint" all the interiors of the tilemap with the darker shade.

chalmersgit avatar May 31 '19 20:05 chalmersgit

I ended up getting a working solution. But it required writing a lot of conditionals (neighbor Sprite checks) - there were quite a few edge cases. I won't share the code since it's very ugly (maybe later I'll clean it up), but I can post some details here when/if I get time.

chalmersgit avatar Jun 25 '19 11:06 chalmersgit

Unity's recent "Extend Neighbors" rule tile allows you to check multiple tiles over ("2 away" or more), which effectively allows us to fix this issue.

I went ahead and made the rules and got it working. It's tedious, as I needed to duplicate the same tile result under multiple different rules (often 5 sets of rules to catch all the edge cases for a single sprite - which I needed to do 4 times for each wall, and then a few more times for corners).

I think it would be more elegant if the rules included a way of doing "or" style checks (similar to what is found in the "Advanced Rule Tile" asset on the asset store - which currently only supports 3x3 Rule Tiles, but it at least has the concept of an "or" rule which I think would make this current process less tedious).

Regardless, the issue should be resolved now.

chalmersgit avatar Dec 11 '20 10:12 chalmersgit

@chalmersgit Nice hear that it is useful to you, but it is not new, it is a feature from one year ago XD: https://github.com/Unity-Technologies/2d-extras/pull/140

Maybe you can try Sibling Rule Tile from here: https://github.com/johnsoncodehk/rule-tile-extras It is a simple but supports Extend Neighbors implementation.

johnsoncodehk avatar Dec 11 '20 17:12 johnsoncodehk

@johnsoncodehk yeah true haha. Just felt kinda new given the age of this thread and how recent I found it :p

Yeah I tried that siblings one. It has an issue (at least I couldn't figure out) that stopped me from using it. The problem is that the "sibling" is a TileBase type. But when you draw from your palette, the tiles I believe are from the rule type type, not specific to the tile you selected in the sibling GUI.

So the sibling rule only worked if I drag+dropped that specific tile into the palette. So effectively it was useless.

In concept, if the siblings script worked as I had hoped, it would fix this issue. Please let me know if this is possible. That would save a lot of time.

chalmersgit avatar Dec 11 '20 20:12 chalmersgit

@chalmersgit this is a design limitations, some related discussion here: https://github.com/Unity-Technologies/2d-extras/issues/67#issuecomment-680804320

I really want to solve it, but it is "close source". :(

johnsoncodehk avatar Dec 11 '20 20:12 johnsoncodehk

@johnsoncodehk ah ok. Thanks for sharing.

I wonder what the intended use case for the sibling was then.

To be clear, in referring to "siblings 1": https://docs.unity3d.com/Packages/[email protected]/manual/CustomRulesForRuleTile.html

Siblings 2 makes complete sense (making two sets of rule tiles interact with one another), but siblings 1 doesn't practically work with rules+palette.

chalmersgit avatar Dec 11 '20 21:12 chalmersgit

@chalmersgit this is no a good example from here... https://github.com/Unity-Technologies/2d-extras/pull/38

the problem is, we know RuleTile can only refresh by RuleTile, so we should do:

public class MyTile : RuleTile<MyTile.Neighbor> {

    public List<RuleTile> sibings = new List<RuleTile>(); // <-- not allow TileBase

    public class Neighbor : RuleTile.TilingRule.Neighbor {
        public const int Sibing = 3;
    }

    public override bool RuleMatch(int neighbor, TileBase other) {

        // should support RuleOverrideTile
        if (other is RuleOverrideTile) other = (other as RuleOverrideTile).m_InstanceTile;

        switch (neighbor) {
            case Neighbor.Sibing: return sibings.Contains(other);
        }

        return base.RuleMatch(neighbor, other);
    }
}

johnsoncodehk avatar Dec 11 '20 21:12 johnsoncodehk

@johnsoncodehk interesting. I'll check it this evening and report back.

chalmersgit avatar Dec 11 '20 21:12 chalmersgit

@chalmersgit You need to add the two Siblings Tile 1 to each other to get the same behavior as Siblings Tile 2, so yes it is not practical, just for as an example.

johnsoncodehk avatar Dec 11 '20 21:12 johnsoncodehk

@johnsoncodehk Just a minor fix to make your code compile:

public class MyTile: RuleTile<MyTile.Neighbor>
{
    public List<RuleTile> sibings = new List<RuleTile>(); 

    public class Neighbor : RuleTile.TilingRule.Neighbor
    {
        public const int Sibing = 3;
    }

    public override bool RuleMatch(int neighbor, TileBase other)
    {
        // should support RuleOverrideTile
        if (other is RuleOverrideTile) other = (other as RuleOverrideTile).m_InstanceTile;

        RuleTile otherRuleTile = other as RuleTile; // <-- Need to cast it to make it compatible with siblings list

        switch (neighbor)
        {
            case Neighbor.Sibing: return sibings.Contains(otherRuleTile);
        }

        return base.RuleMatch(neighbor, other);
    }
}

But regardless, the list is of type RuleTile, which means it only lists entries of entire rule sets (whereas before when it was a list of type TileBase, it would at least allow me to select the specific tile I want to react to (e.g., a wall tile)).

chalmersgit avatar Dec 12 '20 08:12 chalmersgit

If you have incompatible rules, say for example if your fill tile does not have all 8 connections as green arrows, inner corners will not work. But if you properly set up that fill tile it will work! :) Make sure your fill tile has 8 green arrows :)

andrewdavidjamesburke avatar Mar 01 '21 00:03 andrewdavidjamesburke