Changing the player's position in Update() or FixedUpdate() method? - c#

In my game I've a case that when the player is touching the collider of a specific GameObject and the player press the X key I want to change his position to the position of another GameObject.
To do that I use the following code
void FixedUpdate()
{
if (Input.GetKeyDown(KeyCode.X))
{
// Check Colliders collition
// End of Check Colliders collition
var playerRigidBody = GetComponent<Rigidbody2D>();
playerRigidBody.position = AnotherObject.transform.position;
}
}
I've realized that very often when I press the X key the player don't change his position to the position of the other GameObject. However if I move the code to the Update() method it works as expected (with no delay).
But I've read in the official documentation and in a lot of blogs that the code related to physics and Rigidbodies must be put in the FixedUpdate instead of Update
So the question is the following:
For this case putting the code in the Update() method is not recommended? (Note that I'm not using forces nor torques)
If it's not recommended then how can I assure that every time the player press the X key he will change his position to the other GameObject position in FixedUpdate()?

In general if you want a smooth movement you should rather use MovePosition in FixedUpdate otherwise everything regarding Physics (so also RigidBody) you should always do in FixedUpdate (as explained in the API there as well).
You should however catch the GetKeyDown in Update and split those two things:
bool wasClicked;
RigidBody playerRigidBody;
void Start()
{
// do this only once!
playerRigidBody = GetComponent<Rigidbody2D>();
}
void Update()
{
// Catch user input here
if (Input.GetKeyDown(KeyCode.X))
{
wasClicked = true;
}
}
void FixedUpdate()
{
// Handle physics here
if(!wasClicked) return;
playerRigidBody.position = AnotherObject.transform.position;
wasClicked = false;
}

Related

Ranged attack in Unity3D

I have two objects: a player and an enemy. The main problem is the enemy, which should start shooting at the player when it approaches him at a certain distance (in short, just start the animation).
At first I tried to implement this through Vector 3, as with other ordinary opponents. But he is as stupid and crutch as possible, however, you yourself can see everything in the pinned.
I started to implement it more allegedly correctly, through the trigger and the collider (box collider) of the enemy itself. And everything works right as it should, but there is a nuance. The enemy also has boxing implemented through the box collider, the player, hitting which, causes damage to him. There is only one box collider for these two tasks, and since I had to increase this collider so that the enemy could stop in front of the player at a certain distance. Because of this, the player can hit at a great distance (at which the enemy can shoot) from the enemy and the enemy takes damage anyway.
I tried to make a separate object the size of the enemy himself and use it as a box to receive damage. Then he already transmits data about receiving damage to the enemy object. But this does not work, all links between scripts and objects are made, but he does not want to transfer data. That is, simply making two colliders for different tasks does not work.
In general, here my powers are all. I searched the entire Internet, but I did not find how to implement this mechanic in a different way so that it does not conflict with others. Therefore, I ask the help of the local experts, where I screwed up.
private Animator ch_animator;
// Start is called before the first frame update
void Start()
{
myAgent = GetComponent<NavMeshAgent>();
myAnim = GetComponent<Animator>();
EnemyH = GetComponent<GDHealth>();
}
// Update is called once per frame
void Update()
{
dist = Vector3.Distance(/*checker.*/transform.position, target.transform.position);
if (dist > range)
{
myAgent.enabled = false;
myAnim.SetBool("Idle", true);
myAnim.SetBool("Move", false);
myAnim.SetBool("Attack", false);
}
if (dist <= range & dist > atRange)
{
myAgent.enabled = true;
myAgent.SetDestination(target.position);
myAnim.SetBool("Idle", false);
myAnim.SetBool("Move", true);
myAnim.SetBool("Attack", false);
}
if (dist <= atRange)
{
StartCoroutine(Attack());
}
if (PlayerH._health <= 0)
{
atRange = 0;
}
if (EnemyH._health < 0)
{
myAgent.enabled = false;
}
}
public IEnumerator Attack()
{
yield return new WaitForSeconds(0.5f);
myAgent.enabled = false;
myAnim.SetBool("Idle", false);
myAnim.SetBool("Move", false);
myAnim.SetBool("Attack", true);
}
void OnTriggerStay(Collider col)
{
if (col.tag == "Player")
{
//gameObject.GetComponent<Animator>().SetBool("Attack", true);
StartCoroutine(Attack());
transform.LookAt(col.transform.position);
transform.eulerAngles = new Vector3(0, transform.eulerAngles.y, 0);
}
}
void OnTriggerExit(Collider col)
{
if (col.tag == "Player")
{
myAgent.enabled = true;
myAgent.SetDestination(target.position);
myAnim.SetBool("Idle", false);
myAnim.SetBool("Move", true);
myAnim.SetBool("Attack", false);
//gameObject.GetComponent<Animator>().SetBool("Attack", false);
}
}
But this does not work, all links between scripts and objects are made, but he does not want to transfer data.
From this description it could be anything: wrong layers, no rigidbody on either of objects, misstyped tags in OnTriggerStay method.
In my project, I successfully created 2 colliders for 2 separate tasks, so this is how I would see it in your problem:
Use two colliders
Attach one collider with one script to the enemy object - this collider should have a size of the enemy. The OnTriggerStay method here should deal damage to the enemy, check for death, etc.
Create child object to the enemy. Attach new collider to it with the size of enemy's attack range. Attach a script with OnTriggerStay method that will stop enemy and begin ranged attack (or whatever you want to do).
If this doesn't work: check collision matrix or try adding a kinematic rigidbody to either of objects.
Measure distance between player and the enemy in update (which you are already doing) and apply necessary code based on distance (stop or attack) thus replacing one of the colliders.
Hope that helps!

OnCollisionEnter2D only gets called once

I have two objects in my scene, one which only has a BoxCollider2D (the Column class) and the second object has a Rigibody2D as well as its own BoxCollider2D (Player class).
I added a script to the first object to have an OnCollisionEnter2D. I see it gets triggered when my second object collides with it, and it bounces my 2nd object back when it tries to enter.
I do see my OnCollisionEnter2D method getting called. But if I move my 2nd object again to my first object it gets bounced back again, however I don't see my OnCollisionEnter2D method getting called the 2nd time.
Is this intended behavior? If so, what method would get called every time a collision occurs between these two objects?
Note: I saw OnCollisionStay2D get called a few more times and then it stopped. I assume this is when it's bouncing my 2nd object out. I also see that OnCollisionExit2D never got called. I zoomed into the editor and saw clearly the green lines of the BoxCollider2D did not overlap between my objects so it should've exited the collision when it bounces it back.
public class Column : MonoBehaviour
{
private BoxCollider2D columnColl;
// Start is called before the first frame update
void Start()
{
columnColl = GetComponent<BoxCollider2D>();
}
// Update is called once per frame
void Update()
{
}
private void OnCollisionExit2D(Collision2D collision)
{
Debug.Log("Collision Exit");
}
void OnCollisionStay2D(Collision2D collision)
{
//Debug.Log("Collision Stayed");
}
void OnCollisionEnter2D(Collision2D collision)
{
Debug.Log("Collision Happened");
}
}
and
public class Player : MonoBehaviour
{
public float xMoveSpeed = 1f;
private Rigidbody2D rbody;
// Start is called before the first frame update
void Start()
{
rbody = GetComponent<Rigidbody2D>();
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown("d"))
{
rbody.position = rbody.position + new Vector2(xMoveSpeed, 0);
}
}
}
You shouldn't "manually" change rbody.position.
Rather use Rigidbody.MovePosition
Use Rigidbody.MovePosition to move a Rigidbody, complying with the Rigidbody's interpolation setting.
If Rigidbody interpolation is enabled on the Rigidbody, calling Rigidbody.MovePosition results in a smooth transition between the two positions in any intermediate frames rendered. This should be used if you want to continuously move a rigidbody in each FixedUpdate.
Set Rigidbody.position instead, if you want to teleport a rigidbody from one position to another, with no intermediate positions being rendered.
You also should rather do it in FixedUpdate which is used for the Physics instead of Update
void FixedUpdate()
{
if (Input.GetKeyDown("d"))
{
rbody.MovePosition(rbody.position + new Vector2(xMoveSpeed, 0);
}
}
Also make sure at least one of the objects is not kinematic as to OnCollisionEnter:
Collision events are only sent if one of the colliders also has a non-kinematic rigidbody attached.

Unity 2d game movement script issue - unable to jump

So basically, after hours of torment trying to create basic movement script for simple platformer game I succeeded, but not quite. Square character is able to move around and jump just ok, but sometimes it won't jump, usually while moving on short distances or, rarely, standing in place and trying to jump. I can't figure out how to fix that. Here is entire script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerMovement : MonoBehaviour {
private Rigidbody2D rgdb2;
public float movementSpeed;
public float jumpHeight;
private bool isJumping = false;
// Use this for initialization
void Start ()
{
rgdb2 = GetComponent<Rigidbody2D>();
}
// Update is called once per frame
void FixedUpdate ()
{
float moveHorizontal = Input.GetAxis("Horizontal");
HandleMovement(moveHorizontal);
if (Input.GetKeyDown(KeyCode.Space) && isJumping == false)//by typing Space player jumps, cant double-jump
{
rgdb2.AddForce(new Vector2(rgdb2.velocity.x, 1 * jumpHeight), ForceMode2D.Impulse);
isJumping = true;
Debug.Log("jumped");
}
}
private void HandleMovement(float moveHorizontal)//applying player horizontal controls and customing player's speed by movementSpeed variable
{
rgdb2.velocity = new Vector2(moveHorizontal * movementSpeed, rgdb2.velocity.y);
}
private void OnCollisionEnter2D(Collision2D coll)
{
if (coll.transform.tag == "Platform") //if player is touching object with Platform tag, he can jump
{
Debug.Log("on ground bitch");
isJumping = false;
}
}
}
It may not be that important, but I want to polish this game as much as possilble, even if I don't need to, since it's basically my first game made in Unity3d with C#.
An important thing to keep in mind: Unity3D Engine's inputs are only updated during the time the engine calls Update() methods for your GameObjects.
What this means is that you should not read any type of input in the FixedUpdate() method. Methods like GetKeyDown() and other methods from the Input class which read keyboard/mouse/axis buttons/values should not be called during FixedUpdate(), as their returned values are unreliable.
Due to this, what is probably causing your jump implementation to fail is that the GetKeyDown() method you're calling in FixedUpdate() is returning inconsistent/invalid (false) results, when the user presses the jump key.
Fixing this can be quite simple. I suggest you keeping a boolean variable which keeps track of whether the jump key has been pressed, and gets its value updated during Update(). This should fix your problem.
bool jumpKeyPressed;
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
jumpKeyPressed = true;
else if (Input.GetKeyUp(KeyCode.Space))
jumpKeyPressed = false;
}
private void FixedUpdate()
{
/* Update "moveHorizontal", call HandleMovement(...) here, as you've already done. */
if (jumpKeyPressed && isJumping == false)
{
// IMPORTANT: this prevents the "jump force" from being applied multiple times, while the user holds the Space key
jumpKeyPressed = false;
/* Remaining jumping logic goes here (AddForce, set "isJumping", etc) */
}
}
It's because your logic for jumping is inside FixedUpdate()
When you use GetKeyDown to register input make sure to use Update instead because if you press the key using FixedUpdate it may or may not run during that frame, test it with Update instead.
You already have in comments how Update works, it is called every frame but FixedUpdate according to Unity documentation: This function is called every fixed framerate frame
void Update ()
{
float moveHorizontal = Input.GetAxis("Horizontal");
HandleMovement(moveHorizontal);
if (Input.GetKeyDown(KeyCode.Space) && isJumping == false)//by typing Space player jumps, cant double-jump
{
rgdb2.AddForce(new Vector2(rgdb2.velocity.x, 1 * jumpHeight), ForceMode2D.Impulse);
isJumping = true;
Debug.Log("jumped");
}
}

