Unity 3D introductory tutorial P. 2.2- Moving the character

Welcome back! Again we have no downloadable assets because everything you need you can get it somewhere else, like in the internet or something like that. There are no new art assets and the modified gameobjects and created script are better done by yourself following the tutorial.
But there is a playable Live Demo of this tutorial! Go take a look if you want to!

First of all, last time I didn’t mentioned it, but this is a pretty crucial point to take into account, also, there is interesting theory you should know about. We already have collision detection on our character (CapsuleCollider and CharacterController), but do you know what that is or how does it work?
You have to determine where and how the character is going to interact with the world through collision so the metrics can work adequately. Collision is determined from a primary point on the character model. There are generally three locations on a character from which the player can interact with the world:

The head: This point as the main collision point can cause problems, specially when determining ground collision (isGrounded character controller function on Unity). The character can appear to not be rooted to the ground, giving the appearance of slightly floating.
The feet: this may seems the logical location for collision detection, but it can cause issues if it is used to determine where a power-up is collected (for example, if it is mid-air, the character will have to jump to collect it).
The torso: according to Level Up, this is the best place to determine the character’s collision with the world. It provides enough coverage for both halves of the body and feels ‘right’ when the player runs or jumps through an item for collection.
But let me tell you a secret, this won´t matter as much in Unity! Yeah, you still have to consider all those points of collision, an all body collision may be needed even. Especially if you are using a completely rigged 3d model: it can extend arms and touch objects with its hands (or even forearms, upper arms, fingertips!), it can extend legs and kicks some stuff, it can even extend its head and touch the ceiling. Whatever can be an important collision point for gameplay!
You may not know about this, especially if you are just beginning with game design and are young, but before, people only had at its disposition collision boxes, circles and sometimes capsules, and they had to be placed in a way it covered the majority of the character’s sprite so everything touched by the box could be interactable and the player could see it as the character touching something and not some mystical invisible box.
Well, those days are over, because in unity, you can have as many mini colliders as you may want! Each fingertip with its own collision box (even cylinder, or even better, mesh collider with just the shape of that fingertip!). Each leg, each arm, whatever you want can have its own collider, according to its particular form, and move alongside with it (through child-parent relationships). Whatever can have its own collision detectors so you can have micro interactions alongside your model.
Think of a giant troll touching everything in its path! Stepping over townsfolks, grabbing houses, eating a little sheep, whatever you can dream of can be done! Of course, all those micro collisions will consume its share on resources, but you can worry about that when you get to it.
For now, we have already put our collision detection on this character of ours, remember CharacterController, right? Since this is a very simple 2D game, we won’t indulge in such things as micro collisions on fingertips, we just need something that will cover the entirety of the sprite, something simple, since a 2D game can do without those complex things.
However, Unity manages simple collision detectors in the form of the various Colliders it has available. One of these is through the CharacterController itself, also, the RigidBody has its own kind. But the most easy to learn are the ones below and including then BoxCollider, because their detection is more easily controlled and programmed by a beginner.

Remember we had a CapsuleCollider on our character? Well, let’s change it a bit, because right now we won´t indulge too much on that aspect (first we need to configure the items and props it will collide with, and that’s for another tutorial! Wait for it).
First of all, mark the checkbox on your MeshRenderer, so that you can see the CapsuleDrawn By the collider, and next up click on the little cog wheel besides the name and select Remove Component. We won´t need that pesky capsule anymore! Do the same with the CapsuleCollider and the Capsule (Mesh Filter). We don’t need no more of these things!

Make the CharacterController as slim as you can modifying the Radius. Remember that BoxCollider from before? Select and add it to you Character Control gameobject

We need the BoxCollider it will cover more ground that the capsule (because of its shape). Next up, change the BoxCollider to trigger by marking the checkbox on isTrigger (this will allows the collider to be penetrable by objects, while at the same time, detecting collision). And play with the size fields until you have something that covers around the middle body and head of our character, while at the same time, getting out of the CharacterController zone.
When you have both CharacterController and whatever other Collider in the same gameobject at the same time, the first tend to block the latter, making it unable to detect collision. Besides, we don´t really need the CharacterController to cover up all the character sprite, that would make it difficult for it to navigate in narrow spaces. You know how in real life you can always compress your arms to your body so that they won´t bother you in these situations? Well, this is the equivalent of it.
Also, it’s good to have the collider a little outside the body, because that way the collision detection is more natural; not so much that it appears the character has psychic powers! But enough so that you don’t really need to stand directly up and cover all the things you need to collide with.

