Creating powerups by Using script to copy script to another object?

Good day,
I’m trying to create a script to give powerup to an object, when certain event is trigered the script and all the components attached to the script is suposed to be copied to the main object temporarily thus giving it a powerup, I’m certain the event is being trigered correctly but the powerp is still not active which means the script is not being copied to the main object correctly.
Can anyone help me resolve this issue, any help would be much appreciated.
Below is the script(S) used

using System.Collections;
using System.Reflection;
using UnityEngine;
public class PowerUpEffectsHandler : MonoBehaviour
{
    [SerializeField] private float powerUpDuration = 10f; // Duration for the power-up effect
    [MonoBehaviourDropdown][SerializeField] private MonoBehaviour powerUpScript; // Serialized reference to the power-up script
    private SpriteRenderer spriteRenderer;
    private Collider2D powerUpCollider;
    private LineRenderer lineRenderer;
    private GameObject lineGameObject;
    void Start()
    {
        spriteRenderer = GetComponent<SpriteRenderer>();
        powerUpCollider = GetComponent<Collider2D>();
    }
    void Update()
    {
        if (lineGameObject == null)
        {
            FindLineGameObject();
        }
        if (lineRenderer != null && powerUpCollider != null)
        {
            CheckLineIntersection();
        }
    }
    private void FindLineGameObject()
    {
        GameObject[] lines = GameObject.FindGameObjectsWithTag("Line_drawn");
        if (lines.Length > 0)
        {
            lineGameObject = lines[0]; // Assuming you want the first found object
            lineRenderer = lineGameObject.GetComponent<LineRenderer>();
            if (lineRenderer == null)
            {
                Debug.LogError("LineRenderer component not found on the assigned GameObject.");
            }
        }
        else
        {
            Debug.LogError("No GameObject with tag 'Line_drawn' found.");
        }
    }
    private void CheckLineIntersection()
    {
        int pointCount = lineRenderer.positionCount;
        for (int i = 0; i < pointCount - 1; i++)
        {
            Vector2 start = lineRenderer.GetPosition(i);
            Vector2 end = lineRenderer.GetPosition(i + 1);
            if (powerUpCollider.OverlapPoint(start) || powerUpCollider.OverlapPoint(end))
            {
                Debug.Log("Line segment endpoint inside collider.");
                DeactivateVisuals();
                ActivatePowerUp();
                break;
            }
            if (IsLineSegmentIntersectingCollider(start, end, powerUpCollider))
            {
                Debug.Log("Line segment intersecting collider.");
                DeactivateVisuals();
                ActivatePowerUp();
                break;
            }
        }
    }
    private bool IsLineSegmentIntersectingCollider(Vector2 start, Vector2 end, Collider2D collider)
    {
        RaycastHit2D hit = Physics2D.Linecast(start, end);
        if (hit.collider == collider)
        {
            Debug.Log("Line segment intersected with the collider.");
            return true;
        }
        return false;
    }
    private void ActivatePowerUp()
    {
        MonoBehaviour powerUpScriptInstance = (MonoBehaviour)lineGameObject.GetComponent(powerUpScript.GetType());
        if (powerUpScriptInstance == null)
        {
            Debug.Log("Power-up script not found, adding new script.");
            // Add and copy the script and its fields from the power-up to the line object
            Debug.Log($"Adding power-up script: {powerUpScript.GetType().Name}");
            MonoBehaviour newScript = (MonoBehaviour)lineGameObject.AddComponent(powerUpScript.GetType());
            FieldInfo[] fields = powerUpScript.GetType().GetFields();
            foreach (FieldInfo field in fields)
            {
                field.SetValue(newScript, field.GetValue(powerUpScript));
            }
            powerUpScriptInstance = newScript;
            Debug.Log($"Power-up script {powerUpScript.GetType().Name} added to {lineGameObject.name}");
        }
        Debug.Log($"Activating power-up script: {powerUpScript.GetType().Name}");
        powerUpScriptInstance.enabled = true;
        StartCoroutine(DeactivatePowerUpAfterDuration(powerUpScriptInstance, powerUpDuration));
    }
    private IEnumerator DeactivatePowerUpAfterDuration(MonoBehaviour script, float duration)
    {
        yield return new WaitForSeconds(duration);
        script.enabled = false;
        Debug.Log($"Power-up script {script.GetType().Name} deactivated after {duration} seconds.");
    }
    private void DeactivateVisuals()
    {
        Debug.Log("Deactivating visuals.");
        if (spriteRenderer != null)
        {
            spriteRenderer.enabled = false; // Disable the sprite renderer
        }
        if (powerUpCollider != null)
        {
            powerUpCollider.enabled = false; // Disable the collider
        }
    }
}


