-
-
Notifications
You must be signed in to change notification settings - Fork 21.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Giving tiles a property dictionary #12634
Comments
What this makes me wonder is if there'd be any good way to have tiles inherit properties from other tiles, or perhaps from the TileMap node itself. It'd be nice to, for example, have the TileMap node set default properties for all tiles within it, and then have individual tiles override those defaults if they need to. Imagine making a Minecraft clone with this feature -- as an example, all tiles might need to have a walk_speed setting that defines how fast the player moves over it, but only some will need to change it from its default. It would be nice to have the TileMap implement the default value, and then only have it overridden for specific tiles. |
I don't think that would be very needed. Default settings could be handled through anything ranging from level settings, other nodes, assumptions when the per-tile setting is missing etc. On top of that, the TileMap node and the TileSet resource are definitely very different in function, there wouldn't be much sense in terms of organization for the TileMap node, which simply handles populating the scene with tiles, to hold any further information about these tiles or the TileSet. At most I could imagine seeing that in TileSet, but even there I don't think it's terribly useful other than for very minor convenience. |
If tiles were a resource, it would have been easy to just add a script of yours on them, in which you can put whatever you like :p |
First off, I didn't actually mean to confuse TileMaps and TileSets. But that's my bad for mixing the two. Second, I'm not sure how it would be only a minor convenience. If you only have a few tiles, sure; but after a while, after you've added a lot of tiles to a TileSet, having a way to set the default for all of them rather than going through one by one would then be considered a possibly major convenience. Perhaps I'm not considering the reality of how TileSets are actually used? I'd still like to hear why you would consider it only a minor inconvenience, though, as perhaps I'm missing something. Third, you mentioned using other nodes or other methods of creating a default properties list. While I won't deny that there is other ways, I'm not sure why you mention those options -- doesn't using one of those options contradict the point of custom properties being an option in the first place? |
I just had an idea: |
I may be misunderstanding slightly, but I'd like to make sure I get the basic idea. Are we talking something like this sort of setup?:
Is this correct? Because if it is, I can see why it would work as an alternative and I could see it working, but once you get into creating your own editor panel, that to me feels like something that should be handled by whatever built-in editor panel will already be handling custom properties. |
@LikeLakers2 you can do this without custom panel, just exporting vars in the inspector (with Then, if the scripted feature becomes great and popular enough, you will be able to port it to C++ with no hassle and have it merged into the engine ;) Not saying it shouldn't be done, just pointing the fact that adding game-specific properties to tiles is currently doable with scripting :) |
Talked with @Zylann over Discord because I realized that I was having a hard time understanding his replies and figured a chat interface would help that a bit. I think I understand the suggestion for a tool script now (primarily I was confused over what was being suggested), though this still leaves me wondering why the primary suggestion for this is a tool script. Using a tool script would work, but it just feels sorta wrong for this purpose, so I'm going to stick to my suggestion for TileSet having a default properties dictionary that all tiles within that TileSet would inherit and be able to override. EDIT: As it turns out, this whole debacle may have been a sort of misunderstanding. Whether from my side, Zylann's, or both, I think we both just misunderstood what the other was trying to do. Woops! |
Just had another idea, maybe the scene-to-tileset exporter could make use of this #11384 ? |
@Zylann I've actually already implemented something similar to what you're suggesting into my workflow as outlined here, but that is an approach that only really works by using a custom importer like that, since you can't set meta data on nodes through the editor interface itself yet. @LikeLakers2 I was saying that having these default values on TileSet wasn't especially needed because any script that may want to access these custom tile properties could handle the default case itself. Picture something like my earlier "friction" example, where tiles communicate different friction values to bodies that come in contact with them. Ranges from 0 to 1, we want a default of 1. var tile_friction
if "friction" in TileSet.get_tile_props(tile_id):
tile_friction = TileSet.get_tile_props(tile_id).friction
else:
tile_friction = 1.0 Now every single tile that does not have a friction value explicitly set will assume that it wants to have a friction of 1. Also off-topic, but seeing your name surprised me a lot, I remember you from wayyy back on SMW Central! Small world. |
@leoddd Eyyyy. I barely use SMWC anymore. But anyways. Sleepiness does a lot to you, eh? If we're in agreement that a per-TileSet default is useful, then I'd also like to make it a point to have a way to figure out if we're getting a default value or one specified by the tile. Perhaps I'm asking for a little much now, but it would allow for any single script to easily use its own override just like different TileSets can have their own defaults. We could perhaps have a function that only ever returns what's explicitly defined in a tile's properties. Something like this (look at the second and fifth lines for the changes): var tile_friction
if "friction" in TileSet.get_tile_props_list(tile_id):
tile_friction = TileSet.get_tile_props(tile_id).friction
else:
tile_friction = 0.8 In this context, I'm unsure what the use cases for such a thing would be, and thus unsure how useful this would be, but I'm sure some developer will find a use case, so having some easy way to figure it out rather than forcing the user to work around such a limitation themselves would probably be pretty useful. Plus, I don't imagine it would be too much trouble to implement either. |
I don't know how useful that would be either. I guess that decision would just be left up to whoever would implement it in the end? After reading through some related issues, I realized that one thing preventing this from being implemented is that there currently is no way to actually edit dictionaries through the Godot Inspector, which I am not sure what the status on that is as of right now (#12015). |
Apparently there was a PR regarding this topic a while back, which was closed due to lack of discussion: #3089 |
FWIW, I never built a tile-based game not needing tile properties. So very interested in this, as otherwise I think everyone would just need to build it themselves. Edit: Looking at the PR, it really should be done as a dictionary, and that dictionary would of course have to be editable, so this kind of depends on #12015 being done first. |
The idea I had is similar to #3089, but without a way of setting metadata in-editor this is a bit pointless. If you're doing this via code, is quite trivial to use the TileSet resource metadata to store per-tile information. However, I do agree that a convenience function would be used often enough to warrant a core addition. |
For 3.1, to make this a bit cleaner, do you think it'd be possible to make an API change to Tilemaps as well. It'd be a lot more intuitive to have various properties attached to the Tile itself, instead of having to keep going through the Tileset and pass the ID around to get different things about the tile. Then the map and tileset can just share references to a tile in memory. Something more like
so then I can go
instead of
I know it'd require quite a big refactoring of the tilemap stuff as well as the editor to support it, but it's definitely an API improvement and could make it easier to extend the tiling code in the future. |
@nhydock what you are asking for is a new function |
What I'm asking for is that each cell just has a pointer to a tile object/struct, instead of some index in a tileset and having to do lookups manually. Doing it that way could also decouple the tileset from the tilemap, since the tile reference is aware of what it needs to draw, or what tileset it belongs to. This is how libgdx does it. |
@nhydock then I would advise that Tile becomes a resource, because creating a new dictionary with all the data everytime one cell is get/set seems quite of an overhead to me (not to mention the refcounts going on when you get an object instead of an int) |
A resource is what I wanted. I by no means meant creating a dictionary on every single cell, that'd be stupid and incredibly wasteful. That tiledata struct also seems to be exactly what I wanted exposed directly through the tilemap instead of just providing the int id, just so then additional lookups would be unnecessary. |
I agree that just being able to manipulate the Tile structs directly would be a lot more convenient than what currently exists, but you should make a separate issue for this. |
The main point of this issue is to add custom properties on tiles, right? First things first, just making sure everyone is aware of this, Tiled main purpose is for making Tilemap levels, not for creating tilesets. In Tiled you can have multiple Layers for building your level, in this case would be Tile layers, where in each layer you can edit properties and add Custom Properties, this mean every property you add to this layer would apply to all the tiles in this layer. Now, if you have multiple tiles that hurt the player when touched then create a new layer and add the wanted propertie without adding it one by one for every tile, that would be tedious and inefficient, right? Well, Godot's TileMap node works sorta the same, every TileMap node you add is a Layer, each layer has its own collision, and you can attach an script and then add variables there for the properties, without getting the tile location or things like that.
on Tilemap script:
on Character script:
|
@aaronmzn The very first line of the issue says that OP is looking for properties on TileSets. Making multiple layers is a hacky work around for this problem. For something like a top down, grid-locked RPG, utilizing Godot's 2D physics and colliders is incredibly excessive when all I would actually need to do is check a boolean on a tile in the tileset. It's also a pain in the butt having to manage multiple tilemaps for something like procedural room generation in a roguelike and then have the character check between them all to understand what stuff they can do. This isn't demanding we have props on each cell in a tilemap, we want props on each tile definition in a tileset, so then each cell can have inherited traits. It's an incredibly common use case, and would be incredibly helpful to have out of the box. |
Also, the layer properties in Tiled aren't inherited by every tile on that layer in Tiled. Because Tiled doesn't manage any of that, it just exposes the data. And yeah, I'm aware there are workarounds but they're all kinds of unintuitive and messy compared to just saving the data in the TileSet, where it can be reused easily across TileMaps and even projects. |
I haven't used Tiled since ver 0.14.2 and I just saw that since 1.0 you can open, edit, and save tileset separated from the tilemap, thats a cool feature but quite new for me.
@nhydock I was suggesting the most logical solution from how TileMaps and Godot works. I understand what you guys mean and it makes sense, seems like you guys want all the tiles in a single TileMap node and for a procedural generated levels seems a good reason. But while there is no way of doing that, instead you could use TileMap as Layers and script, when you have a single tilemap and you use get_collider() you'll be able to get the properties inside the script attached to the TileMap node. TileMap Layers on the other hand are the basics of Tilemaps. Again, as I said, godot is the same with the TileMap node as layers, you can workaround with this in the meantime. And yes, sadly, on godot the only way to check if a tile has a certain property is by colliding with it, there is no overlap detection in KinematicBody for topdown games like you would do on Area2D with signals and setting trigger/disabled to true has no utility on this rather for Area2D detection, KinematicBody or even every physics body should have an overlap detection function and neither bodies should collide unless you tell them too wich collision layer/group will collide with, same with overlapping. @leoddd Yes, that's what I meant but I formulated my words bad I guess, but you got me. Rather than adding properties to every single tile you consider it as dangerous, just add a new TileMap where you put all the dangerous slopes and blocks in this layer and then add the property with a script, and access them with the code I writed before Trust me, this is not messy, give a it test, try the dictionary method, it's easier and clean in comparison of what you guys think, and Tilemap layers are the basics of tilemaps, there is nothing abnormal. instead getting the properties from the tileset, you get it from the tilemap layer, like the old Tiled.
|
Alright, but you're missing that I'm not asking for advice on how to handle this. This isn't the Q&A site. A property field for each tile index in TileSets would be an ideal way to make this possible and easy to use in-editor. |
I know this is a bit of a bump, but couldn't this be solved by simply letting users add child nodes to "tile" nodes? Even without a resource, you could do something like so:
This way, you use the node system itself to determine what type of metadata a tile has? |
The whole process of converting a scene into a tileset is called "Convert", as there was no proper user-friendly TileSet editor so the scene editor was used until now. If we end up adding nodes to the engine whose sole purpose is to be used by this converter and only in the editor, then why bother converting? And why not have a proper editor in the first place? |
Some related discussion: #15583 |
... my 3 cents. I could see it like this :
The 'TileSet' node itself could be super simple, having fiew functions like get_tile_at( x, y, z) Im allready using this way of creating tiled-levels for long time, but since theres no editor for it, i use tiled editor to create levels, then game loads level_xyz.json and creates whole level from it in the fly. You can see me 'moaning' about slow instancing performance here #16769 ...and thats exacly what i been doing there, - building tiled level by instancing tiles which are scenes. ...And its been long time a go. Theres just no editor to use real-scenes as 'tiles' and thats how TileSet node and its editor should be. love ❤️ the godot for its nodes and scenes inheritance. |
@avril-gh I'd love to have a sample code for your workflow actually, it seems very powerful. Do you have a small demo online somewhere? |
@HummusSamurai actualy i thought about attaching some simple demonstration. |
My post got closed as a duplicate of this one! I just wanted to add some ideias. Apart from the dictionary idea, tiles could have a resource slot... resources are easier to edit, are memory friendly and more performant than using dictionaries. This way we could create a custom Resource for instance TerrainType with some variables: friction, damage, movement cost, walkable, etc. Then create different kinds of instances of that resource for each terrain/tile type: lava, water, ice, rocks, etc. The only drop back from the previous ideia is if someone wants tiles to have exclusive variables like, HP and when that specific tile HP drops to 0 the tile breaks... this wouldn't work with resources as they all would share the same resource reference. I have one question!! what is the purpose of using scripts in the TileSet editor?! or even the tileset_script, variables.. if I export variables within them... I'm not able to access them elsewhere. |
For note, I have my tilemaps use an ECS style setup. I have pure primitive data arrays where the index matches to a rotating out index of tile positions (since mine are 0,0 centered, it's simple), or dictionaries for uncommon data. I'm effectively treating each tile as an entity in the ECS setup. I then have 'systems' that operate over those data sets for fast mutation that can trivially be multi-threaded, then a final join. It's a bit horrifyingly ugly in GDScript unlike C++ or Rust or so (quite the swap there!) but it works really well and is actually very efficient even in GDScript. |
May I ask you how do you add the data to each tile?! I get each tile is an entity... but how do you add the data to it?! Manually, load from a JSON file, hard-coded into the GDScript file?! The ideia was how to do it without... hard coding it inside the code file. When working with a large team with programmers and designers you don't want designer messing with code, you know. Well just asking |
I don't take tiles as entities. I encode their position into an integer packed to the positive near 0 (swizzling and so forth the coordinate positions, it's simple bit work), then just index into arrays or dictionaries using that value as per proper ECS standards (The 'Entity' in ECS is ALWAYS an integer, just an integer, if something does otherwise then its probably not ECS). I do load it from a JSON file, it's pretty simple, though GDScript makes it irritating enough that I'm leaning to using C++/Rust even for simple mockups again... |
My take: I looked at tile_set.cpp, and adding an extra property for custom meta properties would probably be under 50 lines of code in total. That would be including the inspector property to visually manage it, the storage under the TileSet class and the binding to gdscript. I think it's pretty clear that the need for meta properties per tiles is real, and it could be as simple as the Godot developers want it to be.
I don't know, in my eyes it's weird this hasn't been done yet. Not only is this a simple pull request (again, under 50 new lines of code so merging is easy, I could probably add this feature in a few hours) and would benefit pretty much everyone making 2D tile-based games. |
@Lucrecious @xDGameStudios I think that both approaches could be used here, to support both Resource/Resource script approaches and the looser Dictionary approach.
This strategy gives Resource support both for TileSets and their underlying TileData structs (for maximum power/flexibility) but also grants the more user-friendly Dictionary access as desired. |
FYI, I will be ready to launch a PR for this tomorrow. In my fork's "tileset" branch, I have added a Dictionary "meta_properties" to the TileSet class and made it so that all script variables and the meta properties Dictionary are both visible from the TileSetContextEditor's Inspector. This means that you can expand the TileSet sub-inspector from the TileMap Inspector to view all of the TileSet properties or you can do so by opening the TileSet editor and expanding the "TileSet" group of properties which display the same information. In addition, a TileProperties class has been added to TileData and exposed in the "Selected Tile" group of the TileSetContextEditor. The TileProperties script is also accessible from here, so that it can be easily re-assigned without having to separately open the TileProperties resource in its own Inspector. Finally, a default TileProperties script can be assigned to the TileSet so that it forces all tiles in the TileSet to use said script. It assigns the script to all existing tiles as well (that way a common set of properties may exist on all tiles in the TileSet). This keeps you from having to individually set the script on every TileProperties resource used by the tiles in the TileSet. I plan to re-post the above information + pictures in the PR. Let me know what you guys think. |
It looks like it fails to build in Travis CI!! |
@xDGameStudios I'll be fixing it up in an hour or two when I get off work. No worries! Also, if you'd like, you should be able to cherry-pick the commit once its ready (i.e. once I submit the PR) so that you don't have to wait for it. :-) |
Feature and improvement proposals for the Godot Engine are now being discussed and reviewed in a dedicated Godot Improvement Proposals (GIP) (godotengine/godot-proposals) issue tracker. The GIP tracker has a detailed issue template designed so that proposals include all the relevant information to start a productive discussion and help the community assess the validity of the proposal for the engine. The main (godotengine/godot) tracker is now solely dedicated to bug reports and Pull Requests, enabling contributors to have a better focus on bug fixing work. Therefore, we are now closing all older feature proposals on the main issue tracker. If you are interested in this feature proposal, please open a new proposal on the GIP tracker following the given issue template (after checking that it doesn't exist already). Be sure to reference this closed issue if it includes any relevant discussion (which you are also encouraged to summarize in the new proposal). Thanks in advance! |
One very, very basic TileSet functionality that I believe to be missing is a simple dictionary to hold miscellaneous data, which other nodes in the scene can then read to be aware of how exactly they should treat the tile.
I've personally seen this feature in almost every single tilemap implementation I've seen and think it's pretty essential.
To give you an idea of what exactly I mean, we can use Tiled as an example:
This tile, I would like to hurt any character that touches it. Of course, I'm not expecting the tile itself to run this code or be aware of anything, but for my characters to be able to see a tile they are colliding with and check its property dictionary for an "ontouch" value and do whatever that value tells them, in this case run a routine to hurt the player.
Another example I would actually like to use is, can you see that ice block on the left of that image? I gave it a property of "friction" with the value 0.3, which I would like to be able to read from within the game to adjust my character physics while walking over this tile. (This friction value isn't related to the built-in physics engine, it's just for my own KinematicBody physics handling.)
These applications are I think the most important reasons to implement something like this, just because I think it's actually impossible to get this sort of effect in Godot in any way at the moment, because these rely on the tile's own collision shape. The only workaround would be to completely strip the tiles' collision shapes and manually place TileMap independent staticbodies that can handle this interaction, which is... not optimal. Error-prone, inaccurate, cumbersome, all the bad things.
Similarly, I may want to run a script on level-load that places an object, such as a particle emitter, over specific tiles. It would be very helpful if I had some way to communicate to that script what type of particle emitter should be placed, and that one should be placed at all via these tile properties.
I realize that this example in particular could be done manually by hand, but so could a lot of things that a game engine is there to alleviate!
Another issue with doing it by hand is the much higher chance to make mistakes or to forget placing something.
I've seen people recommend to check the tile number in the TileSet resource and act on that, but I personally am not very happy with how inflexible this suggestion is. I may have different TileSets with different tiles in the same tile number slot, because a dozen tiles used in one TileSet would make no sense to even have in the other, so if I were to go by tile numbers then I would have to insert a lot of empty tiles just to make it fit.
Not to mention how hard to manage such a system would be in a bigger game, as well as its unintuitiveness.
The TileMap node and its lack of features is easily one of Godot's biggest 2D weaknesses, which is why I find myself having to resort to external tools (Tiled) to design my maps, and while it's absolutely no shame for an editor feature in an all-purpose editor like this to lose out to a specialized tool developed for exactly this use in terms of comfort and usability, it's not a great feeling to be stuck without a way to actually implement any sort of game logic inside the game based on a TileMap, something that even Tiled thought of.
Thanks in advance to anyone who may consider this idea, it'd really make me very happy if this were to actually be implemented eventually, especially since I personally feel like it's actually not that complex of a change!
The text was updated successfully, but these errors were encountered: