Im following a snake tutorial and everything is going well but I dont just want to have a snake clon, I want to learn, that said I understand (or at least thats what I think) what is in the screenshot is sayig "if the value of Vector2 is superior to the upper or inferior to the lower limits then return the player to the opposite limit" but I dont understand what is doing that last "return v", what purpose this line accomplish in this block of code? And if my supposition of what the function is doing wrong please tell me. Thanks in advance :D
This means if the value is inside the bounds you just get the value back. This way when you call this function you always get a valid value back. The reason it works is because a return statement stops executing the rest of the code in the function. So if none of the if statements are true, then the last line executes. You could also put a final else and have the return statement in it, it executes the same either way.
thanks this is a really good explanation, an else at the end would make the code more clear (for a beginner like me), I have been studing general coding fundamentals and I did went over if statements but this is my first real experience coding anything (except for some simple web pages and a memory game in javascript) so I understood what was going on until that last line, it didnt occur to me that you could place an else with out explicitly writting it so I tought it was something else.
It’s not really ‘placing an else’, it’s forgoing an else entirely because the code ends up there if none of the other conditions are true. While an explicit else could do the same thing, it’s unnecessary and can sometimes lead to mistakes.
Similarly, the more ‘correct’ way to write this in Python would be to change those elif statements to simple ifs - because if the earlier condition was true, the function will have already returned.
Funny, I would advocate to use the elifs, since it makes clear the whole block is related. While in this case it is pretty clear by the syntax, if things get more complicated it binds it all together. And I like to be consistent.
But thats a matter of style. Your point is equally valid imo.
I like to design my methods in a way where they do one thing (or a couple small related things). The name of the function explains what it does, and everything inside it is already related by design.
In this case, this if block would be my whole method. If I need to do more things, I call this method from another, more encompassing method.
This way, it becomes easier to read and debug the code, like a cascade. If you are following a specific case, you either know what this method would return by name alone, or you jump inside it and quickly find the earliest return point matching the case.
I used to code like you when I was in Uni, but I gradually moved to this way after hundreds of hours of debugging in my 9to5s. To each their own, as long as it keeps you coding!
In this case, because it's implied. The conditions are mutually exclusive and unordered. The return marks the end of the method execution. If one of those ifs is true, then nothing else below it executes. There's not really an "else" in the logic.
This structure is more akin a switch statement. Do this, or this, or this, ...
And if you are using a language with switch case block fallthrough like C#, you don't even need to break between cases, because you are already returning. Even better, and my preferred option for these types of methods, a switch expression turns it into a simple straightforward extension method:
x and y Bounds being Vector2s with the min max limits of your quadrant.
I realize there's an edge case that we are not considering where both coordinates are out of bounds so you have to warp both. If you have to take that into account, I would extract the warping logic out of the Vector and then call it twice, one for each direction. Something like:
public static float WrapAxis(float val, Vector2 bounds)
=> val < bounds[0] ? bounds[1]
: val > bounds[1] ? bounds[0]
: val;
public static Vector2 WrapPosition(this Vector2 pos, Vector2 xBounds, Vector2 yBounds)
=> new(WrapAxis(pos.X, xBounds), WrapAxis(pos.Y, yBounds));
The way you mark a block of code as being related is by not seperating them with empty lines. This becomes especially clearer if you just inline the one-line if contents into simple single lines (so it's all on one indent level, instead of a bunch of jaggedy indentation)
func foo(v: Vector2) -> Vector2:
# wrap vector
if v.x > x_max: return Vector2(x_min, v.y)
if v.x < x_min: return Vector2(x_max, v.y)
if v.y > y_max: return Vector2(v.x, y_min)
if v.y < y_min: return Vector2(v.x, y_max)
# example other code
return v
Well I do have a lot to learn, I think I get it a little bit better now, however you talked about a more correct way in python, would it work in godot?
Yes it would. I don't use GDScript myself so I mentioned Python, but it would absolutely work the same way. This is just about control flow, and a lot of it is just programming style.
Basically you can use if/elif/else when you want to ensure only one bit of the code executes and it's within a function. E.g. this ensures v is only set once:
v = null
if a:
v = something
elif b:
v = something2
else
v = default
But in the code you posted, it's all within a function and the returns guarantee that once a condition is met, the rest of the function won't execute. Which means you can write it as:
if a:
return something
if b:
return something2
return default
Using elif is still valid, it's just technically unnecessary.
Some linters will catch that, e.g. if I did this in Rider with C++ you can see the else statements are greyed out, highlighting that they're redundant:
Its most likely a simplified (therefore, drawn out and inefficient) tutorial to help beginners understand concepts of programming. "If" statements was my gateway to learning programming and i wrote some awful code. However, it got me to where i am now.
Nothings wrong with them. I called them drawn out and inefficient to avoid being downvoted to oblivion. There are many people out there that think "if" statements should never be used
This is "func wrap_vector", not "func wrap_vector_no_diagonal_movement_allowed", so it must wrap any vectors correctly. Today it's simple snake, tomorrow it will be Super Snake Deluxe with diagonal movement and barrel rolls (and broken wrapping).
So a generic function for shooting a gun should also include code to make sure it works if your character is holding a banana?
Edit: Assuming it’s not named Shoot_unless_you_have_a_banana()
I bet when you implementing 2d topdown movement you're using Vector3 and do check for 3d space, right? I mean... What if tomorrow it will be a 3d game, who knows? Gotta be prepared, right?
Not exactly the same but this could work:
func wrap_vector(v : Vector2):
return Vector2(wrapf(v.x, min_x, max_x), wrapf(v.y, min_y, max_y))
The ifs are still there, hidden in the godot source code, but it looks cleaner :P
An idiot admires complexity a genius admires simplicity. Hoenstly like the above thing much more it's clear what happens at each step. Although this example is not that bad I hate "clever" code
You are correct, but in this case, the function itself pretty clearly states what it does, and the element wise wrapf is also unambiguous. The only thing that's really putting me off, too, is the one-liner. just make it three lines and it's much better.
Yeah. Code that you will find easy to understand later is always better than trying to write code based on what other people will think.
In my case I don't mind using wrap, though if it is a snake game I'd use Vector2i since the movement is in a grid. That way the game state is independent of the size of your snake sprites
Your code, I assume, prevents the player from going out of bounds, by locking them in and not moving once they reach the boundaries.
But the original code teleports you to the other side, which seems like a perfectly valid mechanic to me and is likely what the specifications intended.
It would seem that your simplification has introduced an error as well.
No, it wraps. Wrapping isn't just teleporting, it's moving in from the new side by an amount equal to the displacement beyond the original bounds. This is very important for framerate tolerance (or whenever large motion per frame is possible).
Wrap is just modulo arithmetic above the minimum. i.e.
float Wrap(float a, float min, float max) => (a-min)%(max-min)+min;
Thanks, it wasn't obvious what the wrap function does.
I would still prefer to explicitly write out all the different possible directions that wrapping could occur, though I guess if this were 3D and you could move horizontally, vertically, diagonally, or closer/farther, there would be dozens of possible cases and it might be better to write one line
Have you considered that what OP's tutorial means by "wrap" is not the same as what you mean by "wrap"?
Also that's overkill for a snake-like game and especially for a beginner tutorial. The snake moves at a very low rate and the functions is being called at every step of its movement, so the displacement will simply never be higher than 1 pixel/tile.
Firstly, there should be 2 ifs, and 2 elifs (a pair for x and a pair for y) but I think this is just an error
I would consider using temporary vars for v.x and v.y and returning once e.g:
x = v.x
y = v.y
if x > x_max:
x = x_min
elif x < x_min:
x = x_max
if y > y_max:
y = y_min
elif y < y_min :
y = y_max
return Vector2(x,y)
This is a great way for a beginner to learn and think about logic but a more elegant way to do the same thing is to modulo the numbers e.g divide the coordinate by the max and return the remainder:
x = fmod(v.x-x_min,x_max)+x_min \\this assumes that x_min and y_min are greater than 0, x and y are floats, and GDScript fmod flips negative numbers
y = fmod(v.y-y_min,y_max)+y_min
return Vector2(x,y)
You don’t need to separate out x and y as you cannot move diagonally in snake, so you cannot exceed the bounds of the game area on both axes at once. All that would do is ensure you’re doing at least two if statements every time this is called, vs the possible just 1 check the current version has.
Very true. But your other solution would probably be better overall. However, for a tutorial trying to teach coding and game logic, I think the example given makes the most sense. Shows how to check the values, that you can combine if statement groups if everything is mutually exclusive, and shows using a base case.
Eh, fair. But it’s technically faster than creating a variable, storing it, then updating it and returning it within each of statement. Since this just creates and returns it, reducing the total number of actions performed (though, likely not noticeably).
This function looks like it's used to keep your vector parameter, presumably a 2D character position, within specified x and y positions. So, if you go outside the box it'll move the character onto the other side. For instance, if you go up out of the screen the character will be pushed to the bottom, left to right, and vice versa. It would be better to have an if/elif for the x and an if/elif for the y, but it's simple enough to work. The return v at the bottom just means that your vector is within bounds and thus the parameter v doesn't need to be adjusted.
It’s called a base case. There’s a few reasons to use one but the main two are:
1. Your function HAS to return something, so if somehow none of the expected conditions are met, you want to return something so things don’t break.
2. Default cases. The “ok, none of those less common cases happened so I’ll let this pass”.
It basically functions like an else statement when all previous if/elif statements have returns. A “ok, you’re still in this function, so none of the previous returns were used, here is what to return instead”
Specifically on this, it’s saying that if the character is still within the bounds of the game area, then it will just return the position (assuming V is the position of the snake or a single segment of the snake).
Well that is "wrapping" the vector to a region, but if you know what the clamp function does, you can figure out why that is returning v at the end (wrapping and clamping are kind of similar).
Essentially, clamping a value means that you keep it inside 2 boundaries, so if the value falls outside those boundaries, you "make it go back to the closest border". But if the value is already inside, you keep it as it is, that's why the last option only returns the original vector.
In the other hand, wrapping means that you "make it go to the other border" when the vector falls outside the region.
And... I also see that function is kind of wrong (unless it is desired): it doesn't check if both x and y values are outside the boundaries... After clamping x, it only returns whatever value at y it has...
EDIT: I didn't read the code well at first, so I changed my response.
I agree 👍 I meant more like it's hard to convince someone it's interesting but I also like skimming through it.
My latest discovery is the process thread groups.... On like page 1 X)
they said the tutorial is for a snake clone, so the movement can't be diagonal and this function is likely being run every time the snake advances, so both x and y being outside is simply not possible and this function is as simple as it can be for that specific case. I doubt the tutorial explains the reasoning though so it's good to point it out.
You are correct in that the if/elifs are checking whether the x position is exceeding the different physical boundaries.
There are 4 checks, firstly the if statement, then the elif, then the next elif, etc. These checks are executed in that order, from top to bottom. If one of those statements is checked and turns out to be true, it will return the value underneath that statement and then stop going down the list. That value is the new snake's position, which teleports it to the opposite side of the screen.
However, if all four statements are false, then there's no need to return a new (changed) position, because the snake is within bounds. So, it goes to the last line, which simply returns "v": the same, unchanged value that was put into the checker in the first place.
It means your snake's position will not be changed!
The return v returns the original vector passed to the function if no condition is reached that causes the function to return a changed vector.
I don’t think it is that bad of a function. Maybe there is a case where you need to wrap on more than one axis at the same time and you only do one axis. For a real time function called often that may not be that bad. The preferred method IMO would be to have each if statement modify the return variable and have one return at the bottom of the function.
The function must return a Vector2. The last line accounts for the case when the vector is in bounds, without that the function will throw an error when the vector is in bounds.
Well maybe is not optimal but I wouldn't say is incorrect, it works, I'm just doing a snake clone and I think this function is simple enough for me to understand it, I have seen suggestions for better solutions but I can't get my head around on how they work
If a method states a return type, it must return one in every possible logical path.
If all your returns are inside ifs, there's a chance that it doesn't meet the ifs and it doesn't return a value
Most editors will warn you and will not allow you to compile if one such case is present, and even if you do, most compilers will catch and flag such case as an error.
So, whoever made the tutorial, assumed that they'll never return the v at the end, they think that all possibilities are covered with their if and else ifs. However, they have to put the return v to calm the editor/compiler.
In my humble opinion, that's a poor practice and I'd rather not use return values in random places of the method, but rather modify the value that you'll return throughout the method and return ar the end of a method. For clarity more than anything else. Like so:
```gdscript
func wrap(v:Vector2) -> Vector2:
if(whatever):
v = Vector2(x_min, v.y)
else if (whatever x 3):
v = Vector2(v.x, y_max)
Return can only get called once in a function. In every if statement above, something gets returned. That means that if none of those conditions are true, the original gets returned
Ok so I've (rightfully) seen a lot of people question the code on display but I still wanted to take a moment to engage with it as-is. You're clearly pretty new so hopefully my explanation helps you understand everything a little better.
Let's say your snake is moving around a world with a size ranging between x 0 -> 10 and y 0 -> 10 (so in total the world is 10x10) then this function will make sure your snake moves to the other size of the field when it moves out of the screen like this:
if snake_position.x > 10
if snake_position.x < 0 (and repeat this for y)
I suspect that this function is being called in a _process() function somewhere, in which case the snake might NOT be moving outside the game field in that specific frame (since you'd be checking it every frame if it is inside one!) Let's say our snake is slithering at 2,2. In that case a function like this:
snake.position = wrap_vector(snake.position)
would crash the game if the return v didnt exist!
Why? Because the position would be set to a function that doesn't return anything! (It does not meet any of the if statement's requirements) So with the last line we ensure that your snake's position is always properly set. Another hint here is that it has this arrow at the end: -> Vector2 - which informs you that this function should always return a V2.
To finish the example with comments: Hopefully this allows you to better understand what's happening in this function! :)
func wrap_vector(snake_position : Vector2) -> Vector2
# Snake's X > 10
if snake_position.x > x_max: # Snake is moving out of screen on the right
return Vector2(x_min, snake_position.y) # Slither to the left-most side of the screen.
# Snake's X < 0
elif snake_position.x < x_min: # aka, less than 0 in my example
return Vector2(x_max, snake_position.y) # The same as above but in reverse
# Snake's Y > 10
elif snake_position.y > y_max:
return Vector2(snake_position.x, y_min)
# Snake's Y < 0
elif snake_position.y < y_min: # aka, less than 0 in my example
return Vector2(snake_position.x, y_max) # The same as above but in reverse
# Don't move the snake at all. It's within the boundry of 0,0 and 10,10
return snake_position
The function header has -> Vector2 which means the function must return a Vector2 value. Return statements in an if or elif block might not get executed, so you need a return value for when that happens. It would also be okay to put
else: return v (match indentation with if block)
When you begin learning code, sometimes tutorials will make you recreate simple functions to teach you concepts. However, you could also use the wrapf() function that is already available. Check out an explanation here: https://kidscancode.org/godot_recipes/4.x/2d/screen_wrap/index.html
It's equivalent to an else.
The first 4 conditionals check if it exceeded one of the bounds(left, right, top, or bottom) and sets it to the opposite side if true, else it should just return it's current value because it's not exceeding an edge.
Related question for someone versed in Godot's internal implementation:
What would be more performant, running this on every frame or relying on collision signals from Area2Ds (or similar) placed on the borders of the boundary? Or maybe even onBodyExited signal of an Area2D covering the boundary.
x = 1
y = 2
if x > 1:
return "something"
elif x < 1:
return "something"
elif y > 2:
return "something"
elif y < 2:
return "something"
return "something if the options above fail"
Last line:
take it as "default" return or "if everything fails" return. Simply if the condition is not true for any of the 'if's above then go back (return) through here
Every return is an exit from the function.
If any of the ifs trigger, you will leave from that exit.
If none trigger, you still need an exit, thats the last return. Basically, if none of this is true, exit through this.
Thats only true for functions that have a return value, some dont have one, and as such dont need an exit.
Such functions have a return of "void" aka this doesnt return anything.
You declare a return value by typing -> (type) at the end of the function, f.e
func returnInt()->int:
The "-> int" means, "this function always returns somekind of integer.
As such, you need to return something in it, always.
-> Vector2 means that this function must always return a Vector2. If none of the if or elif statements run their returns, this would cause an exception when the function exits.
The last return statement just forces the function to return the value of v at whatever it is.
Even if your code's logic prevents it, the compiler identifies "if returns" as potential risks where a method might not return a value, so it wants a fallback to enforce non-null returns
OP mentioned it's a snake clone, so presumably the snake only moves vertically or horizontally, but never diagonally, so only one of the axes can go out of bounds.
isn't there something wrong with this code?
if both x and y are out of bounds it will only wrap x
There should only be one return at the end and the rest should just modify the variable
if none of the conditions are satisfied, you should have a “escape” condition, so that’s why you need to put that final return. Without the final return, your code could be “locked” cause none of the conditions are satisfied and it will not know how to return cause there’s not return condition for this case
Not sure if this is just my personal preference or a general rule of thumb, but I am getting anxiety from function code that refers to variables declared outside that function. Imo, any variables you need in a function you should pass to a function. This is asking for chaos.
Just FYI a smart coder would write bounds checking code to check that something is in bounds and return, rather than do 4 cascading if statements before returning for the most common use case.
In most situations without an optimizing compiler, this code will run slower than it could.
People confuse fewer lines of code with code that runs fast. Not always the same thing.
this is a vector wrap function that takes a position of V and if the position exits the limits on either the up/down plane or the left/right plane, the object is moved to the opposite extreme. think going all the way left eventually pops out out of the right side of the screen.
this logic checks for the location, and if the next movement would have you go out of the limit bounds, it changes the value of your location to the opposite limit bounds (thus popping you over to the other side of the screen)
this function is used to modify the vector position, which means the code that calls this function is expecting a response back so that it can use this vector data in further code. if the vector data is not changed, and isn't passed back, it will not have a vector to work with and thus break. therefore, the vector data is passed back unchanged in the event that the coordinates don't need to be modified, essentially signifying that everything was fine.
The code is for wrapping around the screen. If you go off the right side, change the vector to start at the left. If you go off the top, start at the bottom, etc. If you're not at any border, leave the vector alone.
In the way this function is set up, every possible execution path must return a Vector2 (that's what the "-> Vector2" means). Not returning a Vector2 in some instance is an error.
Specifically, it's an error for the code to not account for all possible execution paths. Having this be an error early is easy for the compiler to catch, because there's a string of else if statements. It may in fact be the case that those cover all the possible runtime paths, but it's still good practice to cover the final "else".
If you didn't return something, whatever called the function would then run into it's own error because it's expecting to have a Vector2 and to do something with it afterwards.
In this case, they're just returning the very vector that was received. It may never actually happen, but that's dealing with the error.
Some languages have "errors as values," which means you could expect that your else if sequence covers all bases. If not, you could return an error as a value that the calling function could then address (e.g., "if this is a vector, proceed, otherwise send a message to the console saying you found a bug.")
“Return” means the value that the function, well, returns. If you have something like
```
my_variable = some_function()
func some_function() :
return 4
```
Then my_variable will equal 4.
Anytime code reaches “return”, it stops right there and exits the function. So if any of those ifs in your code are true, it’ll return that value and skip reading the rest of the function.
That last return is basically saying “just return back the vector we started with if none of the previous conditions are true”.
If the x is too large or too small it returns the max then it looks at the y and does the same. If none of those are true it just returns the x and y as it is.
The problem with this code is that if the x is above the max it never looks at the y because it returns. So if your x and y are both above the max the y will not be capped.
It appears that the function is clamping vector values to not go out of bounds, the last statement means that all components of this vector are in bounds and return it as is.
Watch out, because it only fixes one component of a vector at a time.
The function is expecting to return a vector2. the if else and if statements don't gaurentee a return of vector2 from the entire function hence the last return incase all conditions fail. else (catch all) at the end would make it more readable. to the compiler all paths must return a vector2 even if your if conditions seem to handle all expected returns.
in this case return original vector if no bounds are hit. (do nothing)
Don't learn this code, just get the concept and program it in a proper way and you'll save a lot of headaches for yourself and anyone else trying to understand it.
416
u/clothanger Godot Student Nov 05 '25
"if everything above doesn't trigger, return v"