Move character in unity - c#

I am new to unity and was wandering how to move a character so that it will stop when it hits a wall.
Currently i have used code like this:
Vector3 pos = transform.position;
if(Input.GetKey("a")) pos.x -= 1;
if(Input.GetKey("d")) pos.x += 1;
transform.position = pos;
However with this the character will move through walls.
I have added a rigidbody component to the char.
EDIT: Yes they do have a box collider on them, and the char does actually start to "bounce" when they collide, but the char goes right through the wall.

You need to let the Physics engine do the moving for you, so don't set the transform.position yourself. Set the rigidbody.velocity instead.
int xVelocity = 0;
if(Input.GetKey("a"))
{
xVelocity = -1;
}
else if(Input.GetKey("d"))
{
xVelocity = 1;
}
rigidbody.velocity = new Vector3(xVelocity, 0, 0);

Related

How to realistically reflect a 3d sphere in Unity with C#

I've been trying to realistically reflect a 3d sphere on the walls of a box for a while now in Unity. For some reason, the reflection is generally correct, but when the ball hits a wall in certain directions, the reflection is incorrect.
To illustrate what happens to the ball upon hitting a wall: T = top wall, R = right wall, L = left wall, and B = bottom wall. Let r = the ball comes/goes to the right, l = for the left, and s = the ball stops/slows down significantly. The instructions below take this format: Xyz, where X = the wall the ball is about to hit, y = the ball's initial direction, z = the reflection. The game has a top-down perspective, and the instructions are based on the wall's perspective. I'm also new to C#, so the code is potentially eye burning.
Instructions: Tll, Trl; Bll, Brl; Rls or after hitting another wall Rlr, Rrl; Lls or after hitting another wall Llr, Lrl
Generally, when the ball stops, it jumps in the air. I wonder if this is because the angle reflects along the wrong axis, but why would this only sometimes happen? Also, when only one key is held, the ball bounces back and forth until it leaves the arena. I know about discrete and continuous hit detection, and the setting is on discrete, but the walls generally contain the ball well enough, with this case being the exception.
What I tried:
Figuring out how to use Vector3.Reflect. I do not understand what variables this function should contain and how to apply this to my code. I did look at the Unity Documentation page for help, but it did not answer my question.
Changing the negative signs, as the angle has to be a reflection on the y-axis, and this does change how the reflections work, but does not solve the problem. The current way the negatives are ordered are the most ideal I found.
Giving the ball a physics material for bounciness.
Adding a small number to the denominator in the arctan equation to help prevent a division by zero. This did not help at all.
Creating different equations (basically changing the negatives) for varying combinations of positive and negative accelerations. Since there is a certain positive or negative acceleration associated with each button press (see Movement script), and there seems to be an issue with said signs, I wondered if associating each acceleration with its own set of equations could solve the problem. It did not work.
Checked if the walls were at different angles.
Deleting the variables xA and yA, or placing them in different spots.
Experimented with finding the speed of the object, but had no idea how to implement it.
The code for the Movement script for the player named Controller:
public class Movement : MonoBehaviour
{
public static float xAcceleration = 0.0f;
public static float yAcceleration = 0.0f;
// Update is called once per frame
void Update()
{
if (Input.GetKey(KeyCode.W)) //If the key W is pressed:
{
Vector3 position = this.transform.position; //Variable position is set to transform the players placement in the game.
if (yAcceleration >= -5 && yAcceleration <= 5) //If the y vector of the acceleration is >= -5 and <= 5:
{
yAcceleration = yAcceleration + 0.01f; //The y vector of the acceleration increases by 0.01 as long as the key W is pressed.
}
position.z = position.z + (0.1f * yAcceleration); //The position of the object on the z-axis (pretend it is the y-axis in the game world) is transformed by its original position plus its speed times its yAcceleration.
this.transform.position = position; //The gameObject is now transformed to a position equal to the variable position by the z-axis.
}
else //If the key W is let go of:
{
Vector3 position = this.transform.position;
position.z = position.z + (0.1f * yAcceleration);
this.transform.position = position; //The position of the gameObject continues to update, but its acceleration does not change. Basically, it continues to move forward.
}
//The rest of the code is very similar to the above, but I included it just in case there was something wrong.
if (Input.GetKey(KeyCode.S))
{
Vector3 position = this.transform.position;
if (yAcceleration >= -5 && yAcceleration <= 5)
{
yAcceleration = (yAcceleration) - 0.01f;
}
position.z = position.z + (0.1f * yAcceleration);
this.transform.position = position;
}
else
{
Vector3 position = this.transform.position;
position.z = position.z + (0.1f * yAcceleration);
this.transform.position = position;
}
if (Input.GetKey(KeyCode.A))
{
Vector3 position = this.transform.position;
if (xAcceleration >= -5 && xAcceleration <= 5)
{
xAcceleration = (xAcceleration) - 0.01f;
}
position.x = position.x + (0.1f * xAcceleration);
this.transform.position = position;
}
else
{
Vector3 position = this.transform.position;
position.x = position.x + (0.1f * xAcceleration);
this.transform.position = position;
}
if (Input.GetKey(KeyCode.D))
{
Vector3 position = this.transform.position;
if (xAcceleration >= -5 && xAcceleration <= 5)
{
xAcceleration = (xAcceleration) + 0.01f;
}
position.x = position.x + (0.1f * xAcceleration);
this.transform.position = position;
}
else
{
Vector3 position = this.transform.position;
position.x = position.x + (0.1f * xAcceleration);
this.transform.position = position;
}
}
}
This is the code for the collider and reflection:
public class Collider : MonoBehaviour
{
public float xA;
public float yA;
void OnCollisionEnter(Collision collision) //If a gameObject enters the collision of another object, this immediately happens once.
{
if (gameObject.tag == "Boundary") //If the gameObject has a tag named Boundary:
{
yA = -Movement.yAcceleration; //yA stores the value of yAcceleration after being called from script Movement as a negative. Its a reflection.
Movement.xAcceleration = (Movement.xAcceleration * -Mathf.Atan(yA / Movement.xAcceleration)); //xAcceleration is changed based on this equation: A * artan(A_y / A_x). The 0.000001 was here, adding to A_x to help prevent a 0 as the denominator.
xA = Movement.xAcceleration; //This is declared now...
Movement.yAcceleration = (Movement.yAcceleration * Mathf.Atan(Movement.yAcceleration / xA)); //This uses xA because Movement.xAcceleration is changed, and the yAcceleration calculation is based on the xAcceleration prior the collision.
}
}
void OnCollisionStay(Collision collision)
{
if (gameObject.tag == "Boundary")
{
yA = Movement.yAcceleration; //The same thing happens as before.
Movement.xAcceleration = (Movement.xAcceleration * -Mathf.Atan(yA / Movement.xAcceleration));
xA = Movement.xAcceleration;
Movement.yAcceleration = (Movement.yAcceleration * Mathf.Atan(Movement.yAcceleration / xA));
Movement.xAcceleration = -Movement.xAcceleration / 2; //On collision, the ball is reflected across the x-axis at half its speed.
Movement.yAcceleration = Movement.yAcceleration / 2; //yAcceleration is half its original value.
}
}
}
The picture below is the game setup. I apologize that it is a link; I do not have enough Reputation to merit a loaded image on this page. Also, if anything is unclear, please message me.
https://i.stack.imgur.com/VREV4.png
I would really appreciate the help. Thanks!
One very important note here: As soon as there is any Rigidbody involved you do not want to set any values through the .transform - This breaks the physics and collision detection!
Your Movement should rather alter the behavior of the Rigidbody e.g. by simply changing its Rigibody.velocity
I would then also place the collision check directly into the balls's component and check whether you hit a wall ("Boundary")
Then another note: Your code is currently frame-rate dependent. It means that if your target device runs with only 30 frames per second you will add 0.3 per second to the acceleration. If you run however on a more powerful device that manages to run with 200 frames per second then you add 2 per second.
You should rather define the de/increase per second and multiply it by Time.deltaTime
All together maybe something like this
public class Movement : MonoBehaviour
{
// Adjust these settings via the Inspector
[SerializeField] private float _maxMoveSpeed = 5f;
[SerializeField] private float _speedIncreasePerSecond = 1f;
// Already reference this via the Inspector
[SerializeField] private Rigidbody _rigidbody;
private void Awake()
{
if(!_rigidbody) _rigidbody = GetComponent<Rigidbody>();
}
// Get User Input in Update
private void Update()
{
var velocity = _rigidbody.velocity;
velocity.y = 0;
if (Input.GetKey(KeyCode.W) && velocity.z < _maxMoveSpeed)
{
velocity.z += _speedIncreasePerSecond * Time.deltaTime;
}
if (Input.GetKey(KeyCode.S) && velocity.z > -_maxMoveSpeed)
{
velocity.z -= _speedIncreasePerSecond * Time.deltaTime;
}
if (Input.GetKey(KeyCode.A) && velocity.x > -_maxMoveSpeed)
{
velocity.x -= _speedIncreasePerSecond * Time.deltaTime;
}
if (Input.GetKey(KeyCode.D) && velocity.x < _maxMoveSpeed)
{
velocity.x += _speedIncreasePerSecond * Time.deltaTime;
}
// clamp to the max speed in case you move diagonal
if(velocity.magnitude > _maxMoveSpeed)
{
velocity = velocity.normalized * _maxMoveSpeed;
}
_rigidbody.velocity = velocity;
}
}
And then finally simply add a PhysicsMaterial with desired settings to the walls and ball.
I used Friction = 0f and Bounciness = 0.7f for ball and walls. For slow movements you also might want/have to adjust the Bounce Threshold in the Project's Physics Settings otherwise there will be no bouncing if the velocity is smaller then 2 by default.
This depends a bit on your definition of "realistic". I disabled gravity so the ball also has no rotation and angular friction:

How to detect if player is moving Left/Right/Back/Forward

My player can only move along straight horizontal and vertical lines. I want to detect whether they are moving left, right, back, or forward at any given point in time. They can rotate any way they like in the game.
I've tried detecting this when users press the arrows keys and then checking if the current player position along the X or Y (z transform) planes is different than it was before. That didn't seem to work.
I also tried using this code from Unity answers where a similar question was asked but it isn't working for me:
float dot = Vector3.Dot(transform.forward, Vector3.forward);
if(dot > 0.9) // going forward direction
else if (dot < - 0.9) // going opposite to forward direction
else{
Vector3 cross = Vector3.Cross(transform.forward, Vector3.forward);
// This could be the other way around...never remember which order
if(cross.y < 0) // going right
else // going left
}
The best way would be to check the velocity if your Character gets moved by Physics. If your Character moves with something where velocity does not fulfill the job, then you would save the last position and check it with the new one like your first idea was.
Velocity
So like I said when your Character moves with Physics you could check the direction like this:
public RigidBody rb; // Add the rigidbody of your Player to your script
void Update() {
// What ever your code is here
Vector3 vel = transform.rotation * rb.velocity;
if(vel.z > 0) {
// forward
} else if(vel.z < 0) {
// backwards
}
if(vel.x > 0) {
// right
} else if(vel.x < 0) {
// left
}
}
Position
You can also check the movement by position. For this you need to compare not only old Pos and new Pos but also old Rotation and new Rotation.
What I could think of is:
(Not tested)
Vector3 oldPos;
Quaternion oldRot;
Update() {
Vector3 movement = oldRot * (transform.position - oldPos));
if(movement.z > 0) {
// forward
} else if(movement.z < 0) {
// backwards
}
if(movement.x > 0) {
// right
} else if(movement.x < 0) {
// left
}
oldPos = transform.position;
oldRot = transform.rotation;
}

