Unity2D I can't change the TrailRenderer material of a cloned object - c#

In the game I'm making, I clone a Ball object whenever a "split ball" power-up is acquired. Everything works as intended except the TrailRenderer material. The Ball prefab has a default material used for TrailRenderer in the beginning. This, however, changes when the ball hits an object controlled by the player (which is called a "bumper"). The material changes work perfectly on collision. Here is the shortened script for the Ball object:
[NonSerialized]
public TrailRenderer trailRenderer;
[SerializeField]
private Material defaultTrailMaterial;
void Start()
{
trailRenderer = GetComponent<TrailRenderer>();
trailRenderer.material = defaultTrailMaterial;
}
private void OnCollisionEnter2D(Collision2D collision)
{
if (CollidedObjectIsABumper(collision))
{
// SetTrailColorToBumperColor() is called along other calculations
}
}
private bool CollidedObjectIsABumper(Collision2D collision)
{
return collision.gameObject.CompareTag("Bumper");
}
private void SetTrailColorToBumperColor()
{
trailRenderer.material = lastCollidedBumper.material;
}
I discarded a few things for clarity.
Now, here is the shortened script for the split ball power-up:
void OnTriggerEnter2D(Collider2D collision)
{
if (!CollidedWithABall(collision))
return;
Ball mainBall = collision.gameObject.GetComponent<Ball>();
// I added this part as a desperate attempt, didn't work.
mainBall.trailRenderer.material = mainBall.lastCollidedBumper.material;
Ball splitBall = Instantiate(mainBall, pos, Quaternion.identity);
splitBall.tag = "Split Ball";
splitBall.GetComponent<TrailRenderer>().material = mainBall.lastCollidedBumper.material;
Destroy(gameObject);
}
private bool CollidedWithABall(Collider2D collision)
{
return collision.gameObject.CompareTag("Ball") || collision.gameObject.CompareTag("Split Ball");
}
pos is a Vector3 variable that is declared in the cut portion. After getting the power-up, this is how the game scene looks like:
None of the balls touched a bumper after getting the power-up. I expect the split ball to have a red trail but it doesn't. I'm sure I'm missing something with Instantiate() but I don't know what.
One thing I assumed was that Instantiate() used the prefab of the main ball, in which case the trail would have a neutral color, but I added an assignment statement after Instantiate() so I don't think that's the only problem here. For reference, the split ball DOES change its trail color when it hits a bumper.
Thank you and please let me know if you need additional information.

As mentioned this is a timing issue.
You have
void Start()
{
trailRenderer = GetComponent<TrailRenderer>();
trailRenderer.material = defaultTrailMaterial;
}
which overwrites your material.
The call of Start on new instantiated objects is delayed until the beginning the next frame for the purpose of being able to still change some field values right after Instantiate before Start is called.
So you set the material in
splitBall.GetComponent<TrailRenderer>().material = mainBall.lastCollidedBumper.material;
But then it is later changed again by Start.
Awake however is called right away.
So either you change it to
void Awake()
{
trailRenderer = GetComponent<TrailRenderer>();
trailRenderer.material = defaultTrailMaterial;
}
so this is done first and then the line
splitBall.GetComponent<TrailRenderer>().material = mainBall.lastCollidedBumper.material;
can correctly overwrite the material or alternatively you could also make
public Material defaultTrailMaterial;
and instead of directly setting the material in
splitBall.GetComponent<TrailRenderer>().material = mainBall.lastCollidedBumper.material;
you rather only set
splitBall.GetComponent<YourComponent>().defaultTrailMaterial = mainBall.lastCollidedBumper.material;
and then let Start do the job as currently.

Related

Player unable to move after being teleported/transported Unity3d

