I have an NPC that my player can talk to when the players collider is colliding with the NPC, I do that using this piece of code:
private void OnTriggerStay2D(Collider2D other)
{
if (other.gameObject.tag == "InteractiveArea")
{
if (Input.GetKeyDown(KeyCode.E))
{
Debug.Log("PRESSED NPC");
CreateAndShowDialog();
}
}
}
However, this gets called really randomly, sometimes the first time I press "E", sometimes the second or third time, etc.
My rigidbodies:
The colliders I use are standard BoxCollider2D, my players collider is a trigger, the NPCs is not.
Why are some key press not detected in the OnTriggerStay function?
OnTriggerStay2D is called randomly. This is why you should never check for Input inside of it.
Set to a flag to true and false in the OnTriggerEnter2D and OnTriggerExit2D functions then check for that flag and input in the Update function which is called every frame. Also, always use CompareTag instead of other.gameObject.tag to compare tags.
private void Update()
{
if (Input.GetKeyDown(KeyCode.E) && triggerStay)
{
//
}
}
bool triggerStay = false;
void OnTriggerEnter2D(Collider2D collision)
{
Debug.Log("Entered");
if (collision.gameObject.CompareTag("InteractiveArea"))
{
triggerStay = true;
}
}
void OnTriggerExit2D(Collider2D collision)
{
Debug.Log("Exited");
if (collision.gameObject.CompareTag("InteractiveArea"))
{
triggerStay = false;
}
}
My guess would be because the value returned by Input.GetKeyDown is only true for the single (Update()) frame that the key was pressed, whereas physics (including OnTriggerStay) are called during physics frames, i.e. FixedUpdate()
Doing a bit of research I pull up this question which suggests:
I've been struggling with this issue all afternoon!
My OnTriggerStay and Update/FixedUpdate methods were not in sync resulting is split-second undesired effects.
I finally found the solution when I read in the OnTriggerStay docs that this function can be a co-routine. I simply added a WaitForFixedUpdate in the correct location in my OnTriggerStay and it worked. They now both run in sync with each other. This even corrects the issue in Update.
Another question has this as a solution (Programmer's answer):
OnTriggerStay will not call on every frame. One way to get around this is to have OnTriggerEnter, and OnTriggerExit set a bool. Then execute your code in the FixedUpdate().
While the documentation no longer says what this post says,
OnTriggerStay gets called every FixedUpdate.
"Note: OnTriggerStay function is on the physics timer so it wont necessary run every frame. "
That confirms my guess and that the documentation was changed to no longer include this note, for some unexplained reason.
You can do this :
bool eIsOnclick;
float wait = 0.03f;
float nextFire = 0.0f;
void Update() {
if(Input.GetKeyDown(keyKode.E))
eIsOnclick = true;
else if(nextFire < Time.time)
nextFire = Time.time + wait;
ebas = false;
}
void OnTriggerStay2D(Collider2D collision) {
if(eIsOnclick == true) {
// your cods
}
// not work in alwaays aquestions
Related
I got a simple "OnTouch" script on my enemies, which knocks back the player if they come in contact. The player then gets invincible for a short time. Something like this:
void OnTriggerEnter2D(Collider2D collider) {
if (collider.gameObject.CompareTag("Player")) {
if (Time.time > isInvincible) {
isInvincible = Time.time + invincibleTimer;
if (enemy.IsFacingRight) {
player.SetVelocity(knockback * Vector2.right);
} else {
player.SetVelocity(knockback * Vector2.left);
}
}
}
}
(SetVelocity is just a method i use to set.. velocity)
The problem with this is when the player gets invincible after been pushed away. While invincible you can then run on top of an enemy and stay there, even after the invincible timer runs out. Which i guess makes sense since it only triggers on enter.
Using the same code but inside a "OnTriggerStay2D", works as expected. You get pushed away, go invincible, run on top of an enemy, invincible runs out and you then get pushed away out of the enemy.
But having multiple enemies running around with OnTriggerStay colliding with different objects feels like it would be bad performance wise? Is there any more efficient way to do this? Or is TriggerStay the way to go?
The way I found is manually tracking the collisions like this:
List<Collider2D> hitColliders = new List<Collider2D>();
void OnTriggerEnter2D(Collider2D collision) {
if (hitColliders.Contains(collision)) { return; }
hitColliders.Add(collision);
}
void OnTriggerExit2D(Collider2D collision) {
hitColliders.Remove(collision);
}
// Perform operations to the colliders.
void Update() {
foreach (var col in hitColliders) { DoStuff(col); }
}
Although you may not run into performance problems even with the solution you have now, if you do in the future you can try using Physics.IgnoreLayerCollision:
At the start of your invincibleTimer call:
IgnoreLayerCollision(playerLayer, enemyLayer, true);
And at the end of your timer call:
IgnoreLayerCollision(playerLayer, enemyLayer, false);
And acording to the docs:
IgnoreLayerCollision will reset the trigger state of affected
colliders, so you might receive OnTriggerExit and OnTriggerEnter
messages in response to calling this.
This means that when you call IgnoreLayerCollision(false), OnTriggerEnter will be called again, even if you are already on top of an enemy. This is exactly the behaviour you are after.
I am making a multiplayer game using Unet. Currently, the players can move independently (i.e., if user one presses d, then character one will move right if user two presses d then character two will move right) - however, both players shoot 'through' the host.
My movement code is inside the PlayerUnit script. It has one !isLocalPlayer check at the top, in the Update() method. This check is performed before the script detects for user inputs (i.e., w, s, mouse1, etc.).
This all works fine when the correct key is pressed; the proper action is performed.
Eg:
if (Input.GetKeyDown(up))
Jump();
Shooting works in a slightly different way as it involves a different script.
When mouse1 is pressed, the Shoot() method in PlayerUnit is called. This, in turn, uses a Command inside the PlayerUnit script to call a ClientRpc in a separate WeaponMaster script, which calls the Fire() coroutine.
In PlayerUnit:
private void Shoot ()
{
//WeaponMaster.wM.Shoot();
CmdShoot();
}
[Command]
void CmdShoot ()
{
WeaponMaster.wM.RpcShoot();
}
In WeaponMaster:
[ClientRpc]
public void RpcShoot ()
{
if (ammo != 0)
{
if (isAutomatic == true)
{
keepFiring = true;
StartCoroutine(Fire());
}
if (isAutomatic == false && noQuickFire == true)
StartCoroutine(Fire());
}
}
WeaponMaster has one !isLocalPlayer check as well, once again in the Update() method.
When debugging, I know that playerTwo's (client) Shoot() method is being called, but playerOne's RpcShoot is being executed. Everything works fine when playerOne shoots - hence the problem of both players shooting 'through' the host.
All the scripts are on the same object with one NetworkIdentity component.
I'm convinced the problem is just a missing authority check - but I've tried adding !isLocalPlayer tests in the various methods to no avail.
As said my guess is that a Singleton pattern in WeaponMaster.wM is not a good approach here since you get the wrong reference and since everything goes through the server might simply always get the same reference no matter who called CmdShoot.
You should rather store each players own WeaponMaster reference and go through it like in PlayerUnit
// Already reference this via the inspector (drag&drop)
[SerializeField] private WeaponMaster weaponMaster;
// or as fallback get it on runtime
private void Awake()
{
if(!weaponMaster) weaponMaster = GetComponent<WeaponMaster>();
}
public void Shoot ()
{
if(!isLocalPlayer) return;
CmdShoot();
}
[Command]
private void CmdShoot ()
{
RpcShoot();
}
// Personally in general I would leave any RPC
// as close to the method calling it
// as possible for better maintainability
[ClientRpc]
private void RpcShoot ()
{
weaponMaster.Shoot();
}
and accordingly in WeaponMaster
public void Shoot()
{
if (ammo == 0) return;
if (isAutomatic)
{
keepFiring = true;
StartCoroutine(Fire());
}
else if (!isAutomatic && noQuickFire)
StartCoroutine(Fire());
}
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");
}
}
I have an object that moves towards another object and physically collides with it, I want that collision/colliding event to happen only once. I tried using a bool but it didn't work as intended. It seems that I'm doing something wrong.
bool doDamage = true;
void OnCollisionEnter2D(Collision2D other)
{
if (other.gameObject.tag == "Target" && doDamage)
{
doDamage = false;
// damage code
}
}
void OnCollisionExit2D(Collision2D other)
{
if (other.gameObject.tag == "Target")
{
doDamage = true;
}
}
Edit:
I want the "damage code" to run only once, even if the 2 objects are still in contact. This script is only assigned to 1 object, not both.
I don't know the easiest way to explain why your code is not working but I will try.
You have two GameObjects:
GameObject A with doDamage variable.
GameObject B with doDamage variable.
When GameObject A collides with GameObject B:
A.The OnCollisionEnter2D function is called on GameObject A.
if(other.gameObject.tag == "Target" && doDamage) executes because doDamage is true.
B.The doDamage variable from GameObject A is then set to false.
This does not affect the doDamage variable from GameObject B.
Then
C.The OnCollisionEnter2D function is called on GameObject B.
if(other.gameObject.tag == "Target" && doDamage) executes because doDamage is true.
D.The doDamage variable from GameObject B is then set to false.
Both your damage code will run because doDamage is always true in each OnCollisionEnter2D call. What you are currently doing is only affecting doDamage variable in each individual script.
What you are currently doing:
Setting doDamage in the local/this script to false while also checking if local/this doDamage is set or not.
What you need to do:
Set doDamage in the other script to false but read the local/this doDamage to check if it is set or not.
This is what it should look like:
public class DamageStatus : MonoBehaviour
{
bool detectedBefore = false;
void OnCollisionEnter2D(Collision2D other)
{
if (other.gameObject.CompareTag("Target"))
{
//Exit if we have already done some damage
if (detectedBefore)
{
return;
}
//Set the other detectedBefore variable to true
DamageStatus dmStat = other.gameObject.GetComponent<DamageStatus>();
if (dmStat)
{
dmStat.detectedBefore = true;
}
// Put damage/or code to run once below
}
}
void OnCollisionExit2D(Collision2D other)
{
if (other.gameObject.tag == "Target")
{
//Reset on exit?
detectedBefore = false;
}
}
}
If you want to run a snippet of code once, run the code directly in the callback event that you want to trigger your code.
void OnCollisionEnter2D(Collision2D other)
{
DoSomeDamageTo(other.gameObject);
}
This should only trigger once upon collision.
If you want this to only ever happen once (e.g. if the object holding the script were to hit something again), you'll need a flag to say that the damage has already been done:
bool hasHitSomething = false;
void OnCollisionEnter2D(Collision2D other)
{
if (!hasHitSomething)
{
DoSomeDamageTo(other.gameObject);
hasHitSomething = true;
}
}
I suspect given the code you've shared, that either the objects are colliding multiple times in engine (very possible if they're solid and physics-controlled), leading to OnCollisionEnter being called multiple times. OnCollisionEnter should only be called once per collision. If you are observing it being called multiple times, either you are actually observing multiple collisions, or you've found an obscure Unity bug. The former is much, much more likely.
I have a problem with my script at Unity. I apply this script to my character and nothing happens when it reaches the collider, no HP is taken.
public int HP = 100;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}
void LoseHP(Collider col)
{
if(col.tag == "trap")
{
HP -= 100;
}
}
I'm not an expert of Unity but I'd use the method OnCollisionEnter that allows you to know if your object has begun to touch another rigidbody/collider. Here the link
Or another solution could be that you check in the Update() method if your object has been touched from another rigidbody. If the statement is true, call your LoaseHP(...) method.
also i'd consider using FixedUpdate() method if you want to increment a players health on being hit. Update() method may be called to often if you're not going to use the OnCollisionEnter method