Search Unity

Best level-creation workflow for chess-like turn based game

Discussion in '2D' started by VacuumBreather, Apr 18, 2020.

  1. VacuumBreather

    VacuumBreather

    Joined:
    Oct 30, 2013
    Posts:
    68
    I am currently slightly stuck at finding the right workflow using the available Unity tools to achieve what I'm trying to do. Maybe someone can help me with that.

    My game is basically chess like. It's tilemap based, the board is not fixed however, but the levels will be rectangular, will have certain terrain tiles and units moving on them to fight eachother.

    First problem: Terrain Serialization

    There seem to be two options for me to actually serialize a "level/scenario" I want to create. I'm currently using my own variant of the "GridInformation" scriptable object from the 2D extraspackage. basically I'm storing the terrain type of each grid position of my level, from which I then generate on map load the appropriate tiles on the terrain tilemap.

    However this requires me to load the tiles both in editmode as well as playmode whenever I assign a new "level" to my Grid gameobject. The custom behavior attached to that object triggers an update on the terrain tilemap child object and passes it the relevant information so it can assign the appropriate tiles.

    Is that a good workflow? Or should I simply just paint the tiles, have the necessary data (like movement cost) of terrain assigned on the actual terrain tiles themselves and instead serialize the tiles into my map object? I'm unsure what the best way of doing this is.

    Second Problem: Should the grid be a prefab


    Also... my Scene setup for gameplay is a scene with a grid object and several tilemaps attached to it. One for terrain, one for units, one for overlays etc etc. That grid hierarchy is a prefab. However this results in the Tile Palette window complaining that painting on an instance of a prefab is not a good idea and performance drops while painting.

    Now the recommended best practices always say that most of the stuff in your scenes should be prefabs so the scene assets themselves remain relatively clean in diffs. Also I'm using that grid prefab both in my gameplay scene as well in a sandbox experimentation scene and probably in the future in some testing scenes where the UI is not included and so on.

    So how do I work around this? TilePalette complains and the performance goes down like crazy when painting on the Tilemap prefab instances, but the only way around this I see currently is painting in actual prefab mode and have every single level be its own perfab. That sounds weird and wrong. The prefab should not be the map itself, it should only be able to execute the map.
    How do people handle this usually? Do you have a scene for each level? This seems weird to me since except for the actual tiles on the grid, the scenes would be 100% identical

    Third Problem: Unit serialization

    Currently I have my own custom brush which can "paint" a unit type onto the gridinformation scriptableobject that the GridComponent is currently holding.
    This causes an update in my UnitLayer object which instantiates a unit prefab at the correct cell position and assigns it its unit type, which contains the appropriate sprite and default data for that unit.

    So far so good. However here I have to do this again at edit and playtime, I instantiate a lot of gameobjects in editmode whenever I switch out the map, and again.. I'm unsure if this is the best workflow. I'm currently serializing only the unit types on the gridinfo object and that serves as enough information to instantiate my units. But to actually SEE something in edit mode while creating a map, I need all this [ExecuteAlways] code that triggers updates so stuff gets instantiated for me to see what I'm actually editing.

    Again, what and how would I serialize units the best?

    Forth problem - Units on grid or inside tiles?

    I know that tiles have a gameobject property. So I can create tile for my units with an additional unit type property, paint that on the tilemap, and at playtime that gameobject prefab gets instantiated. So far so good.
    That would be an alternative to what I'm doing right now.
    However... that way I'd need one tile for each unit type. Which can get a lot in my game. Also, if the tile only has a gameobject but no sprite, that unit shows up fine in the scene view when painting (not sure how, since apparently no gameobject is actually instantiated, I'm assuming the tile somehow renders the prefab preview), the docs don't explain the gameobject property at all pretty much.)
    But... in the actual tile palette those kinds of tiles don't render anything. Any tile that only has an object but no sprite is bascially invisible in the tile palette window and therefore completely unusable. How do people use these kinds of tiles? I don't see the point in also giving it a sprite, since I don't want a sprite, I want an object.
    Also, if I use those tiles, since no gameobject is actually instantiated while painting, it seems impossible to modify the starting values of specific gameobjects. Like the one on tile x,y should have +10 strength.. that seems impossible to do with this workflow.
    So I'm guessing this gameobject property is only useful for gameobjects that are stationary decoration objects and not for units which differ and will move around?

    So again.. what's the best workflow for "painting" units onto a grid and then serializing them into a level. Here again it seems wrong to have a scene per level, since only the units differ, the rest of the scene remains unchanged.

    The Unity tutororials are a nice resource for creating one single level and playing around with that, but they never really go into the workflow of creating multiple levels, how to serialize tilemap based levels and whether handling/placing units on the grid is something that should be done with the tilepalette window, or by hand or with a completely custom editor.

    Fifth problem - Internal classes... why?

    The 2D Tilemap package used for all this has classes like "paintable grid" etc, which seems to be the basis of the entire tilemap editor window, for the nice overlay which lets us select grid cells and have access to the entire workflow of calling paint/erase/etc on a selected brush.

    I was thinking about creating my own inspector for my custom compontent which sits on the grid, for loading, refreshing maps and painting stuff with completely custom brushes which don't really work with a palette. Using those classes might possibly give me out of the box functionality for grid cell selection, painting with brushes etc. However ALL of these classes are internal and therefore cannot be used for any inhertiance or instantiation for custom editors. Why was this decision made? Why are those types not public so people can use them in their own editors?
    It seems a bit overkill for me to completely re-do all this logic, so creating my own editor to manipulate the tile maps seems out of the question also. It seems Unity is really forcing the user to use the tile palette window, even when the brushes used are not even using the palette... I can't even collapse the palette section of that window properly so I only see the brush selection and the tool bar. I always have this big empty useless tile palette section when I'm using brushes which operate without a palette. I wish that was collapsable.

    Sixth problem - Brush instances

    Brushes have this custom attribute indicating whether the default instance should be shown in the palette window etc etc.
    What I am not 100% sure about... if I create several instances of one Brush.. I know how to use them in the palette window... but are those brushes useful for anything other than the palette? As I mentioned the use of these brushes seems to be hidden away in internal classes so it seems palette window, paintable grid and GridBrushBase are all linked together and are not supposed to be used apart from eachother? Is that a conscious design decision? Are they supposed to be selected and used in that one window and nowhere else?

    I know this is a lot but these are all questions I spend weeks trying to find good information on, both on youtube, these forums and other resources, like discord channels and so on. And I found very very little unfortunately. Most of the stuff I find is about custom editors which do their own custom grid layout, probably from before the unity packages were released. There is very little that goes beyond the hello world level on using the 2d tilemap package in more advanced workflows and what the idea behind them is for level loading/saving.
     
    Last edited: Apr 18, 2020
    hoskope likes this.
  2. VacuumBreather

    VacuumBreather

    Joined:
    Oct 30, 2013
    Posts:
    68
  3. Lo-renzo

    Lo-renzo

    Joined:
    Apr 8, 2018
    Posts:
    1,513
    I procedurally generate my maps so I nearly completely avoid the Editor (due to some of the reasons you've mentioned above!), so I'm not an authority on it, but since nobody else has helped let me try my best at a few suggestions. I tend to use the Editor and brushes purely for testing visuals.

    Wacky idea: could you simply and clarify the design work you do in Editor by instead of painting your final tiles instead paint simple colored tiles that represent units, actual tiles, etc? This would be akin to the simplified map mode in games like Civilization. Then on Play you translate these simple tiles into instantiations of everything you need? E.g. Knight is red, King is black, etc. That would at least help you see what you're doing clearly, with the obvious drawback that you only see a simplified visualization at design-time. This might improve the workflow vs. checking your GridInfo. You could create an Editor script that specifies shortcuts for flipping between the different "views" of your design data: movement cost, chess pieces, terrain etc.
    Code (CSharp):
    1.         [MenuItem("MyShortCuts || SwitchViews &v")] // ALT+V
    2.         public static void SwitchViews(MenuCommand cmd)
    3.         {
    4.             // find your grid, find cycle turn on next tilemap, disable previous tilemap
    5.         }
    I believe you could use StartUp() here coupled with a Strategy pattern. So, in your tile you override StartUp to access that tile's associated GameObject. You GetComponent<MyProperty> then here's where the strategy pattern comes in. You have a ScriptableObject called, say, PropertyChanger. You have a custom tile class with that PropetyChanger as a field you can plug in from the inspector for unique tiles, or the PropertyChanger lives on your GridInfo so to avoid custom tile instances. This PropertyChanger has a list of properties it knows how to change on MyProperty component. One could be "Change Strength by X amt" or "Increase movement points by 3" etc. The different instances of your PropertyChanger ScriptableObj can be given these different property changes, thus allowing you to initially assign your chess pieces different values. I describe something similar here in more detail.

    On the other hand, personally, I don't associate GameObjects with my tiles. I leave them independent. When I want to find out where a G.O. is, I'll use grid.WorldToCell(goTransform.position) to get its cell. StartUp might still be useful for assigning the initial G.O. properties of the board, either with the Tile reaching out to the GridInfo for a PropertyChanger or the tile itself having a field that does the same.

    If the cost is less clean diffs, does that matter enough to not make them prefabs? I don't prefab them and, granted, I don't do much in Editor but it doesn't cause problems for me and the tradeoff seems worth it. Alternatively, could you break apart your prefabs differently? For example, you say that most of it doesn't change between levels... so perhaps you parent the mostly-unchanging stuff under an empty GameObject that sits below the Grid, then do not prefab the grid, then have seperate empty GameObjects that are parent to the more frequently changing tilemap(s)? Or could you avoid prefabing anything that doesn't strictly need to be a prefab? The best practices are just recommendations and it sounds like you have an exception to the rule.

    Anyway, hope that gave you some help or at least rejiggered your thinking.
     
  4. VacuumBreather

    VacuumBreather

    Joined:
    Oct 30, 2013
    Posts:
    68
    I thought about that, but decided against it. I now moved all my code that generates stuff on the map at edit time into a custom editor, so it's no longer mixed with my runtime code.

    I thought about that also, but there seems to be no way to actually access a unique tile. They seem to be all the same instance if the tile data is identical. ANd there's no way to even SELECT a single tile on the tilemap in the editor. At least I don't know how and I doubt that's how it's supposed to work. The tilemap probably uses shared instances of tiles for optimization. It doesn't seem like you're supposed to edit data on individual tiles if they're coming from the same tile asset.

    I don't do that anymore either now. At least not for units, that just doesn't work. It probably works well if every gameobject on a tile asset is always the same. But that doesn't work for units. I just wanted to ask that in case there's some fun workflows with that property on tiles that I'm missing. But it seems that's not the case.

    Well the issue rises as soon as the tilemap component is on a prefab instance. And if I don't have THAT inside the prefab, then I won't have a prefab at all. So no, splitting the prefabs up into different stuff doesn't work. It's not a matter of what prefab the tilemap is in, the palette window complains as soon as it is in any prefab at all.
    Maybe I should just ignore that warning. It works after all. Yes it's weird for performance since I'm causing tons of overrides, but if I want to reuse my grid hierarchy in other test scenes the way it is, then I see no way around that,
    unless I write a complete custom editor for just my map asset. THis is actually something I wanted to do, but since there is apparently no way to instantiate the paintable grid, since it's an internal class, I'd have to rewrite a ton of things from scratch. I guess if this becomes a problem at some point I'll just have to create a custom scene just for map editing and break the prefab instances there. But I will do that maybe later, when I'm certain enough my prefab remains stable.

    Thanks
     
  5. Lo-renzo

    Lo-renzo

    Joined:
    Apr 8, 2018
    Posts:
    1,513
    The Tilemap API should really be coupled with some kind of GridInstance system because I've seen so many posts like this with people wanting instances associated with their tiles and the way GameObjects integrate is lacking. I found I need to maintain whole parallel collections for cells where I need instance data and use Tilemaps just as one of many possible visual representations. That Startup() doesn't instead work in such a way that it could supply a cell position seems like a major oversight.
     
  6. VacuumBreather

    VacuumBreather

    Joined:
    Oct 30, 2013
    Posts:
    68