I've been trying to fix this one bug in my code for over 7 hours now, upon being teleported, the movement controls cease to function, the mouse works fine, you can look around, but you can't move around.
I wanted to set up some simple code that would teleport the player to a "checkpoint" upon achieving a negative or null y level. I was doing this for a parkour based game, if the player fell off the platform they would have to start over, but after teleporting, it becomes impossible to move as I'm sure I have already said. My code is pretty simple:
public class Main : MonoBehaviour
{
float Fall;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
Vector3 Checkpoint = new Vector3 (0,3,0);
GameObject Player = GameObject.FindGameObjectWithTag("Player");
Fall = GameObject.FindGameObjectWithTag("Player").transform.position.y;
if (Fall<-4)
{
Player.transform.position = Checkpoint;
}
}
}
You would think that this would just simply change the coordinates of the player, but I think this might be screwing with the FPSController script.
I am using Unity3d, with Standard Assets imported, All of the code is in C#.
Instead of checking the Y value of your character, I would instead place a death collider under the map. Make this a trigger and if the player touches this trigger, then teleport them back. Nothing with your code should screw with the FPS controller so it might be something else. I would also highly recommend not using a FindGameObjectWithTag in the Update() method as it is extremely expensive to use this every frame, especially twice. If you would rather keep the Update() Y component of the position check, please rewrite the code to something like this:
public class Main : MonoBehaviour
{
// assign this object of your player in the inspector - it stores the reference to reuse
// instead of grabbing it every frame
[SerializeField] private Transform playerTransform = null;
// make this a variable as it is not changing - might as well make this const too
private Vector3 checkpoint = new Vector3(0, 3, 0);
// constant value of what to check for in the Y
private const int FALL_Y_MARKER = -4;
// Update is called once per frame
void Update()
{
if (playerTransform.position.y < FALL_Y_MARKER)
{
playerTransform.position = checkpoint;
}
}
}
With your current code, there should be nothing breaking your input/movement, but with that said, we can not see your input/movement code. All the above snippet does is check if the Y component of the player objects position is below a certain value, and if it is, it sets the position to a new vector. Can you post a bit more movement code or somewhere else it can go wrong that you think is the issue?

Unity OnBecameInvisible() fires though object is still visible

I have a Mesh Renderer and a script assigned to a rotating sphere with a hole in it. The sphere has no specific or special place in hierarchy, its just next to the camera. The script part looks like this:
void OnBecameInvisible() {
Destroy(gameObject);
}
Problem is, that when I pass the sphere with my ball, even though the sphere is still half visible, it gets deleted. I have no other camera in the scene, and the one Im using is marked as the main camera.
Video
Instead of using OnBecameInvisible for culling objects you've passed, just check if it's sufficiently behind the camera in Update:
Camera mainCam;
[SerializeField] float maxBehindDistance = 0.5f;
void Awake() { mainCam = Camera.main; }
void Update()
{
Vector3 relPos = mainCam.transform.InverseTransformPoint(transform.position);
if (relPos.z < -maxBehindDistance)
{
Destroy(gameObject);
}
}

2D Portal only working sometimes in Unity