using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(MonoBehaviourDropdownAttribute))]
public class MonoBehaviourDropdownDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        if (property.propertyType == SerializedPropertyType.ObjectReference)
        {
            EditorGUI.BeginProperty(position, label, property);
            MonoBehaviour[] monoBehaviours = GetAvailableMonoBehaviours(property);
            List<string> options = monoBehaviours.Select(mb => mb.GetType().Name).ToList();
            options.Insert(0, "None");
            int selectedIndex = monoBehaviours.ToList().IndexOf(property.objectReferenceValue as MonoBehaviour) + 1;
            selectedIndex = EditorGUI.Popup(position, label.text, selectedIndex, options.ToArray());
            property.objectReferenceValue = selectedIndex > 0 ? monoBehaviours[selectedIndex - 1] : null;
            EditorGUI.EndProperty();
        }
        else
        {
            EditorGUI.LabelField(position, label.text, "Use MonoBehaviourDropdown with MonoBehaviour.");
        }
    }
    private MonoBehaviour[] GetAvailableMonoBehaviours(SerializedProperty property)
    {
        MonoBehaviour component = property.serializedObject.targetObject as MonoBehaviour;
        if (component != null)
        {
            return component.GetComponents<MonoBehaviour>();
        }
        return new MonoBehaviour[0];
    }
}


using System;
using UnityEngine;
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public class MonoBehaviourDropdownAttribute : PropertyAttribute
{
}

Can you update your post to use code tags please? Using code tags properly

1 Like

Thank you for the info

Technically you are not copying a script, you are adding a component. That makes a difference!

Then this approach is pretty flawed to begin with and you should change that approach. Use of reflection in runtime code is a pretty big code smell. You also shouldn’t simply copy the fields values with reflection, not only is that slow it might also copy internal fields’ values that shouldn’t be copied.

You could simply already have that component on the target object and merely enable/disable that component. This will be fine unless you have many objects or many powerups.

Instead, create a ScriptableObject asset that contains the values the component uses, and assign that SO as a reference. You can then use this SO’s values in any component. Note however that these values are global and any component changing a value will change it for all components. To avoid that, nest a struct inside the SO that contains all the values. You can then simply assign that struct with the data to a field in the component, effectively creating a local copy (a struct is a value type).

You can apply the same nested struct way of sharing data to a MonoBehaviour too if you don’t want to create SOs. In that case your source component will hold the initial values, and the activated or added component simply gets that struct assigned respectively (recommended) you call an Initialize(theData) method on the target component, passing in the data struct.

1 Like

After more then 2 weeks of debuging i finaly fixed it and solved the issue, funny enough it was the power up script it was copying, it had this logic at the top which I had forgotten about, which made it look like the powerup was not activating :eyes::sweat_smile::stuck_out_tongue:
void Start()
{
// Disable this script at the start
//this.enabled = false;
}
here is the full code for anyone who needs it in the future:

using System.Collections;
using System.Reflection;
using UnityEngine;
public class PowerUpEffectsHandler : MonoBehaviour
{
    [SerializeField] private float powerUpDuration = 10f; // Duration for the power-up effect
    [MonoBehaviourDropdown][SerializeField] private MonoBehaviour powerUpScript; // Serialized reference to the power-up script
    private SpriteRenderer spriteRenderer;
    private Collider2D powerUpCollider;
    private LineRenderer lineRenderer;
    private GameObject lineGameObject;
    void Start()
    {
        spriteRenderer = GetComponent<SpriteRenderer>();
        powerUpCollider = GetComponent<Collider2D>();
    }
    void Update()
    {
        if (lineGameObject == null)
        {
            FindLineGameObject();
        }
        if (lineRenderer != null && powerUpCollider != null)
        {
            CheckLineIntersection();
        }
    }
    private void FindLineGameObject()
    {
        GameObject[] lines = GameObject.FindGameObjectsWithTag("Line_drawn");
        if (lines.Length > 0)
        {
            lineGameObject = lines[0]; // Assuming you want the first found object
            lineRenderer = lineGameObject.GetComponent<LineRenderer>();
            if (lineRenderer == null)
            {
                Debug.LogError("LineRenderer component not found on the assigned GameObject.");
            }
        }
        else
        {
            Debug.LogError("No GameObject with tag 'Line_drawn' found.");
        }
    }
    private void CheckLineIntersection()
    {
        int pointCount = lineRenderer.positionCount;
        for (int i = 0; i < pointCount - 1; i++)
        {
            Vector2 start = lineRenderer.GetPosition(i);
            Vector2 end = lineRenderer.GetPosition(i + 1);
            if (powerUpCollider.OverlapPoint(start) || powerUpCollider.OverlapPoint(end))
            {
                Debug.Log("Line segment endpoint inside collider.");
                DeactivateVisuals();
                ActivatePowerUp();
                break;
            }
            if (IsLineSegmentIntersectingCollider(start, end, powerUpCollider))
            {
                Debug.Log("Line segment intersecting collider.");
                DeactivateVisuals();
                ActivatePowerUp();
                break;
            }
        }
    }
    private bool IsLineSegmentIntersectingCollider(Vector2 start, Vector2 end, Collider2D collider)
    {
        RaycastHit2D hit = Physics2D.Linecast(start, end);
        if (hit.collider == collider)
        {
            Debug.Log("Line segment intersected with the collider.");
            return true;
        }
        return false;
    }
    private void ActivatePowerUp()
    {
        // Attempt to find the existing script on lineGameObject
        MonoBehaviour powerUpScriptInstance = (MonoBehaviour)lineGameObject.GetComponent(powerUpScript.GetType());
        // If the script is not found, add it to the lineGameObject
        if (powerUpScriptInstance == null)
        {
            Debug.Log("Power-up script not found, adding new script.");
            // Add the script to lineGameObject
            powerUpScriptInstance = (MonoBehaviour)lineGameObject.AddComponent(powerUpScript.GetType());
            // Copy the fields from the original powerUpScript to the new instance
            FieldInfo[] fields = powerUpScript.GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
            foreach (FieldInfo field in fields)
            {
                field.SetValue(powerUpScriptInstance, field.GetValue(powerUpScript));
            }
            Debug.Log($"Power-up script {powerUpScript.GetType().Name} added to {lineGameObject.name}");
        }
        // Activate the script
        Debug.Log($"Activating power-up script: {powerUpScript.GetType().Name}");
        powerUpScriptInstance.enabled = true;
        // Confirm the script has been enabled
        if (powerUpScriptInstance.enabled)
        {
            Debug.Log($"Power-up script {powerUpScript.GetType().Name} successfully enabled on {lineGameObject.name}");
        }
        else
        {
            Debug.LogError($"Failed to enable power-up script {powerUpScript.GetType().Name} on {lineGameObject.name}");
        }
        // Start coroutine to deactivate the power-up after a duration
        StartCoroutine(DeactivatePowerUpAfterDuration(powerUpScriptInstance, powerUpDuration));
        // Additional debug information
        MonoBehaviour scriptInstance = lineGameObject.GetComponent(powerUpScript.GetType()) as MonoBehaviour;
        if (scriptInstance != null)
        {
            // Script exists on lineGameObject
            Debug.Log($"Power-up script {powerUpScript.GetType().Name} found on {lineGameObject.name}");
        }
        else
        {
            // Script doesn't exist on lineGameObject
            Debug.Log($"Power-up script {powerUpScript.GetType().Name} not found on {lineGameObject.name}");
        }
    }
    private IEnumerator DeactivatePowerUpAfterDuration(MonoBehaviour script, float duration)
    {
        yield return new WaitForSeconds(duration);
        script.enabled = false;
        Debug.Log($"Power-up script {script.GetType().Name} deactivated after {duration} seconds.");
    }
    private void DeactivateVisuals()
    {
        Debug.Log("Deactivating visuals.");
        if (spriteRenderer != null)
        {
            spriteRenderer.enabled = false; // Disable the sprite renderer
        }
        if (powerUpCollider != null)
        {
            powerUpCollider.enabled = false; // Disable the collider
        }
    }
}

Take that as a clear warning sign that you’re making every effort to shoot yourself in the foot with your awkward reflection copy approach! :wink:

There are far easier and more reliable ways to achieve a temporary powerup effect.