For now, we are fine in the collisions department, our character is ready to begin colliding with things! But our focus right now is only on movement, so let’s get going with that!

Remember last chapter how Gun McShooter was sleeping soundly and dreaming of black waves, then all hell broke loose and the train with the money exploded? Whew, do you!? I don’t! Tell me more! Hahaha, because I remember something like Gun being unable to turn around and just walking around from side to side and such, wasn’t it fun?
Kidding! Don’t kill me (yet!) Now, I’m going to show you, not only how to make him move differently, but also, how to move faster, jump higher and even fall again to the ground! Put your gun belt and prepare yourself for some more coding action!
Well, now that we are going to add some physics controls to our hero which in turn, will serve as the metrics controls too! We need to define some basic things that will improve or difficult our gameplay, that´s right, the character will affect directly into the players experience of the game!
You have to know and always remember that all gameplay flows from the main playable character (it can be a person, vehicle, animal, an abstract object, even geometrical ones like tetris! Whatever you move, that is the main character!). You have to think about the player’s relationship to the world:
Its height, reach, means of transportation, longitude and volume. Whatever you can think of that constitute the proportions for metrics.
While moving, the character must be able or unable to reach some gameplay objects (whatever the character can interact to, is a gameplay object).
There are distances for climbing (you have to jump? You can climb directly from the ground? You need some force to help you climb?); walking (how wide must a path be for the character to walk through it? How steep the ground until the character falls back down sliding? How far can you go until the character tires itself by walking or running?); jumping (what can you reach in a single jump? Can you jump twice to reach farther? Can you extend your jump by running? Which direction can you run, vertical, horizontal, diagonal?); crouch (do you have to crouch to get into small spaces? Can you reach other superficies and objects by crouching?).
And whatever else you may think. Always think first on the action and immediately after, the conditions this action can affect gameplay.
Just remember, walking alone is not gameplay, but the variations of conditions in which you can walk and the alternatives you can have to walking that will allow you to reach farther and better some places in the playable world!

Metrics are very important to players, because they use them to gauge movement and reaching distances just “by eye”, even if they are conscious of it or not, they will catch up to it sooner or later and try to exploit it to their advantage.
There are few things more satisfactory than knowing how you can mess around with metrics and controls to reach hidden places and secrets. I remember always trying everything in my reach to play through a zone faster, or breaking some limit, or achieving a trial with less effort. You can feel a pro just by getting used to these metrics and controls, or like a dummy for not knowing what to do with your character.
It all depends on the design, so think carefully about these aspects and play with them consistently so the player can enjoy the game and have some good time trying to best himself in whatever trial you put him through!
In order to correctly asses metrics, consider the basic height of the character, the speed the character travels, the height it can reach, how much it can fit in a place, so on and so forth, it all depends on the context of your game.
I can suggest you to take your character as the measure unit, like ‘5 character units (CU) wide’ or something like that. Also, when designing you environment, props and enemies, make sure to use your character as the base unit to scale them up. Your enemies can be like ’10 CU of height and 5 CU width’, and some pathway like ‘3 CU wide’. Always keep this in mind and the world around the player will feel right.
If you don’t believe me about the importance of metrics, I will suggest you to take a look at that creepy Lara Croft videogame (I think it inspired the movie, but I´m not sure), where she killed a bunch of innocent tigers in the Himalayas. In that very same game, her house was this gargantuan mansion where she could take a bath in the water basin or cook herself in the oven!
It was bizarre, like some kind of Gulliver with guns and polygonal boobs xD. But I can understand why they did that, from a designer point of view, maybe their collision detection weren’t so perfect as what we have today, and in order for her to fit in her house, it should be like 10 CU wide in scale. Weird!
Fortunately, Unity is far better at collision detection, so we can measure correctly and make pretty games at the same time!
In this case, we will leave these options free for the game designer to play around with…
-Height: the height of the player character.
-Width of passage: usually wider than the player character.
-Walking speed: how far the player travels per second or unit of time.
-Running speed: how far the player travels per second or unit of time but faster.
-Jump distance: usually farther than a walk, but not as far as a run; can also be based on the player’s width (like 5 CU across or so).
-Jump height: based on the character height, a jump can be half the height of the character, while a double jump can be twice as tall the character and so on.
-Melee attack distance: usually not much farther than the length of the player´s arm and weapon.
-Projectile distance: this can be as short as the player´s reach or width to as far as the player can see.
An example of how to apply this, can be seen in this image taken from Level Up…

