#i m not sure i have the capacity to
1 messages Β· Page 1 of 1 (latest)
one sec
so i'm trying to use T as my switch conditional
essentially
in general i need a way to be able to say "if <T>"
and i don't now
T needs to be a concrete type
it should be though, they're assigned by the palette as one of my tile types like grass, etc
What is one of those types you are trying to check for?
Yeah but what are those? Classes? Part of an enum?
the names of sprites before i dragged them to the palette. they're then converted into TileBase T
i just don't know how to supply another T to compare the tilebase's T to
Can you show the inspector of one of your tiles?
sure anything you need
Unity cannot create a new concrete implementation for a generic type from you just dragging something in the inspector, that would require code generation
are you familiar with the tilebase tilemaps thing? i'm not very, but it's part of the functionality. idk how it works behind the scenes, i'm just saying that you create the template by dragging the sprites, which creates an asset. unity then references each of those as having a Type <T>
Yes, I use them a lot myself.
The asset you create is usually just a Tile, which inherits TileBase
Select a tile in the inspector, it will tell you the type at the top
ok, hang on
\
As you can see, it says dirt (Tile)
Meaning the dirt asset is an instance of the Tile class
ok. so then how would i create a reference to compare a given tile to
There's a few options. It kind of depends on what you want to do gameplay wise
You can either get a reference to all the different tiles, or you can implement your functionality on top of a custom tile type
simply put: terraria with other stuff
In this case you basically want to add, "additional" information to each tile, correct?
i'm in the middle of trying to make a process to make water work. first is looking at extant tiles and making a dictionary of vector3ints with their data, then is to look over that and start making changes. the issue i'm running into is in the first part. i'm hoping to be able to grab the actual tile as i do a foreach (bounds etc) and then use that information as the conditional for a switch to add other info depending on which type it is
yeah exactly
The two options are roughly as follows:
- Store a reference to the tile types you want to check.
For example, set the tiles you have in the inspector, e.g.
[SerializeField] private TileBase _waterTile;
...
TileBase tile = TileMap.GetTile(position);
if (tileBase == _waterTile)
{
//do something relating to water here
}
- The other option is to write a custom tile type that has the information you need, and then read that in your code
i started w/ strategy 2 and it just seemed less useful. but the first seems good. let me try #1 out again but i thought i tried that once
I would recommend the second option, as you will eventually need to write custom tiles in a tile based game
Especially if gameplay data is tied to those tiles
It's very useful
ok so the way i was doing it was basically to associate scriptable objects. can't remember the exact method off the top of my head but is that more or less what you're talking about?
when i painted them they had the SOs already on them
or are you saying you can create a whole tile with custom fields
TileBase is a ScriptableObject
I'll write you some code as an example for a custom tile
ah man we're getting above my head here. i've only been doing unity a couple months total and half of that was 3d MLAgents stuff, totally different
didn't use SOs
thank you
I would get familiar with them, they are very useful, and you are already using them. As I mentioned, the tiles you currently have are scriptable objects
i'll take a few hours to go over some docs and videos. anything you suggest in particular?
I think you will understand it a lot more from this example I will show you in a second
ok no rush, ty π
[CreateAssetMenu(menuName = "Tiles/TerrariaTile")]
public class TerrariaTile : Tile
{
public enum TileType
{
Water,
Dirt,
Rock
}
[SerializeField] private TileType _tileType;
public TileType Type => _tileType;
}
This gives you the option to create a new asset type:
And on these tiles, you can now set the defined data:
This data can be checked in your code
e.g.
var tile = TileMap.GetTile(position) as TerrariaTile; //Assume all tiles are of type TerrariaTile
if (tile.Type == TerrariaTile.TileType.Water)
{
//do something here
}
You can then also use a switch case here:
switch (tile.Type):
case TerrariaTile.TileType.Water:
//...
break
To use the tile in the tile palette, you just need to drag the TerrariaTile asset you create (e.g. dirt) onto a square in the pallette, then you can draw it on the tilemap
what does => do?
It's C# shorthand for a Get only property
ah
It's the same as:
public TileType Type
{
get => _tileType;
}
(since you probably don't want outside code being able to change the tile's type)
ah so i put in the custom tiles on the palette instead of the other ones
Exactly
that's amazing
Anything that inherits TileBase can be put on the canvas iirc
yes, this is extremely useful
You can store any data you want here, e.g. drop tables, max health, etc
sick
Just note that this data is not unique per tile
For example, you cannot use this to store the current health of a tile
All tiles of the same type share the instance of the scriptable object
ok that's fine bc i already have a process started to keep track of all that. being able to set specific info like that is definitely going to solve the issue and do a lot more though, from the looks of it
A better example for a use-case would be a game object that is created when the tile is destroyed
i actually would like to get you to look at this, but it's in the middle of major construction so would be too confusing. will you be around in like an hour you think?
It's 4am here so I was just planning to go to bed, but I'm here every day. Just write in this thread if you have any questions and I'll try to answer when I see it
thank you very much that would be really appreciated. this thread won't close on its own?
I actually have no idea π
lol io should at least screenshot what you wrote then.
It seems to stay open for 24 hours, after which point it is archived, meaning you can still access it by going to the original message where it was created
That would be this message: <#π»βcode-beginner message>
you mind if i message you later if it does close then?
Sure, go ahead
alright thanks again. have a nice night!
Ping me in one of the code channels (or probably #πΌοΈβ2d-tools ) since I have DMs disabled by default), and I'll see it
are you still here? i'm wondering if i'm better off actually filling empty tiles, at least ones that i encounter, with a custom 'empty' tile or if i'm better off just leaving them null. in your experience. as i said it's terraria-esque so there will be tiles coming and going all the time
Why would you want to fill them with empty tiles?
Since null is essentially just that
idk
no actual reason at the moment just wondered if other ppl had found one
er, you had
sorry i had assumed you went to bad my bad
i got all my tiles set up though
It's now 5am β οΈ ill check back tommorrow
looks like you're still away, but here's my very updated code. http://pastie.org/p/3ASnba1f7DMXzI08m49fbI it's working fine for Start() but errors out on update at 49 (our old friend) for tile 3,0,0. most of the script seems to be working right, but 3,0,0 shouldn't be still on the active tiles list, and i'm wondering if maybe there's some issues going on with my not timing these reads/write correctly for unity so it gets confused. any help would be very appreciated!
the other lists seem to be working (although i haven't gone through and checked grid coordinates, but there's the right # of them given the context)
actually i take that back, the 3,0,0 is erroneous with just start(). it flows correctly and is deleted correctly, but still is on activeList...hmm
Hard for me to say, but I don't see anything wrong in relation to unity, so it is probably just a logic error somewhere. I recommend you use an enum instead of integers for tiletypes though
i think you're right, it's a logic issue. i'm going back through. i am using both. it's just easier for me to conceptualize when i'm writing. i use the enum to convert the actual tiletype in the custom tile script to an integer of the same number.
an enum is just a number, so you can do both with the enum if you really want to
for some reason when the context to use the enum comes up, it gives the name instead of the integer, which is why i named them things like zeroEmpty oneGrass fiveStone etc
Although I don't really see the point, it's just more error prone
are you saying i can just replace fiveStone with 5 and it will work?
when i use the enum i mean
Why are you trying to use numbers for this in the first place?
I feel like Stone is easier to remember and gives more intention than 5
How is it easier?
so i can just say if this.5 == this.5 instead of trying to use all the enum logic that i barely understand
it's just easier for me to do all that IN the enum
er that.5
You can check enum type just like anything else.
e.g. if (tile.Type == TileType.Stone) //do something if it is a stone
i'm just saying it's a lot easier for me to conceptualize a tiletypeint than enums, idk
I certainly recommend you read up on them, they are very useful π
i can always convert once i understand it better.. it seems clunks idk
c-style "enums" (basically constants) is something that isn't really used a lot anymore, with good reason
clunky.. but yeah it's probably just my ignorance
Enums also specifically let you set the numbers per value, and even let you have a single value be "multiple" types
e.g.
[System.Flags]
enum MultiTileType
{
None = 0,
Stone = 1,
Water = 2,
StoneAndWater = Stone | Water
}
i actually did do that. it's in CreateTileEntry or whatever... i guess i just never tried to use the numbers instead of the names
to call
does the approach i'm using seem reasonable though? i was hoping to get an overall opinion on my process if you have one
well it's a tilemap game where i want some tiles to be active (like water) and updated on their own. this process is basically the backbone of that
So you want the water to flow in a similar fashion to terraria?
for now i'm just trying to get them to make a tile below them and delete the tile if it no longer has enough water
yeah exactly
Then you are basically writing this: https://smashwaredotcom.wordpress.com/2013/05/21/falling-sand-algorithm/
It's the same algorithm games like noita use
yeah and i've looked at a bunch of them, and am more or less modeling after. just curious about my actual usage of C# to accomplish
if i'm missing anything obvious
or if i should take a different strategy in places
It kind of depends on your map size and how performant this needs to be
i would like it to be relatively performant i guess, but it's a game like terraria. idk if that's considered performant for what it does or not tbh. if i could reach something along those lines that would be def fine.
Terraria has a relatively large world, so you will at least need to do some kind of chunking system that can detect what chunks needs update. You won't want to iterate over the entire map each frame
i don't think the map size will be nearly as big as that. i would rather make stages
e.g. a chunk that doesn't have any flowing water doesn't need to do anything
kind of like a largish puzzle game area
ok...where would i start looking into chunking?
There's no real definition of it. Basically you would need to subdivide your world into some partition (e.g. 16x16 tiles), and then only update the tiles required.
If your map isn't very large you probably don't need to do it though
I think that should be fine, but it's hard to say
ok... well that gives me an idea at least
i'll just try and get it working and go back over it after if it's too slow i guess
One thing to note about the tilemap system in unity, is when you are setting a large amount of tiles use the batch update methods, setting individual tiles is very slow
What is considered a large amount? I would think maybe at the VERY most it would be 500ish tiles? i'm just trying to think what a large 'lake' would be. 50x100 seems about as big as i'd go
I can check how large my player view is in tiles, give me a second
awesome i fixed it by checking for a list entry before adding. i forgot they can take double values.
where would be a good place to put a delay in between tile 'updates' if i were to do that? the water flows at warp speed at the moment lol
and would the best method be yield delay? i'm gonna have to look that up but i've heard some methods can suspend threads or something so i'm just making sure
I would probably add a "flow speed" to tiles, which is the required amount of update ticks a tile needs to move
would implementing this require me to do the iteration interface (idk really what this means)? i'm getting an error when i try to add yield return new WaitForSeconds(0.5f); to anything
yield only works inside an IEnumerator if you are attempting to write a coroutine
is there a way to add a delay to a regular method?
You can write an asynchronous method
Which achieves something similar to a Unity coroutine
hm.. would i be better trying to learn how to convert my EvaluateTiles method to coroutine? not really sure what that would look like...
it's basically iterating anyway
It depends on how you want to implement your waiting
e.g. do you want to just not run your simulation every frame, do different tiles need different update speeds, etc
For example, sand falls slower than water
i just don't want the tiles to flow at warpspeed. the less fuss the better, but obviously i want to set the architecture up smartly now that the project is still smallish
yeah, i could see some things needing different flow speeds
Then I would just set a static simulation speed, e.g. 60 times per second, and then add a flow speed to tiles, incrementing a counter per tile instance, and only if that counter exceeds the flow speed, update the tile and reset the counter
So if you set the flow speed of water to 60, it would "flow" once per second
ok that sounds good. can you point me to a doc on that or maybe give me an example? or both xD
If you replace your Update method with FixedUpdate, the simulation will run at a fixed time step, the default is 50 times per second
You can set this in Edit -> Settings -> Time -> Fixed Timestep
oh nice. is using a divisible number like 64 a good idea or no?
It doesn't make a difference
not even to having some kind of matching with fps?
You can't really predict your FPS
oh good point.
alright sounds good thank you. so once i've got a fixed update, what's the syntax for waiting a portion of that time?
Just watch out that this update value is also the update speed of the physics system, if you are using it
is it bad to use both update and fixedupdate? my understanding was "fixedupdate is for physics" and "update is for other things" more or less
It doesn't matter. It just depends on if you want your logic to run at a fixed timestep, or if you want it to run when the game refreshes (which can be limited by things like VSync)
The fixed time step will always be the same, while the update speed will vary vastly from machine to machine
alright, i'm not even close to that fussy in this game. just as long as unity doesn't get confused i'm good
so what is the command to wait during the fixedupdate? sorry for repeat xD
er i mean alongside
You wouldn't wait, you would just update all tiles in that fixed timestep.
for example
[2, 3, 1, 3, 2, 4] <- frame update array
The above array is incremented each update, and if the number exceeds the "flow speed" set on the CustomTile object, then you actually update the tile (make it flow)
The positions in the array correspond to the tiles on the tilemap
so each fixedupdate i increment each counter until it exceeds whatever i set
For example. There are multiple ways to achieve this, but that's how I would do it
i believe i'm following.. please continue π
oh
nvmd lol i misread
ok yeah that seems like it should work
just put that array in the main class with my other lists?
My game runs at 100+ FPS at 368x160 tiles, and these aren't static, so you should be fine
nice, yeah that's more than performant enough
I also use the Z axis on the tilemap so it's actually more than that
I recommend you do use chunk rendering on the tilemap, since that significantly improves performance
your project does?
Yes
It changes the internal rendering to use a lot less draw calls, instead of 1 draw call per tile
You can set it on the tilemap renderer component:
so let me resummarize. i'm making an array of values that correspond to each tile that is 'active', and on each fixedUpdate, i iterate the value ++. then when the value exceeds the 'flowspeed' of the custom tile type, i tell my code to actually move the tile and whatnot
?
Like I mentioned, there's a million ways to do this, that's how I would do it
cool, just making sure i understand
You will probably need an array that contains information per tile anyways, so you can just write your current counter value there
i'm storing them in a dictionary. is it slow to iterate the dictionary that often?
i mean the stuff in the dict
An array is a lot faster
ok i'll figure something out with an array then. thank you so much for all the help (again). i definitely have plenty to go on here i think.
oh one question: the chunk rendering you showed me. does that require a change in the code as well or is it really just changing it in inspector?
You need to set it on the tilemap and make sure that all tiles you are rendering are on the same texture. To easily achieve this, you can use a sprite atlas
If I were to implement a prototype for this, I would start with a much simpler version than what you have done:
Basically a struct that contains the instance information for each tile:
public struct TileData
{
public int FlowCounter;
//other stuff
public void Tick()
{
FlowCounter++;
}
}
public TileData[] _tileData = new TileData[TileMapWidth * TileMapHeight];
Then I would iterate that each fixed update:
for (int i = 0; i < _tileData.Length; i++)
_tileData[i].Tick();
Then grab all tiles from the tilemap:
public TileBase[] _tiles = new TileBase[TileMapWidth * TileMapHeight];
...
TileMap.GetTilesBlockNonAlloc(new BoundsInt(0, 0, TileMapWidth, TileMapHeight), _tiles);
for (int i = 0; i < _tileData.Length; i++)
{
//decide how to update the visuals of the tilemap according to its data here
}
TileMap.SetTilesBlock(new BoundsInt(0, 0, TileMapWidth, TileMapHeigh), _tiles);
hm i think i'm understanding
sorta
and you think doing that is more performant than just keeping track of tiles of a certain type?
It depends a lot on the setup, it's hard to say
The easiest thing to optimize here is storing tile data as a struct in an array
And using SetTilesBlock instead of SetTile
hmm... how would i go about in the bottom part updating the visuals as you suggested? is that using setTile or some other method? sorry i'm pretty new to tilemaps so it's all a bit confusing
My example is lazy and sets the entire tilemap each frame, you can only set a subsection of it to improve performance even more
the irony is i started with a struct and converted it all to class
this TileMap.GetTilesBlockNonAlloc(new BoundsInt(0, 0, TileMapWidth, TileMapHeight), _tiles); copies each tile in the tilemap into the array
You can then modify all enties in the array
and write them back with TileMap.SetTilesBlock(new BoundsInt(0, 0, TileMapWidth, TileMapHeigh), _tiles);
so i can do a foreach with that? i guess i'm not sure how i would go about actually setting the new tile w/ the data
once i grab the tilemap data
It's not super straightforward since a part of the falling sands algorithm is deciding what to do when two tiles "merge"
e.g. in a single update two water tiles fall to the same position
yeah, i've already sort of run into that. just decided to cheat with arbitrary cutoff points basically lol
until it's more built at least
There's different ways to work with it, e.g. store a byte per water tile that indicates how "filled" that tile is with water, and if two water tiles collide, they fill, and if they overfill, they split their overflow value to the tiles left and right for example
so if i replace my cellData class with a nearly identical cellData struct...is there going to be issues? what might i not be anticipating? i just don't really understand structs which is why i switched to class
obviously i need to declare my variables and dicts differently... but beyond that...?
It's complicated to explain easily since it requires a good grasp of computer science knowledge, but the gist of it is this:
- structs are allocated on the stack, classes are allocated on the heap
- this makes structs faster for simple data storage since they are layed out linearly in memory, which helps cache coherency immensely
- this makes structs better for video game development since they are not managed by the garbage collector, meaning less garbage collector stalls
- structs are much more limited in what they can do compared to classes
- structs are copied entirely when passing the variable, while a class copies the reference to the memory location on the heap (meaning you modify the same instance, instead of copying it)
This is the reason why unity math types are all structs, e.g. Vector2, Vector3, etc
i actually watched a few hours on structs and the heap/stack thing so i sorta know what you mean. i am just mainly asking in practical terms as far as my script and what i'm trying to accomplish here. i make regular backups so i can def experiment...
like as far as breaking my script more or less
As long as you don't try to store the struct anywhere (e.g. by adding it to different collections) then you should be fine
Basically store all your tile data in one array and update that array
Can I use a list? why array?
You have a fixed world size, why do you want to use a list
out of range exceptions scare me lol idk i just found them slightly easier to manage i guess
You can get out of range exceptions with a list π
You cannot expand the capacity of an array
exactly... but you need to be adding tiles to it all the time so why would you choose that? i just don't quite understand
Why do you need to "add" tiles when you have a fixed world size?
The array already has a "spot" in it for each tile you could add
That's why the size is set to new TileData[TileMapWidth * TileMapHeight]
ah. the strategy i was using only had data for tiles that actually had been drawn
and deleted them after they were moved
That will be a lot slower than just having all of your possible data in an array
hm, ok
With your dimensions it probably won't matter though, I doubt you need to optimize much for a tilemap of size 200x200
That's 40000 tiles, arrays can easily be multiple million entries long
This really only becomes an issue if you world is exceedingly large
My game world is 134217728 "tiles" large (1024*512*16*16), which I cannot put into an array without running out of memory
out of curiosity, just how much slower are dicts than arrays? would it, for example, be a bad idea to index all 40,000 of the tiles in a dictionary (just hypothetical)
For your sizes it's not an issue. Access to a specific key is slower, because GetHashCode needs to be run on your key. You are basically trading performance for memory. The dictionary uses less memory, but is slower to access and slower to read
ok... i will definitely experiment with arrays, but i find the key value pairing really useful. that other strategy seems interesting as well but almost like it should be a different project or just an overhaul version. i'm still using this to learn too so i don't mind trying one thing and seeing then fiddling later...
The way you write your code for an array or dictionary is very similar. You just write a helper function to convert your 2D coordinate to the 1D index in the array
e.g. int Index2Dto1D(int x, int y, int size) => y * size + x;
And then do _tileData[Index2DTo1D(xPosition, yPosition, _tileData.Length)]
helper function i'm gonna have to write that down lol
that sounds basic xD
wait, is that a formula to create an index for a coordinate? i'm not quite following
i tried to figure that out for like 5 hours lol
Yes
damn i even looked on google and math ppl were saying it is impossible so i gave up
If you want it even more easy to use, you can write an extension function
e.g. static T From2D<T>(this T[] arr, int x, int y) => arr[y * arr.Length + x];
Then you can do _tileData.From2D(xPosition, yPosition);
And your set function would be static T Set2D<T>(this T[] arr, int x, int y, T value) => arr[y * arr.Length + x] = value;
well that's awesome. yeah i distinctly remember running into this roadblock now. i think that's why i went dicts actually.
You can use dictionaries if you want to, like I mentioned, for your tilemap size you don't really need to optimize heavily
(again it's a tradeoff between memory and speed)
this is really nice to know though.
If you tried to store only a single integer at the tilemap size I mentioned earlier, it would require 536 megabytes of memory, so not really feasible
what is the int size in your conversion formula?
The length of the array
oh, right
Oh wait, my bad
This won't actually work for you
It needs to be one dimension of your tilemap
The width ideally
oh, yeah... i think that's what they were saying on the math forum lol
The extension function won't work then, since it needs to be passed the width in addition to that
the way i did it was literally just counting each tile i make, they don't even really need an index number though with the dict
You could wrap your struct in this case if you wanted to, but there are many ways to go about this
The index is the coordinate
the key. right, i just mean i gave each tile a never-repeated number (for reasons lol)
You can do that, but it's more complicated to upkeep
it's pointless. i'm probably going to just remove it
i figured might as well put it in now and remove it later if i don't use it lol. probably a few SE jokes about that exact mentality...
anyway thanks again i'm gonna try and get that delay system working. cheers!
Since you asked this earlier, this is how coroutines work (although I personally wouldn't use them in this case): https://docs.unity3d.com/Manual/Coroutines.html