In my Unity game I have some moving objects. There are some collectables ( triggers ) and whenever a moving object enters its trigger the collectable should
Replace itself with a moving object
Destroy itself
Unfortunately the current moving object collides with the new spawned moving object so it will be out of position ( very little ). I would like to avoid that, so one piece connects smoothly to the other one.
For reproduction purposes:
Moving
Create a cube GameObject
Add a Rigidbody component but disable the usage of gravity
Attach the following script to it
.
public class MoveForwardBehaviour : MonoBehaviour
{
private void FixedUpdate()
{
GetComponent<Rigidbody>().velocity = Vector3.forward; // just for testing purposes
}
}
Make this GameObject a prefab
Collectable
Create a cube GameObject
Add a Rigidbody component but disable the usage of gravity
Enable the collider trigger and modify the following values
Attach the following script to it
.
public class Collectable : MonoBehaviour
{
[SerializeField] private GameObject movingPrefab;
private void OnTriggerEnter(Collider other)
{
GetComponent<Collider>().enabled = false;
Instantiate(movingPrefab, transform.position, transform.rotation);
Destroy(gameObject);
}
}
Assign the moving prefab to the script
Make this GameObject a prefab
Now setup a sample scene like so
and start the game. After creating a new moving prefab you can see that the initial moving prefab is not in position anymore
I think this is because of the collision with the new instantiated prefab. Do you have any suggestions how to avoid this?
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?
I'm new, and I've got hard time to respawn my AI (right now he just a cube that follow my player) after he been destroy. I believe its because the script sits on the object that get destroyed. but what I need to do to respawn it?
(although I'm sure my respawn code is not good :\ (It's mobile-android project) )
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyTesting : MonoBehaviour
{
[SerializeField]
public GameObject player;
public GameObject enemy;
private Rigidbody body;
Vector3 accelerationDir;
// Use this for initialization
void Start()
{
body = GetComponent<Rigidbody>();
}
private void Update()
{
accelerationDir = Input.acceleration;
if (accelerationDir.sqrMagnitude>=5)
{
EnemyDead();
}
}
void EnemyDead()
{
Destroy(enemy);
Invoke("Respawn", 5);
}
void Respawn()
{
enemy = (GameObject)Instantiate(enemy);
enemy.transform.position = transform.position;
}
// Update is called once per frame
void FixedUpdate()
{
Vector3 toTarget = player.transform.position - transform.position;
float speed = 1.5f;
transform.Translate(toTarget * speed * Time.deltaTime);
}
}
Thanks very much!
I am supposing that your enemy GameObject is inside the scene holding your EnemyTesting MonoBehaviour instance (correct me if I'm wrong).
If this is the case, you cannot instantiate a gameObject that is destroyed.
As #derHugo pointed out, you should not use Destroy and Instantiate for your use case. It would be better to set inactive the enemy GameObject, move it to the position that you want, an (re)set it active. It will look like the enemy respawned.
If you want to dig the subject later, look at the object pooling game optimization pattern.
Otherwise, if you still want to use Instantiate for respawning, I would create a prefab of your enemy GameObject. The enemy GameObject referenced in the EnemyTesting field (in the Inspector view), would be your prefab from the project hierarchy instead of a GameObject inside the scene.
This way, you would be able to instantiate an enemy GameObject as many times as you want (and use it in other scenes!). Don't forget to hold a reference to the instantiated enemy GameObject so you can know which one you want to destroy. It would looke like this :
enemy = Instantiate(enemyPrefab, transform.position, transform.rotation);
You can replace transform with the transform of your choice, for example the transform of an empty enemyRespawnPoint GameObject in your Scene.
Do you see any error in the console ?
In my platform game I have just added some checkpoints, so that if the player dies doesn't necessarily spawn at the beginning of the track.
ghfdghdggfhfg
using UnityEngine;
public class CheckPoints : MonoBehaviour
{
[SerializeField] private Grounded game;
void Update()
{
transform.Rotate(0, 0, 5);
}
private void OnTriggerEnter() {
game.updatedCheckPointPosition = transform.position;
Destroy(this);
}
}
What I unsuccessfully tried to do is to set the public float variable of the Grounded script to the current position of the CheckPoint itself, which should be destroyed after doing that.
Any information or help on how to do this is really appreciated.
From Destroy
The object obj will be destroyed now or if a time is specified t seconds from now.
If obj is a Component it will remove the component from the GameObject and destroy it. [But keep the rest of the GameObject intact!]
If obj is a GameObject it will destroy the GameObject, all its components and all transform children of the GameObject.
this refers to the according component instance. What you want is rather
Destroy(gameObject);
OnTriggerEnter requires a parameter of type Collider in order to work
private void OnTriggerEnter(Collider other)
{
game.updatedCheckPointPosition = transform.position;
Destroy(this);
}
Note however that this way round the player has to be a trigger while the checkpoint a non-trigger! I would actually rather do it the other way round and make the chackpoint a trigger and rather let the player object check for OnTriggerEnter.
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 have made a Raycast that goes from my camera to the point of the object clicked. However, I am trying to make an object (in this case a bullet) to fly along the path of the ray. At the moment it flies straight forwards from the camera no matter where on the object you click because of the vector 3. How would I get it to follow the Ray?
C#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RaycastShot : MonoBehaviour {
public Camera camera;
private Ray ray;
private RaycastHit hit;
public GameObject bullet;
private GameObject createBullet;
private Collider collider;
void Update () {
if (Input.GetMouseButtonDown (0)) {
ray = camera.ScreenPointToRay (Input.mousePosition);
createBullet = Instantiate (bullet, camera.transform.position, bullet.transform.rotation);
createBullet.AddComponent<Rigidbody>();
createBullet.GetComponent<Rigidbody>().AddRelativeForce (new Vector3(0, 1500, 0));
createBullet.GetComponent<Rigidbody>().useGravity = false;
collider = createBullet.GetComponent<Collider> ();
Destroy (collider);
if (Physics.Raycast (ray, out hit)) {
}
}
Debug.DrawLine (ray.origin, hit.point, Color.red);
}
}
You would want to use ray.direction property instead of (0,1500,0) as the direction of the force.
The add force should occur in FixedUpdate, and should only occur if the Ray hits something. Where you have it now is probably not the best spot.
Of course, make sure the bullet gets instantiated at the camera's location first.
Ray.direction gives you the vector3 direction of the ray object. If you need the distance at which it hit, you could also use ray.distance.
Edit: I'm near my computer now, so here's a more detailed answer relating to your comments.
First off: Here's the way I set up the test Project:
I Created a prefab bullet. This is just a sphere with a rigidbody, with my "BulletController" script attached to it. The point of prefabs is to avoid all of those lines where you have to add components. For testing purposes I set the rigibody to ignore gravity and its mass to 0.1.
Next, I created the BulletController script, which will be attached to the bullet prefab.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BulletController : MonoBehaviour {
Rigidbody rb;
public float bulletForce;
bool firstTime = false;
Vector3 direction;
// Use this for initialization
void Start () {
rb = GetComponent<Rigidbody> ();
}
public void SetDirection (Vector3 dir) {
direction = dir;
firstTime = true;
}
void OnCollisionEnter () {
//code for when bullet hits something
}
void FixedUpdate () {
if (firstTime) {
rb.AddForce (direction * bulletForce);
firstTime = false;
}
}
}
This script is is charge of controlling bullets. The (later on) script that will create the bullets doesn't really care what happens to them afterwards, since its job is just to create bullets. This BulletController script is in charge of dealing with bullets once they're created.
The main parts are the SetDirection method which tells the bullet which direction to travel in. Also it adds a one-time force in its FixedUpdate method that pushes it in the direction you just set. FixedUpdate is used for physics changes like adding forces. Don't use Update to do this kind of thing. It multiplies the force by a force that you set called "bulletForce".
Finally the BulletListener Script, which is simply attached to an empty game object in the scene. This script is in charge of listening for mouse clicks and creating bullets towards them.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BulletListener : MonoBehaviour {
public Camera mainCamera;
public BulletController bulletPrefab;
void Update () {
if (Input.GetMouseButtonDown (0)) {
//create ray from camera to mousePosition
Ray ray = mainCamera.ScreenPointToRay (Input.mousePosition);
//Create bullet from the prefab
BulletController newBullet = Instantiate (bulletPrefab.gameObject).GetComponent<BulletController> ();
//Make the new bullet start at camera
newBullet.transform.position = mainCamera.transform.position;
//set bullet direction
newBullet.SetDirection (ray.direction);
}
}
}
In the inspector for this empty game object, I added this script, and then dragged the camera, and the bulletPrefab into the appropriate fields. Be sure to drag the prefab from the FOLDER, not from the SCENE. Since this will use the prefab, not an object in the scene.
Now click around and you'll see the bullets flying! Note that using a low force is good to test, and then increase it later.
The main things to take away from this is to split up your logic. A script should only be in charge of one thing. For example, your enemies might also fire bullets. You can now reuse your bulletController script for those bullets as well. Also, say you have different sized or shaped bullets, you can just drag the bulletcontroller script onto the different prefabs you've made for your bullets. This will not affect your listener script which will still create bullets where you click.
If you have the end point then you can move along the vector with MoveTowards:
Vector3 target = hit.point;
StartCoroutine(MoveAlong(target));
private IEnumerator MoveAlong(Vector3 target){
while(this.transform.position != target){
this.transform.position = MoveTowards(this.transform.position, target, step);
yield return null;
}
}