#Drawing arrows in a tilemap

1 messages · Page 1 of 1 (latest)

past verge
#

I want to make a feature in my game where you can draw on a TileMap, and an arrow would appear in the tilemap.

The arrow can bend, make u-turns, and overlap itself, but not go diagonal.

I use different sprites and place those on the tilemap. I can rotate them to create all sorts of different arrows. Below is a list of the description of each piece, and what directions have exits in their default rotation.

I'm mostly stuck on figuring out the proper data types to store this stuff. I've got a pretty good idea on how to program things like the neighbour checking, but I want some advice on how to structure this data.
If anyone know any related tutorials or resources that addresses this, please let me know! I can't seem to find much when searching for something like 'drawing arrows on a 4-way grid'

These are the pieces:
enum { START, END, STRAIGHT, OVERLAP, CORNER, DOT }

The START piece is what the start of the line. It replaces the DOT piece once the DOT piece has gained a neighbour. The START piece has a connection in the NORTH direction.
The END piece is a pointing arrow. The arrow points in the opposite direction of it's connection, which is in the NORTH side. (the arrow points SOUTH by default).
The STRAIGHT is a straight piece. It has connections towards the NORTH and SOUTH side.
The OVERLAP is two straight pieces that overlap. Connections are NORTH, EAST, SOUTH, WEST, where NORTH->SOUTH is the 'above' route and EAST->WEST is the 'below' route.
The CORNER is a corner piece that has two connections, the NORTH and EAST side.
The DOT is a dot. This piece has no connections, and is used when no neighbours are connected.

How would I best make a script that draws arrows like in my example picture on the tilemap?

#

I know that

  1. we should start drawing when the mouse is clicked.
  2. if we still have the mouse button down, and move the mouse over to a neighboring tile, then that tile gets an arrow piece added. It should connect to the tile we just came from. We also update the previously drawn piece, so it connects to the tile we just drew.
  3. if we finally release the mouse, the drawn tiles should be cleared

For example,

  1. we click the mouse at tile (0,0).
  2. It has no connections, so we add the DOT sprite to tile 0,0 and do not have to rotate the tile.
  3. we drag the mouse to tile (1,0)
  4. It has a connection with the previous tile, so we draw the END sprite at tile 1,0, ensuring we rotate it so the connection is on the WEST side. Furthermore, as the DOT sprite is a neighbor, we need to see if the DOT sprite needs to change. In this case, the DOT sprite now has a neighbour at the EAST side, so we should update it to a START sprite and rotate it so it has a connection on the EAST side.
  5. We drag the mouse to tile (1,1)
  6. It has a connection with the previous tile, so we draw the END sprite at tile 1,1, ensuring we rotate it so the connection is on the NORTH side. Furthermore, as the END sprite is a neighbor, we need to see if the END sprite needs to change. In this case, the END sprite now has a neighbour at both the WEST and SOUTH side, so we should update it to a CORNER sprite and rotate it so it has a connection on the WEST and SOUTH side.
  7. We release the mouse button
  8. We delete all the drawn tiles
tranquil jay
#

First i'd start by making a manageable texture sheet.
Place all the parts of the arrow like you would in a sprite sheet, and reserve the rows below for variations.

So:

Arrow1 | Crossing | Straight1
Arrow2 |          | Straight2
Arrow3 |          |
Arrow4 |          |

You can store the X coordinates of each sprite in the sheet in a Dictionary alongside your enum (EnumName : int pair). This means that you can do this:
var arrow_x_coord: float = sprite_size.x * EnumName.ARROW

Since SART is 0, the coordinates for its sprite are at the left edge.

Same goes for the variations, you can make them enums as well or just make it more hard coded.

Similar approach to an Atlas, altho you don't necessarily need an AtlasTexture to get it done.

From there you can guess all variations by simply knowing what the last 3 hovered spaces are. You can just throw them in an Array, insert any newly selected space at position 0 and remove the one that would now be at position 3 (if any). The third one is for corners.

  • If there is only one spot, place a dot.
  • If your 2nd spots is to the left, the 1st spot gets an arrow to the right (same for the other orientations, just get the opposite one)
  • If the 2nd spot has the same Y coordinate as the 1st, make it an horizontal straight. And the opposite for the X coordinate.
  • If you move onto an horizontal/vertical straight from the opposite orientation, turn it into a crossing.
  • If the 2nd is a straight and the 3rd one has a different orientation (does not share the same Y/X coordinate that would make it the same kind of straight), change the 2nd spot to a corner.
  • If a move would cause any of the 3 spots to be a duplicate of another. Deny the movement.
  • etc
narrow sand
#

Turn all 16 combinations of the cells into a sprite sheet. Think of the corners as bits, making a total of 4 bits with 16 possible values. You can use a number that goes from 0 to 16 as an index to get the frame on the SpriteFrames, when programming this you can make an Enum that represent these numbers for you instead of using them directly.

Make a SpriteFrames resource and create the sprite sheet as a default animation on it. Now you can easily attach this resource to an animated sprite and set the arrow frame you want by just setting the frame number.

Since you want arrows to cross over each other, I suggest making the cells AnimatedSprite2D instead of using a tilemap. This will simplify things since you only have to worry about two points being active at once, the START and EXIT, thus no need for using the two cross frames. When instancing the Animated sprite, you don't have to worry about the crossing sprite because it appears like they're crossing already, just set the depth layer to a higher number than the previous one and the most recent one will always appear on top.

Btw im not sure what will happen if the mouse is moved behind the head of the arrow, In my scenario it will just continue instancing sprites on top of each other with no problem, but it will look weird shadowing the ones below it.

frosty ruin
#

You could just use a Line2D to draw the body of the arrow and have the arrow head be a sprite that moves and rotates to match the last point you added.

past verge
knotty oyster
#

Good stuff. I'd like to see.

forest path
#

I'd like to see as well. This is cool!

past verge
#

@knotty oyster @forest path

forest path
#

Thank you!

rose grove
#

cool result!

#

I have the feeling this can be done automatically with godot's autotiling no?

#

in particular set_cells_terrain_connect and set_cells_terrain_path

#

it would even fix the tile that keeps a hole

#

it'll need configuring of the tileset but once it's done it would result in fewer lines of code. What do you think?

#

I can put up a proof of concept if you're interested

#

(it's a cool problem :D)

past verge
#

@rose grove I don't think that would work very well. You would need custom tiles for each overlapping combination of sprites, and I don't think it could tell the difference between a couple of edge cases. But you're more than welcome to prove me wrong, because a solution using just Terrain rules would be cool.

I worked on this a bit more and now have overlapping working, using Sprite2D instead of tiles.
I also added a custom resource to allow you to easily swap out different spritesheets, and even use spritesheets that aren't laid out in the exact same way as in my example.

rose grove
#

yes you're right, due to the fact that you can revisit a tile from any direction and they can't merge you would wind up with 4 over 8 = 70 combinations and that's excluding the starts and arrows.

#

~~unless one divides your 16x16 into 9 smaller tiles, then it might be feasable mmmmmmmmh 🙂 ~~

#

nah I think it's more or less the same. Anyway.

past verge
rose grove
#

The non-merging requisite is the deal breaker for autotile