Gamepad Cursor with Input System - Unity Tutorial

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
today i'm going to show you how to make a gamepad cursor with the new input system so right now i'm using the mouse and i can press the button as usual but when i switch over to a controller you'll see that it immediately changes to this little icon and we can hover over the button and click and then when we click out of the button it will unselect the button and so when we switch back to the mouse you'll see that the mouse is at the same position where the cursor was and when we switch back the cursor is at the same position where the mouse was and so you can have this seamless transition between gamepad and mouse before we start i want to thank geek zebra on our discord server his help made this video possible so thank you so much and be sure to thank him as well so first we need the new input system so go to windows package manager up here you go to unity registry then scroll down until you see input system and so this will work on either 1.0.2 or 1.1.1 it should work on the previous versions i haven't tested it but it should work the same so you can choose the version you want i'll just choose 1.0.2 and click install then click yes to restart the editor all right once it's restarted we have the new input system installed now i want to mention that under the input system you have these samples and there's a gamepad mouse cursor so unity has made their own implementation of this however i would not recommend using this one for example if i import that there's a couple problems with this one being that you can't actually use the mouse for anything it only works with the gamepad so if we use the gamepad you'll see that it moves this little mouse for some reason it's not even showing the cursor correctly and it scrolls but we're also going to be able to do that with our implementation and the cursor can go out of bounds which is not good and so another issue with this is that if you want to change it it's pretty complicated because if you get the cursor they have this virtual mouse script and that's what they use and if you edit the script you'll see that there's just a lot of stuff going on and it's just kind of hard to change it because it's just pretty confusing so let's get started with our own implementation so let's make a new scene create scene and let's just call this gamepad cursor i'm going to put that under scenes let's go to that all right and let's make a folder for our scripts if you don't have one already and then here we're going to create a script called gamepad cursor and we'll be using this script and attach it to our game object so before we dive into that script let's just set up a simple button so right click and create a ui button all right you can double click that and let's just kind of put it near the center so we can see it better and increase this scale all right and then in the event system we're going to want to replace this old input system module with the new one so just click this button and you'll see that now the event system works with the new input system however we'll be changing this in a bit now let's create our cursor which is just an image so right click on your canvas ui image and let's call this cursor and for our cursor the samples already comes with a crosshair if you want to use that or if you'd like kenny has a free crosshair pack that you can download and it comes with a bunch of crosshairs for free so if you download it and extract the zip file you can go into png and there's different colors you can choose from if you click black then you'll see the black cross hairs and if you'd like you can just make a new folder here called images or sprites and you can drag whichever one you want so let's say we do crosshair 137 then under texture type go to sprite so we can use it in our ui and just click apply if you go to the actual image we can go to the properties and we'll see that this is 64 by 64 pixels so if you want to make it accurate with the unity pixels just put 64 here and click apply and then in the cursor you can just drag and drop your source image there and you'll see that we have this image now so a few things i want to mention is that for the pivot so if you click here we can set the pivot of this image so make sure it's set to the center if it's not this might not work properly to set the pivot just press shift and just select the middle one and if you like you can press shift and all to also change the position and finally we want to disable raycast target under the image and so this will ignore our cursor when shooting raycast from the camera to determine what ui we're hovering over and we also want to make sure that our cursor is at the bottom of the canvas in the hierarchy so it can get rendered over the other ui elements so in the unity ui the bottom most element is rendered on top of the previous ones so if we actually move the cursor upwards you'll see that it disappears all right so now let's make our script i'm just gonna make an empty game object and i'm just gonna call it gamepad cursor and we can just attach our script we made there you can right click and edit the script all right so the way we're gonna do this is we're going to use a virtual mouse in the new input system and so a virtual mouse is basically similar to a normal mouse but it's not a physical mouse we can actually change the position of a virtual mouse and this is used for gamepad input so in our case we're using our gamepad to move the cursor around and in reality we're moving a virtual mouse and that is what the ui will be reading the virtual mouse input so we'll take the gamepad input we'll pipe it into a virtual mouse and that virtual mouse will be used in our event system to see if we're over a ui or if we're pressing down over a ui element so i'm just going to delete these functions and also remove this system collections namespaces all right so let's begin so when we enable the script on enable what we want to do is make a virtual mouse so what we can do here is do private mouse virtual mouse so this is going to be our mouse and we can say if virtual mouse equals null so we don't have one yet then let's create one virtual mouse equals and then here we're going to have to add in a class and it will be using unity engine dot input system dot low level so we're going to be going into the low level of the input system so we need this to be able to change the virtual mouse state and we'll also need the unity engine dot input system namespace for the mouse so here we can do input system dot add device parentheses and then we pass in something called virtual mouse which is a string and you'll see there's an error we can't convert from an input device to a mouse so we can just cast it to a mouse up here and now we can assign it to our virtual mouse and actually we'll be using this namespace a little bit later all right so a virtual mouse is null if it's not null so else if it's not null but it hasn't been added yet so virtualmouse.added to the system then we can do input system dot add device and then we can just add in the existing virtual mouse now once we add this virtual mouse if we're using a player input component it won't actually link it to our input action asset that the player input component is using so we need to link it manually so we'll need a reference to our player input component so we can do private player input player input which we'll actually make soon and i'm just going to make this a serialized field so we can drag it from the inspector and then we need to import another namespace actually called using unity engine dot input system dot users so we'll need this to change the users all right then here we can do input user dot perform pairing with device pass in the virtual mouse that we made and then we pass in our player input user which is playerinput.user so this will connect our virtual mouse with our player input component so we can use it let's that's if you're using the player input component all right now that we've linked it we have to make a function that kind of runs like an update function that updates our cursor when we move our gamepad so we can make a function here called private void update motion and this will update the virtual mouse accordingly and what we can do is call input system dot on after update plus equals so we're subscribing to an event and it will call our update motion on each frame that this is updated after it's updated similar to a late update function but before we do this we want to actually make sure the virtual mouse is in the correct location before we update it so kind of like setting the initial position of the cursor so to do that we'll need our reference to our cursor so serialize field and we can do a private rect transform and we can call this cursor transform so we'll get a reference to our cursor transform and what we can do here is do if cursor transform does not equal null then we can get the position of the cursor so we can do vector 2 position equals cursor transform dot anchored position so the anchored position takes into account the anchors of a rect transform you can see the position of the pivot of this rect transform relative to the anchor reference point and so we'll actually be setting this later so it can change according to our gamepad input and then we can do input state dot change so we're changing the state in the input system we're changing the state of the virtual mouse position with our new position which is basically wherever our cursor is currently at so we can set the starting position in the editor and this uses the input system low level namespace all right and make sure to make an on disable function and unsubscribe from this event input system dot on after update minus equals update motion because we don't want to do this if the script is disabled all right in the update function of our cursor movement what we'll do here is basically read the value of our gamepad pipe it into the virtual mouse and then change the position of the cursor image on the screen so first we want to run some checks if virtual mouse equals null or the current gamepad so gamepad.current the one that's connected to your computer or your system equals no then let's just return from this function so if we don't have a virtual mouse or we don't have a gamepad then we don't even want to run this right now we want to read the value of our gamepad so we can do vector2 stick value so this is the joystick value then we can do gamepad.current dot left stick or whichever stick you want you can do dot right stick as well just gonna do dot left stick and read value this will return a vector too so this returns how much the stick is moving and we want to take into account our delta time as well as some speed that we want to move it at so up here let's just declare a speed like a multiplier so we can do private float cursor speed as an example and maybe set it at like a thousand i found that to be a good value and you can make this modifiable in your game all right and then here we can do stick value times equals speed times time dot delta time so now we're taking into account the speed and the time delta time so now that we have the delta which is the change in position between the previous and the current frame we want to take into account our current position and add the delta to it similar to the slope mx plus b so we can do vector2 current position equals let's do virtualmouse.position.readvalue so we're reading the value and then we can just do vector2 new position equals the current position plus the stick value or in this case i'm just going to rename it delta value so it could be easier to understand now we want to make sure that this doesn't go out of bounds from our screen we have to do that check manually so we have to clamp it to the screen so we can do new position dot x equals math f dot clamp and it takes in the current value which is newposition.x this is the current the minimum and the maximum so the minimum we can put in zero which it will clamp it right at the edge of the screen and then for the maximum we can put screen dot width for the x and you can just copy that line and replace the x with the y and instead of the width we do a height now we're going to actually want to add some padding to this but i'll show you how to do that later alright and now that we have the new position we can do input state dot change we can change the virtual mouse dot position with our new position and let's also change the input state so input state.change virtualmouse.delta delta with our delta value so now we're changing the virtual mouse to take in a new position and a new delta value now this is for movement if we actually want to click a button which obviously we do we need to do something else so we only want to do this when our button state has changed for example if we're pressing it down we want to change it to be pressed down but when we release our finger we want to emit a state change within the input system so we only want to do this if the previous state does not equal the current state so for that let's take into account the previous state which would just be a simple boolean so private bull previous mouse state you can just copy that and then if you go down here you can do a simple if statement so if the previous mouse state does not equal and then in this case i'm just going to use the a button of the gamepad you can change this later if you want so gamepad.current.a button dot is pressed so if we haven't been pressing it before and we are now or vice versa then let's emit a state change in this case we're gonna have to do something a little different so we can do virtual mouse dot copy state and we have to add these brackets here and pass in a mouse state so this is the type and we're gonna do here is do out var mouse state so basically what we're doing here is we're outputting this variable called mouse state that we can use now within this if statement scope and it's basically just copying the state of the current virtual mouse so now we can use it mouse state and i made a little mistake this should be lower case because we don't want it clashing with the type name so now we can do mouse state dot with button so here we're changing the state of the button that we're pressing so we can do mouse button dot left so remember this is the virtual mouse so on the game pad we'd be pressing the a button and we're mapping that to our mouse left button or the virtual mouse left button and then we want to change it with the gamepad dot current dot a button dot is pressed and make sure this is capitalized all right and then we can do input state dot change virtual mouse with our new mouse state and make sure to store the previous mouse state which is this gamepad current a button is pressed which let's actually store this bool a button is pressed since we're calling it so many times right so is it pressed make sure it's capitalized with the parentheses and let's just replace these two instances with our new boolean and let's just store our previous mouse state with a button is pressed alright so this will move the virtual mouse in accordance to our gamepad input now we actually have to change the cursor on the screen so we're just going to make a function for this called anchor cursor or you can name it whatever you want and we're going to be passing in a new position so we can do here private void anchor cursor and take in a vector 2 position so this is the position that we want to move our cursor to so this is a little bit tricky you'd think hey we can just set the anchored position directly as so cursor transform anchored position equals position however there's a lot of scaling issues especially when you have screens of different sizes this will not work properly with the scale additionally if you have a canvas scaler and you use scale with screen size then if you set the anchored position directly it also won't work what you need is a way to map a position a screen coordinate position which is what we're passing in right now we're passing in a position in screen coordinates to a wrecked transform coordinate and unity has something for this luckily so let's just erase this here and i'm going to declare a variable called vector 2 anchored position so this is going to be the position we moved the cursor to and we can use something called direct transform utility dot screen point to local point in rectangle and what we do here is that we pass in these arguments so here we pass in the rec transform of our canvas so we need a reference to that then we pass in the position our camera and it'll give us our anchored position called local point which will just pass in this variable that we made so let's get a reference to our canvas transform it's your lice field private rect transform canvas transform you can call it canvas rec transform to be more accurate and then if you're using screen space overlay you don't need the main camera but if you're using screen space uh camera then you do need a reference to the camera so we're just gonna get a reference to the camera as well here private camera main camera and in the on enable function i'll just do main camera equals camera.main and this will only work if you have your main camera tagged here as main camera so once you have a reference to your main camera we can go down here we can pass in the canvas rect transform we can pass in the position the camera and this is only if it's in screen space mode so if you want to do an if statement to check what mode you're in you can get a reference to canvas which once again we go back up here we get a reference to our canvas lots of references here private canvas and we can do canvas and right back here we can do canvas dot render mode and if the render mode is render mode dot screen space overlay then we don't need a camera reference else we can just pass in the main camera and finally we can do out anchored position so what this is doing is just a simple way to do an if statement in one line so if it's a screen space overlay question mark is it yes if it is it's null else we do this colon we pass in the main camera and we output our anchored position and i just want to thank aether omar sorry if i mispronounced that because this video helped me out with figuring out how to use this function alright now that we have our position now we can just simply do cursor transform anchored position equals anchored position and now our cursor will follow accordingly all right so that was quite a doozy let's see if this actually works so let's go back to unity and in our gamepad cursor game object let's pass in a couple of things here so pass in the cursor transform so drag your cursor to cursor transform drag the canvas to the canvas and also drag the canvas to the canvas rect transform and then we need a player input component so you can just make a player input component here if you don't have one already and if you do have an input action asset you can just use years i'm just going to create a new one so create actions and i'm going to put it under scripts here and i'm just going to save then under scripts we have our input action asset so we'll have to change some stuff here in a second but back to the gamepad cursor we can now drag our input action asset into our player input and you can also drag your event system to the ui input module and also you can drag your main camera to the camera if you want and so for the event system now instead of using the default input actions let's just use ours and you'll see it'll replace it immediately and then you'll see we have a warning here that something should be set to pass through so it can work properly between different devices so i don't know why it doesn't do that automatically but if you go back to the input action asset under the ui navigate just set it to pass through here and save your asset all right now that we have that let's add this stuff to our input action asset that we need for our virtual mouse so what we need is under point this is what determines if the cursor is over the ui object so here we can add a new binding and under the path here we can add a virtual mouse position so the position of the virtual mouse then under click which is you know we click the button we need to add in the virtual mouse left click so let's add a binding here virtual mouse let's put left button and then if you want to add a scroll wheel you can add a binding for that as well and we can just do a virtual mouse scroll and then something else you want to do is under the control schemes go to gamepad and then go back here click edit control scheme and so you'll see that for the gamepad control scheme you need a gamepad required however i also added here a virtual mouse and i put it as optional and i click save and just save your asset and then for the new bindings that we added for the virtual mouse i just put them under the gamepad because this is only going to happen when we're using the gamepad and with the player input component it always chooses a control scheme one at a time so if we switch from keyboard to mouse to gamepad we want to make sure that it allows a virtual mouse alright so now you can exit out of that and before we press play make sure to drag your player input component into your gamepad cursor script what i'm going to do is under the button to make it easier to see i'm going to make the pressed color red and the highlighted color blue and the selected color light blue all right so if we click play you'll see that we can move our cursor around with the gamepad stick which is really cool i have an xbox connected to my computer and unity just recognizes it however we can actually click the button we can click it with the mouse but not with this cursor so there's a lot of steps involved in this and a lot of things can go wrong so let's make sure the pivot is at the center we don't have the raycast target selected and in the input action asset under click let's actually also add a binding here i'm just going to click listen and press a on the button south of the gamepad so that it also registers the gamepad if the virtual mouse left button is not working make sure to assign the gamepad control scheme to it save your asset hey this is sam from the future realizing that i made a mistake so here when we're adding the mouse to our input system device you can see that when we go to window analysis input debugger we're not actually removing it when we're done using it so now we have a bunch of devices like virtual mouse one two three etc and when you click play you'll see that we have a user which is our player input that made a user and you can see the paired devices so you can see that when we move the gamepad it's paired to the first virtual mouse however the virtual mouse that's actually getting moved is the latest one which is virtual mouse 3. if you double click it it opens up this window and you can see under position it is changing so it's not pairing the correct virtual mouse so make sure to delete all of these virtual mices you can right click and remove device on each one of them and then back in the code in the on disable function you can do input system dot remove device and you can pass in the virtual mouse so that it removes itself after it's being done used now we open back up the input debugger from window analysis you see when we click play we get a new device added which is the virtual mouse and when we unclick play then it disappears alright so we click this button everything's fine now we move our gamepad and it works as well awesome and you can see if we go to our canvas and you change the ui scale mode to scale with screen size and you put a reference resolution of whatever you want in this case i put 1920 by 1080 then it also works the same so you can use any canvas mode you want for example we can put screen space camera and then attach our camera here and if we move it over the button you'll see that it works the same which is awesome all right so that's great but what if we're switching between the mouse and the keyboard let's say the mouse is over here and you see the game pads down here so we want to be able to use the mouse when we're moving it and not see the gamepad cursor but also only see the gamepad cursor when we're using the gamepad and not see the mouse so we can do that easily with the player input component they have a function called on controls changed that we can subscribe to and see what control the user is currently using and we also want to add some padding so that the cursor isn't cut off here so back to the script and let's quickly add some padding so for the padding you can just do a serialized field here so you can change it while you're in play mode you can do private float padding and you can set a value i found 35 to be a nice value 35f and then for the padding all we have to do is go down to the update motion function and then right here where we clamp the values we just want to add our padding so instead of zero we can add the padding and then for the maximum we can do the width minus the padding and then the height minus the padding and i can just erase this so that's pretty simple we go to here now in the play mode you'll see that now the padding is working properly you might want to change it a little depending on your setup which we can easily increase the padding here maybe to 50 and you'll see that now it doesn't go in the corners so i can just set that to 50 here and i'll just change the default value alright so now that we have the padding done let's subscribe to that player input function so that we can get an event notification when the player has changed their controls so you can do it directly from the editor here there's send messages there's also invoke unity events or invoke c-sharp events i found that the unity events one didn't work however if we set the behavior to invoke c-sharp events and then you go to your script then in our on enable function right down here we can do player input dot on controls changed plus equals and then we're going to make a function called on controls changed we can just copy that and in the on disable function we can paste it and put minus equals because we want to unsubscribe from this event so i'm just going to copy this function name and down here we can do private void on controls changed and it takes in a parameter player input which i'm just going to call input so this is called whenever the player changes the current control so we're switching from keyboard to gamepad and vice versa so we want to do a simple if statement to check if the current control scheme does not match the previous one specifically if the current one is keyboard and mouse and the previous one is gamepad or vice versa so if we go up i'm just going to declare these values so we can make it easier for us so we can do private cons string and we can call this gamepad scheme and call this gamepad so basically we have to index this with a string so instead of using it constantly let's just store it up here as a constant variable and we can do the same private con string mouse scheme and this one in this case is called keyboard and mouse similar to how the input debugger displays it alright so now if we go back down to our on controls changed we can do if you can use this player input or the one we have stored already if player input dot current control scheme equals and let's do mouse scheme and we have to set some previous control scheme previous control scheme does not equal the mouse scheme then we want to do a couple stuff so first let's declare this previous control scheme so if you scroll all the way up and it's just right here we can do private string and previous control scheme and we can just set it to default string here all right and now if we scroll back down we got the air out of the way so once we switch to the mouse we want to do some stuff first we want to set the cursor to false so we don't see it so we can do cursor transform dot game object get the reference to the game object dot set active and then we can just put false in there then we want to make the cursor visible so we can do cursor.visible equals true which is the mouse cursor not the gamepad cursor then we want to set the previous control scheme now to the mouse scheme so we can remember our previous control scheme so the next time we change it we can remember what the previous one was and we also want to make it seamless so that the mouse ends up where the gamepad cursor was position wise so to do that we first need a reference to the current mouse which we don't have so we can go up here and we can just declare somewhere private mouse current mouse and then in the on enable function we can do something like current mouse equals mouse dot current and get a reference to the mouse all right and now we can scroll back down and now we have a reference to the mouse we can do current mouse dot warp cursor position so this changes the position of the system mouse and we'll change the position to match our virtual mouse position virtual mouse dot position dot read value which our virtual mouse is basically being driven by our game pad all right so this is one case in the other case else if the current control scheme is the gamepad so player input.currentcontrol scheme equals gamepad scheme so now we're switching to the gamepad because that's the current and the previous control scheme does not equal the gamepad scheme then it's kind of the opposite of the other one we want to do cursortransform.gameobject.setactive equals to true now we want to set the image active then we want to do cursor.visible equals false and we want to make sure to set the previous control scheme now to the gamepad scheme and finally we want to do two things so we want to change the virtual mouse to match the actual mouse position so the transition is seamless so similar to how we do it up here in the update motion function where we do input state dot change virtual mouse position we're just going to do it down here input state dot change we do virtual mouse dot position and we want to change the virtual mouse position to match the current mouse position so current mouse dot position dot read value and then we want to actually change the cursor image location to match our new virtual mouse so we can do anchor cursor and we can just now do currentmouse.position.readvalue and so quick mention if this isn't working for you like this isn't getting called for some reason then you can always do it manually so you can have like this update function as an example and you can do if the previous control scheme does not equal the playerinput.currentcontrol scheme then you can call your you know on controls changed function and you can just set the previous control scheme here to equal the current control scheme this is just if for some reason you know this isn't working right because i've heard that there's some bugs with this alright and now back to the editor if you click play you move the mouse around and then when we move the gamepad you'll see that it starts from where the mouse left off and vice versa the mouse now starts off from where the gamepad is and what i've done is under the canvas i've also just disabled the cursor so that it doesn't appear when it's at the start you'll see that it appears because the on controls changed event hasn't been called yet because no controls have been changed so to fix that you can just disable this cursor image and now it won't show and finally to avoid any of those errors you saw the with the null reference errors that sometimes pop up in the on disable here when we remove the device just make sure to check that it actually exists so if virtual mouse is not equal null and it has been added so virtual mouse dot added then we can remove it from the input system all right so i hope you enjoyed this video you can see that we can click this button and we switch the gamepad and now we can switch this button and we can also add a scroll view i'm just going to move it down and you see that when we hover over it with the mouse we can change it but when we switch the gamepad we can also change it which is pretty cool and always make sure your cursor is on top of all of the other ui elements or under better set that way it's rendered over these two so yeah thanks so much for watching i hope you enjoyed the video and thank you once again geek zebra for all your help i'd also like to thank my patrons for all of their support they make these kinds of videos possible and i really appreciate their support so with that i'd like to introduce my new patrons in the enthusiastic tier we have glenn roy logan c-c-x-v-e-e justin lyra borealis me face trent timothy manuel michelle and mice thank you so much for the support i really appreciate it and in the dedicated tier we have ian smith yoni sang and final attack thank you so much for all of your support i really appreciate it if you're interested the link is in the description i offer source code early access to videos on exclusive discord chat and other perks and if you haven't joined already make sure to join our discord chat where you can chat post memes or ask for help so thank you so much for watching once again i hope you enjoyed this video and learned a lot and i'll see you next time [Music] [Music] [Music] you
Info
Channel: samyam
Views: 33,597
Rating: undefined out of 5
Keywords: virtual cursor, input system, virtual mouse, new input system, cursor controller, mouse cursor, gamepad cursor, controller cursor, control cursor, gamepad cursor input system, joystick, move mouse with joystick, move mouse cursor with joystick, move mouse, joystick mouse, joystick cursor, stick mouse, input system gamepad support, input system gamepad cursor, mouse cursor with gamepad joystick, virtualmouseinput, gamepad input, virtual mouse cursor, mouse position, stick, move
Id: Y3WNwl1ObC8
Channel Id: undefined
Length: 35min 36sec (2136 seconds)
Published: Thu Oct 14 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.