I'm making a Balloon Fight style game and I'm having trouble with object collision. Each character has two balloons on top of his head and each balloon has an on trigger Box Collider. I want to make it so only one balloon can be hit at a time so you can't destroy both balloons at the same time. In order to do this I added a boolean called isAttacking to prevent it from destroying more than one balloon at the same time.
Hello, I'm making a Balloon Fight style game and I'm having trouble with object collision. Each character has two balloons on top of his head and each balloon has an on trigger Box Collider. I want to make it so only one balloon can be hit at a time so you can't destroy both balloons at the same time. In order to do this I added a boolean called isAttacking to prevent it from destroying more than one balloon at the same time.
public bool isAttacking = false;
private void OnTriggerEnter(Collider collision)
{
if (collision.GetComponent<Collider>().gameObject.layer == 7 && collision.GetComponent<Collider>().gameObject.tag != this.gameObject.tag)
{
if (!isAttacking)
{
Destroy(collision.GetComponent<Collider>().transform.parent.gameObject);
transform.parent.gameObject.GetComponent<Jump>().jump = true;
isAttacking = true;
}
}
}
void LateUpdate()
{
if (isAttacking)
{
isAttacking = false;
}
}
While it does prevent two collisions from registering I still found this solution to be insufficient, since the balloon that is destroyed is not necessarily the one closest to the character destroying it. How could I improve the collision code in order for it to only register the collision happening closer to the character?
Within one frame afaik there is no reliable order of OnTriggerEnter calls (it is somewhat based on the instanceID of objects but that won't really help you).
What you could do instead would be comparing distances, somewhat like e.g.
private readonly HashSet<GameObject> hittingObjects = new();
[SerializeField] private Jump jump;
private void Awake()
{
if(!jump) jump = GetComponentInParent<Jump>(true);
}
private void OnTriggerEnter(Collider collision)
{
if (collision.layer == 7 && !collision.CompareTag(gameObject.tag))
{
hittingObjects.Add(collision.transform.parent.gameObject);
}
}
private void LateUpdate()
{
if (hittingObjects.Count > 0)
{
var closestHit = hittingObjects.OrderBy(hit => (transform.posiion - hit.transform.position).sqrMagnitude).First();
Destroy(closestHit);
jump.jump = true;
hittingObjects.Clear();
}
}
Note: This still doesn't prevent this object from colliding with the other balloon in the very next physics update. If you wanted to track this as well you could make it slightly more complex and only allow collisions if you are newly entering the trigger => You have to exit the object again before you can hit it again.
Somewhat like maybe
private readonly HashSet<GameObject> hastoExitFirstObjects = new();
private readonly HashSet<GameObject> newHittingObjects = new();
[SerializeField] private Jump jump;
private void Awake()
{
if(!jump) jump = GetComponentInParent<Jump>(true);
}
private void OnTriggerEnter(Collider collision)
{
if (collision.layer == 7 && !collision.CompareTag(gameObject.tag))
{
var hit = collision.transform.parent.gameObject;
if(!hastoExitFirstObjects.Contains(hit))
{
newHittingObjects.Add();
}
}
}
private void OnTriggerExit(Collider collision)
{
if (collision.layer == 7 && !collision.CompareTag(gameObject.tag))
{
var hit = collision.transform.parent.gameObject;
hastoExitFirstObjects.Remove(hit);
}
}
private void LateUpdate()
{
if (newHittingObjects.Count > 0)
{
var closestHit = newHittingObjects.OrderBy(hit => (transform.posiion - hit.transform.position).sqrMagnitude).First();
newHittingObjects.Remove(closestHit);
Destroy(closestHit);
jump.jump = true;
foreach(var hit in newHittingObjects)
{
hastoExitFirstObjects.Add(hit);
}
newHittingObjects.Clear();
}
}
First, check if your OnTriggerEnter is executing for both balloons. It probably is?
Then in the moment OnTriggerEnter executes, compare the position of collision with the position of your balloons and see which one is the closest, then destroy the closest balloon and maybe set a variable isInvulnerable as true, so that if it is true, no balloon can be destroyed inside OnTriggerEnter.
I'd just enable only one balloons box collider at once, and when it gets popped enable the other balloon after 1 second delay.
Related
I am creating a game in unity where the player can drag one object (a card) over another (an enemy). If they drag the card over the enemy I want to run some code, and if not I want to return the card to its initial position. I placed a collider on both objects but it isnt working the way it should. I am assuming this is because the object is getting moved back to its initial location before the physics engine sees the collision, but I don't know how to fix this. I am deactivating the collider on the card while moving it to avoid having it trigger collisions until the player has placed it using the onmousedown and onmouseup events. Any tips on how to fix this behavior? can I force the physics engine to check collisions with the onmouseup event?
I know the collisions are working because when I turn off the return to initial position behavior the game functions as expected.
How about useing the collider as trigger and do not use rigidbodys or anything.
Then if there is a trigger enter event set bool as true. If there trigger exit reset the bool to false.
Now if you "Release" the card check if bool is true or false.
true: Set the cards position to the player or what you want
false: Reset the cards position to the start
Now beeing a little mroe fancy you can set a lighted border around the card when bool is active (just check in update)
Example:
public class Card : MonoBehaviour {
private bool isHolding;
private bool isHovering;
public Vector3 startPos;
public void Start() {
startPos = transform.position;
}
public void Update() {
// Code where you check if the card is Clicked and Moved by the player
// If so set isHolding = true
// dont enter the check if holding blablabla when animation stuff is happening
if (doAnimationStuff) {
// do animation
// Destroy Object
return;
}
// Code to check if isHolding == false
if (!isHolding) {
if (!isHovering) {
transform.position = startPos;
} else {
doAnimationStuff = true;
}
}
}
private void OnTriggerEnter(Collider other) {
// Check if other is a player
// if so set isHovering = true
}
private void OnTriggerExit(Collider other) {
// Check if other is a player
// if so set isHovering = false
}
}
so here is the scenario,
i want the player to be able to perform a trick, and then be able to hold the trick for a longer duration if they want to, but if they don't hold the input then they instead just continue the animation until completion as i haven't actually tried implementing too much and its not giving me the desired result i figured i'd just ask if people have already done it so i don't spend the next 2 hours down a rabbit hole, any and all help is appreciated thanks! :D
(Unity Script)
{
[Header("Trick Attributes")]
public string GroundTagName; // Tag used for all the platforms
public KeyCode stuntKey;
public float AnimationFreezeTime = 0.75f;
public SpriteAnimator anim; // Put the Sprite animator here
public Animator spriteAnimator; // Put the Animator here
private bool isGrounded;
public bool stunting = false;
private void Start()
{
}
private void Update()
{
if (Input.GetKeyDown(stuntKey) && !isGrounded)
{
anim.StartAnimation("FlyingSquirrel");
stunting = true;
Invoke("FreezeAnimation", AnimationFreezeTime);
}
if (Input.GetKeyUp(stuntKey))
{
stunting = false;
spriteAnimator.speed = 1;
}
}
void FreezeAnimation() {
spriteAnimator.speed = 0;
}
private void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.tag == GroundTagName)
{
isGrounded = true;
}
else
{
isGrounded = false;
}
if (stunting && collision.gameObject.tag == GroundTagName)
{
Destroy(gameObject);
}
}
private void OnCollisionExit(Collision collision)
{
isGrounded = false;
}
}
FlyingSquirrel needs to be broken up into 3 different animations. This can be done in Maya, Blender, uMotion, or even Unity's animation importer:
You need 3 Animator states:
Start_FlyingSquirrel // Player moves into position
Idle_FlyingSquirrel // Player is "holding" the trick
End_FlyingSquirrel // Player let go, return to normal
When the player presses button to start the trick, play the Start animation. For the transition, use "has exit time" to proceed to the Idle State. The Idle state will be true (use a boolean Animator parameter) while the trick is "held". Uncheck "has exit time" for the transition to the End state- you want this to happen instantly.
Some part of the code works (hp1 -= damage1;), but the second part doesn't work.
Where did I make a mistake?
Here are parts of two scripts:
Player1.cs
private Bullet1 b1;
void Start()
{
b1 = FindObjectOfType<Bullet1>();
}
void OnCollisionEnter2D(Collision2D col)
{
if (col.gameObject.tag == "Bullet1")
{
hp1 -= damage1; // it works, my player loses hp
Destroy1(); // doesn't work
}
}
void Destroy1()
{
b1.hit1 = true; // hit for bullet1.cs
}
Bullet1.cs
public bool hit1;
void Update()
{
if (hit1)
{
hit1 = false;
Destroy(gameObject);
}
}
If I switch bool hit = true in real time in Unity, destroying works. It means that Bullet1.cs can't recieve hit = true;
If I swap lines hp1 -= damage1; and Destroy1();, my player can't get damage. So, Destroy1(); stops my code and then can't activate other lines. Also if I change Destroy1(); to b1.hit1 = true; nothing new happens.
Just destroy the bullet through the collision instead of using the boolean. It's creating unnecessary resource usage. Of the given code, this is all you need to do to destroy the bullet using the player script. The parts of the bullet script shown are unnecessary. If you want additional logic handled inside the bullet when destroyed then use a OnDestroy function to handle it.
void OnCollisionEnter2D(Collision2D col)
{
if (col.gameObject.tag == "Bullet1")
{
hp1 -= damage1;
Destroy(col.gameobject);
}
}
Hi everyone I have a little problem with my bow shooting script. I mean i want to shoot an arrow when play the one of last frames from my animation. I try to do it by setting an firePoint GameObject, put it by recording in my Animation Tab in desired frame. It's of course disabled but its enabled when animation plays and then its again disabled. So the problem is:
- When i hit button which match my Shooting input, the animation plays,
- My Instantiate appears and it produces multiple arrows,
- When its disabled it stops to produce arrows.
I want to produce only one arrow. Could anyone help?
CombatScript.cs:
/* private bool shootBow;
* public bool needReload = false;
* public float reloadTime = 1.5f;
* public float realoadCD;
*/
public void RangeAttack()
{
if (needReload == false && gameObject.GetComponent<PlayerControls>().grounded == true && Input.GetButtonDown("Ranged"))
{
animator.SetTrigger("shootBow");
attack1 = false; // Melle attacks sets to false in case of lag or smth.
attack2 = false;
attack3 = false;
needReload = true;
if (needReload == true)
{
reloadCD = reloadTime;
}
}
if (reloadCD > 0 && needReload == true)
{
reloadCD -= Time.deltaTime;
}
if (reloadCD <= 0)
{
reloadCD = 0;
needReload = false;
}
if (firePoint.gameObject.activeSelf == true)
{
Instantiate(Missile, new Vector3(firePoint.position.x + 1, firePoint.position.y), firePoint.rotation);
Debug.Log("It's a bird, a plane, no.. it's arrow.");
}
}
Arrow Controller.cs:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ArrowController : MonoBehaviour {
public float speed;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update ()
{
GetComponent<Rigidbody2D>().velocity = new Vector2(speed, GetComponent<Rigidbody2D>().velocity.y);
}
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.tag == "Enemy")
{
Destroy(collision.gameObject);
}
Debug.Log("Arrow Broke");
Debug.Log(gameObject.name);
//Destroy(gameObject);
}
public void OnCollisionEnter2D(Collision2D collision)
{
}
}
Example of my situation:
Example of true/false needReload statement:
in right Inspector you have my Player informations, in left (or bottom) Inspector you have Missile (Arrow) inspector
You can use Animation Events and for example a boolean that is your firerate.
And you Code can look something like this:
float LastShoot;
float FireRate = 10;
public void OnShoot()
{
if (LastShoot <= 0)
{
//Shoot your arrow
LastShoot = FireRate;
}
}
void Update()
{
LastShoot -= 0.5f;
}
But i don't know if this is the best solution to calculate a firerate. If someone knows a better one feel free and edit my awnser :)
Okey... couple of hours later (which I wasted...). The answer was ridiculously easy. For future "problems" like that... the thing was to create in my script function called "ProduceArrow()"and (additionaly to what i did) something like Animation Event it's in Animation Tab, when you create your animation timeline you just need to call it clicking right mouse button and then choose it and pick proper function.
Some feedback - gif
The way your code works right now, Instatiate will be called every frame. So one button click, which is probably longer than one frame, will trigger multiple arrows, so you need to set a condition for when you want Instantiate to be called. You already calculate a reload time, which is exactly what you need. You just need to include it in your if-statement like follows:
if (firePoint.gameObject.activeSelf == true && !needReload)
{
Instantiate(Missile, new Vector3(firePoint.position.x + 1, firePoint.position.y), firePoint.rotation);
Debug.Log("It's a bird, a plane, no.. it's arrow.");
}
I am working on a tutorial and the "Side objectives" that they don't walk you through to try and get a feel for it.
So, the way things work at this time is that there is the Player object. The player object has the player script.
public class Player : MonoBehaviour {
private Animator anim;//reference for animator component
private Rigidbody rigidBody;//reference to component for rigidbody
private AudioSource audioSource;
[SerializeField] private float force = 100f;
[SerializeField] private AudioClip sfxJump;
[SerializeField] private AudioClip sfxDeath;
}
void Awake() {//these are assertions that will ensure when writing the cocde that you wont miss them. use for team work.
Assert.IsNotNull (sfxJump);
Assert.IsNotNull (sfxDeath);
}
private bool jump = false; //check for jump
// Use this for initialization
void Start () {//all these are getting components at the start to update them as the code goes onwards.
anim = GetComponent<Animator> ();
rigidBody = GetComponent<Rigidbody> ();
audioSource = GetComponent<AudioSource> ();
positionStart = GetComponent<Transform> ();
}
// Update is called once per frame
void Update () {
if (!GameManager.instance.GameOver && GameManager.instance.GameStarted) {
if (Input.GetMouseButtonDown (0)) {//if press mouse key
GameManager.instance.PlayerStarted ();
rigidBody.useGravity = true;//turn gravity on for component so it goes back down.
audioSource.PlayOneShot (sfxJump);
anim.Play ("jump");//play the animation jump
jump = true;
}
}
}
//Fixed update for physics
void FixedUpdate() {//use this for any physics due to frame rate. time.deltatime wont cut it.
if (jump == true) {//if we are jumping, turn the jump off.
jump = false;
rigidBody.velocity = new Vector2 (0, 0);//turn velocity to 0 so speed doesnt increase while falling
rigidBody.AddForce (new Vector2 (0, force), ForceMode.Impulse);//give a impulse upwards.
}
//print (rigidBody.velocity.y);//print velocity. turn this shit off.
}
//Code to create collision with obstacles and then die and fall through the floor.
void OnCollisionEnter (Collision collision) {//call collision component
if (collision.gameObject.tag == "obstacle") {//if you slap a tagged object called obstacle
rigidBody.AddForce (new Vector2 (-50, 20), ForceMode.Impulse);//add force to push back cause you ded
rigidBody.detectCollisions = false;//turn off the ability to detect collisions
audioSource.PlayOneShot (sfxDeath);//play ded noise
GameManager.instance.PlayerCollided ();
GameManager.instance.Restart ();
}
}
}
The game manager, of course exists in the camera to control the states of the game.
public static GameManager instance = null;//only one in memory. only one gamemanager ever.
[SerializeField] private GameObject mainMenu;
[SerializeField] private GameObject replayBtn;
[SerializeField] private GameObject playBtn;
private bool gameEnd = false;
private bool gameStarted = false;
private bool playerActive = false;
private bool gameOver = false;
//getters setters start
public bool PlayerActive {
get { return playerActive; }
}
public bool GameOver {
get { return gameOver; }
}
public bool GameStarted {
get { return gameStarted; }
}
//to create a state between gameover and main menu
public bool GameEnd {
get {return gameEnd; }
}
//getter setters end
void Awake(){
if (instance == null) {
instance = this;//this means the current instance. one instance of this class.
} else if (instance != this) {//if a seocnd one gets created destroy that bitch.
Destroy (gameObject);
}
DontDestroyOnLoad (gameObject);//allows a game object to persist between the scene. Dont need with one scene.
}
// Use this for initialization
void Start () {
replayBtn.SetActive (false);
}
// Update is called once per frame
void Update () {
}
public void PlayerCollided(){
gameOver = true;
}
public void PlayerStarted(){
playerActive = true;
}
public void EnterGame (){
mainMenu.SetActive(false);
gameStarted = true;
}
//When player dies start coroutine Hold.
public void Restart (){
StartCoroutine (Holdexit());
}
//The hole Coroutine waits 2 seconds then turns on the menu.
IEnumerator Holdexit (){
yield return new WaitForSeconds (2);
playBtn.SetActive (false);
replayBtn.SetActive (true);
mainMenu.SetActive (true);
//add character movement to location
}
}
So, When the player hits the object he dies, loses the ability to touch colliders and falls through the map, after 2 seconds the main menu comes back and the play button is replaced with a replay button. When I press replay, I need to reset the position, the state of the game, and the ability to collide.
I Tried all kinds of things. I did a get component for transform and tried to call it in the coroutine and then set it there, but I couldnt figure it out. I tried just changing the position after etc after the game managers state to restart gets called but the position change occurs before the main menu comes back on because its not being used in the coroutine.
Once thing I though would work, is i created a new method,
public void PlayerReset (){
if (GameManager.instance.Restart()){
//put new changes to player here.
}
}
The errors I came across here was I could not convert type void to bool, I assume its cause I was trying to say if the restart instance existed then function, but the way the restart function is created isn't true or false its just - is.
I really appreciate any help. I think what im going to try and do is make another script to the side and have it call the class of player to pull the components, and then manipulate them from there maybe. AUGH. So confusing. Lmao.
You can just set a public Transform variable on your object, and then in the inspector, drag the selected transform you want to call to that variable.
When that's done, you can use that transform variable in any way you want.
You can save the start position of the player in GameManager as it's a singleton. So, you set the position of the player to this saved position after restart.
You'll need to reinitialize all the variables(i.e. gameEnd,gameStarted, playerActive, gameOver etc.) on restart.
if(GameManager.instance.Restart()) will not work, as Restart() returns void not a boolean.