i have ShootingAgent.cs for ml-agents. this script is on EmptyObject named "ShootingAgent", which child of "Player".
i need to learn ai to fire bullets. as observation space it will be "Main Camera" which is also child of "Player".

here's bad and broken shootingagent, make it awesome:

using UnityEngine;
using Unity.MLAgents;
using Unity.MLAgents.Sensors;
using Unity.MLAgents.Actuators;

public class ShootingAgent : Agent
{
    public int observationResolution = 10; // The desired low-resolution observation space size
    public PlayerController playerController;
    private Texture2D observationTexture;

    public override void OnEpisodeBegin()
    {
        // Reset the player and environment for a new episode
        // ...
    }

    public override void CollectObservations(VectorSensor sensor)
    {
        // Capture the game view and convert it to low-resolution observation
        RenderTexture renderTexture = RenderTexture.GetTemporary(observationResolution, observationResolution);
        playerController.RenderToTexture(renderTexture);
        RenderTexture.active = renderTexture;

        observationTexture = new Texture2D(observationResolution, observationResolution, TextureFormat.RGB24, false);
        observationTexture.ReadPixels(new Rect(0, 0, observationResolution, observationResolution), 0, 0);
        observationTexture.Apply();

        Color[] pixels = observationTexture.GetPixels();
        for (int i = 0; i < pixels.Length; i++)
        {
            sensor.AddObservation(pixels[i].r);
            sensor.AddObservation(pixels[i].g);
            sensor.AddObservation(pixels[i].b);
        }

        RenderTexture.active = null;
        RenderTexture.ReleaseTemporary(renderTexture);
    }

    public override void OnActionReceived(ActionBuffers actions)
    {
        // Get the joystick control values from the agent's actions
        //float horizontal = actions.ContinuousActionsArray[0];
        //float vertical = actions.ContinuousActionsArray[1];

        // Use the joystick control values to control the game
        //playerController.ControlJoystick(horizontal, vertical);

        // Set a reward based on the game state
        // ...
    }
    public float[] GetAction()
    {
        // Logic to retrieve the action values
        // Return an array of float values representing the actions
        return new float[2]; // Replace with the actual implementation
    }

}

here's PlayerController.cs:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Unity.MLAgents;
using Unity.MLAgents.Sensors;
using Unity.MLAgents.Actuators;
using UnityEditor.U2D;

public enum CharacterGroup
{
    Player,
    Ally,
    Enemy
}

public enum ControlMode
{
    Mouse,
    Joystick,
    AI
}

public class PlayerController : MonoBehaviour
{
    #region Public Variables

    public CharacterGroup group;
    public ShootingAgent shootingAgent;
    public OpponentGenerator opponentGenerator;
    public VariableJoystick variableJoystick;
    public ControlMode currentControlMode = ControlMode.Mouse; // Default control mode is mouse
    public GameObject bulletPrefab;
    public string nickname = "lovekpek";
    public string brawlerName = "Nita";
    public int maxHealth = 3000; // Add a new variable for maximum health
    public int damage = 750; // Add a new variable for damage
    public bool damagePiercing = true;
    public float movementSpeed = 5f;
    public float aimingDistance = 5f;
    public float bulletSpeed = 10f;
    public int maxBullets = 3;
    public float reloadTime = 2f;
    public float shotDelay = 0.5f; // Delay between consecutive shots
    public float bulletWidth = 0.1f; // Bullet width

    [SerializeField] private HealthBar _healthbar;
    public Text healthText; // Reference to the UI text component for health display
    [SerializeField] private BulletBar _bulletbar;
    public Text bulletsText;
    public int currentHealth = 3000;
    public int currentBullets = 3;
    public Text speedText;

    #endregion

    #region Private Variables

    private GameObject sightLine;
    private bool isAiming;
    private bool canShoot = true; // Flag to allow shooting
    private Vector2 aimDirection;
    private Vector2 lineEndPosition;
    private float currentDistance;
    private float reloadTimer;
    private float shotTimer; // Timer for shot delay
    private bool hasFiredRecently; // New flag
    private Vector2 movementInput;
    private float currentSpeed;

    private bool isPlayerControlled = false;

    private Collider2D lastCollision; // Class-level variable to store the last collision

    // Joystick control variables
    private float horizontalInput;
    private float verticalInput;

    #endregion

    #region Unity Callbacks

    private void Start()
    {
        InitializePlayer();
    }

    private void Update()
    {
        if (IsPlayerControlled())
        {
            HandleAimingAndShooting();
            HandleReloading();
            HandlePlayerMovement();
            UpdateUIElements();
        }
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        ProcessCollision(collision);
    }

    #endregion

    #region Initialization