The cliff ledge shown in the above graphic is obviously completely unobtainable with a normal jump/ double jump distance. So the player will know that they won´t ever be able to reach this height and will look for another way to get to their destination, a well thought out obstacle!

For this very same reason, we are going to stablish in our nifty code some variables that will allow us to modify the properties of physics and scale it during play test in real time! (when we have the code that will make it work, you can click play, then modify the variable values to make them affect the movement in real time, so you can know which values are right and so on).
What we will need to add are the following variables…

These variables have names that are self-explanatory, also, I added some default values in case you forget to set them in the Inspector, they are public so that you can do so.
Now, for what are they? We are going to use them to construct our rotation control for the character, that way it can turn around and face different directions! But they do need more friends to make it so! Just add these other variables, this time, inside the Movement() function, just above whatever other code you have there, because variables are better declared at the top of everything…

They are called somewhat close to what they do, but I can’t really explain what they do without showing you the instructions where they are used! So let´s add some more code to this!

Wow, we added some new instruction we haven’t seen before! You learn new things each time you open one of my tutorials (XD). Well, that little thingie if (CharaControl.isGrounded) is the application of a helpful conditional instruction called ‘if’, have you heard that phrases “if you eat vegetables, then you grow taller!”? Well, this is like the same thing, only with a twist! If you do whatever condition is written inside the (), like CharaControl.isGrounded, then whatever you encased in the {} below the if condition will be executed!
But what about !CharaControl.isGrounded? Do you remember that variable that kept the CharacterController of our playable character? Well, since it refer to a class, and classes tend to have functions, isGrounded is a Boolean (yes or ! not values) function. When it doesn’t have the !, it should check for a positive value (the character should be grounded!), but if it has it (! Not symbol before the variable name), then it should get a negative value (it is floating and not grounded at all!)
Also, it checks whether your CharacterController is touching the ground or not, that’s why it is important to have a ground and make sure the CharacterController is exactly the same height your character sprite is.
This basically means that, whenever your character is not touching the ground, it can move with velocity being equals to move * moveSpeed (since we don’t have gravity activated yet, so we won’t be able to make sure it is always touching the ground while not jumping!). Remember what was velocity in your physics classes? Well, me neither, it was something like distance / time or something like that (xD I’m not physician either, but I make do with what I have!). Move is supposed to be the distance (like when you triangulates the walked distance in a map? With the latitude, longitude and height or something like that, I’m not geographer either!). MoveSpeed should then be the time, but whatever, it works xD.
Now erase the velocity = move; code you put the last tutorial and click test play! How was that? Did it moved more quickly? Try changing the MoveSpeed value and see what happens! That’s fun! You can move at whatever speed you want now, just changing this thing! Yeah!
But what about the other variables? Well, each one has its use specially for changing the problem with the facing of character while it moves. Let’s see about that. Add this code just above the CharaControl.Move (velocity * Time.deltaTime); and play close attention to the localization of velocity = transform.TransformDirection (velocity); an instruction we already have set…

We have assigned the value of velocity.z (in its z axis) to ForwardAmount, then did some kind of mathematical operation (class Mathf, function Atan2, I really don’t understand what it does, but you can check on the Reference Manual and see if you have better look xD), that plays with the axis x and z of velocity.
Another mathematical function (Lerp) that plays with all the other variables we assigned first (StationaryTurnSpeed, MovingTurnSpeed, ForwardAmount), because reasons (I really don’t know, I just followed an example on rotating the transform of an object and go on with it xD). But I can say Atan2 gets you the angle distance between point a (velocity.x) and point b (velocity.z), so that you can get how much distance is needed to cover that up when rotating your gameobject. Lerp gets some kind of value for setting the speed, so the rotation can be gradual and not sudden and choppy!
The interesting bit is the transform.Rotate, transform refers to the Transform Component of the gameobject the actual script is attached to, is like a predetermined variable of type Transform you can use to avoid assigning it yourself, Unity detects it automatically. The rotate function refers to the Rotation fields on the same Transform Component, it passes through the values you set as angle an makes the rotation according to that values.
Now you are ready to try it out! When you advances forward, the character goes that direction, but whenever you press any other direction, left, right or down, the sprite rotates, taking the camera with it and changing its facing direction! This can work, if you want a tank like control on your character.
However, if you want something more natural and useful for a third person shooter game, the character needs to be able to go up, down, rotate when going to left or right, and also, maybe being able to go left and right without rotating.
There are many complicated ways to do this, but I have one simple solution using only the code we already have plus some little aditions. Do you want to see that? Then add this code to what you already have…