Physics.IgnoreCollision Doing Nothing

I'm trying to make a BTD game. Since different balloons (enemies) have different speeds, they collide with each other when traveling on the path. I'm currently using this code:
void OnCollisionEnter (Collision coll)
{
if (coll.gameObject.tag == "Enemy")
{
Physics.IgnoreCollision(coll.collider, gameObject.GetComponent<SphereCollider>());
}
}
However, it doesn't appear to be working at all. The enemies still collide with each other. On the otherhand, the collision with the enemies and bullets from towers is working.
void OnTriggerEnter (Collider col)
{
if (col.tag == "Bullet")
{
CurrentHP -= col.GetComponent<TackShooterBullet>().Damage;
}
I've tried layer-collision (enemies to same layer & unchecking of the same layer collision in the layer collision matrix, but that doesn't work either. The enemy contains a sphere mesh filter, sphere collider, mesh renderer, rigidbody, material, and 2 scripts. Is there a better way to avoid collisions between the enemies. I'm asking this question since I've seen duplicates, but their solutions aren't working at all. I can provide more of my code if needed.
Edit for Clarity: Again, what I'm trying to accomplish is have the enemies be able to go through each other.
Edit (Fixed Problem): I found out to avoid Enemy Collisions, I could also remove rigidbody. However, removing the ridigbody would mess up the bullet --> enemy trigger in the enemy class. Therefore, I just wrote the collision between bullet & enemy in the bullet class instead.
using UnityEngine;
public class TackShooterBullet : MonoBehaviour {
private GameObject target;
public float Damage;
// Use this for initialization
void Start () {
target = transform.parent.GetComponent<TackShooterRange>().Target; // Target = Enemy[0] (First Enemy To Enter Range - Enemy is Removed from JList when exiting Range)
}
// Update is called once per frame
void Update()
{
Damage = gameObject.transform.parent.transform.parent.GetComponent<TackShooterLimitingRange1>().level * 20; // Upgrade Level * 20 = Damage Done
if (target == null) // If Enemy Exits Range
{
Destroy(gameObject); // Destroy Bullet
}
if (target != null) // Enemy Exists In Range
{
transform.position = Vector3.MoveTowards(transform.position, target.transform.position, 20 * Time.deltaTime); // Bullet Follows Enemy
Destroy(gameObject); // Destroy Bullet Upon Contact With Enemy
target.GetComponent<HealthOfEnemy>().CurrentHP -= Damage; // Enemy Loses Health
}
}
This allowed me to remove the OnTriggerEnter & OnCollisionEnter methods as well as the RigidBody from the Enemy Class as stated before, so these properties no longer affect the collisions between Enemies.
Unity has a built in function for easier collision detection called layer-based collision detection:
https://docs.unity3d.com/Manual/LayerBasedCollision.html
The documentation is really good. Just comment if you need further clarification.

How to stop a moving object

My script is about when my ball hit a "Trap Object", it'll be moved to start position and STOP right there. How to do that?
void OnTriggerEnter (Collider other)
{
if (other.gameObject.CompareTag ( "Trap" ))
{
//move object to start position
transform.position = startposition.transform.position;
// I want to stop the object here, after it was moved to start position. Because my ball was moving when it hit Trap object, so when it was moved to start position, it keeps rolling.
}
}
As I mentioned in my comment, you need to reset the force of the rigidbody to make sure that your ball is stopped completely. The following code could fix your issue.
// LateUpdate is triggered after every other update is done, so this is
// perfect place to add update logic that needs to "override" anything
void LateUpdate() {
if(hasStopped) {
hasStopped=false;
var rigidbody = this.GetComponent<Rigidbody>();
if(rigidbody) {
rigidbody.isKinematic = true;
}
}
}
bool hasStopped;
void OnTriggerEnter (Collider other)
{
if (other.gameObject.CompareTag ( "Trap" ))
{
var rigidbody = this.GetComponent<Rigidbody>();
if(rigidbody) {
// Setting isKinematic to False will ensure that this object
// will not be affected by any force from the Update() function
// In case the update function runs after this one xD
rigidbody.isKinematic = false;
// Reset the velocity
rigidbody.velocity = Vector3.zero;
rigidbody.angularVelocity = Vector3.zero;
hasStopped = true;
}
//move object to start position
transform.position = startposition.transform.position;
// I want to stop the object here, after it was moved to start position. Because my ball was moving when it hit Trap object, so when it was moved to start position, it keeps rolling.
}
}
The code is untested, so I wouldnt be suprised if it didnt compile on first try, I could have misspelled Rigidbody or something.
(I don't have Unity at work either so hard to test ;-))
Hope it helps!
Do you add some form of speed or velocity to your ball? If you do, you need to reset this to zero to stop your ball from rolling.

Categories

Resources