I want my character to jump only when he's standing on the ground. Despite I wrote the collision function he doesn't jump when I press the key. What is the problem?
void OnCollisionEnter2D(Collision2D col)
{
if (col.collider.tag == "groundTag")
{
if (Input.GetKeyDown(KeyCode.Space))
{
rb2d.AddForce(new Vector2(rb2d.velocity.x, Jumpforce));
}
}
OnCollisionEnter2D will only run when it collides with the ground(single frame). probably you need to create a bool for this condition. this may not be the best option. make it true when it collides with ground and make it false when it exits the ground. Write your code in the update function then.
bool _canJump;
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
rb2d.AddForce(new Vector2(rb2d.velocity.x, Jumpforce));
}
}
void OnCollisionEnter2D(Collision2D col)
{
if (col.collider.tag == "groundTag")
{
_canJump = true;
}
}
void OnCollisionExit2D(Collision2D col)
{
if (col.collider.tag == "groundTag")
{
_canJump = true;
}
}
You are triggering your action only when you enter the ground trigger (the function you are implementing is OnCollisionEnter2D). Your function only works if you are pressing the space key on the same frame the collider collides with a groundTag object.
You could try with OnCollisionStay2D or use a CharacterController; that should make implementing the character a bit easier.
Related
I am trying to shoot a projectile and damage all of the blocks in a spherical area.
Bullet object has a fitting size box collider and a bigger shpere collider(isTrigger selected).
Here is my Bullet script for detecting objects in sphere collider area and detonating once it hits to a block.
`
List<GameObject> blocksInDistance;
private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("block_big"))
{
if (!blocksInDistance.Contains(other.gameObject))
{
blocksInDistance.Add(other.gameObject);
}
}
}
private void OnTriggerExit(Collider other)
{
if (other.CompareTag("block_big"))
{
if (blocksInDistance.Contains(other.gameObject))
{
blocksInDistance.Remove(other.gameObject);
}
}
}
private void OnCollisionEnter(Collision collision)
{
if (isActive)
{
if (collision.gameObject.CompareTag("block_big"))
{
Debug.Log("bullet collision detection");
explode();
deliverDamage();
rb.useGravity = true;
isActive = false;
}
}
}
void deliverDamage()
{
foreach (GameObject block in blocksInDistance)
{
block.GetComponent<Block>().takeDamage(damage);
}
}
`
Almost half of the time ontrigger enter falls behind and oncollision enter gets called first and makes bullet explode but since OnTriggerEnter was never called before explosion none of the blocks gets damaged. How can i give more priority to the Sphere Collider or fix it in a different way?
I am new to Unity2D. I am trying to make castle defense game. When the spawners start to inheritance the enemies overlap (they should), but when the archer arrow collides whit the enemies it kills them all. I searched everywhere for the answer of this but nothing...
My questions is:
Is there a way to only hit one target at time?
Arrow script:
void Start()
{
target = GameObject.FindGameObjectWithTag("Enemy").GetComponent<Transform>();
}
// Update is called once per frame
void Update()
{
transform.position = Vector2.MoveTowards(transform.position, target.position, speedProjectile * Time.deltaTime);
}
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.CompareTag("Enemy"))
{
Destroy(gameObject);
}
}
Enemy script:
void Start()
{
target = GameObject.FindGameObjectWithTag("target3").GetComponent<Transform>();
}
// Update is called once per frame
void Update()
{
transform.position = Vector2.MoveTowards(transform.position, target.position, speed * Time.deltaTime);
}
private void OnTriggerEnter2D(Collider2D col)
{
if (col.gameObject.CompareTag("arrow"))
{
EHealth -= HDamage;
}
if (EHealth <= 0)
{
Destroy(gameObject);
}
Your enemies are all taking damage because destroying a GameObject isn't something that happens immediately (for good reason). Because of this, in a single frame, any number of enemies can be hit by the same arrow.
If you'd like to rely on these collision methods, I'd suggest controlling the damage from the arrow instead of from the enemy, so you can be sure it only hits something once:
private bool dealtDamage = false;
private void OnTriggerEnter2D(Collider2D col) {
// Only deal damage once
if (dealtDamage) {
return;
}
// Does the thing I hit have this "EnemyScript" ?
var enemy = col.gameObject.GetComponentInChildren<EnemyScript>();
if (enemy == null) {
return;
}
// Make this "DealDamage()" method public on your EnemyScript
enemy.DealDamage();
dealtDamage = true;
Destroy(gameObject);
}
Then get rid of the Enemy Script's OnTriggerEnter2D because the arrow is handling it all. I don't know what the name of the script is on your enemies so I just called it EnemyScript. This is also calling a DealDamage() method that you'd have to make (which would probably look a lot like the one you current have listed in your "Enemy Script.")
its hard to say with the information given, based on what you have said it seems that all the entity's are just 1 entity(so when you kill 1 enemy you kill the only enemy which is all of them). you can have them run independently from each other.
Maybe you can use collision enter function to check bullet is hitting enemy's body.
You can use this code below...
private bool isEntered = false;
void OnCollisionEnter(Collision collision)
{
if(isEntered) return;
if(collision.gameObject.tag == "enemy") isEntered = true;
....
....
}
I hope it will work for you.
Finally figured it out. First of all the script is dealing damage so I should hit the target specific number of times, so I can't stop the collision of the arrow without turning it back on. So I made a method, that is being Invoked (InvokeRepeating()) after the arrow collides, that turns a bool back to false. Which method (function) of course should be in Update();
I am not sure if I am saying it right bc I am a beginner but I hope this helps somebody. :D
And here is the code:
Arrow Script:
private void DealDamage()
{
if (hit == true)
return;
eHealth.EHealth -= hDamage.HDamage;
hit = true;
}
void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.CompareTag("enemy"))
{
DealDamage();
Destroy(gameObject); //Destroying arrow on collision
}
}
private void HitSetter()
{
hit = false;
}
void Update()
{
InvokeRepeating("HitSetter", 0f, 1.1f);
}
Enemy Script:
void Update()
{
if (EHealth <= 0)
{
anim.SetBool("EnemyDie", true);
Destroy(gameObject, 0.833f);
}
Also thanks to #Foggzie
How to make the end of the game in pakman when all the dots have been eaten?
This is the end game code now
void OnTriggerEnter2D(Collider2D co)
{
if (co.name == "PacMan")
{
Destroy(co.gameObject);
EndMenu.SetActive(true);
GameObject.Find("EndGameConvas/EndGamePanel/Score").GetComponent<Text>().text = GameObject.Find("Canvas/Score").GetComponent<Text>().text;
Time.timeScale = 0;
}
}
This is the point eating code
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.name == "PacMan")
{
Destroy(gameObject);
GameObject.Find("Canvas/Score").GetComponent<Score>().ScoreChange(1);
}
}
If what you're asking is "how do I let the game know the level is over and trigger the end" then just have a variable to hold how many dots are in the level, and every time you eat one and that trigger collider fires, have a counter go up. When the counter equals the total, level ends.
In your class for the dots you could use something like
public class YourDotClass : MonoBehaviour
{
// Keeps track of all currently existing instances of this class
private readonly static HashSet<YourDotClass> _instances = new HashSet<YourDotClass>();
private void Awake ()
{
// Register yourself to the instances
_instances.Add(this);
}
private void OnDestroy ()
{
// Remove yourself from the instances
_instances.Remove(this);
// Check if any instance still exists
if(_instances.Count == 0)
{
// => The last dot was just destroyed!
EndMenu.SetActive(true);
GameObject.Find("EndGameConvas/EndGamePanel/Score").GetComponent<Text>().text = GameObject.Find("Canvas/Score").GetComponent<Text>().text;
Time.timeScale = 0;
}
}
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.name == "PacMan")
{
Destroy(gameObject);
GameObject.Find("Canvas/Score").GetComponent<Score>().ScoreChange(1);
}
}
}
However, you should really rethink/restructure your code and think about who shall be responsible for what.
Personally I would not like the dots be responsible for increasing scores and end the game .. I would rather have a component on the player itself, let it check the collision, increase its own points and eventually tell some manager to end the game as soon as all dots have been destroyed ;)
I think you could use something like this. Just store all of the pacdots in an array and once the array is empty you could end the game.
GameObject[] pacdots = GameObject.FindGameObjectsWithTag("pacdot");
void OnTriggerEnter2D(Collider2D collision)
{
// pacman collided with a dot
if (collision.name == "PacMan")
{
Destroy(gameObject);
GameObject.Find("Canvas/Score").GetComponent<Score>().ScoreChange(1);
if (pacdots.length == 0)
{
// All dots hit do something
}
}
}
(This is a 2D project)
I have a jumping issue where if my character WALKS into an X platform, she won't jump, but when she JUMPS ONTO the X platform, she can perform a jump.
For the platforms I am currently using 2 Box Collider 2Ds (one with "is trigger" checked)
For the character I am currently using 2 Box Collider 2Ds (one with "is trigger" checked) and Rigidbody 2D.
Below is the code for jumping and grounded I am currently trying to use.
{
public float Speed;
public float Jump;
bool grounded = false;
void Start()
{
}
void Update()
{
if (Input.GetKeyDown(KeyCode.UpArrow))
{
if (grounded)
{
GetComponent<Rigidbody2D>().velocity = new Vector2(GetComponent<Rigidbody2D>().velocity.x, Jump);
}
}
}
void OnTriggerEnter2D()
{
grounded = true;
}
void OnTriggerExit2D()
{
grounded = false;
}
}
Issue arises on the same part of every platform. (Each square represents a single platform sprite and they have all the same exact characteristics, since I copy pasted each one of them). Please check the photo on this link: https://imgur.com/a/vTmHw
It happens because your squares have seperate colliders. Imagine this:
There are two blocks : A and B. You are standing on block A. Now you try to walk on block B. As soon as your Rigidbody2D collider touches block B, your character gets an event OnTriggerEnter2D(...). Now you claim, that you are grounded.
However, at this moment you are still colliding with block A. As soon as your Rigidbody2D no longer collides with block A, your character receives OnTriggerExit2D(...). Now you claim, that you are no longer grounded. But in fact, you are still colliding with block B.
Solution
Instead of having bool variable for checking if grounded, you could have byte type variable, called collisionsCounter:
Once you enter a trigger - increase the counter.
Once you exit a trigger - decrease the counter.
Do some checking to make sure you are actually above the collider!
Now, once you need to check if your character is grounded, you can just use
if (collisionsCounter > 0)
{
// I am grounded, allow me to jump
}
EDIT
Actually, after investingating question further, I've realized that you have totally unnecessary colliders (I'm talking about the trigger ones). Remove those. Now you have only one collider per object. But to get the calls for collision, you need to change:
OnTriggerEnter2D(...) to OnCollisionEnter2D(Collision2D)
OnTriggerExit2D(...) to OnCollisionExit2D(Collision2D)
Final code
[RequireComponent(typeof(Rigidbody2D))]
public sealed class Character : MonoBehaviour
{
// A constant with tag name to prevent typos in code
private const string TagName_Platform = "Platform";
public float Speed;
public float Jump;
private Rigidbody2D myRigidbody;
private byte platformCollisions;
// Check if the player can jump
private bool CanJump
{
get { return platformCollisions > 0; }
}
// Called once the script is started
private void Start()
{
myRigidbody = GetComponent<Rigidbody2D>();
platformCollisions = 0;
}
// Called every frame
private void Update()
{
// // // // // // // // // // // // // //
// Need to check for horizontal movement
// // // // // // // // // // // // // //
// Trying to jump
if (Input.GetKeyDown(KeyDode.UpArrow) && CanJump == true)
Jump();
}
// Called once Rigidbody2D starts colliding with something
private void OnCollisionEnter2D(Collision2D collision)
{
if(collision.collider.tag == TagName_Platform)
platformCollisions++;
}
// Called once Rigidbody2D finishes colliding with something
private void OnCollisionExit2D(Collision2D collision)
{
if(collision.collider.tag == TagName_Platform)
platformCollisions--;
}
// Makes Character jump
private void Jump()
{
Vector2 velocity = myRigidbody.velocity;
velocity.y = Jump;
myRigidbody.velocity = velocity;
}
}
Here can be minor typos as all the code was typed inside Notepad...
I think there are a couple of issues here.
Firstly, using Triggers to check this type of collision is probably not the best way forward. I would suggested not using triggers, and instead using OnCollisionEnter2D(). Triggers just detect if the collision space of two objects has overlapped each other, whereas normal collisions collide against each otehr as if they were two solid objects. Seen as though you are detecting to see if you have landed on the floor, you don't want to fall through the floor like Triggers behave.
Second, I would suggest using AddForce instead of GetComponent<Rigidbody2D>().velocity.
Your final script could look like something like this:
public class PlayerController : MonoBehaviour
{
public float jumpForce = 10.0f;
public bool isGrounded;
Rigidbody2D rb;
void Start()
{
rb = GetComponent<Rigidbody2D>();
}
void OnCollisionEnter2D(Collision2D other)
{
// If we have collided with the platform
if (other.gameObject.tag == "YourPlatformTag")
{
// Then we must be on the ground
isGrounded = true;
}
}
void Update()
{
// If we press space and we are on the ground
if(Input.GetKeyDown(KeyCode.Space) && isGrounded)
{
// Add some force to our Rigidbody
rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
// We have jumped, we are no longer on the ground
isGrounded = false;
}
}
}
I have some code that when it executes, it pushes a character forward. The issue is that the character never stops moving and continues forever. Is there a way to stop the character from moving after 2 seconds? Here is the code I'm using:
public class meleeAttack : MonoBehaviour
{
public int speed = 500;
Collider storedOther;
bool isHit = false;
void Start()
{
}
void Update()
{
if (isHit == true )
{
storedOther.GetComponent<Rigidbody>().AddForce(transform.forward * speed);
}
}
void OnTriggerStay(Collider other)
{
if (other.gameObject.tag == "Player" && Input.GetKeyUp(KeyCode.F))
{
storedOther = other;
isHit = true;
}
}
}
I'm not sure if there's a way to stop the update() function so it stops the character movement.
The Update function is part of the life cycle of a Unity script. If you want to stop the Update function to be executed, you need to deactivate the script. For this, you can simply use:
enabled = false;
This will deactivate the execution of the life cycle of your script, and so prevent the Update function to be called.
Now, it looks like you are applying a force to your character to make it move. What you might want to do then is, after two seconds, remove any force present on your character. For that, you can use a coroutine, which is a function that will not just be executed on one frame, but on several frames if needed. For that, you create a function which returns an IEnumerator parameter, and you call the coroutine with the StartCoroutine method:
bool forcedApplied = false;
void Update()
{
if (isHit == true && forceApplied == false)
{
storedOther.GetComponent<Rigidbody>().AddForce(transform.forward * speed);
forceApplied = true;
StartCoroutine(StopCharacter);
isHit = false;
}
}
IEnumerator StopCharacter()
{
yield return new WaitForSeconds(2);
storedOther.GetComponent<Rigidbody>().velocity = Vector3.zero;
forceApplied = false;
}
Those might be different ways to achieve what you want to do. It's up to you to choose what seems relevant to your current gameplay and to modify your script this way.