Samuel Shumake
Samuel Shumake

Reputation: 21

Point-and-Click movement within a tilemap

I'm trying to learn how to set up an isometric point-and-click game. I have a ChacterBody2D that moves towards wherever the user clicks, and a tilemap with some tiles placed around.

How do I code it so the player only moves if the user has clicked on a valid tile? And to prevent the user from flying across void spaces?

Player Movement Script

extends CharacterBody2D

@export var speed = 400

var target

func _ready():
    target = global_position

func _input(event):
    if event is InputEventMouseButton and event.pressed:
        target = get_global_mouse_position()
        print(event)

func _physics_process(delta):
    velocity = global_position.direction_to(target) * speed
    if global_position.distance_to(target) > 10:
        move_and_collide(velocity * delta)

Thank you!

Game window Scene navgation Tilemap inspector

Upvotes: 1

Views: 76

Answers (3)

Lucifer Morningstar
Lucifer Morningstar

Reputation: 9

extends CharacterBody2D

@onready var map: TileMapLayer = get_parent().get_node("TileMapLayer")


func _physics_process(_delta: float) -> void:
    if Input.is_action_just_pressed("left_click"):
        var mouse_position := get_global_mouse_position()

        if is_cell_valid(mouse_position):
            # Here, write code to move code to the desired block
            pass
        

func is_cell_valid(mouse_position: Vector2) -> bool:
    var grid_position := map.local_to_map(mouse_position)

    if map.get_cell_source_id(grid_position) == -1:
        return false

    return true

Upvotes: 0

Roci49
Roci49

Reputation: 61

You are working with a TileMap, composed of a TileSet composed of "many little pieces". Every piece of your tileset is basically a "piece repeated a certain amount of times". Since Godot 4.0 you can use scenes as tiles. I didn't test this, but if u can use scenes then you can attach script to them. This means that you can send signals from childs to father. So your code could seem just like this.

In Tile.gd

signal pressed_on_me(my_coordinates)
...
if event is InputEventMouseButton and event.pressed:
    pressed_on_me.emit(my_coordinates)

In father.gd (which can be map2 or TileMapLayer, depends what you prefer)

func _on_pressed_on_me(my_coordinates):
    // move action...

In this way:

  • If u press in a non-valid tile, nothing happens because no signal will be emitted
  • A tile has a specific coordinate, so you don't have to check for non-valid input
  • You separate actions, so if in a future u want to add "special tiles" (lava tiles, water tiles....) you can just configure your tile to have a different speed/effect without touching too much code (or at least you are gonna "touch less your code" than creating something monholitic).

Hope this helps. Greetings

Upvotes: 0

Samuel Shumake
Samuel Shumake

Reputation: 21

I was able to get this working using get_node() to find the TileMapLayer. For some reason, the normal reference wasn't working but this worked for me

extends CharacterBody2D


@export var speed = 200
var target
var map

func _ready():
    target = global_position
    map = get_node("/root/Node2D/TileMapLayer") 

func _input(event):
    if event is InputEventMouseButton and event.pressed:
        var mouse_pos = get_global_mouse_position()
        var cell_coord = map.local_to_map(mouse_pos)
        print(cell_coord)
        if (map.get_cell_tile_data(cell_coord)): # If null tile or not
            target = mouse_pos
        

func _physics_process(delta):
    velocity = global_position.direction_to(target) * speed
    if global_position.distance_to(target) > 10:
        move_and_collide(velocity * delta)

Upvotes: 0

Related Questions