I'm creating a game in which there are enemies, I want to have headshots in the game so I have 2 colliders: one to the head and one to the body. I can't find any good way to tell which is which in the code.
I thought of a solution but I don't like it- a different type of collider to the head, and different type to the body (like polygon and box colliders). It works but I don't think it's good enough (if I want to add more colliders or have two of the same type that wouldn't work).
virtual protected void OnTriggerEnter2D(Collider2D collider2D)
{
if (collider2D.gameObject.tag.Equals("Zombie"))
{
Destroy(gameObject);//destroy bullet
Zombie zombie = collider2D.gameObject.GetComponent<Zombie>();
if (collider2D is BoxCollider2D)
zombie.HeadShot(demage);//headshot
else zombie.BulletHit(demage);//normal hit
}
}
I want a way to tag the colliders somehow so I can tell between them.
You need to create public variables of type BoxCollider2D and assign your colliders. When a collision occurs call an IF statement inside of OnTriggerEnter to see which one has collided. This will work no matter if there are more of the same types of collider.
public class Example : MonoBehaviour
{
public BoxCollider2D box01;
public BoxCollider2D box02;
private void OnTriggerEnter2D(Collider2D collision)
{
if(collision.IsTouching(box01))
{
Debug.Log("1");
}
else if(collision.IsTouching(box02))
{
Debug.Log("2");
}
}
}
isTouching is a Unity method which returns a bool depending on a collider that is comparing.
I would suggest to not add all colliders on the same GameObject but rather give each collider it's own child GameObject (this way you also can see easily which colliders belongs to which outline in the scene view ;) )
Then you could use a class with an enum to define which type of collider you have there:
public class BodyPart : MonoBehaviour
{
public BodyPartType Type;
}
public enum BodyPartType
{
Head,
LeftArm,
RightArm,
Body,
LeftLeg,
RightLeg
}
and attach it to all body parts next to each collider.
Then you could do something like
virtual protected void OnTriggerEnter2D(Collider2D collider2D)
{
if (collider2D.gameObject.tag.Equals("Zombie"))
{
Destroy(gameObject);//destroy bullet
// Note you then should use GetComponentInParent here
// since your colliders are now on child objects
Zombie zombie = collider2D.gameObject.GetComponentInParent<Zombie>();
var bodyPart = collider2D.GetComponent<BodyPart>();
switch(bodyPart.Type)
{
case BodyPartType.Head:
zombie.HeadShot(demage);//headshot
break;
// you now could differ between more types here
default:
zombie.BulletHit(demage);//normal hit
break;
}
}
}
Related
I'm looking for a way to obtain data from an object I have "selected/targeted" in game. Example: I want to select an enemy to attack and then take the hp value from the script attatched to them and compare it to the attack value on the player script. I've looked around on google and youtube but have found no luck. Hoping someone could help me or point me to a guide to look at.
Example in code form:
Script 1:
public class Enemy : MonoBehaviour
{
public int enemyHealth;
}
Script 2:
public class Player : MonoBehaviour
{
public Enemy enemy;
public int playerAtk;
}
public void Attack()
{
"Selected enemy's enemy script".health -= playerAtk;
}
You must access the enemy through a specific event. For example, when hitting a bullet or clicking the mouse on it. Below are some examples of how to get the enemy, but keep in mind that there are countless ways to access the enemy.
On Trigger Enter
This is a very simple way, This code works in the player class. The collider key gives you access to the enemy, now you can access the methods by holding its component.
public void OnTriggerEnter(Collider other)
{
var _enemy = other.GetComponent<Enemy>(); // // Get Enemy class
_enemy?.Damage(10); // damage method run.
_enemy?.Damage(this, 10f); // Send player to method and cause damage
}
Physics Raycast
This will happen by throwing a ray from the camera, what the raycast code does is return the point of impact in raycastHit format. After getting it, you can access your other raycastHit components as shown below.
public void Update()
{
var ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (!Input.GetKeyDown(KeyCode.Mouse0)) return;
if (Physics.Raycast(ray, out var RaycastHit))
{
var _enemy = RaycastHit.transform.GetComponent<Enemy>();
_enemy?.Damage(10);
}
}
Specify in the Inspector
In this method, you can put all the enemies in an array and damage them by calling the index.
public Enemy[] myEnemy;
public void Damage(int index)
{
myEnemy[index].Damage(10);
}
Find By Type
Another popular method is to capture all enemies and filter them based on a specific condition. This method is also performed in this way.
var enemies = FindObjectsOfType<Enemy>();
foreach (var _enemy in enemies.Where(e => e.health >= 40f))
{
_enemy.Damage(100);
}
I have a Player script. I have the variables on there
public class Player : MonoBehaviour
{
float horimove;
Rigidbody rb;
public float speed;
public GameObject thrown;
public bool win;
On my other script (I'm making a game where you instantiate spheres into a hole and win.)
public class spheresciprt : MonoBehaviour
{
public Player pscript;
public GameObject player;
// Start is called before the first frame update
void Start()
{
player = GameObject.Find("Player");
pscript = player.GetComponent<Player>();
}
private void OnTriggerEnter(Collider other)
{
if (gameObject.CompareTag("win"))
{
pscript.win = true;
}
}
However pscript.win doesn't change. The bool always stays false but I want the player to win after the sphere hits the trigger in the hole I want it to go to. I tried different computers. The same thing happened in different games I think so I'm probably just missing something.
First of all, make sure that the collision is detected. You can easily do something like
void OnTriggerEnter()
{
Debug.Log("Pass1");
if (gameObject.CompareTag("win"))
{
Debug.Log("Hey, it worked!");
}
}
in the OnTriggerEnter to check that.
If the collision is NOT detected, make sure there are colliders on all the affected GameObjects, as well as a RigidBody component on the sphere. Also, make sure that the collider is marked as Trigger. If you still have an issue with that, we will need to see your inspector to figure it out.
If the collision IS detected... Well, start by making sure that you have assigned the proper tag on the hole. Then, make sure that the player GameObject is actually called "Player" (since you are trying to find it by name) and that it has the script you are trying to get on the object you actually find (and not on one of its children). I hope that works, because I can't see anything else seriously flawed at your code.
Have you tried to change
if (gameObject.CompareTag("win"))
to
if (collider.gameObject.CompareTag("win"))
or just add the rigidbody to both player and other object.
does the other object have "win" tag?
Here is your script:
public class spheresciprt : MonoBehaviour
{
public Player pscript;
public GameObject player;
// Start is called before the first frame update
void Start()
{
player = GameObject.Find("Player");
pscript = player.GetComponent<Player>();
}
private void OnTriggerEnter(Collider other)
{
if (gameObject.CompareTag("win"))
{
pscript.win = true;
}
}
}
It sets pscript to the current values in Player script. This is the problem, because it is setting a variable to the current values, and you want access to the component. You could change this by, just replace all of the pscript variables with player.GetComponent<Player>(). There are other ways of doing this. You could, at the end of OnTriggerEnter, add this:
void OnTriggerEnter(Collider other)
{
...
player.GetComponent<Player>() = pscript;
}
In both of these solutions you fix it. In one, you re-apply the variable. In the other, you are changing the component directly. Which is what you want, because you have pscript as an instance of player script, not the player script that is attached to player.
In my scene, there are multiple objects with the same layer and tag. I have a variable that will be set to the gameobject for one of these objects. I would like to do something when I collide with the object that is set to the variable.
My current code is:
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.otherCollider == targetFood)
{
Debug.Log("I am touching food");
}
}
I've tried a few different methods of checking if it is touching the food, but none have worked. Help would be appreciated!
If you get the game object from the collision, you should be able to determine if the target food has been touched.
using UnityEngine;
public class Example : MonoBehaviour
{
void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject == targetFood)
{
Debug.Log("I am touching food");
}
}
}
This is assuming that targetFood is a variable that is of type GameObject.
I need to make an item in my scene clickable but only when the player is near the item. In my script I make the item in question go automatically to an empty GameObject that is child of my Player in the hierarchy to define the position but the click is able as soon as the camera have it framed. I'm using the character controller provided in the 2d physics and not a 2drigidbody so I'm even more confused because I can't use a collider.
I'm pretty sure you can have both a character controller and a collider on a gameobject (at least a trigger collider).
Then instead of whatever you're using to detect the click, you should use in an Update loop something like Input.GetKeyDown(KeyCode.Mouse0), and use a raycast where you can specify the length of the ray. https://docs.unity3d.com/ScriptReference/Physics.Raycast.html
In order to make anything clickable I would recommend IPointerXHandler interfaces (replace X with Click, Enter, Exit, Down, Up, etc).
Note:
Ensure an EventSystem exists in the Scene to allow click detection. For click detection on non-UI GameObjects, ensure a PhysicsRaycaster is attached to the Camera.
If you only want to click IPointerClickHandler is enough. If you want some visiual feedback like changing colors etc you'll have to expand it with at least IPointerEnterHandler and IPointerExitHandler.
public class MyClickable : MonoBehaviour, IPointerClickHandler
{
public void OnPointerClick(PointerEventData pointerEventData)
{
...
}
}
Then in order to get the distance between two obects you can simply use Vector3.Distance e.g. with a ceratin threshold
// configure those e.g. in the Inspector
public float distanceThreshold;
public Transofrm playerTransform;
public Transform itemTransform;
and than use something like
if(Vector3.Distance(playerTransform.position, itemTransform.position) <= distanceThreshold)
{
...
}
so if you directly implement this into the MyClickable you could do visual feedback also in Update something like
public class MyClickable : MonoBehaviour, IPointerClickHandler
{
public float distanceThreshold;
public Transofrm playerTransform;
// this gives you an event you can configure in the Inspector
// exactly like you would with a button
public UnityEvent onClick;
private bool isInRange;
public void OnPointerClick(PointerEventData pointerEventData)
{
// if too far away do nothing
if(Vector3.Distance(playerTransform.position, transform.position) > distanceThreshold) return;
....
onClick.Invoke();
}
private void Update()
{
if(Vector3.Distance(playerTransform.position, transform.position) <= distanceThreshold)
{
// e.g. make object green
}
else
{
// e.g. make object grey
}
}
}
My suggestion is to use the onMouseDown() method. If you have a collider or trigger attached to your gameObject, onMouseDown() will detect mouse clicks on the object. Then in then body of onMouseDown() you can test if it is in range.
This is an example script how it could work:
public class ItemClickable : MonoBehaviour
{
public Transform player; // player-transform reference (depends if you have a singleton or not)
public float range; // radius (maybe you have a range or radius already set in your player instance)
void Start()
{
// Setup for your references
}
private void OnMouseDown()
{
// Checks if the item is in the range of the player
if ((player.position-gameObject.transform.position).magnitude < range) // Vector3.Distance() is also possible
{
Destroy(gameObject); // or do whatever you want in here
}
}
}
I couldn't come up with a suitable title for this question but I'll explain.
I have a collision box, which holds a script. This script has an if statement that detects collision from object "Cube001" and sends a Debug.Log to console.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class cubeDetect : MonoBehaviour {
void OnCollisionEnter(Collision collision) {
if (collision.gameObject.name == "Cube001")
{
Debug.Log("Cube001 hit!");
}
}
}
With this method, the box collider knows what cube has touched it, as I have instructed so with
collision.gameObject.name == "Cube001"
But say if I have 10 cubes colliding with the collision box, how can I change the if statement so instead of writing another 9 if statements that check if it touches the collision box, I can just have 1 if statement that just first detects a collision from another cube, knows what cube hit the box, and with this knowledge, is able to do a Debug.Log to display the name of the cube that hit the box.
I've tried going through the documentation for OnCollisionEnter but couldn't find anything to help with this.
One option would be to tag all of your similar objects you want to collide with, with the same name. Say we give them the tag "Cubicle". Then we can do the following:
Gameobject myCube;
void OnCollisionEnter(Collision collision) {
if (collision.collider.tag == "Cubicle")
{
Debug.Log(collision.gameObject.name + " hit!");
myCube = collision.gameObject; // store colliding gameobject info for use elsewhere
}
}
You need to use Dictionary. This eliminates the need for all the if statements.
This is what the Dictionary should look like:
public static Dictionary<GameObject, System.Action> objToAction = new Dictionary<GameObject, Action>();
Then a function to add objects to the dictionary when they are instantiated or in the Start function if they already exist
public void registerObject(GameObject obj, System.Action action)
{
objToAction.Add(obj, action);
}
The key in the Dictionary is the GameObject(Cube), you can also use string(name of the GameObject) but using the GameObject is better. The value in the Dictionary stores what you want to to do when OnCollisionEnter is called. So the code that should've been inside that if statement should be placed here. This is done with Action and delegate. You can add as many GameObjects (Cubes) as you wish.
You must add those Cube Objects to the Dictionary with the function above:
public GameObject prefab;
void Start()
{
//Create new Cube
GameObject obj = Instantiate(prefab);
obj.name = "Cube001";
//Add it to the Dictionary
registerObject(obj, delegate { Debug.Log("Hit: " + obj.name); });
}
Your new OnCollisionEnter code is as below. No if statement required. If the Object exist in the Dictionary, execute that code we stored in the value of that Dictionary for each key GameObject.
void OnCollisionEnter(Collision collision)
{
Action action;
if (objToAction.TryGetValue(collision.gameObject, out action))
{
//Execute the approprite code
action();
}
}
Note that the objToAction variabel should either be made static or placed in another script attached to an empty GameObject so that you can access it. There should only be one instance of it.
What was working best for me was to use interfaces and components and check for that. This is working great if you have certain logic on collision but when you don't you can just use tag and set it to something like "collidable".
interfaces solution:
public interface ICollidableObject
{
void CollidedWith(ICollidableObject other);
}
public class CollidableBlock : MonoBehaviour, ICollidableObject
{
public void CollidedWith(ICollidableObject other)
{
Debug.Log((other as MonoBehaviour).gameObject.name);
}
}
public class CollidableSphere : MonoBehaviour, ICollidableObject
{
public void CollidedWith(ICollidableObject other)
{
Debug.Log("sphere001");
}
}
And just use it like such :
void OnCollisionEnter(Collision collision)
{
ICollidableObject collidedWith = collision.gameObject.GetComponent<ICollidableObject>();
if ( collidedWith != null )
collidedWith.CollidedWith(this);
}