As you can see, we still have ForwardAmount = velocity.z; and TurnAmount = Mathf.Atan2(velocity.x, velocity.z); assignments, only enclosed within the conditional if (Input.GetAxis (“Vertical”) == 0) {}, here we are checking whenever the player is not going up or down (Vertical axis is not -1 or 1), the instructions on this code will execute. The ==0 is the comparison we are making.
When we need to compare a numerical value (Input.GetAxis retrieves a float value, which means, numerical values with decimals) and see if it does have a given value (in this case, by pressin Vertical axis you can get 1 or -1 value, positive going up or negative going down), we need to add < (minor than), > (major than), == (equals to), <= (minor or equals to), >= (major or equals to) or != (different to) signs. If you want to learn more about this, look in C# documentation about conditional instructions and comparison signs so you can get a clearer idea about what they are all about.
In this case, when the character is just standing there, we can rotate it by pressing left or right. An extra ability is that when you are going up or down you can go sideways in that direction by pressing both Horizontal and Vertical axis. That’s great, isn’t it?
The “else” part is the alternative behaviors to what happens when the condition in “if” is not happening in the game. You can have as many “else” as you want (many alternative behaviors) by writing “if else” instead. This is like saying “IF you do have eggs for breakfast, you can bake them, IF ELSE you don’t have them, eat ham, or ELSE if you have neither of them, take a bowl of cereal”. Now it have more sense, isn’t it?
Go ahead and test the game! You can see how when you press up, the character goes normally up front, when you press down it backs down facing the same direction (useful if you are shooting enemies while at the same time, trying to get away from them!).
Also, when you only press left or right, the character rotates and changes direction of facing. This is going to be useful to correctly navigate maps and such, because now you can see where you are going and not just going sideways while always facing the same boring direction forever!
And the extra ability happens when you press a combination of Vertical and Horizontal axis, try it out! Now you can go sideways while advancing or retreating, perfect for dodging enemies, hiding behind obstacles and looking for an opening to begin your attack!
Try changing the variable values and see what happens, experiment with them until you got the correct combination. If you do this during gameplay, maybe you may have noticed the changes won’t stick when you stop the play test! Oh no! How are you supposed to set the right values now? Well, I will show you a little nifty trick.
Remember that little cog wheel beside the Component name? Change values and click on it while play testing and choose Copy Component, then stop play testing and click on the same cog wheel, choose Paste Component Values and you are good to go now! You can do this with whatever component you may want to change, but remember doing it with just one at the time, because it only works like that.

Now we have learned the basics of coding with Unity and C#, established controls, collisions and make our little cutie character move according to our will, muahahaha!! What’s left to do in this tutorial? Nothing, goodbye, I have to sleep…zzzzzzzzzzzz

Hahahha, just kidding! We haven’t added the acceleration and jump stuff (we already have the controls, don’t we?) and I promised you to introduce the music controls too! So let’s go on with some final details!!

Let’s begin with the dash movement, do you know what we need first? I will give you some minutes to figure it yourself. Remember we always need to manage variables in order to get values and play with them!
Do you know the answer now? That’s right! Variables! (I fell like Dora the Deported Explorer xD).
Let’s add the variables… You can see I added a little title comment, I like to tag my stuff like that so I can classify it better and know what everything does, try it out yourself ;D.

Then add and modify the if (!CharaControl.isGrounded) part under Movement() function, that is where we control the main movements of our characters, so everything that modifies it should affect here!
Erase the “!” from where it is and add an “else” condition, where you tell what to do whether the CharacterController is grounded or not. If it is floating (while jumping, for example) it can’t use the dashSpeed to increase the velocity, because you can’t dash without touching the ground (unless you have motors and are an airplane or something!).
If it’s touching the ground, dashSpeed is going to be added to MoveSpeed, increasing the velocity! (that’s why it’s important to have a base MoveSpeed not so much quickly as the dashing one).

