Code Monkey home page Code Monkey logo

godot-fov-tilemap's Introduction

Godot-FOV-TileMap

latest version issues open pr

Simple Godot 3.x class that allows TileMap manipulation depending on a Camera2D's FOV. It loads and unloads all cells depending on Camera2D position and settings.

It works by only setting map cells that are visible to the Camera2D node, which you can select in the TileMap's settings tab. Note that Godot itself takes care of the TileMap rendering, but with much better performance (see comparison, and also this script is "only" GDScript, not the faster C++ GDNative).

This allows you to, for example, easily work with a procedural map generator. You can add your generated cells to the map dictionary, so that you only have to generate around the Camera2D's FOV, but with a buffer of an arbitrary number of cells so that players won't notice. If you combine this with Godot's RandomNumberGenerator class, you can generate tiles using a location-dependent seed and add them to the map when needed. This way you can delete them after some time to free up memory and when needed re-generate the tiles using the same location-dependent seed.

Note: Currently there is no garbage collection so if you add a cell to the map dictionary it will stay there forever. And if you add cells without deleting them they will be using more and more RAM when moving through your world, generating more and more of them. If you plan on using it this way, you should implement a function that manipulates the map dictionary directly to delete old tiles.

You could also use this to have a very crude visual range effect if you set the CellMargin to a negative value, which makes the visible TileMap smaller than the Camera2D FOV. More below at media.

Note that this script obviously introduces a clear performance loss, which also depends greatly on the Camera2D's zoom, the Viewport size and the cell quadrant size; more info about this below at comparison. So if you want to use this in your own game please be aware of that, or just rewrite it in GDNative.

The only file you will need to get started is this one: fov_tilemap.gd It includes the latest FovTileMap class for use.

You can also still use the old script which needs to be loaded and called manually to work: fov_tilemap_script.gd (Not Recommended)

Usage

You only need to add the fov_tilemap.gd file to your project folder. As with all classes in Godot you can then add the new node called FovTileMap to your scene, although it will then already have its own script attached. This is OK if you don't need any special code for your TileMap or you don't mind working with the exisiting code.

The better way to use it, is just to add a standard TileMap node to the scene, attach a script and tell it to extend from the FovTileMap class like this:

extends FovTileMap

This will give you a clean, new script to begin with and all the FOV-TileMap functionality.

Editor Settings

These are all the export variables that will be visible in the TileMap's settings tab:

export(NodePath) onready var CameraNode = get_node(CameraNode)

This is the Node Path to the relevant Camera2D Node. You can select any Camera2D node in your scene.

export(int) var cell_margin := 0

This is the number of cells drawn on the border of the FOV. If negative, this number of cells will be removed from all sides inside the FOV. If positive, this number of cells will be additionally drawn just outside the FOV. Default: 0

export(bool) var enabled := true

Enable or disable FOV-drawing. Default: True

export(bool) var auto_rebuild := true

Rebuild the whole map after FOV-drawing is disabled. Default: True

Functions

func load_map()

This function loads the existing TileMap that is drawn on the TileMap Node and generates the "map" dictionary used for FOV-drawing. This deletes any old map and is automatically called from inside the TileMap's _ready() function. No manual calling needed.

Useful when generating a TileMap on the fly, after the TileMap's _ready() function already concluded.

func rebuild_map()

This function re-builds the whole TileMap from the map dictionary.

Can be used to force a re-draw of the whole map after disabling FOV-drawing. Will be automatically called after disabling FOV-drawing (setting enabled to false) if auto_rebuild is set to true.

Map Dictionary

The map dictionary is structured like this:

Dictionary map = {
    int x0 : { int y0 : int TileID, int y1 : int TileID, ... },
    int x1 : { int y0 : int TileID, int y1 : int TileID, ... },
    int x2 : { int y0 : int TileID, int y1 : int TileID, ... },
    ...
}

You can add and remove cells from this dictionary and the FOV-TileMap will automatically update accordingly - nothing else to do. Note, however, that updating the map too often will likely result in a hefty performance loss as the entire map will be drawn once and then reloaded for every single map change call. So to circumvent this, you can try to cluster your adjustments and then change the map dictionary all at once.

Example

Conveniently, this repository is also a Godot testing project. By downloading this repo and importing it in Godot 3.0+ you can instantly see and learn how this could be used and how the script works. Default FOV CellMargin is set to 0.

