Unity3D: Run-time generated AnimationClips leaking - c#

I currently create AnimationClips dynamically while my game is running to move some pieces around. You can see what I mean here: https://youtu.be/u1My9JX4K-c?t=8s , when the pieces get grouped together and then open again. I cannot reuse the clip because the pieces will have a different position next time, so all the movements need to be changed.
Right now, I create a new clip whenever I need them to be grouped and separated, and attempt to replace any existing clip with this:
private void ReplaceAndPlay(AnimationClip clip, string name) {
var old = Anim.GetClip(name);
if (old != null) {
Anim.RemoveClip(name);
Destroy(old);
}
Anim.AddClip(clip, name);
Anim.Play(name);
}
(Anim just points to the gameObject's Animation component)
But using the profiler, I see that the animation clips just keep accumulating, they don't seem to be garbage collected. Am I missing something or doing something wrong? Can they be garbage collected?

Related

How to remove an instance of an object so a new, updated one can take over?

In my game, I declare a PlayerClass (warrior/rogue/etc) like so:
public PlayerClass playerClass;
On the first frame of the game (for now) I instantiate my player's PlayerClass like so:
playerClass = new WarriorClass(player, 100);
This works great. The WarriorClass lets my player use some abilities. However, now I'd like to make my player a rogue:
playerClass = new RogueClass(player, 100);
But for whatever reason, I now have two instances, not one. I push my ability button and it triggers both the Warrior and Rogue ability.
How do I wipe or remove the WarriorClass reference before assigning the new Rogue one?
What also happens if that I exit to the main menu and log into the game again, my player has two instances of the WarriorClass. I'm not sure how to proceed.
Ultimately, the source of my problem is that I don't understand why playerClass isn't getting overwritten each time like another variable would.
Try to remove all references of your player object in your PlayerClass.
Create a method called Destroy() in your PlayerClasses(WarriorClass, RogueClass) and call this method whenever you will create a new object.
public void Destroy(){
this.player = null;
//remove all other references, stop tasks-threads if exists.
}
Apologies for not giving enough info. I didn't understand my problem enough to know what to give. I had implemented an idea that I didn't realize was causing the bug. I was holding onto a list of Abilities and not clearing them on game start. Every game start would append more to it:
In player:
public List<Ability> ActiveAbilities = new List<Ability>();
Appending to that in each PlayerClass instantiation:
ability1 = new LeapAbility();
player.GetModPlayer<MyPlayer>().ActiveAbilities.Add(ability1);
At that point my keybinds would be triggering everything in the list. The fix in player:
ActiveAbilities.Clear();
playerClass = new TestClass(player, 100);

Floating texts in Unity

My 2D platformer game level has treasure chests placed all over the map and when a chest is collected I need to display a message. The messages are contained in a List<string> and they are displayed one by one as the treasures are collected.
These messages are to be displayed in a UI>Text gameObject which is anchored to the top-center of the canvas. I want to display these texts as floating up(fading in/out) when the treasures are collected, by updating the text component of this gameObject. However, the problem arises when two or more treasures are collected before the animation for the previous one could be complete. I can easily concatenate the new messages to the existing ones, but I want the old ones to fade out and new ones to fade in. This can be done by creating multiple UI>Texts, but there are a lot of messages and I do not want to create so many redundant gameobjects. Is there any good workaround for this problem?
The way I handled this in a project of mine was to create a queue of messages to display (as immediacy was not a concern, but being able to only display one at a time was). This sounds very similar to your own problem.
// NotificationItem is just a wrapper around some text and accompanying image
private static List<NotificationItem> notificationQueue = new List<NotificationItem>();
// reference to the on-screen object
public GameObject notification;
// "Hey! I want to display a notification!"
public static void ShowNotification(NotificationItem item) {
notificationQueue.Add(item);
}
// I was using the DO Tween asset here, but the logic can be converted to
// coroutines or straight update cycles
private void Update() {
// If there is no currently displayed notification (ie the notification object is
// not being animated) and there is at least one item to display
if(!DOTween.IsTweening(notification.transform) && notificationQueue.Count > 0) {
// ...get the first one
NotificationItem item = notificationQueue[0];
// ...pop it from the list
notificationQueue.RemoveAt(0);
// ...set the notification object to the details
notification.transform.Find("Title").GetComponent<Text>().text = item.title;
notification.transform.Find("Text").GetComponent<Text>().text = item.text;
notification.transform.Find("Img").GetComponent<Image>().sprite = item.image;
// ...start the animation
// (in my case, the notification animates down from the top of the screen
// waits 2.5 seconds, then animates back up)
notification.transform.DOMoveY(Screen.height - 85, 0.5f, false).SetEase(Ease.InOutQuad).OnComplete(PauseCallback);
}
}
// An admittedly hacky way of getting the notification to do nothing for 2.5 seconds:
// Animate it to where it already is.
private void PauseCallback() {
notification.transform.DOMoveY(Screen.height - 85, 2.5f, false).SetEase(Ease.InOutQuad).OnComplete(ReturnCallback);
}
private void ReturnCallback() {
notification.transform.DOMoveY(Screen.height + 2, 0.5f, false).SetEase(Ease.InOutQuad);
}
The difference between my implementation and yours will be largely in the animation (as well as your Queue lists's type; e.g. you might be able to just use a List<string>). You already have your animation coded, all you need is the queue and a way to determine that your animation is complete.
You will not be generating more objects than the system can handle if you utilize the Flyweight pattern (object pooling). Unity has an Object Pooling tutorial on their site.

How to differentiate between an old instantiated object and a new one?

public void PlaceIconToSlot() //gets called by a button
{
GameObject IconClone = Instantiate(Icons[properIconIndex], Slots[properSlotIndex].transform.position, Quaternion.identity);
}
Icons and Slots are Arrays. The first one tells the program what to instantiate and the second one where to instantiate. Quaternion.identity just means no rotation.
What I am trying to do: Duplicate an image and place it in a slot, then if another image is placed on top of the old one, the old one should be destroyed.
What is happening: Everything works, except that the old one doesn't get destroyed and the new one sits on top of the old. I mean of course it doesn't get destroyed since I didn't program that, but this is my question. How can I Destroy(OldClone) when there is only an IconClone? How can I introduce to the function the concept of an OldClone?
Since you call the function PlaceIconToSlot I'd guess, you might have a Slot component. If so, you can add a member variable to it that holds the current icon (assuming it's one icon per slot) and just work with that.
Something like this:
public class Slot
{
public GameObject Icon;
public void PlaceIconToSlot()
{
// If you overwrite it, the garbage collector will destroy it a some point anyways,
// but it doesn't hurt to do this destroy call
Destroy(Icon);
Icon = Instantiate(...);
}
}
Potentially pass the parameters (the new icon to instantiate) to this function if you above function at some centralized spot. Something like SpotXYZ.PlaceIcon(icon) or SpotXYZGameObject.GetComponent<Slot>().PlaceIcon(icon).
An idea would be to set a tag (let's say oldImage) to your original image. When you instantiate, destroy the object with that tag and then add the oldImage tag to the new image so that it will then be destroyed when another image is instantiated.
public void PlaceIconToSlot() //gets called by a button
{
GameObject IconClone = Instantiate(Icons[properIconIndex], Slots[properSlotIndex].transform.position, Quaternion.identity);
Destroy(GameObject.FindWithTag("oldImage"));
IconClone.gameObject.tag="oldImage";
}
I haven't tried this but it's worth a go!

Particle system behaviour different for game objects consisting of several gameobject components

I have a 2D game. I have game objects that are made of a singular shapes with colliders and some that are made out of several shapes and included in an empty game object to which I have added a character collider. All the game objects have particle systems added and the single shaped game objects work as expected and explode onCollision, the multi shaped objects do not.
The explosions work as expected when using Play On Awake and Looping to Test them, but they do not explode onCollision. I have tried putting the particle system on one of the shapes inside the outer game object and then it shows an error Missing Component System, trying to access particle system for x object, which makes sense.
Within each game Object C# class I have the following methods:
private void OnCollisionEnter(Collision coll)
{
Explode();
}
private void Explode()
{
var exp = GetComponent<ParticleSystem>();
exp.Play();
GetComponent<Renderer>().enabled = false;
Destroy(gameObject, exp.duration);
}
The bombs are set to a rate of 0, to go off in one burst.
I have tried searching and cannot find the missing information required when using particle systems in game objects that are made up from multiple 3D game object shapes.
What am I missing?
I sorted it out, there were a few issues created from not understanding what to do and creating hacks.
The first issue is the renderers were being created in the child objects, so it was better to get all the child objects and loop through these and then disable the renderer in each of these.
I also removed the character colliders, as this was not needed and used sphere colliders.
I then unchecked is kinematic,as this had been enabled to stop my gameobjects in the air from being kicked about, instead I added mass in the rigid body.
In the explode method I commented out removing the rendering visibility as this had interfered with the explosion:
private void Explode()
{
var exp = GetComponent<ParticleSystem>();
exp.Play();
//GetComponent<Renderer>().enabled = false;
Destroy(gameObject, exp.duration);
}
I also extended the particle system start lifetime to exceed the duration. To ensure the particles were visible when the object is destroyed.
I'm still needing to tweak the explosions, but this has made a huge dent for me.

How to swap cubemap when game environment changes

I'm a unity newbie and need a little help...
I have two different environments the user can choose from. I have generated a cubemap for each environment. When the user switches environments, I need to swap out the cubemap being reflected in the scene. Here's some ideas:
Create 2 materials for all of the assets and assign the material at runtime, when the scene changes. The problem I have with this is these are large scenes with tons of assets. How would I go about doing this without having to go through every single object in the scene.
Find a way to re-assign the cubemaps dynamically when the user switches environments. Maybe loop through all scene materials looking for the presence of one cubemap, then replace it with the other.
Seems like that's all I can come up with, and I'm leaning towards #2. Just curious if it would cause a performance hit. Anyone have a better suggestion?
Using concept #2 above...
// replaces the reflection cubemap for the selected environment (garage or gallery)
public void setReflectionMaps(Cubemap cubeMap)
{
// get all of the mesh renderers
var renderers = truckGO.GetComponentsInChildren<Renderer>();
foreach (Renderer r in renderers) {
// get the material for each renderer
Material mat = r.sharedMaterial;
// check if the material has a cubemap
if (mat.HasProperty("_ReflectionMap")) {
// replace existing cubemap
mat.SetTexture("_ReflectionMap",cubeMap);
}
}
}

Categories