+3 votes

What is the best way to calculate camera limits (stopping the scrolling) using a TileMap?

The camera limits is set using something like:

set_limit(MARGIN_TOP, 0)
set_limit(MARGIN_LEFT, 0)    
set_limit(MARGIN_RIGHT, calculated_limit_x)
set_limit(MARGIN_BOTTOM, calculated_limit_y)

The idea is that the camera stop scrolling horizontally when arriving in the last right tile and stop scrolling vertically when reaching the last bottom tile in the used map.

Is possible to do this by setting limits (x, y) manually on each map (exporting vars via GDScript), but it would be interesting to automate the process in order to determine the limits automatically ;)

in Engine by (34 points)

2 Answers

0 votes

I came up with a camera script that limits the camera to the current level's size, including handling of the camera's zoom.

If the viewport is smaller than the level either horizontally or vertically, the viewport is kept inside the level. If the viewport is bigger than the level, the level is centred.

extends Camera2D

export(Vector2) var world_size setget _world_size_changed

func _world_size_changed(size):
    world_size = size
    _sync_bounds()

func _ready():
    _sync_bounds()
    get_viewport().connect("size_changed", self, "_sync_bounds")

func _sync_bounds():
    var viewport = get_viewport()
    if viewport != null:
        var zoom = get_zoom()
        var scale = Vector2(1 / zoom.x, 1 / zoom.y)

        var resolution = viewport.get_rect().size

        if (world_size.x * scale.x) > resolution.width:
            _set_big_margins(world_size.x, resolution.width, scale.x, MARGIN_LEFT, MARGIN_RIGHT)
        else:
            _set_small_margins(world_size.x, resolution.width, scale.x, MARGIN_LEFT, MARGIN_RIGHT)

        if (world_size.y * scale.y) > resolution.height:
            _set_big_margins(world_size.y, resolution.height, scale.y, MARGIN_TOP, MARGIN_BOTTOM)
        else:
            _set_small_margins(world_size.y, resolution.height, scale.y, MARGIN_TOP, MARGIN_BOTTOM)

func _set_big_margins(world_size, screen_size, scale, margin_neg, margin_pos):
    set_limit(margin_neg, 0)

    var diff = (world_size * scale) - screen_size
    diff /= scale
    set_limit(margin_pos, ceil(screen_size + diff))

func _set_small_margins(world_size, screen_size, scale, margin_neg, margin_pos):
    var margin = (screen_size - (world_size * scale)) / 2

    set_limit(margin_neg, floor(-margin / scale))
    set_limit(margin_pos, ceil((margin / scale) - world_size))

TileMap itself doesn't have a way to get the bounds though, so you'll have to calculate that yourself.

by (290 points)

I just opened an issue about getting the extents of a TileMap: https://github.com/godotengine/godot/issues/4390

0 votes

as of godot 3.0 you can use get_used_rect():

func _ready() -> void:
  var cam:Camera2D = $Player/Camera2D
  var limits:Rect2 = $Terrain.get_used_rect()
  var size:Vector2 = $Terrain.get_cell_size()
  cam.limit_left = limits.position.x * size.x
  cam.limit_top = limits.position.y * size.y
  cam.limit_right = limits.end.x * size.x
  cam.limit_bottom = limits.end.y * size.y
by (41 points)
Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read Frequently asked questions and How to use this Q&A? before posting your first questions.
Social login is currently unavailable. If you've previously logged in with a Facebook or GitHub account, use the I forgot my password link in the login box to set a password for your account. If you still can't access your account, send an email to [email protected] with your username.