A live web (HTML5) demo can be found here.

Please note, however, that the performance of the web version is much worse than any native export.

Media

This is what it looks like if you set CellMargin to 1:

1 Margin

No real difference, right? :)

This is what it looks like if you do not set any CellMargin (aka 0):

0 Margin

Note that this screenshot has been taken while moving, when the camera doesn't move you can't really see any difference as well.

And finally, this is what it looks like if you use a negative (e.g. -3) CellMargin:

-3 Margin

That's the "very crude visual range effect" I have been talking about earlier.

Comparison

Environment: Tilemap 100x100 cells, 13 different tiles (20x20), Camera Scrolling Speed 200px/frame, VSYNC off, else default settings.

You can test it yourself, see example.

Camera Zoom: 0.5 1.0 1.5 2.0 Max RAM usage
FPS normal: 3016 2858 2688 2566 32.9 MiB
FPS with this script: 2668 1985 1475 1089 32.1 MiB

Same Env as above, but this time with a Tilemap size 1000x1000:

Camera Zoom: 0.5 1.0 1.5 2.0 Max RAM usage
FPS normal: 2637 2369 1934 1751 299.4 MiB
FPS with this script: 2525 1309 837 546 330.4 MiB

godot-fov-tilemap's People

Contributors

imgbotapp avatar nuclearphoenixx avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

godot-fov-tilemap's Issues

Fix max and min coords from tilemap

Now it just gets the first and last entries, however, because it's a dictionary you can never be sure of the element's orders. I'll have to sort through the dict to really get the first/last coordinates.

Implement garbage collection

The placeholder for garbage_collect is already there, now I need to work on that function and link it with the main draw_map function.

Upload GIFs

I'm working on recording some nice videos and uploading them for a comparison and stuff.

Minor optimization suggestion or How to get O(lol) performance

Yeah, another one.

About get_start_point() / get_end_point().
(how can this even work, indentation in get_start_point() (or at least github does not display it correctly) seems to not be syntax compliant; also there is an empty loop; but nevermind that, cant test it anyway)

First you get arrays of all x and y values in your tilemap; then you sort those arrays and read index 0 to find the smallest/greatest value. I think this is from the Python standard library, so I just googled a little and found out, that list.sort() uses the timsort algorithm for sorting. In general, NO ordinary sorting algorithm can have a better worst case performance than O(nlog(n))*. So is timsort (even tho it's best case performance is O(n)). A better alternative for finding the smallest/greatest) number in an unsorted array is just searching sequentially (given that you don't need the sorted list afterwards). That way you always have a performance of O(n).

NB: In fact, you actually can get linear performance, if you know about the distribution of your data. For example, if you have just plain random values, every value is equally propable; lets say 10 values between 1 and 10; take an array that can hold a little more values (say, 14). Now just guess their positions!

Say values are 1 6 10 8 9 4 3 2 4 7 (just randomly generated); you would sort them in like this:
floor(10/14*value), e.g.

1*14/10 -> 1,4 -> 1
6*14/10 -> 8,4 -> 8
10*14/10 -> 14 -> 14
8*14/10 -> 11,2 -> 11
9*14/10 -> 12,6 -> 12
4*14/10 -> 5,6 -> 5
2*14/10 -> 2,8 -> 2 
3*14/10 -> 4,2 -> 4
4*14/10 -> 5,6 -> 5 (collision) -> 6
7*14/10 -> 9,8 -> 9

So now you have [1, 2, , 3, 4, 4, , 6, 7, , 8, 9, , 10]. Now you would "tighten" that array, removing empty spaces, resulting in 2 operations per element (at most) -> O(n) for sorting (oh, shouldn't even have multiplied by the actual array size, a smaller number would've been better (accounting for collisions at the end), also the example is a little dumb without decimal places, but you get the idea). Anyway. BOOM. Linear performance for sorting. Problem is, you need to know the distribution of your values for the strategy to work. For normal-distributed values you would take into account the distribution's parameters when guessing (actually calculating) the right index for a given value and reserve more space for more probable values - that way you minimize the probability of collisions; reserving more space than actually necessary can help reduce the number of collisions too. Most algorithms try to analyze the data set given and use this for estimating the indices. Proxmap sort is a good example of an algorithm using this kind of strategy. Uhh, yep, this doesn't even have anything to do with this issue. Still I wrote it. pLox HeLp

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.