I'm trying to make a 2D portal in Unity that doesn't just transport the player to the other portal gameobject. But keeps the players position and movements direction and velocity after going through the portal.
I'm be no means an artist, but for example:
The player is a ball bouncing around an area, when he does through the portal his velocity is maintained, along with the fact that he entered he center of the portal.
Where here for example:
If the player enters the bottom half of the portal, he will come out the bottom half of the portal.
When this works, it works great! However, it only works 50% of the time, that 50% can have a bunch of different issues though, sometimes the ball will just not teleport. Sometimes the ball hits the first portal, teleports to the second portal, then back to the first portal, and it does this repeatedly forever. And it experiences these issues seemingly at random.
Here is my Script:
public GameObject otherPortal;
public PortalController otherPortalScript;
private BallController ballController;
public float waitTime = 0.5f;
[HideInInspector]
public bool teleporting;
// Use this for initialization
void Start ()
{
}
// Update is called once per frame
void Update ()
{
}
void OnTriggerEnter2D(Collider2D other)
{
if (other.gameObject.tag == "Ball")
{
ballController = other.GetComponent<BallController>();
if(!teleporting)
{
var offset = other.transform.position - transform.position;
offset.x = 0;
other.transform.position = otherPortal.transform.position + offset;
otherPortalScript.teleporting = true;
teleporting = true;
StartCoroutine("Teleport");
}
}
}
void OnTriggerExit2D(Collider2D other)
{
if (other.gameObject.tag == "Ball")
{
teleporting = false;
otherPortalScript.teleporting = false;
}
}
IEnumerator Teleport()
{
yield return new WaitForSeconds(waitTime);
teleporting = false;
otherPortalScript.teleporting = false;
ballController.teleporting = false;
}
}
The script is attached to both portals, which are both prefabs of the same object. I set both "otherPortal", "otherPortalScript", & "waitTime" in the editor. "waitTime is something I had" to add after the fact to fix another issue I was having where sometimes "teleporting" never got set to false, I believe the the cause of the that problem is the same cause of this problem, making "waitTime" just a bandage for a larger issue. Also, anytime the Portal Script changes a variable in "ballController" such as "ballController.teleporting = false;", it's only there because the ball is add/removing points from a score system, it doesn't at all affect the ball's movement.
Consider getting rid of the teleporting property of the portals and the ball as well as the waitTime.
Now give the ball a List<PortalController> inUseControllers (note you need to add using System.Collections.Generic). Whenever it collision-enters one portal, check if the list is empty via inUseControllers.Count == 0, and if so, add both involved PortalController's to that list and handle the teleporting movement. When the ball collision-exits a PortalController, remove it from the inUseControllers list; it will thus only be emptied again once the ball leaves every portal zone.
This approach should simplify the code, yet safely protect against accidental back-and-forth circles.

How to have a powerup have a slight chance of spawning when killing an enemy?

so we have a class assignment and pretty much we are doing the Survival Shooter project from the Unity tutorials. I've managed to make health packs and little boxes that give you speed when you pick them up, but now I want the power-ups to have a slight chance of dropping when enemies die. Can someone help me out? I'm not really asking for entire code, I have some down below:
public float percentDrop = 50f;
public GameObject HealthPack;
void Awake()
{
HealthPack = GetComponent<GameObject>();
}
public void TakeDamage (int amount, Vector3 hitPoint) { if(isDead) return;
enemyAudio.Play ();
currentHealth -= amount;
hitParticles.transform.position = hitPoint;
hitParticles.Play();
if(currentHealth <= 0)
{
Death ();
}
}
void Death ()
{
isDead = true;
capsuleCollider.isTrigger = true;
anim.SetTrigger ("Dead");
enemyAudio.clip = deathClip;
enemyAudio.Play ();
var randChance = Random.Range(0f, 100f);
if (randChance < percentDrop)
{
//GameObject.Healthpack.setActice(true);
}
}
I'm not too sure how to make the Game Object spawn when they die, can someone help me out?
Create a "Health Pack" prefab from the editor. If you need more information about prefabs, the information is out there on the web. To create a prefab, simply drag and drop a GameObject from the scene to your project. Its name in the scene will become blue. You can delete it from the scene and it will still be in the project.
Drag the prefab from the project to the HealthPack slot in the inspector of your ennemy. The ennemy will now have a reference to the prefab.
When you want to create a new health pack, you can use the Object.Instantiate static method as specified by rutter. Here is the official doc. When you instantiate your new Health Pack, I guess you want it to appear where the ennemy is, wich means you'll want to use one of the method's overloads which takes a Vector3D position as a parameter, which will most likely be transform.position. Since those methods also ask for a Quaternion, just pass in the Quaternion.identity constant.
This is how your code could look:
if (randChance < percentDrop)
{
Object.Instantiate(HealthPack, transform.position, Quaternion.identity);
}
Another problem, as mentioned in my comment, is the Awake function: HealthPack = GetComponent<GameObject>();.
The HealthPack prefab should be assigned to the ennemy from the inspector. That line in the Awake function will assign your ennemy's GameObject component to HealthPack, which is not desirable in the current context.
I hope this helps!

