How to convert the mouse position to world space in Unity

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
in unity getting the position of the mouse on the screen is relatively straightforward just use the mouse position property of the input class and you'll get the pixel coordinates of the cursor however while getting the screen position of the mouse can be simple if you want to actually do something with it in your scene you'll need to convert it to a position in the game world in this video i'm going to show you how to do exactly that and how to translate the mouse's on-screen position to a real position in your scene how to fire rays from the camera to interact with objects in 3d and how you can use planes to use the mouse in your scene without using colliders so the first thing i need to do to convert the mouse's screen position to a world position is to get the screen position in the first place and this works by using the mouse position property of the input class so in this scene i have a sphere and i have a script on that sphere i'm going to add a public vector3 old screen position and i'm going to set the screen position to input dot mouse position save that and what you'll see here is when this is running you'll see that input.mouseposition is a pixel value from the bottom left of the screen that is the position of the cursor so for compatibility reasons this is a vector 3 although the z value is always zero one thing to note this method only works if you're using unity's legacy input manager which as of right now it's still the default input method in unity however if you're using the new input system you'll need to replace input.mouseposition with mouse.current.position.readvalue so once i have the screen position i need to convert it into a real position in the world that i can actually use with other objects and i can do that using the screen to world point function so for example let's say that i want to move this sphere to the position of the mouse on the screen so the sphere has a script on it and in that script i'm going to add another public vector3 called world position i'm going to set the transform position of this object so this is the sphere to world position then in between those two things i'm going to set the world position using screenworld to point so i'm going to do that by setting world position to camera dot main and then dot screen to world point and pass in screen position so let's go back and test that except that it doesn't work so what's happening is the screen to world point function uses the z value of the vector3 that you pass in for depth except that it has no depth which is a problem for two reasons uh so one it means that the object doesn't move at all and this is because when you're using a perspective camera and the the frost of the camera starts at the origin of the camera's object then at that point at the origin a position in the top left is as far as the camera is concerned the same position as the top right it's here so you can't actually perceive any movement when the value is converted to a world position two even if it did move you wouldn't see it because it's behind the near clip plane of the camera so this is the near clip plane and anything behind that isn't visible to fix it all i need to do is add a depth value to the screen position before passing it in to the screen to world point function so back in the script i'm going to type screen position let's see equals and then passing the value now i could use any value for this as long as it's enough to put it in front of the near clip plane this is going to work but what i can just do is i can set it to the near clip plane directly so camera domain dot near clip plane then if i change the knit clip plane it's still going to work what i'm going to do is i'm going to add one unit to that as well the reason i've given it an extra unit of depth is because if it's just on the near clip plane then half of it will still be behind it and will still be cut off so giving it an extra unit just puts it just in front and now if i test it the object should follow the mouse's position just in front of the camera so this technique can be useful when you want the mouse to occupy a space in the world for example if you want another object to face where the mouse is or to follow it around like in this example however if you actually want to interact with the world using the mouse such as to select an object or to find where the mouse is pointing you'll need to use a different function which is screen point to ray so screen point terrain works in a similar way to screen to world point in that it takes the mouse's pixel position and converts it into a real world position how it's different however is that instead of returning a vector 3 position it returns a ray which can be used with a raycast function to check collisions against other objects which you could then use to find a position against a wall or the floor or you could use it to select objects by getting a reference to them through their colliders first i need to define array in the update loop by using the screen point array function so i'm going to go back into the script and i'm going to take this out because i don't need it and take that out so i'm going to declare a new type of ray called ray and set it to camera dot main dot screen points array and pass in the screen position array is essentially all it is is a definition of a line based on a point of origin and a direction so when you use this function when you use screen point array you're setting this to be a particular direction from a particular point which in this case if we look at the camera is from the near clip plane wherever the mouse was in the direction of the frustum so if your mouse is up here in the top left then the point of origin is also up here in the top left and the direction of the ray is out along the direction of its frustum depending on the field of view or whether it's a perspective or orthographic camera so if you have an orthographic camera then it will still start from the same position but it'll just go straight forward so once i've defined what the ray is i can use that ray with a raycast function so unlike the screen to world points function which all it was really doing was projecting the mouse into the scene for this to work it actually needs to hit something else so i'm just going to enable an object with a collider i've got this cube object and now i can check for collisions against that i do that with the physics.raycast function so if physics.raycast and then i can pass in arguments the first being array and then next i'm going to type out raycast hit hit data and then curly brackets so raycast hit is a data type that will contain information about the hit and then i'm going to use the hit data to set the world position so well position equals hit data dot point so this is the position in the world where the hit happened and i'm going to save that and go back into unity to test it so what's happening here is a raycast hit against the cube and if it hits the cube it's going to move the sphere to the position of the hit except that it doesn't work and the sphere just flies towards the camera in an annoying way so this happens because the sphere has a collider on it too the easy way to fix this is to just turn off the collider which works but realistically there will be other colliders in your game that you may want to ignore when doing this so a different solution is to exclude certain layers and just ignore them using a layer mask what's a layer mask a layer mask is an integer value that identifies multiple layers to include or exclude when using a raycast function the easiest way to use one is to simply add a public layer mask public layer mask layers to hit and then back in unity i can choose the layers i do want it to hit so i could choose that it only hits the cube layer or i could choose that it hits everything except the sphere then if i go back into the script i can pass that layer mask value into the raycast function now before i do this some of the overload methods of the raycast function allow you to pass in a layer mask value which is helpful however be careful when you do this if you add it on to the existing function like exactly like i'm about to do sometimes the pattern of arguments that visual studio likes to show you makes it look like you're entering the layer mask next when really it's a different overload so if you've done this if you do if physics.raycast ray raycast hits and then you think oh i'd like to filter some of those please with a layer mask and you go to add another argument you could be fooled into thinking that layer mask is the next argument when really if i actually add layers to hit i'm actually using a different overload overload six which is array raycast hit because that's the pattern of the first two arguments i've entered and then max distance but it doesn't show an error because a layer mask is just an integer value it's just being used in a different way so as far as unity is concerned i've put in my array i've put in my raycast hit value and a really weird value for max distance but what does it care it's going to do it anyway but what will happen is i will not get the behavior i think i'm going to get instead the same problem will happen and it doesn't matter what i do here it's not going to fix it now this is a very specific issue to have but if it does catch you out it can be very frustrating so if you do want to use a layer mask with a raycast hit value as well the third argument is max distance so we'll put in 100 units and then the last argument is layers to hit and now back in unity this will work as expected so using a public layer mask value is the easiest way to define what the raycast hits should or should not uh impact with however it's not the only way to do it you can enter the value directly so the way unity uses a layer mask is that it's an integer number but it uses the binary value of that decimal number to decide which layers are used and which are ignored the binary version of an integer will be a series of zeros and ones from the right to the left and any position that is a one will be a layer that is used so if i go to the list of layers so for example if i wanted to enter a normal decimal value to use the cube layer which is what i'm using the cube layer it's called layer seven but it is the eighth layer in the list then the binary value needs to be a one with seven zeros after it now in decimal that's the number 128 because there are no ones no twos no fours no eights no 16s no 32s no 64s and 1 128 to make that decimal number so if i go back into the script and instead of passing in the layer mask value i simply type 128 this should still work and it does this works but it's not the easiest way to work out what the number should be and if you're just using one layer there's an easier way to do it instead of entering 128 all i need to do is enter the number one bit shifted by the layer number which is layer number seven and again this should work uh it's just a convenient way of thinking i want to use layer seven so all you need to do is one bit shift seven alternatively if you want to use all of the layers except for one you can use the bit inverse operator so say for example i wanted to do every layer except for the sphere which is layer eight do the same thing here except instead of seven it's eight and put that in brackets so it happens first and then put a tilde in front of it so again this should work and it does so what if you don't want to use colliders to get the mouse position in your game for example what if you just want to be able to find the mouse position against a flat surface so you can do that using the plane raycast method plane raycast is an extremely useful function for getting the mouse position against an invisible surface i like this method a lot because it's lightweight and it works without using colliders so to use this method i need to declare a new plane so i'm going to go back to the script i'm going to remove this because i'm not using it now and i'm going to remove the layer mask as well because that's not relevant to this and i'm going to declare a new plane called plane equals new plane and then to define it i need to pass in two values so the first is the direction that the plane is facing using its in normal so if i want a flat horizontal plane i could use vector3 up for this or i could use vector3 down these are just shorthand for zero one zero for up or zero minus one zero for down so the unit vectors they're normalized vectors and it needs to be a normalized vector that you pass into this and the second value using this particular overload is a distance value so this is the distance from the origin so i could use this to make the plane two units higher or two units lower from zero alternatively i could pass in another vector three that is a point in the world the plane has to pass through but because this is a horizontal plane that's not going to make any difference here the only thing that's going to make a difference is how high or low it is now the reason that i've used vector 3 downed for this and feel free to correct me in the comments if i'm wrong about this but as i understand it the normal is the direction the plane faces the in normal which is the value that the plane constructor takes is the opposite side of that which means that if you use vector3 down to create a horizontal plane you can use a positive value to raise it up higher so if i want to create a horizontal plane and i decide i want to i want it to be five units up from zero i can do that whereas if you think i want the plane facing up i'm going to pass in vector3 up and then give it five units it'll actually go down by five units which just it's it's not a problem you can just you can just minus it and put it the other way but it just makes it a bit easier to be able to add a positive value to raise something up or a negative value to raise something down also this makes it a bit easier to match the height of certain object for example so now i've defined the plane i can use it in a raycast function so i can just type if plane dot raycast so this is not the playing class this is the instance of the plane and then pass array into that so the array that we're already using with screen point array and then because this doesn't hit anything there is no raycast hit data to come out of it but there is a distance value so if i type out float distance and this will be the distance of the ray when it intersected with the plane i'm going to set well position equals ray dot get point pass distance in and that should give me the position on the plane in the world where that impact happened now back in unity what should happen is the sphere will move along a infinite flat plane without using any colliders which it does and this can be really useful for strategy games simulation games that kind of thing anything when you can be using the mouse to place objects or plot positions but you don't necessarily want to do that against real objects using colliders this method's great for that it's very easy to do and you don't have to mess around with things like layer masks to ignore certain objects so there you go there are three methods for translating the mouse position on the screen to a world position that you can use in your game if you found this video useful click the like button to let me know get subscribed if you'd like to see more videos from me in the future and leave a comment if you've got some advice on this that you know someone else will find useful see you next time
Info
Channel: Game Dev Beginner
Views: 28,335
Rating: undefined out of 5
Keywords:
Id: 5NTmxDSKj-Q
Channel Id: undefined
Length: 18min 13sec (1093 seconds)
Published: Sun May 22 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.