in unity3d I have a player that moves constantly forward with a certain speed and I control only it's left or right position.
I want my player to speed up instantaneously when it encounters an object and a trigger is enabled.
Here is what I tried but it doesn't seem to work correctly. Any ideas?
void Update ()
{
GetComponent<Rigidbody>().velocity = new Vector3(Input.GetAxisRaw("Horizontal") * 4, 0, horizVel);
}
private void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "SpeedUp")
{
GetComponent<Rigidbody>().velocity = new Vector3(Input.GetAxisRaw("Horizontal") * 4, 0, horizVel * 10.0f);
}
}
horizVel is a public variable for my velocity set to 10.
Seems to be because you have hardcoded your speed variable OnTriggerEnter methods, instead of updating it.
Update is called once a frame. If your horizVel is set to 10, it will move at a speed of 10 once per frame.
When you hit OnTriggerEnter your horizVel gets updated to 10x that of what it was before, i.e: 100.
BUT, because you've not updated your speed variable, when you come back round to the Update method, your horizVel will be back at 10 again.
I think what you should be trying is this:
private void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "SpeedUp")
{
horizVel *= 10f;
}
}
That way your speed variable will stay at 10x that of what it was before INSTEAD of for just the collision period.
EDIT
"I tried this but the speed remains boosted not only in the collision period"
Then you can use a coroutine to reset the speed variable back to its original value:
private void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "SpeedUp")
{
horizVel *= 10f;
StartCoroutine(ResetSpeedAfterTime(5f));
}
}
// Resets the speed variable back to the original value after a set amount of time
private IEnumerator ResetSpeedAfterTime(float time)
{
yield return new WaitForSeconds(time);
horizVel = 10f; // the original speed value;
}
Though I am a beginner at C# and my answer may not be correct but have you tried assigning an id to the game object that is supposed to make the character speed up and then calling it in the statement
if (other.gameObject.tag == "SpeedUp")
It maybe because the engine is unable to calculate the exact moment the collision is taking place
if you just want the speed to be boosted when your object is in within the trigger bounds you can reverse the speed after your character leaves the collider.
private void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "SpeedUp")
{
horizVel *= 10f;
}
}
private void OnTriggerExit(Collider other)
{
if (other.gameObject.tag == "SpeedUp")
{
horizVel /= 10f;
}
}
Related
In my game I have a shooting enemy that I want to attack the player only when he's in range, and to attack him once every 3 seconds. This is the code I need repeated
Whatever I try doing just results in the enemy shooting a metric ton of bullets at the player to the point when it sometimes crashes my project(tried for loops, while loops, played with booleans and timing with Time.deltaTime). Can someone smarter than me give me some pointers how I could do this?
You'd probably want to do the check not every frame but whenever the state changes. So you cache the player.
Transform target;
private void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("Player"))
target = other.transform;
}
private void OnTriggerExit2D(Collider2D other)
{
if (other.CompareTag("Player"))
target = null;
}
Whenever target is not null, you may shoot to it.
private void Update()
{
if (target != null)
ShootAt(target.position);
}
private void ShootAt(Vector3 position)
{
// Spawn your stuff here to shoot at the given location
}
Now how about the timing? I am not a fan of coroutines in Unity. They tend to get messy, are overused and the "WaitForSeconds" is not very precise. It just waits until the time has passed, but not exactly. Maybe you want more precision, who knows?
So let's extend the Update call to have a small timer.
private float shootCooldown;
private void Update()
{
if (target != null)
{
shootCooldown -= Time.deltaTime;
if (shootCooldown <= 0.0f)
{
shootCooldown += 3.0f;
ShootAt(target.position);
}
}
}
One last thing: If you want the timer to reset at some point, just set it to 0.0f. A good location for that would be when the player leaves the trigger. Also, maybe you want the timer to continue to go down, even if the target is null, so move the shootCooldown -= Time.deltaTime line out of the if-scope but make sure it does not count down forever, but rests at 0.
I would avoid using OnTriggerStay2D() and use OnTriggerEnter2D and OnTriggerExit2D in this situation. You need to call the 'yield return' statement within a loop as shown below. The below code should do what you are asking.
private void OnTriggerEnter2D(Collider2D other) {
if (other.tag == "Player") {
StartCoroutine(ShootAtPlayer());
}
}
private void OnTriggerExit2D(Collider2D other) {
if (other.tag == "Player") {
StopAllCoroutines();
}
}
private IEnumerator ShootAtPlayer() {
while (true) {
Instantiate(enemyBullet, enemy.transform.localPosition, transform.LocalRotation);
yield return new WaitForSeconds(3.0f);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyControler : MonoBehaviour
{
private Transform player;
// Start is called before the first frame update
void Start()
{
//Note in this case we're saving the transform in a variable to work with later.
//We could save the player's game object (and just remove .transform from the below) but this is cleaner
var player = GameObject.FindWithTag("Player").transform;
}
// Update is called once per frame
void Update()
{
//**** The code below you need to modify and move into your own method and call that method from here.
//**** Don't overthink it. Look at how Start() is defined. Your own Move method would look the exact same.
//**** Experiment making it private void instead of public void. It should work the same.
//How much will we move by after 1 second? .5 meter
var step = .5f * Time.deltaTime; // 5 m/s
var newPosition = Vector3.MoveTowards(transform.position, player, step);//This is one way to move a game object by updating the transform. Watch the videos in this module and it should become apparent what goes here.
}
}
The transform does not map to the tagged player. I was trying to get an enemy in unity to move towards a moving player for a class, but I'm completely lost.
You had three issues in your code. The first was that you were defining a new player variable in the Start method. This hid the player member field you defined earlier.
The second issue was that you were getting a value to move towards, but you weren't assigning that value to the current objects position.
The third issue was that you were feeding in a Transform into the MoveTowards method, when you should have been feeding in a Vector2.
The code below should rectify these issues.
public class EnemyControler : MonoBehaviour
{
private Transform _player;
void Start()
{
_player = GameObject.FindWithTag("Player").transform;
}
void Update()
{
var step = .5f * Time.deltaTime;
transform.position = Vector3.MoveTowards(transform.position, _player.position, step);
}
}
Try doing something like this ive used this in the past to do something similar once you understand the code change it to what you desire
public int minRange;
private Transform target = null;
private void OnTriggerEnter(Collider other)
{
if (other.tag == "Player")
target = other.transform;
}
private void OnTriggerExit(Collider other)
{
if (other.tag == "Player")
target = null;
}
void update()
{
transform.LookAt(target);
float distance = Vector3.Distance(transform.position,
target.position);
bool tooClose = distance < minRange;
Vector3 direction = tooClose ? Vector3.back : Vector3.forward;
if (direction == Vector3.forward)
{
transform.Translate(direction * 6 * Time.deltaTime);
}
else
{
transform.Translate(direction * 3 * Time.deltaTime);
}
}
So, my jumping is inconsistent, basically, every time i press space the character jumps different heihgts, even when standing still (not moving left, and right), why is that?
here is my code:
public bool onGround = false;
public float JumpHeight;
public Rigidbody2D gravidade;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if (onGround == true)
{
if (Input.GetKey(KeyCode.Space))
{
gravidade.AddForce(new Vector2(0, JumpHeight * Time.deltaTime), ForceMode2D.Impulse);
}
}
}
void OnCollisionEnter2D(Collision2D outro)
{
if (outro.gameObject.CompareTag("ground"))
{
onGround = true;
}
}
void OnCollisionExit2D(Collision2D outro)
{
if (outro.gameObject.CompareTag("ground"))
{
onGround = false;
}
}
You shouldn't multiply by delta time when applying a one-time force.
Time.deltaTime is the time completion time in seconds since the last frame. Check the documetation
This time is not constant. MonoBehaviour.FixedUpdate uses fixedDeltaTime instead of deltaTime, this fixedDeltaTime and the FixedUpdate is usually used for physics and it is quite an advanced topic.
Use a constant value instead of the Time.deltaTime to have always the same jump height.
It is different becuase it's calculated off of deltaTime, which is incosistent (time between two rendered frames if i'm not wrong), and it yields a slightly different value every time, which combined with multiplication has a bigger deviation from an average.
Hope i helped to clarify this, cheers!
So, I'm still not the best at this but I'm trying to use this script for 2D movement but the jumping isn't working for some reason. It keeps saying that the OnCollisionEnter function "is declared but never used". Can someone tell me what im doing wrong? Thanks
If I remove the (Collision col) part it says that "void cannot be used in this context".
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RonyWalking : MonoBehaviour
{
Rigidbody2D rigid;
SpriteRenderer sprite;
public bool isJumping;
public float spd = 2.0f;
// Start is called before the first frame update
void Start()
{
rigid = GetComponent<Rigidbody2D>();
sprite = GetComponent<SpriteRenderer>();
}
// Update is called once per frame
void Update()
{
if(Input.GetKey("d")){rigid.velocity = new Vector2(spd, 0);}
else if(Input.GetKey("a")){rigid.velocity = new Vector2(-spd, 0);}
if(Input.GetKeyDown("w") && isJumping == false)
{
rigid.velocity = new Vector2(0, 5);
isJumping = true;
}
void OnCollisionStay(Collision col)
{
isJumping = false;
}
}
}
When using 2D physics, you need to use the 2D lifecycle methods;
void OnCollisionStay2D(Collision2D col)
{
isJumping = false;
}
And you shouldn't put this method inside your Update method... It should be on class level:
public class RonyWalking
{
void Update()
{
// ...
}
void OnCollisionStay2D(Collision2D col)
{
// ...
}
}
Don't worry about "Is declared but never used", this may be because you don't have specific code referencing the method, but Unity will raise events that calls it, "automagically"
Another thing that I can see while reading your code, that may be unintentional behaviour for you, is that when clicking left/right, you set velocity UP to 0, and when clicking up you set LEFT/RIGHT velocity to 0; this will result in freezing the movement mid-air if you jump, then move while in air:
Click D; velocity = 2, 0
Click W; velocity = 0, 5
Character will now move upwards until another input is given
Click D; velocity = 2, 0 and the character will continue moving while in air because when moving sideways the up/down velocity is set to 0
To solve this, either set the other to existing velocity or make the inputs manipulate a Vector that you then apply at the end of the movement code:
Vector2 existingMovement = rigid.velocity;
if (Input.GetKey(KeyCode.D))
existningMovement.x = spd;
else if (Input.GetKey(KeyCode.A))
existningMovement.x = -spd;
if (Input.GeyKeyDown(KeyCode.W) && !isJumping)
{
existningMovement.y = 5f;
isJumping = true;
}
Furthermore, I think you may have some unexpected behaviour with OnCollisionStay; it will fire every frame that you're colliding with the ground, I assume. But I think it may also fire a frame or two AFTER you've jumped since the physics of your character will not INSTANTLY leave the collision, so isJumping will be set to false even after your jump, letting you jump while in the air one more time.
I would recommend that you use OnCollisionExit2D(Collision2D col) to set isJumping = true instead, or OnCollisionEnter2D(Collision2D col) and set it to isJumping = false, depending on the functionality you desire (if you want the ability to jump after walking out of a cliff)
So, I've set up a basic script in Unity to move around a 2D sprite, and it works pretty well, except for the fact that occasionally the player-character will not jump when told to. It seems to only happen while or shortly after the character moves horizontally. I really have no idea why this is happening. Hopefully someone else can shed some light on this. Here is the controller script. Any feedback is helpful, even if it's unrelated to the question, I'm doing this as a learning exercise.
using UnityEngine;
using System.Collections;
public class PlayerControlsCs : MonoBehaviour {
public KeyCode walkLeft;
public KeyCode walkRight;
public KeyCode jumpUp;
public float speed = 5;
public float jumpForce = 750;
public int jumpCapacity = 1;
public int extraJumps = 0;
public bool facingRight = true;
public bool grounded = false;
private Transform groundCheck;
private Animator anim;
void Awake () {
groundCheck = transform.Find("GroundCheck");
anim = GetComponent<Animator>();
}
void Update () {
grounded = Physics2D.Linecast(transform.position, groundCheck.position, 1 << LayerMask.NameToLayer("Terrain"));
if(grounded){
anim.SetTrigger("Grounded");
anim.ResetTrigger("Falling");
extraJumps = jumpCapacity;
}
else {
anim.ResetTrigger("Grounded");
anim.SetTrigger("Falling");
}
}
void FixedUpdate () {
anim.SetFloat("Speed", Mathf.Abs(rigidbody2D.velocity.x));
anim.SetFloat("Ascent", rigidbody2D.velocity.y);
if(Input.GetKey(walkLeft))
{
if(facingRight){
Flip();
}
rigidbody2D.velocity = new Vector2(-speed, rigidbody2D.velocity.y);
}
else if(Input.GetKey(walkRight))
{
if(!facingRight){
Flip();
}
rigidbody2D.velocity = new Vector2(speed, rigidbody2D.velocity.y);
}
else
{
rigidbody2D.velocity = new Vector2(0, rigidbody2D.velocity.y);
}
if(Input.GetKeyDown(jumpUp) && grounded)
{
anim.SetTrigger("Jump");
rigidbody2D.velocity = new Vector2(rigidbody2D.velocity.x, 0);
rigidbody2D.AddForce(new Vector2(0f, jumpForce));
}
else if(Input.GetKeyDown(jumpUp) && extraJumps > 0)
{
anim.SetTrigger("Jump");
rigidbody2D.velocity = new Vector2(rigidbody2D.velocity.x, 0);
rigidbody2D.AddForce(new Vector2(0f, jumpForce));
extraJumps -= 1;
}
}
void Flip ()
{
// Switch the way the player is labelled as facing.
facingRight = !facingRight;
// Multiply the player's x local scale by -1.
Vector3 theScale = transform.localScale;
theScale.x *= -1;
transform.localScale = theScale;
}
}
If it helps at all, here is what I have made:
https://www.dropbox.com/s/ka4vgc0s0205sbd/test.html
https://www.dropbox.com/s/40i8kltwfz1jgyu/test.unity3d
Building on Max's answer...
You should use FixedUpdate() for physics stuff like applying a force to a RigidBody as it runs 50 times a second regardless of how fast the game is running. This makes it frame rate independent.
See the documentation.
Update() runs once per frame, so is frame rate dependent. In here is where most of your non-physics stuff should go, checking for inputs for example.
This video is a good explanation of the difference.
The link in the comment is also correct:
You need to call this function from the Update function, since the
state gets reset each frame
So check if is grounded only when the player presses jump as ray/linecasts are computationally expensive, apply the physics in FixedUpdate(), and check for input in Update().
Update and FixedUpdate aren't guaranteed to happen every time one after another. I haven't ran into this kind of bugs, so I can't say for sure, but you may experience a situation where your grounded state is incorrect. Instead of saving this value as a field, try checking for it every time you need it — at least a separate check in Update and FixedUpdate.
Input should be handeled in Update, because update runs every frame, while fixed update isn't like update and it doesn't run every frame so when input is handeled in fixed update it might miss the input and it won't jump !
I suggest you cut and paste all the input code from fixed update to update !