How to make a projectile turn with a arc

I have a cannon that fires a bullet in a parabolic arc. Right now when I fire the bullet stays in the same rotation as it was when it fired out of the cannon.
How do I make it so the bullet's rotation follows the arc as it travels through the air?
I tried the following as a script running on the bullet
Exhibit 1
public class PointingBehaviour:MonoBehaviour
{
private Rigidbody rb;
private void Awake()
{
rb = GetComponent<Rigidbody>();
}
public void Update()
{
transform.up = rb.velocity;
}
}
And that works fairly well. but I see a slight flicker on the first frame the object exists (I think this is because the velocity is still 0 at this point) and the object spins uncontrollably once it hits the ground.
I got it to stop flickering at the start and stop spinning when it lands by doing the following
public class BulletController : MonoBehaviour
{
private Rigidbody _rb;
private bool _followArc;
private bool _firstFrame;
private void Start()
{
_rb = GetComponent<Rigidbody>();
_firstFrame = true;
_followArc = true;
}
public void LateUpdate()
{
if (_followArc && !_firstFrame)
transform.up = _rb.velocity;
_firstFrame = false;
}
public void OnCollisionEnter(Collision collision)
{
_followArc = false;
}
}
But if I happen to bump something in the air it stops following the arc and just does a free tumble till it lands. What is the "Correct" way to do what I want to do?
Because people wanted to see it, here is the code for spawning the bullet.
public class TankController : MonoBehaviour
{
private Transform _barrelPivot;
private Transform _bulletSpawn;
public GameObject Bullet;
public float FirePower;
public float RotationSpeed;
public float TiltSpeed;
private void Start()
{
_barrelPivot = GetComponentsInChildren<Transform>().First(x => x.CompareTag("BarrelPivotPoint"));
_bulletSpawn = GetComponentsInChildren<Transform>().First(x => x.CompareTag("BulletSpawn"));
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
FireCannon();
}
//(SNIP) Handle turning left and right and pivoting up and down.
}
private void FireCannon()
{
var newBullet = SpawnBullet();
var rb = newBullet.GetComponent<Rigidbody>();
rb.AddForce(_bulletSpawn.up * FirePower, ForceMode.Impulse);
}
private GameObject SpawnBullet()
{
var newBullet = (GameObject) Instantiate(Bullet, _bulletSpawn.position, _bulletSpawn.rotation);
newBullet.transform.localScale = Bullet.transform.localScale;
return newBullet;
}
}
I believe what you're saying is - your're script exhibit1 works great.
If you think about it, all you have to do is turn off that behavior, when you want to.
In this case, you're thinking "that behavior should stop when it hits the ground .. I assume that's what you mean physically.
It's very easy to turn off a MonoBehaviour, as you know just enabled = false;
So, you have some script somewhere like Bullet or perhaps BulletBoss
In there you'll have a variable private PointingBehaviour pb and you'll just pb.enabled = false
(I can't tell you "when" you want to do that, it depends on your game .. so it might be something like "when altitude is less than blah" or "when I hit something" ... probably OnCollisionEnter related.)
Note that - I'm sure you know this - for pointing behavior for a projectile, just setting it along the tangent is pretty basic.
A lovely very easy thing to do when you're writing a pointing behavior for a projectile like that, try lerping it gently to the tangent. The result is amazing real looking. Next perhaps add some random "wiggles" which is very bomb-like. It's amazing how the user can see such things, only only a few frames. (The next step up would be real air physics I guess!)
Note that, certainly, PointingBehaviour should just be its own script. You must keep behaviors totally isolated.
Regarding LateUpdate mentioned, there is never a need to use it.
Unity offer a "script order execution" system (see Preferences menu option), if one truly wants to order within the frame. But about the only reason to do that would be perhaps for some experimentation reason.
It's rather like "using a global" - there's just no reason for it. As with any code, if you have to do something in a given order, just call in order.

Categories