Generating 2d levels
ฝัง
- เผยแพร่เมื่อ 5 ก.ค. 2024
- I adventure into the world of procedural terrain generation for my 2D roguelite platformer and learn a bunch of gamedev maths.
The game engine is Godot 4.2.
I used Canva for the illustrations.
If you want to create x and y like I do at the end of the video you can do it like this:
First generate a random angle between two bounds
var theta = _rng.randf_range(theta_lower, theta_upper)
Next generate a radius length between two bounds
var r = _rng.randf_range(r_lower, r_upper)
Then convert theta and r back from polar coordinates to cartesian coordinates
var x = r * cos(theta)
var y = r * sin(theta) - เกม
An elegant solution! Polar coordinates in situations like this definitely make life a lot easier. For more complicated scenarios (which don't follow circular spawing points), it might be worth reading into basic monte carlo implementations. These will randomly sample a space of x,y points (which you can restrict the range of using polar coordinates), and when chained, you can make them converge to a region of interest.
Very cool. Nice thing with your approach is that you can also clamp the generated values to the allowed range of the angle and distance, so any generated number is valid.
Yeah that's exactly right!
Very simple:
1. Pick random angle from range: var a = rand(range(deg(20), deg(100)))
2. Pick random distance from range: var dist = rand(40, 80)
3. Grab x coordinate: var x = dist * cos(a)
4. Grab y coordinate: var y = dist * sin(a)
5. Place platform at (x, y)
Yep, that's exactly right. It turns out so easy this way
@@nick-brooking it makes a bit difficult when you need to add branching, or when platforms have various widths. The best way, imo, is doing recursion by storing position ant width of last placed platform.
^ can be idea for your next video
I don't need recursion because I'm just generating one platform at a time. I store a list of all the platforms with the most recent one at the end so adding the randomly generated coords to the position of the last platform is thankfully also trivial.
But yeah you're right about more platform variability. I haven't got to that yet
Randommmmmm (but thoughtful)
Awesome video omg!!!
Thanks so much :)
Amazing video! you are so under-rated!!
Thanks! Glad you enjoyed it
You know you could just code like 20-30-ish next platform positions based on the current one and just pick one at random from that list. Well you could play around with it by adding weights to the randomness so that you get more of the ones you want or don't want at certain times. Like let's say 3 minutes in you start adding more of the thin platforms or what not. If you read my previous comment about adding more objects in. I'll give you an example: You have current platform and in the next platform you won't only have the info about the next platform stored, but also some coins and spikes and what not. This would simplify the work greatly. I think.
Yeah interesting. I'm thinking I will have some set pieces, ie areas where I have specifically laid out the positions. But I think I've done enough on the platforms for now, I'll come back to them once I've added a few other things to the game
Great video,Great explanation, anyone could understand it
Very nice loved the explanation. Not very good at math would like to see how you actually implement this.
Hey glad you liked it! I just added a code example to the description of the video
Anyone else notice his eyebrow movement.
🤨
@@nick-brooking No its good, very engaging.
Just jumping on platforms is boring. Add a few more interesting objects that change the mechanics like a simple moving platform or pick ups or an objects that deals damage like spikes or what not. And you can start scratching your head on how to place them in correlation to one another randomly, but thoughtfully.
Yeah I have plenty more planned. Just need to keep testing what is actually fun
How are you generating the shape for the sprite at the end? To represent the spawning range I mean
When I create a new platform I create a Polygon2D and add it as a child. Polygon2D has a variable called 'polygon' which is just a list of vertices. So I calculate a bunch of points around the far edge and then calculate more but backwards on the near edge which creates the loop of the shape. Then I can rerender it when it needs updating by calling the same function again. I wrote it super quick and dirty because it's just a debug tool, not intended for the game.
3:48 realistically speaking the "make a guess and if it is wrong through it out and try again approach" is absolutely fine. did you notice any lag? what did your code look like? you would need to run this code no more than ten-fifteen times (judging by eye) and not every frame and that should take a negligible amount of time. if you really want to squeeze out maximum efficiency from your code to the point where you would care about such small optimizations i suggest gdnative instead of gdscript (i am not trying to judge you btw. if you want to go full optimization on your code be my guest, i am just saying that it's not the best use of your time, especially if you are an indi dev)
Yeah for sure. And you're right that I'm not overly concerned with performance at this stage. However the guess and check method involves the conversion between polar and Cartesian coords anyway so by the time you've gotten 1 wrong guess, the other method has finished running, forget the other 10 - 50 other guesses you might need.
Imo the code is also less intuitive making it messier. Imagine I made the shape really tiny so it took up 1/500th of the x and y range. I would expect to need this to run 500 times, in which case I might want to cut down the range of x and y so it doesn't miss so much and all of that is unnecessary complexity.
My point is not that I care about performance too much at this stage, more that it's a decision with no trade offs
1. We like the bias it introduces
2. The code is more straight forward and simple
3. It's more perfomant
@@nick-brooking ok fair enough but to clarify a couple of things :
the 10-50 other guesses are a matter of milliseconds, your computer is capable of running billions of calculations it truly does not matter at all for code that runs once a second
if you made the shape really tiny you would make the x,y range really tiny as well. for example if your shape had values of x [0,0.005] you wouldnt generate random numbers from 0 to 1. unless you had some really weird shape with some really thin areas that require a lot of empty space in between them it truly doesn't matter
now to give the devil his due i agree with the benefits of the bias but there are more "brainless" ways to induce said or similar bias that would need less thinking and time.
now the reason i am saying all that is that i see a lot of gamedevs that are working on personal projects overthink and over-engineer unimportant parts of their code without taking into account their lack of time/manpower. to write the function in the brainless way i suggested would land you with a couple ms of delay but could be accomplished in 5-10 minutes while to actually sit down and think on how to deform the random generator to fit the area i had in mind and how to change the biases of the generation function to get more platforms slightly closer to the player would take a lot longer which is time an indi-dev could use to add a more important mechanic to their game or to fix bugs. that is not to say you shouldn't do what you want with your project but i am just saying for most indi-devs its not a good idea to focus on such things because most indi-devs leave their projects half way through and over-engineering (i believe) is a big reason why.
@@user-dw5ch6ux3u I completely agree with you that people waste time on things that don't matter and that in general, people spend way too long on solving problems they shouldn't. I laid it out my video the way that I did because I believe it made the most sense to explain the concepts that way. My goal is to share info about such concepts and describe what solutions I choose then let people do what they want with that info.
When I actually wrote the code it took about one minute. Here it is, I don't know how to write the guess and check method and simpler than this but let me know if there's a way.
var theta = _rng.randf_range(_theta_1, _theta_2)
var r = _rng.randf_range(_r_1, _r_2)
var x = r * cos(theta)
var y = r * sin(theta)
@@nick-brooking oh i don't disagree that the code is simple but you also have to factor in the time you spent thinking about it up until it clicked in your head to use polar coordinates and just mess with the radius and angle. now you might have polar coordinates and imaginary numbers fresh in your mind in which case it might come to you automatically or even faster than the "guess randomly and throw away" method but if you haven't recently worked with it it will take some time until that metaphorical light bulb lights up.
since you asked the naive method would have been to get a random x,y pair and do these checks:
x^2+y^2r_small
vector from center to the x,y pair has angle within theta_1 and theta_2
the good with this method is that its easier to come up as a first thought not that its programmatically easier. that is because if you have a specific amount of time to allocate to a project it doesn't matter if that time is spent in thought looking at the ceiling or actually coding.
Yeah totally, thanks for sharing. Hey are you working on any projects of your own? I'd be keen to hear how you apply this progress-focussed mindset
Nice video but why you talk and look so funny, why are you flirting with me 😭😭😭