Moving around GameObject

I'm trying to create MOB AI using rigidbody. I want to make the mob (GameObject) walk around the world using
mobrigid.AddForce((((goal - transform.position).normalized)*speed * Time.deltaTime));
(goal is a random place around mob).
here is where it gets complicated, some mobs fly up in the air at extreme speeds while others move normaly at slow speed. (And yes, I do make sure the goal.Y is a place on the ground and not air). I tried fixing it by changing the drag, but that leads to mobs walking on air and not falling with gravity.
I'm really lost, can't figure out how do I simply move a gameobject around with rigidbody without getting this odd behavior.
Logs of mobs (with Y of goal changed to 0):
Edit:
Image of the mob:
my movement logic:
case MOBTYPE.EARTHMOB:
switch (currentstatus)
{
case MOBSTATUS.STANDING:
if (mobrigid.IsSleeping())
{
mobrigid.WakeUp();
}
goal = this.transform.position;
goal.x = Random.Range(goal.x - 150, goal.x + 150);
goal.z = Random.Range(goal.z -150, goal.z + 150);
goal.y = 0;
currentstatus = MOBSTATUS.WALKING;
// Debug.Log("Transform:"+this.transform.position+ "Goal:" + goal+ "goal-transform:" + (goal - transform.position));
break;
case MOBSTATUS.WALKING:
if (Random.Range(1, 100) == 5)
{
currentstatus = MOBSTATUS.STANDING;
}
if (mobrigid.IsSleeping())
{
mobrigid.WakeUp();
}
mobrigid.AddForce((((goal - transform.position).normalized) * 10000 * Time.deltaTime));
// transform.LookAt(goal);
var distance = Vector3.Distance(goal, gameObject.transform.position);
if (distance <=5)
{
currentstatus = MOBSTATUS.STANDING;
}
break;
}
break;
Terrain Image:
The Rigidbody.AddForce function is used to add force to an Object along a direction. Since you have a position you need to move a Rigidbody to, you have to use the Rigidbody.MovePosition function. You may also want to mark the Rigidbody as kinematic.
A simple coroutine function to move a Rigidbody object to specific position:
IEnumerator MoveRigidbody(Rigidbody rb, Vector3 destination, float speed = 50f)
{
const float destThreshold = 0.4f;
while (true)
{
Vector3 direction = (destination - rb.position).normalized;
rb.MovePosition(rb.position + direction * speed * Time.deltaTime);
float dist = Vector3.Distance(rb.position, destination);
//Exit function if we are very close to the destination
if (dist <= destThreshold)
yield break;
yield return null;
//yield return new WaitForFixedUpdate();
}
}
It better to call this from another coorutine funtion so that you can yield or wait for it to finish then do other task like generating the random postion again and moving the Rigidbody there.
You want to generate new postion then move the Rigidbody there, this is an example of how to call the function above:
IEnumerator StartMoveMent()
{
Rigidbody targetRb = GetComponent<Rigidbody>();
while (true)
{
//Generate random position
Vector3 destination = new Vector3();
destination.y = 0;
destination.x = UnityEngine.Random.Range(0, 50);
destination.z = UnityEngine.Random.Range(0, 50);
//Move and wait until the movement is done
yield return StartCoroutine(MoveRigidbody(targetRb, destination, 30f));
}
}
And to start the StartMoveMent function:
void Start()
{
StartCoroutine(StartMoveMent());
}
While what I said above should work, I do recommend you use Unity's built in pathfinding system. Here is a tutorial for that. It simplifies finding paths to follow towards a destination with NavMesh which can also be baked during run-time.