Now you have to make a function that will control the dash increment, you already had assigned the button for it, do you remember it? The if (Input.GetKey (acelerar)) part will check if Acelerar key is pressed (GetKey checks on the keyboard keys, because they are not axis like with GetAxis).
If the key is pressed, dashSpeed will be assigned the MoveSpeed value multiplied by dash amount (it should be like a percentage of .2% or something like that, a low value but higher than 0 so it can have an increment!).
The “else” part will return dashSpeed assigned by 0, which means, if the acelerar key is not pressed, there will be no acceleration.

The final step is to call the function from the Update() function so that it can constantly affect the movements of our character.

And that’s all! You can even assign the acelerar key from the Start() function so it won’t be empty if you forget to assign it like so… You put a conditional to check if accelerar is equals to KeyCode.None (which means, it has an empty value), if it is, then the default value will be assigned, if not, then it is left as you put it. Now you can play test it!

You may have noticed the character is not accelerating at all when you pressed the designated key, right? Also, when you go out of the terrain limits, the character won’t fall over to a certain death! Well, that’s because the character doesn’t have any gravity to speak of! We need to set it up first! Add the following variables!

They will control how much force the character jump has, and how much pull the gravity that influences it will have.
Let’s go ahead and put default values to the jump key input…

Do you have any idea where the jump value would affect out character movement? Think about it, I will wait for you….
Are you ready? Remember the dashSpeed influencing over velocity? Do you think jump values affect the same? Woah, you are right! (I know you were thinking about it, I’m psychic like that ;D). But it won’t affect the entirety of it, like dash did, because we only ever jump up, but it can combine with other movements to, because it affects the character displacement in the up/ down axis, what’s that axis?
Well, remember this instruction… move = new Vector3 (directionX, 0, directionZ); and then this other velocity = move * (MoveSpeed + dashSpeed); we assigned the displacement in X and Z axis to move, which in turn was assigned to velocity, but we left the Y displacement like 0! Not anymore! Add this code between the others you already have (be careful not to write them down again! Be observant and learn to identify what you have and remember it even if for a few hours =P).

That’s right, velocity.y (the axis Y in velocity that wasn’t assigned by move!) will be assigned by directionY! That’s all, you made it! No, wait! What did you do? Where are the variables we have just declared for this? Aah, do you really thought they were going to be used directly like that?
Nope, direction will keep the value they return, but we need to make a function for it…

That’s it, her you can see we assigned jumpSpeed (and dashSpeed) to direction, but only if the character is already on the ground (if it’s not, it can’t jump again, can it? Unless you want double jump, but this is no magical cowboy, sorry xD).
If you have already jumped, but still want to press saltar key, then you are out of luck, because here we have assigned some value to gravitySpeed (it will increase according to deltaTime), and multiplying it by gravity, both will be decreased from directionY.
Your character will only jump so far (to reach the designated value on jumpSpeed or that plus dashSpeed), then fall all over again. This is the part where you can assign the double jump, assigning jumpSpeed again to directionY.
And even if you don’t try to jump again, you are going to start falling. The good news is that you can still move sideways, so you can do horizontal jumps in whatever x or z direction you want to!
Now you have set up successfully dash and jump moves! Go and try them by yourself, remember the controls you assigned and have fun! Don’t forget to tweak your variable values so that the character behaves as it should ;D
There’s only one more thing to do, the music! Right now, we are just going to set up SFX, that will sound when you walk and also when you jump. Search for sfx that you like from whatever videogame you want and then pass them to your project asset folder SFX.

Now add an AudioSource Component to your CharacterControl gameobject. This will make your gameobject into an living (virtually) speaker! Leave the default values.
You can have a lot of applications of this component, like people walking around listening to loud music, tv speakers, stereo speakers, whatever, try it out when you want! Also, we will need it in order to set some SFX to our character, it will be recycled with every sfx it needs!

Just to be sure, add this extra code above your class definition, so that everytime you add this code, an AudioSource Component will be added! If you forget to do so, don’t worry! Taking this kind of precautions allows you reutilize the same script over and over without having to remember much about it.