    private void InitializePlayer()
    {
        currentBullets = maxBullets;
        UpdateBulletsText();

        switch (group)
        {
            case CharacterGroup.Player:
                isPlayerControlled = true;
                break;

            case CharacterGroup.Ally:

                break;

            case CharacterGroup.Enemy:

                break;
        }
    }

    #endregion

    #region Movement and Aiming

    private void HandlePlayerMovement()
    {
        movementInput.x = Input.GetAxisRaw("Horizontal");
        movementInput.y = Input.GetAxisRaw("Vertical");
        movementInput.Normalize();
        currentSpeed = movementSpeed * movementInput.magnitude;
        Vector3 movement = new Vector3(movementInput.x, movementInput.y, 0f) * currentSpeed * Time.deltaTime;
        transform.position += movement;
    }

    private void HandleAimingAndShooting()
    {
        switch (currentControlMode)
        {
            case ControlMode.Mouse:
                MouseControlLogic();
                break;
            case ControlMode.Joystick:
                JoystickControlLogic();
                break;
            case ControlMode.AI:
                AIControlLogic();
                break;
        }

        if (isAiming)
        {
            UpdateSightLine();
        }

        if (!canShoot)
        {
            shotTimer -= Time.deltaTime;
            if (shotTimer <= 0f)
            {
                canShoot = true;
            }
        }

        if (!canShoot && hasFiredRecently)
        {
            shotTimer -= Time.deltaTime;
            if (shotTimer <= 0f)
            {
                canShoot = true;
                hasFiredRecently = false;
            }
        }
    }

    private void MouseControlLogic()
    {
        if (Input.GetMouseButtonDown(0))
        {
            StartAiming();
        }
        else if (Input.GetMouseButtonUp(0))
        {
            StopAiming();
        }
    }

    private void JoystickControlLogic()
    {
        if (Mathf.Abs(variableJoystick.Horizontal) > 0.1f || Mathf.Abs(variableJoystick.Vertical) > 0.1f)
        {
            if (!isAiming)
            {
                StartAiming();
            }
        }
        else
        {
            if (isAiming)
            {
                StopAiming();
            }
        }
    }

    private void AIControlLogic()
    {
        if (shootingAgent != null)
        {
            // Get the joystick control values from the ML agent
            float horizontal = 0f;
            float vertical = 0f;

            // Retrieve the joystick control values from the ML agent
            var behaviorParameters = shootingAgent.GetComponent<Unity.MLAgents.Policies.BehaviorParameters>();
            if (behaviorParameters != null)
            {
                var actions = shootingAgent.GetStoredActionBuffers().ContinuousActions;

                if (actions.Length > 0)
                    horizontal = actions[0];

                if (actions.Length > 1)
                    vertical = actions[1];
            }

            // Check if the joystick control values exceed the threshold
            if (Mathf.Abs(horizontal) > 0.1f || Mathf.Abs(vertical) > 0.1f)
            {
                if (!isAiming)
                {
                    StartAiming();
                }
            }
            else
            {
                if (isAiming)
                {
                    StopAiming();
                }
            }
        }
    }


    private void StartAiming()
    {
        isAiming = true;

        // Destroy any existing SightLine object
        if (sightLine != null)
            Destroy(sightLine);

        // Create and initialize sight line
        CreateSightLine();

        currentDistance = aimingDistance;
    }

    private void StopAiming()
    {
        isAiming = false;
        Destroy(sightLine);

        if (canShoot && currentBullets > 0)
        {
            if (shotTimer <= 0f) // Check if shot delay has elapsed
            {
                FireBullet();

                currentBullets--;
                UpdateBulletsText();

                canShoot = false; // Set canShoot to false
                shotTimer = shotDelay; // Reset shot delay timer
            }
        }
    }

    private void CreateSightLine()
    {
        sightLine = new GameObject("SightLine");
        LineRenderer lineRenderer = sightLine.AddComponent<LineRenderer>();
        lineRenderer.positionCount = 2;

        lineRenderer.startWidth = bulletWidth;
        lineRenderer.endWidth = bulletWidth;

        // Create a new material and set its color to white
        Material lineMaterial = new Material(Shader.Find("Sprites/Default"));
        lineMaterial.color = Color.white;

        lineRenderer.material = lineMaterial; // Assign the material to the line renderer

        lineRenderer.sortingLayerName = "Background"; // Set the sorting layer
        lineRenderer.sortingOrder = -1; // Set the sorting order to render behind the player

        lineRenderer.SetPosition(0, transform.position);
        lineRenderer.SetPosition(1, transform.position);
    }

