(source: ibin.co)
I have a BoxCollider2D component and a script attached to the black and red boxes. Each script with some things to happen in a OnMouseDown() function. The problem is if I click on the part of the orange box that overlaps the black one, the OnMouseDown() of the black box will be called but I want only the orange box function to be called.
How do you achieve this?
I don't think OnMouseDown is a great approach here. You can use 2D RayCast to only get the top most collider. You are going to have to account for sorting layer and sorting order on your own with this approach.
The trick to checking a single point in 2D is to not give the raycast direction as shown below with:
Physics2D.Raycast(touchPostion, Vector2.zero);
Here is an example I threw together that doesn't account for using sorting layers, just sorting order.
using UnityEngine;
public class RayCastMultiple : MonoBehaviour
{
//Current active camera
public Camera MainCamera;
// Use this for initialization
void Start ()
{
if(MainCamera == null)
Debug.LogError("No MainCamera");
}
// Update is called once per frame
void FixedUpdate ()
{
if(Input.GetMouseButtonDown(0))
{
var mouseSelection = CheckForObjectUnderMouse();
if(mouseSelection == null)
Debug.Log("nothing selected by mouse");
else
Debug.Log(mouseSelection.gameObject);
}
}
private GameObject CheckForObjectUnderMouse()
{
Vector2 touchPostion = MainCamera.ScreenToWorldPoint(Input.mousePosition);
RaycastHit2D[] allCollidersAtTouchPosition = Physics2D.RaycastAll(touchPostion, Vector2.zero);
SpriteRenderer closest = null; //Cache closest sprite reneder so we can assess sorting order
foreach(RaycastHit2D hit in allCollidersAtTouchPosition)
{
if(closest == null) // if there is no closest assigned, this must be the closest
{
closest = hit.collider.gameObject.GetComponent<SpriteRenderer>();
continue;
}
var hitSprite = hit.collider.gameObject.GetComponent<SpriteRenderer>();
if(hitSprite == null)
continue; //If the object has no sprite go on to the next hitobject
if(hitSprite.sortingOrder > closest.sortingOrder)
closest = hitSprite;
}
return closest != null ? closest.gameObject : null;
}
}
This is pretty much just a duplicate of my answer here: Game Dev Question
Related
I have two gameobjects in my game. I want to make one of them after ball collides with that in red color. And another in white color. Right now they both become red after collide with my projectile(ball). How to share this ?
This sounds like you are basically asking "How to handle a collision only on one of the colliding parties?"
Collisions happen in the physics update routine so basically after FixedUpdate.
See Execution Order of event Messages where you can see that FixedUpdate is always executed before all OnCollisionXY are handled.
So what you can do is store a reference in the first OnCollisionEnter to the other object and then ignore the second one if the object is the se you already collided with.
Then simply reset the field in the next FixedUpdate call.
Something like e.g.
public class CollisionDetection : MonoBehaviour
{
public Color color1 = Color.red;
public Color color2 = Color.white;
private GameObject ignoreCollisionWith
void OnCollisionEnter(Collision col)
{
if(col.gameObject == ignoreCollisionWith) return;
if(col.gameObject.TryGetComponent<CollisionDetection>(out var other))
{
// Set the color for both involved objects
GetComponent<Renderer>(). material.color = color1;
other.GetComponent<Renderer>(). material.color = color2;
// Tell the other object to do nothing for this collision with you
other.ignoreCollisionWith = gameObject;
}
}
void FixedUpdate()
{
ignoreCollisionWith = null;
}
}
I'm very new to C# and Unity and am working on a simple platformer for a school project. I'm currently trying to get a piece of group that does damage over time to the player. To detect whether the player is on this piece of ground, I thought I could use the same code as I did for my ground check, but with altered variable names etc. However the ground check code works, and the detection for the damage over time doesn't and I have no idea why.
void CheckIfGrounded()
{
Collider2D collider = Physics2D.OverlapCircle(isGroundedChecker.position, checkGroundRadius, groundLayer);
if (collider != null)
{
isGrounded = true;
}
else
{
if (isGrounded)
{
lastTimeGrounded = Time.time;
}
isGrounded = false;
}
}
void CheckIfDOT()
{
Collider2D collider = Physics2D.OverlapCircle(isDOT.position, checkDOT_AreaRadius, DOT_AreaLayer);
if (collider != null)
{
damageOverTime = true;
}
else
{
damageOverTime = false;
}
}
The first thing is that you need to know how Physics2D.OverlapCircle works.
As in unity documentation, it says :
Checks if a Collider falls within a circular area.
The circle is defined by its centre coordinate in world space and by its radius.
It means that from one point, which is position, they will draw a circle have radius like the radius you use and if there is any collider is in that range, it will result in the collider2d, like your example.
You need to check :
Where is that point : In the player or in the obstacles ?
Is the circle big enough for collider to fall within ? ( depends on radius )
Try to use Debug.Log() to show if the function is executed normally
I'm trying to detect when the mouse clicks on a specific tile on a tile map. The game I'm making is a overhead 2d style, and I want to trigger code when a specific resource node (tile) is clicked on. I've tried to use RaycastHit2D:
public class DetectClickOnTile : MonoBehaviour
{
Vector2 worldPoint;
RaycastHit2D hit;
// Update is called once per frame
void Update()
{
worldPoint = Camera.main.ScreenToWorldPoint(Input.mousePosition);
if (Input.GetMouseButtonDown(0))
{
hit = Physics2D.Raycast(worldPoint, Vector2.down);
if (hit.collider != null)
{
Debug.Log("click on " + hit.collider.name);
Debug.Log(hit.point);
}
}
}
But it only detects collision with the tilemap itself and not any of the individual tiles. Does anyone know why that is? For reference this is the map im trying to detect collision on, only the black tiles are filled in with placeholders, the rest of the tilemap is left blank for now.
Afaik you can get the tile at the given world hit point using something like e.g.
// Reference this vis the Inspector
[SerializeField] TileMap tileMap;
...
hit = Physics2D.Raycast(worldPoint, Vector2.down);
if (hit.collider != null)
{
Debug.Log("click on " + hit.collider.name);
Debug.Log(hit.point);
var tpos = tileMap.WorldToCell(hit.point);
// Try to get a tile from cell position
var tile = tileMap.GetTile(tpos);
...
}
However, I'm pretty sure your Raycast is not what you want to use but rather directly try and get the tile from the worldPoint and if you got one you hit one ;)
Your Raycast goes down meaning you cast from the click point to the bottom of the screen which doesn't sound like what you are trying to achieve.
So probably rather something like
if (Input.GetMouseButtonDown(0))
{
worldPoint = Camera.main.ScreenToWorldPoint(Input.mousePosition);
var tpos = tileMap.WorldToCell(worldPoint);
// Try to get a tile from cell position
var tile = tileMap.GetTile(tpos);
if(tile)
{
...
}
}
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 trying to find how I can check if 4 of the given gameObjects are in a list.
So I have a list with gameObjects. Sometimes a gameObject gets out of the list, sometimes it gets in. I want to check if 4 of the same colors are in a list. In this case, "childTiles" is the list.
I tried it by doing this:
void Update()
{
foreach (GameObject tile in ChildTiles)
{
if (tile.gameObject.name == "tileGreen1" && tile.gameObject.name == "tileGreen2" && tile.gameObject.name == "tileGreen3" && tile.gameObject.name == "tileGreen4")
{
gameManager.finalGreenComplete = true;
Debug.Log (gameManager.finalGreenComplete);
}
}
}
This returns nothing. It does work if I check only 1 object. I also can't use ChildTiles.Contains() I think, because that is only possible with 1 gameObject if I'm right. So how do I check this with multiple gameObjects?
EDIT: A bit more information.
I have 4 different colors, each color has 4 tiles. Everytime the player clicks a button, the parent will rotate with the tiles as child. The childs are automatically child of a specific parent. The parents are the colliders (in the image below the white gameobjects are the colliders. They normally have no sprite renderer) which is behind the button that is pressed. The grey circles are the buttons.
The following script is attached to all the white colliders. The public void Squareclicked is attached to each button, but it is in the same script.
public List<GameObject> ChildTiles = new List<GameObject>();
public void SquareClicked()
{
if (parentPositions.canTurn == true && gameManager.turns > 0f)
{
gameManager.turns -= 1;
gameManager.turnsText.text = "Turns: " + gameManager.turns;
foreach(GameObject go in ParentPositions)
{
parentPositions = go.GetComponent<TileController> ();
parentPositions.canTurn = false;
}
foreach(GameObject tile in ChildTiles)
{
tile.transform.parent = gameObject.transform;
}
StartCoroutine(Rotate()); //this is where you do the rotation of the parent object.
if (gameManager.turns == 0f)
{
gameManager.turnsText.text = "No turns left";
}
}
}
void OnTriggerEnter2D(Collider2D col)
{
if (col.gameObject.tag == "Tile")
{
ChildTiles.Add (col.gameObject);
}
}
void OnTriggerExit2D(Collider2D col)
{
if (col.gameObject.tag == "Tile")
{
ChildTiles.Remove (col.gameObject);
}
}
Only the top-left, the top-right, the bottom-left and the bottom-right colliders are needed to check wheter is has four of the same color in it's list. I can either make a new script and attach that to those colliders (gameObjects) or I can make it so that the script only works for those.
The conditional will certainly never be true, since for a given tile, tile.gameObject.name has some given string value, and you are checking that every tile has each of the four names.
If I understand the question correctly, you want to check that within ChildTiles, there is some GameObject whose .gameObject.name is "tileGreen1", one that is "tileGreen2", and so on. If so, and if performance is not critical, you could check write the condition as follows:
var names = ChildTiles.Select(tile => tile.gameObject.name);
var shouldBeContained = new List<string> { "tileGreen1", "tileGreen2", "tileGreen3", "tileGreen4" };
bool condition = shouldBeContained.All(names.Contains);
In answer to your request to explain how to use enums for colors here:
I don't have unity, so I can't guarantee this will work, but anyway.
First, define your enum:
public enum Color
{
Black,
Green,
Blue,
Yellow,
///...
}
Next inherit a Tile object from GameObject (or whatever you want to use as a base class) and add a color property to it. Use these instead of GameObject as the Type of the ChildTiles.
public class Tile : GameObject
{
public Color Color { get; private set; }
public Tile(Color color) :base()
{
Color = color;
}
}
Then your Update function should look something like this:
void Update()
{
if (ChildTiles.Count(a => a.Color == Color.Green)>=4)
{
gameManager.finalGreenComplete = true;
Debug.Log(gameManager.finalGreenComplete);
}
}