I have a gameobject called "Target" which contains the animator controller, I have a capsule collider on a gameObject in the tree for Target called Robot. The current script I have is attached to a gun object and is as follows and destroys the object and it disappears when the raycast collides with the collider, and prints out the tag and name of the collider it hit(just for testing purposes):
RaycastHit hit;
if (Physics.Raycast (fpsCam.transform.position, fpsCam.transform.forward, out hit, range)) {
if (hit.transform.tag == "Target") {
print(hit.transform.gameObject.name);
print(hit.transform.gameObject.tag);
hit.transform.gameObject.GetComponent<Animator>().SetBool("isHit", true);
Destroy(hit.transform.gameObject);
target.getTargets = GameObject.FindGameObjectsWithTag ("Target");
target.targetCount = target.getTargets.Length -1;
target.countText.text = (target.targetCount ).ToString ();
}
GameObject impact = Instantiate (impactEffect, hit.point, Quaternion.LookRotation (hit.normal));
Destroy (impact, 1f);
}
}
However when i active the animator controller the raycast no longer seems to hit the collider at all, the name nor tag is printed out. I have a isHit variable in the animator that is supposed to play a death animation when set to true and then disappear as it should when no animator is present but I'm not sure how to access the animator on the main object from the child object that has the collider.
It is hard to say why your code does not work or print out the values you are looking for.
Try debugging it and see where it ends up.
However your question on how to access the animator on the main object. Add root to your line.
hit.transform.root.GetComponentInChildren<Animator>().SetBool("isHit", true);
I removed the ".gameObject" because it is not needed. Also GetComponentInChildren will find the first occurence of Animator starting at the root.
Related
I'm currently working on a unity project where I'm testing out how to create a projectile trajectory calculation method, which'll be applied to my overall larger Unity project. Inside of this sub-project is simply a ground plane, a square target GameObject, and my AI Enemy GameObject. For reference, here's what the hierarchy of my enemy GameObject looks like:
Enemy (capsule)
GunPivot (empty, is an anchor to rotate the Gun GameObject)
Gun (square)
BulletSpawn (empty, is the spawn point for any instantiated bullet)
Now how my bullet works inside of this scene is that my bullet has a rigidbody, and moves through the scene using the Unity's physics engine, and my gun GameObject has the script that spawns, aligns, and shoots the bullet forward. It just uses AddForce() and a FirePower variable, which can be set in the unity editor. Below is what the code looks like:
public class EnemyGunController : MonoBehaviour
{
//
// Properties & Fields
//
public GameObject BulletPrefab;
public GameObject BulletSpawn;
public float FireRate;
public float FirePower;
public bool CanFire;
//
// Method(s)
//
void Update()
{
StartCoroutine(Fire());
}
// Fires a bullet.
IEnumerator Fire()
{
if (CanFire)
{
CanFire = false;
// Instantiate a bullet. Set the rotation to be looking forwards relative to BulletSpawn
GameObject bullet = Instantiate(BulletPrefab, BulletSpawn.transform.position,
Quaternion.LookRotation(BulletSpawn.transform.up));
// Set bullet so that it ignores collisions with the gun.
Physics.IgnoreCollision(bullet.GetComponent<Collider>(), GetComponent<Collider>());
// Apply force to the bullet.
bullet.GetComponent<Rigidbody>().AddForce(BulletSpawn.transform.forward * FirePower, ForceMode.Impulse);
// Wait for the fire rate before firing again.
yield return new WaitForSeconds(FireRate);
CanFire = true;
}
}
}
Now here's where the issue now lies. I want my Enemy GameObject to hold a method that'll calculate at what rotational value GunPivot needs to be at in order for the Bullet to hit the target GameObject. I already know that Quaternion.LookAt() will rotate the gun to where the target is, but that only solves half of the issue. That rotates the gun horizontally to where the gun is (y-axis I believe), but now I need to check at which rotational value GunPivot needs to be at so that the bullet, which is a rigidbody and has non-zero values for its mass and drag, will hit the target GameObject.
I did look into this a bit and found out the Physics.Simulate() method and how I can calculate this in a separate scene, however when I ran the unity editor, it just froze and then crashed. I attached it to a separate GameManager GameObject, and here's the code for it below:
public class BulletPredictor : MonoBehaviour
{
//
// Properties & Fields
//
public GameObject BulletPrefab;
Scene _mainScene;
Scene _bulletPredictorScene;
PhysicsScene _mainPhysicsScene;
PhysicsScene _bulletPredictorPhysicsScene;
//
// Method(s)
//
void Start()
{
// Disable auto simulation.
Physics.autoSimulation = false;
// Set the main scene and main physics scene fields.
_mainScene = SceneManager.GetSceneByName("MainScene");
_mainPhysicsScene = _mainScene.GetPhysicsScene();
// Set the bullet predictor scene and bullet predictor physics scene fields.
CreateSceneParameters bulletPredictorSceneParameters = new(LocalPhysicsMode.Physics3D);
_bulletPredictorScene = SceneManager.CreateScene("BulletPredictorScene", bulletPredictorSceneParameters);
_bulletPredictorPhysicsScene = _bulletPredictorScene.GetPhysicsScene();
}
// FixedUpdate is called once per frame (fixed at 50 fps).
void FixedUpdate()
{
// Main scene physics need to work, so simulate main scene normally. Needs to be within a fixed update because variable fps can effect the physics simulation.
if (_mainPhysicsScene.IsValid())
{
_mainPhysicsScene.Simulate(Time.fixedDeltaTime);
}
}
// Simulates various shot trajectories to find an optimal shooting angle to hit target, then rotates enemy's gun in main scene to that angle.
public Quaternion ShootBullet(GameObject shooter, GameObject target, Vector3 shooterPos, Vector3 targetPos)
{
// Checks if the main or bullet predictor physics scene is valid. If it isn't just return a default rotation.
if (!_mainPhysicsScene.IsValid() || !_bulletPredictorPhysicsScene.IsValid())
return Quaternion.identity;
// Clone shooter, and move it to the bullet predictor scene.
GameObject shooterClone = Instantiate(shooter, shooterPos, Quaternion.identity);
SceneManager.MoveGameObjectToScene(shooterClone, _bulletPredictorScene);
// Also clone target, and move it to the bullet predictor scene.
GameObject targetClone = Instantiate(target, targetPos, Quaternion.identity);
SceneManager.MoveGameObjectToScene(targetClone, _bulletPredictorScene);
// Get a reference to shooterClone's GunPivot gameobject.
GameObject gunPivot = shooterClone.transform.Find("GunPivot").gameObject;
// Rotate gunPivot to face targetClone.
gunPivot.transform.LookAt(targetClone.transform);
while (gunPivot.transform.rotation.x > -90)
{
// Instantiate a bullet, and move it to the bullet predictor scene. Set the rotation to be the shooterClone's BulletSpawn's rotation.
GameObject bullet = Instantiate(BulletPrefab, gunPivot.transform.position, Quaternion.LookRotation(gunPivot.transform.up));
// Add force to the bullet.
bullet.GetComponent<Rigidbody>().AddForce(gunPivot.transform.forward * bullet.GetComponent<BulletBehavior>().InitialSpeed, ForceMode.Impulse);
// Loop for 200 iterations, and simulate the bullet.
for (int i = 0; i < 200; i++)
{
// Simulate bullet predictor scene.
_bulletPredictorPhysicsScene.Simulate(Time.fixedDeltaTime);
}
// if the bullet is destroyed, break.
if (bullet == null)
{
break;
}
Destroy(bullet);
gunPivot.transform.Rotate(-0.1f, 0, 0);
}
return gunPivot.transform.rotation;
}
}
For debugging purposes, I just added a variable that referenced the GunPivot on my Gun GameObject, and called the ShootBullet() before I actually started the Fire() method, but my unity editor ended up crashing every single time I ran with this code. I'm not entirely sure why this didn't work, although I believe that this happened since I was using Simulate() in a numerical calculation, which probably is VERY VERY computationally expensive.
I'd really appreciate any insight or tips on how to either improve the simulate code I have above or perhaps try out a different approach from it what I'm doing. Also FYI, you can assume that the target isn't moving, so that might make things easier I suppose?
Trying to figure out how to destroy an object by clicking on it. I have tried using
public void Destroy()
{
if (Input.GetMouseButton(0))
Destroy(GameObject);
}
but realise that this will destroy all gameobjects the script is attached to instead of the one I am clicking on.
Try this one:
public void OnMouseDown() => Destroy(gameObject);
Definitely the best way is to implement IPointerClick interface,
using UnityEngine;
using UnityEngine.EventSystems;
public class ClickDestroy : MonoBehaviour, IPointerClickHandler
{
public void OnPointerClick(PointerEventData eventData)
{
GameObject.Destroy(gameObject);
}
}
This will work both for UI and for 3D Objects, as long as your camera has PhysicsRaycaster and an EventSystem exists on scene
You can get the mouse position on the screen and fire a raycast from that location. If it hits a gameobject then you can pass it to a function to delete it.
void Update {
//Check for mouse click
if (Input.GetMouseButton(0))
{
//Create a ray from mouse location
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit = new RaycastHit();
//Check if the ray hit a gameobject, gameobject will likely need a collider
//for this to work
if (Physics.Raycast(ray, out hit))
{
//If the ray hit a gameobject, destroy the gameobject
Destroy(hit.gameobject)
}
}
}
When destroying gameobjects, make sure your specifying the gameobject component and not the script or another component of the gameobject.
This code can be improved by defining a camera object in your code and assigning a camera to it in the inspector instead of it defaulting to the main or setting up full player controls in unity (Which I can't remember how to do off the top of my head I'm afraid)
I'm attempting to detect when a player wants to open/close a door so I create an emptyObject called Door Hinge that has the tag "Door". I then created a cube object named Door Body which is a child of Door Hinge and gave it no tag but did give it a layer of Ignore Raycast. I have the scaling of the parent set to (1, 1, 1) but did change the scaling of the child as well as its x position slightly.
I'm not sure why but the raycast seems to only be detecting the child cube and not the parent empty object. Could anyone let me know if I'm missing anything or doing something wrong? I'll add my detection code for this below.
void CheckInteraction()
{
// origin starts from the camera
Vector3 origin = cam.transform.position;
// direction of the camera
Vector3 direction = cam.transform.forward;
// The distance for the raycast
float distance = 4f;
// Used to store info about the object that the raycast hits
RaycastHit hit;
if (Physics.Raycast(origin, direction, out hit, distance))
{
Debug.Log(hit.transform.tag);
if (hit.transform.tag == "Door")
{
Debug.Log("HIT");
if (Input.GetKeyDown(KeyCode.E))
{
hit.transform.gameObject.GetComponent<DoorOpen>().enabled = true;
}
}
}
}
The solution that I was missing was that the parent empty object needed to have a box collider added to it.
I have created a Prefab and I load it in the Awake function with the following code:
GameObject bulletPrefab = Resources.Load<GameObject>("Enemy/Bullet");
bulletPrefab.transform.position = new Vector3(0,0,0);
The problem is that the bulletPrefab is not gonna show in the game scene. Its activeSelf property is true but its activeInHierarchy property is false. Does anyone know why it is like this and how to make the bulletprefab show in the scene?
Do not modify a prefab. You tried to modify it when you did bulletPrefab.transform.position = ...
The bulletPrefab is a loaded GameObject which is only stored in the memory. To see it, you have to instantiate it with the Instantiate function. You seem to have done this in your other question but for some reason decided to remove that critical part in this question.
Looking at your last question, it seems like your issue is shooting the prefab. You don't shoot the prefab by setting the bullet's position to another position in one frame. You can use a coroutine and do that over multiple frames or you can use Rigidbody for this. I suggest using Rigidbody because that's the kind of stuff it is made for.
Makes sure that Rigidbody is attached to the prefab you want to load. Load the prefab, instantiate it then get the Rigidbody attached to it. Move the bullet to the front of the player + camera then use Rigidbody.velocity or Rigidbody.AddForce to shoot the bullet to the CameraTransform.forward direction so that the bullet will travel to the direction the camera is facing.
See below for example of loading and shooting a bullet prefab when space key is pressed.
GameObject bulletPrefab;
Transform cameraTransform;
public float bulletSpeed = 300;
private void Start()
{
//Load Prefab
bulletPrefab = Resources.Load<GameObject>("Enemy/Bullet");
//Get camera transform
cameraTransform = Camera.main.transform;
}
void Update()
{
//Shoot bullet when space key is pressed
if (Input.GetKeyDown(KeyCode.Space))
{
shootBullet();
}
}
void shootBullet()
{
//Instantiate prefab
GameObject tempObj = Instantiate(bulletPrefab) as GameObject;
//Set position of the bullet in front of the player
tempObj.transform.position = transform.position + cameraTransform.forward;
//Get the Rigidbody that is attached to that instantiated bullet
Rigidbody projectile = GetComponent<Rigidbody>();
//Shoot the Bullet
projectile.velocity = cameraTransform.forward * bulletSpeed;
}
I got a script like this to create a raycast from maincamera and hit the enemy.
void FixedUpdate(){
if (fire) {
fire = false;
RaycastHit hit;
if (Physics.Raycast (fpsCam.transform.position, fpsCam.transform.forward, out hit, range)){
if (Enemy.distance < 80) {
if (hit.collider.tag == "body") {
Debug.Log ("Bullet in the body.");
Enemy.bodyshot = true; //Changing other script variable.
} else if (hit.collider.tag == "head") {
Debug.Log ("Bullet in the head.");
Enemy.headshot = true; //Changing other script variable.
}
}
}
}
}
Enemy Script in Update;
if (headshot) { //headshot variable is static to reach from other script.
anim.SetTrigger ("isDying");
speed = 0;
death = true;
}
if (bodyshot) { //bodyshot variable is static to reach from other script.
anim.SetTrigger ("isSore");
}
So, when I shoot an enemy, all enemies are dying at the same time. Because these scripts are attached to all enemies. I need to change bodyshot and headshot variables without using static. What can I do to separate them ?
When it comes to raycast, you only need the raycast script attached to an empty GameObject. It does not have to be attached to each individual Object that you want to perform the raycast on.
The only time you have to attach script to a GameObject you want to detect clicks on is when using the Unity EventSystem to detect clicks on the objects.
So, remove this script from other GameObjects and just attach it to one GameObject.
Note:
If you want to access or do something to the GameObject that the ray just hit, the GameObject is stored in the hit variable.
RaycastHit hit;...
Destroy(hit.collider.gameObject);
You can also access scripts attached to the Object hit:
hit.collider.gameObject.GetComponent<YourComponent>().doSomething();
Instead of checking to see what the tag of the collider is, you can first check to make sure the collider is one of the colliders attached to your object, and then check the tag for body location. For example:
public Collider[] coll;
void Start() {
coll = GetComponents<Collider>();
}
void FixedUpdate(){
if (fire) {
fire = false;
RaycastHit hit;
if (Physics.Raycast (fpsCam.transform.position, fpsCam.transform.forward, out hit, range)){
if (Enemy.distance < 80) {
if (hit.collider == coll[0] || hit.collider == coll[1]) {
if (hit.collider.tag == "head"){
or to simplify you can make coll[0] the head and [1] the body and ignore checking for a tag.
edit: As Programmer mentioned, this is not an efficient way to do this since you will be casting a ray for every object that has this script and you really only want to be casting a single ray.