    private void UpdateSightLine()
    {
        switch (currentControlMode)
        {
            case ControlMode.Mouse:
                Vector2 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
                aimDirection = (mousePosition - (Vector2)transform.position).normalized;
                break;
            case ControlMode.Joystick:
                aimDirection = new Vector2(variableJoystick.Horizontal, variableJoystick.Vertical).normalized;
                break;
            default:
                aimDirection = Vector2.zero;
                break;
        }

        lineEndPosition = (Vector2)transform.position + aimDirection * currentDistance;

        LineRenderer lineRenderer = sightLine.GetComponent<LineRenderer>();
        lineRenderer.SetPosition(0, transform.position);
        lineRenderer.SetPosition(1, lineEndPosition);
    }

    #endregion

    #region Reloading

    private void HandleReloading()
    {
        if (currentBullets < maxBullets)
        {
            reloadTimer += Time.deltaTime;
            if (reloadTimer >= reloadTime)
            {
                reloadTimer = 0f;
                currentBullets++;
                UpdateBulletsText();
            }
        }
        else if (!isAiming && sightLine != null)
        {
            Destroy(sightLine);
        }
    }

    #endregion

    #region Shooting

    public void FireBullet()
    {
        GameObject bullet = Instantiate(bulletPrefab, transform.position, Quaternion.identity);

        BulletController bulletController = bullet.GetComponent<BulletController>();
        bulletController.SetDirection(aimDirection);
        bulletController.SetSpeed(bulletSpeed);
        bulletController.SetMaxDistance(currentDistance);
        bulletController.SetGroup(group);
        bulletController.SetDamage(damage);
        bulletController.SetDamagePiercing(damagePiercing);
    }

    #endregion

    #region UI Updates

    private void UpdateUIElements()
    {
        healthText.text = currentHealth.ToString();
        _healthbar.UpdateHealthBar(maxHealth, currentHealth);

        UpdateBulletsText();
    }

    private void UpdateBulletsText()
    {
        bulletsText.text = currentBullets.ToString();
        _bulletbar.UpdateHealthBar(maxBullets, currentBullets);
    }

    #endregion

    #region Collision Handling

    private void ProcessCollision(Collider2D collision)
    {
        if (group.Equals(CharacterGroup.Player) && collision.CompareTag("Bullet") && collision.GetComponent<BulletController>().group.Equals(CharacterGroup.Ally))
        {
            // Ignore bullets from allies damaging the player
            return;
        }
        else if (group.Equals(CharacterGroup.Ally) && collision.CompareTag("Bullet") && collision.GetComponent<BulletController>().group.Equals(CharacterGroup.Player))
        {
            // Ignore bullets from the player damaging allies
            return;
        }
        else if (group.Equals(CharacterGroup.Enemy) && collision.CompareTag("Bullet") && collision.GetComponent<BulletController>().group.Equals(CharacterGroup.Player))
        {
            // Damage the enemy when hit by a player’s bullet
            int bulletDamage = collision.GetComponent<BulletController>().GetDamage();
            bool bulletDamagePiercing = collision.GetComponent<BulletController>().GetDamagePiercing();
            TakeDamage(bulletDamage);

            if (bulletDamagePiercing == false)
            {
                Destroy(collision.gameObject);
            }
            return;
        }
    }


    private void TakeDamage(int damageValue)
    {
        if (group == CharacterGroup.Enemy)
        {
            currentHealth -= damageValue;
            if (currentHealth <= 0)
            {
                Destroy(gameObject);
            }

            _healthbar.UpdateHealthBar(maxHealth, currentHealth);
            healthText.text = currentHealth.ToString();
        }
    }

    #endregion

    #region Observations for ML

    public void CollectObservations(VectorSensor sensor)
    {
        // Add player position to observations
        sensor.AddObservation(transform.position);

        // Get enemy positions from OpponentGenerator
        var enemyPositions = opponentGenerator.GetEnemyPositions();

        // Add enemy positions to observations
        foreach (var enemyPosition in enemyPositions)
        {
            sensor.AddObservation(enemyPosition);
        }
    }

    #endregion

    #region Utility Functions

    public void Shoot(float shootingAngle)
    {
        // Perform shooting logic based on the shooting angle
        // …
    }

    public void RenderToTexture(RenderTexture renderTexture)
    {
        // Render the game view to the specified render texture
        RenderTexture currentRT = RenderTexture.active;
        RenderTexture.active = renderTexture;
        Camera.main.targetTexture = renderTexture;
        Camera.main.Render();

        // Reset the render texture and camera settings
        Camera.main.targetTexture = null;
        RenderTexture.active = currentRT;
    }

    #endregion

    // Helper Methods

    private bool IsPlayerControlled()
    {
        return isPlayerControlled;
    }
}