Change/Flip Player Sprite direction

currently I am working on a networked 2D platformer. I am trying to make the character always face in the direction he is walking. Therefore I am using this code: (BTW I know this is not supposed to work)
if (rigidbody.velocity.y > 0) {
transform.rotation = 0,0,0;
} else if (rigidbody.velocity.y < 0) {
transform.rotation = 0,180,0;
}
So my question would be what code would I have to use to make the character's transform's rotation be 0,0,0 when his velocity on the y axis is over zero and 0,180,0 when its under.
Note:
Yes I do know there are other ways of approaching this but I think this would be the ideal way in this instance and I am curious.
Ways to flip/change the direction where the character is facing:
1.You can use this variable on from the SpriteRenderer:
spriteRenderer.flipX = true;
//OR flipY for the Y-axis
spriteRenderer.flipY = true;
2.OR Multiple the axis you want to flip by -1.
Vector2 newPos = new Vector2(transform.localScale.x, transform.localScale.y);
newPos.x = newPos.x * -1; //Flip X
transform.localScale = newPos;

Unity 2D Top Down Shooter movement issue C#

I am trying to make a simple 2D Top-Down Shooter in Unity. I have the basic movement and following of the mouse, but there is an issue in the movement.
Whenever you press say Up and Right at the same time, it moves diagonally as you would expect, but when you let go of the Up key, it keeps going diagonally where I want it to move Right. My code for handling the movement is:
private void Update ()
{
if (Input.GetKey(Up)) {
Debug.Log("UP");
Vector3 velUp = rigidbody2D.velocity;
velUp.y = walkSpeed;
rigidbody2D.velocity = velUp;
}
else if (Input.GetKey(Down)) {
Vector3 velDown = rigidbody2D.velocity;
velDown.y = walkSpeed*-1;
rigidbody2D.velocity = velDown;
}
else if (Input.GetKey(Left)) {
Vector3 velLeft = rigidbody2D.velocity;
velLeft.x = walkSpeed*-1;
rigidbody2D.velocity = velLeft;
}
else if (Input.GetKey(Right)) {
Vector3 velRight = rigidbody2D.velocity;
velRight.x = walkSpeed;
rigidbody2D.velocity = velRight;
}
else {
Vector3 velStop = rigidbody2D.velocity;
velStop.x = 0;
velStop.y = 0;
rigidbody2D.velocity = velStop;
}
//rotation
Vector3 mousePos = Input.mousePosition;
Vector3 objectPos = Camera.main.WorldToScreenPoint (transform.position);
mousePos.x = mousePos.x - objectPos.x;
mousePos.y = mousePos.y - objectPos.y;
float angle = Mathf.Atan2(mousePos.y, mousePos.x) * Mathf.Rad2Deg;
transform.rotation = Quaternion.Euler(new Vector3(0, 0, angle));
}
How can I get the movement to behave as I mentioned? With it moving diagonally like this makes the movement seem off.
Any help is greatly appreciated. Thanks.
You start with a velocity of 0, then you add up all the movements directions you have. Then you normalize the vector and scale it to your movement speed. Otherwise the player moves faster, if walking diagonal.
I haven't checked the code, but something like this will do:
void Update () {
Vector3 vel = new Vector3();
if(Input.GetKey(Up)){
Debug.Log("UP");
Vector3 velUp = new Vector3();
// just use 1 to set the direction.
velUp.y = 1;
vel += velUp;
}
else if(Input.GetKey(Down)){
Vector3 velDown = new Vector3();
velDown.y = -1;
vel += velDown;
}
// no else here. Combinations of up/down and left/right are fine.
if(Input.GetKey(Left)){
Vector3 velLeft = new Vector3();
velLeft.x = -1;
vel += velLeft;
}
else if(Input.GetKey(Right)){
Vector3 velRight = new Vector3();
velRight.x = 1;
vel += velRight;
}
// check if player wants to move at all. Don't check exactly for 0 to avoid rounding errors
// (magnitude will be 0, 1 or sqrt(2) here)
if (vel.magnitude > 0.001) {
Vector3.Normalize(vel);
vel *= walkSpeed;
rigidbody2D.velocity = vel;
}
//rotation
Vector3 mousePos = Input.mousePosition;
Vector3 objectPos = Camera.main.WorldToScreenPoint (transform.position);
mousePos.x = mousePos.x - objectPos.x;
mousePos.y = mousePos.y - objectPos.y;
float angle = Mathf.Atan2(mousePos.y, mousePos.x) * Mathf.Rad2Deg;
transform.rotation = Quaternion.Euler(new Vector3(0, 0, angle));
}
Regarding Normalize have a look at this image.
If you walk only upwards or right you would move with speed 1 (which we later multiply with your desired speed). But if you walk diagonal you walk with about 1.4 times the desired speed (green vector). Normalize keeps the direction of the vector intact, but gives you a length (also called "magnitude") of 1 (red vector).
In older shooters you may find a bug called "bunny hopping". I'm not sure if this is the source of the problem, but I would guess it is.
Regarding vel.magnitude > 0.001:
We actually want to know if vel.magnitude > 0. But vel.magnitude is the result of a calculation and thus may contain rounding errors. If you work with floating-point values always keep that in mind. The check itself is done because the Normalize method needs to divide by the magnitude and division by zero is evil. Not sure if Normalize checks for this itself.
It seems the only time you force your velocity to nothing is when there isn't any key being pressed.
I might suggest adding some checks to see when Input.GetKeyUp is true, and set the rigidbody2D.velocity's x or y values to 0, maybe something like this:
void Update () {
if (Input.GetKeyUp(Up) || Input.GetKeyUp(Down)) {
rigidbody2D.velocity.y = 0;
}
if (Input.GetKeyUp(Left) || Input.GetKeyUp(Right)) {
rigidbody2D.velocity.x = 0;
}
...

Categories