Now it’s time to add our well known variables for the audio control! AudioSource needs to things to work correctly as a recyclable component. First, assignment of AudioClip type variables, which will contain the sfx clips you search for in the internet! I called mine stepSound (for walking) and JumpSound (when jumping). You can even ad one for dash and whatever else, once you learn how to set it up for something, you can set it up for anything!
The second things is AudioSource type variable, it should be private, because we are going to assign it internally (you can assign it publicly too, but let’s try this one so you learn how to do it yourself!).

So let’s begin with assigning AudoSource, when done internally by the script itself, it is called Initialization (or something along these lines). Basically we tell the script to assign a declared variable (AudioMove in this case) a given component this gameobject should have. If we know it has it, we shouldn’t have any problem (that’s why we make sure this script will always require for our gameobject to have it and add it itself!). But if you forgetfully forgets to add it up (or deactivates it through unmarking the checkbox!), the script will behave erratically and have bugs and make everything else explode!!

Be very observant of your codes and what they need, a well thought-out script is a happy script xD.
Now GetComponent is not a function, variable or conditional of any type, it is a method Unity has defined internally (its own API) in order to control these kind of things like getting external components and such. Whenever you use it, you should add the component name (class) between the ();
Once you are done, you have successfully initialized a component type variable, now we can freely use it!
We need to assign sound when the character does move and when it jumps. Do you remember where we control each one? That’s right, the functions! Go to Movement() and add this code where you see it chilling out…

The “if” conditional checks whether velocity in X and Z is different (!=) to 0 (which means, it is moving in some way! But at the same time, it checks if CharaControl is on the ground (if it’s not, you can’t really step, right? Your feet can’t make a sound over the air!).
But how are we checking various things simultaneously!? Well, play close attention to the && (and) and || (or) symbols, they are used to add or substract conditions to instructions like these.
If you have && then the instructions at both sides of the symbol will be considered (you should be moving up or down while on the ground). If one of these is not valid (you aren’t moving up or down on the z axis or you aren’t on the ground), then it returns false and the instruction in the code block won’t execute.
However, the || symbol checks if whatever instruction at both sides of the symbol is valid (you can be walking on the ground up or down or walking on the ground left or right). If one of these is, then the instructions in the code block (between the {}) will execute!
Also, to facilitate reading and make it clearer to the code what you are trying to do, don’t forget to group instructions around symbols inside (), that way you can easily know what it should accomplish and the code won’t get confused. You can see (velocity.x != 0 && CharaControl.isGrounded) and (velocity.z != 0 && CharaControl.isGrounded) are both encased on their own unique (), because they both have inside a && symbol, but both are grouped around a || under another pair of ().
Also, the if (!audioMove.isPlaying) {} checks whether the AudioSource type variable audioMove is currently playing some sound, if it isn’t (!), then it will execute the instructions insided the code block: audioMove.clip function will be passed the sound that stepSound keeps, and audioMove.Play() will be executed, playing the designated sound. And that’s all!
If you want other things to sound too, all you have to do is copy paste this code bit, like so…

You just need to know what variable keeps your designated AudioClip (jumpSound), pass that value to the clip function and then call the play! Be very observant to where you need to put that code, in the case of jump, it just needed to be when you press the jump sound, because once you leave the ground, you can’t make the jump sound, do you?
Now you know quite a lot more about code than you knew before (I hope!), go and play around making other movements, playing more sounds and even changing displacement patterns for your character. Have fun and try to learn more by yourself. If you have any question, just write it down on the comments.

Next tutorial will be about animation with mecanim, stay tuned for more!

¡Unity 3D introductory tutorial Part 1…

Unity introductory tutorial P.1


¡Unity 3D introductory tutorial Part 2.1 moving the character…

Unity 3D introductory tutorial P. 2.1- Moving the character

Published by

Mary D Kidd

Una misteriosa creativa dispuesta a dejar sus ideas salir y tomar control del mundo. Preparada siempre para innovar ;D

0 thoughts on “Unity 3D introductory tutorial P. 2.2- Moving the character”

  1. Thank you, I have recently been looking for information approximately this topic for a long time and yours is the best I have discovered till now.
    Keep up the great work!

    0

    0

Leave a Reply

Your email address will not be published. Required fields are marked *

Transcribe Unity 3D introductory tutorial P. 2.2- Moving the character

Your email address will not be published. Required fields are marked *

*

*