￼
Considerando i seguenti punteggi per la valutazione di un progetto unity per l’ università, scrivi una lunga relazione nella quale vengono descritti i punti che sono stati eseguiti (facendo riferimento al codice, e come si è rispettato il punto in questione). Riguardo le animazioni, specificare il fatto che tutto il mondo di gioco compreso il player sono state create tutte da zero utilizzando il programma Aseprite specializzato in animazioni in pixel art, gli asset dei nemici, invece, sono stati presi da internet e non creati dal sottoscritto principalmente per una questione di tempo.
* Impostazioni generali (2 elementi diversi obbligatori)
* Build mobile: 1pt
* Splash Screen: 1/2pt
* Icona gioco: 1/2pt
 
* Main menu (2 elementi diversi obbligatori)
* Load Game (CheckPoint in mezzo ad un livello): 2pt
* Options:
    * Sound/Music: 1pt
    * Controls: 2pt
    * Resolution/Quality: 1/2pt
* Credits Screen: 1/2pt
* Classifica
    * Ordinata: 2pt
        * Con rimpiazzo dinamico delle entry (Cancellazione score più basso e inserimento nuovo score): 2pt
    * Non ordinata: 1pt
 
* Tutorial
* Schermata statica(3 max): 1/2pt per schermata
 
* GamePlay (3 elementi diversi obbligatori)
* Score: 1pt
* PowerUp/Shop/Bonus giocatore(Max 3): 1pt ogni powerup
* Gioco a tempo:1/2pt
* Presenza di nemici/sfida: 1pt
    * Livelli di difficoltà: 1pt
    * Difficoltà crescente: 1pt
    * AI base (segue il player, spara automaticamente, non ha comportamenti “Intelligenti”, ecc): 1pt
    * AI complicata (NavMesh, comportamenti basati sulle azioni del player,ecc): 3pt
* Multiplayer Locale (2 o più player nello stesso dispositivo): 2pt
* Strutture (8 elementi diversi obbligatori)
* PlayerPrefs: 1/2pt
* Singleton(4 max): 1pt ogni singleton
* Coroutines(4 max): 1/2pt ogni coroutine
* Enums: 1/2pt
* Classi statiche(2 max): 1/2pt ogni classe
* Generics(Anche interfacce): 2pt
* Method overload: 1/2pt
* Presenza di ereditarietà: 1pt
    * Overriding: 1/2pt
* Interfacce(2 max): 1pt ogni interfaccia
* ExtensionMethods(4 max): 1/2pt ogni metodo
* Delegates: 1pt
* Eventi: 1pt
* Animazioni originali(2 max): 1/2pt ogni animazione
* Sound:
    * Soundtrack: 1/2pt
    * Altri suoni (3 max): 1/2pt ogni suono
* Raycast: 1pt
* User Interface(max 6): 1/2pt ogni elemento grafico (nei livelli di gioco, non nel main menu) 

Sottolineare, appunto tutti i punti che sono stati toccati facendone riferimento. I codici che fanno riferimento al gioco sono i seguenti:
“using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using System.Collections;

public class playerHealth : MonoBehaviour
{
    public dissolvenza_Morte1 morte;
    public Animator animator;
    public int maxHealth = 30;
    private int currentHealth;
    private bool isDead = false;

    public Image[] hearts; // Assicuriamoci che questo array sia popolato nell'Inspector

    void Start()
    {
        currentHealth = maxHealth;

        // Trova lo script di dissolvenza collegato, se non assegnato
        if (morte == null)
        {
            morte = FindObjectOfType<dissolvenza_Morte1>();
        }

        // Inizializza i cuori visibili
        UpdateHearts();
    }

    public void takeDamage(int damage)
    {
        if (isDead) return;

        currentHealth -= damage;
        animator.SetTrigger("Hurt");
        Debug.Log("Il nemico ha inflitto " + damage + " danni al giocatore.");

        UpdateHearts();

        if (currentHealth <= 0)
        {
            Die();
        }
    }

    void UpdateHearts()
    {
        // Calcola quanti cuori devono essere visibili
        int heartsToShow = Mathf.CeilToInt((float)currentHealth / (maxHealth / hearts.Length));

        for (int i = 0; i < hearts.Length; i++)
        {
            hearts[i].enabled = i < heartsToShow;
        }
    }

    void Die()
    {
        if (isDead)
        {
            Debug.Log("Il giocatore è già morto, non può morire di nuovo.");
            return;
        }

        isDead = true;
        Debug.Log("Il giocatore è morto.");

        animator.SetBool("Morto", true);
        Collider collider = GetComponent<Collider>();
        if (collider != null)
        {
            collider.enabled = false;
        }

        // Avvia la dissolvenza
        if (morte != null)
        {
            morte.StartFadeToBlack();
        }

        // Cambia scena dopo 2 secondi
        StartCoroutine(WaitAndChangeScene(2f));
    }

    IEnumerator WaitAndChangeScene(float waitTime)
    {
        yield return new WaitForSeconds(waitTime);
        SceneManager.LoadScene(3); // Sostituisci 3 con l'indice o il nome della tua scena di Game Over
    }
}
”,
“using UnityEngine;

public class controller : MonoBehaviour
{
    public float dashSpeed = 15f;
    public float dashDuration = 0.5f;
    public float dashCooldown = 1.5f;
    public float speedSmoothness = 2f;
    public float runspeed;
    private float currentSpeed;
    private bool isDashing = false;
    private float dashTimer = 0f;
    private float dashCooldownTimer = 0f;
    Audio_Manager audioManager;

    private CharacterController characterController;
    public Animator animator;
    private SpriteRenderer spriteRenderer;
    private Player_combat_Mattina playerCombat;

    void Start()
    {
        characterController = GetComponent<CharacterController>();
        spriteRenderer = GetComponent<SpriteRenderer>();
        currentSpeed = runspeed;
        playerCombat = GetComponent<Player_combat_Mattina>();
        audioManager = GameObject.FindGameObjectWithTag("Audio").GetComponent<Audio_Manager>();
    }

    void Update()
    {
        if(!playerCombat.isAttacking){
            HandleMovement();
            DashControl();
        }
    }

    void HandleMovement()
    {
        float x = Input.GetAxis("Horizontal");
        float z = Input.GetAxis("Vertical");

        Vector3 move = new Vector3(x, 0, z);
        characterController.Move(move * Time.deltaTime * currentSpeed);

        float speed = move.magnitude * currentSpeed;
        animator.SetFloat("speed", speed);

        if (speed > 0.1f) // Minimizza il rumore degli effetti sonori
        {
            audioManager.PlaySFX(audioManager.corsa);
        }

        if (x != 0)
        {
            spriteRenderer.flipX = x < 0f;
        }
    }

    void DashControl()
    {
        if (dashCooldownTimer > 0)
        {
            dashCooldownTimer -= Time.deltaTime;
        }

        if (Input.GetKeyDown(KeyCode.LeftShift) && dashCooldownTimer <= 0 && !isDashing && !playerCombat.isAttacking)
        {
            StartDash();
        }

        if (isDashing)
        {
            dashTimer += Time.deltaTime;

            if (dashTimer >= dashDuration)
            {
                EndDash();
            }
            else
            {
                // Continua a muovere durante il dash
                float x = Input.GetAxis("Horizontal");
                float z = Input.GetAxis("Vertical");
                Vector3 move = new Vector3(x, 0, z);
                characterController.Move(move * Time.deltaTime * dashSpeed);
            }
        }
        else
        {
            currentSpeed = Mathf.Lerp(currentSpeed, runspeed, speedSmoothness * Time.deltaTime);
        }
    }

    void StartDash()
    {
        isDashing = true;
        currentSpeed = dashSpeed;
        dashTimer = 0f;
        dashCooldownTimer = dashCooldown;

        // Puoi aggiungere effetti visivi o suoni per il dash qui
    }

    void EndDash()
    {
        isDashing = false;
    }
}
”,
“using UnityEngine;
using UnityEngine.UI;

public class Player_combat_Mattina : MonoBehaviour
{
    public Animator animator;
    public Transform attackPoint;
    public float attackRate = 2f;
    private float nextAttackTime = 0f;
    public float attackRange = 0.5f;
    public LayerMask enemyLayers;
    private int attackDamage = 40;
    public Image fadeImage;
    public float fadeDuration = 2f;

    private Vector3 attackOffset;
    public bool isAttacking = false;

    void Start()
    {
        // Inizializza l'offset dell'attacco
        attackOffset = attackPoint.localPosition;
    }

    void Update()
    {
        if (!isAttacking)
        {
            GestisciDirezioneAttacco();
            GestisciAttacchi();
        }
    }

    void GestisciDirezioneAttacco()
    {
        float horizontalInput = Input.GetAxis("Horizontal");
        if (horizontalInput < -0.1f)
        {
            // Sposta l'attackPoint a sinistra
            attackPoint.localPosition = new Vector3(-Mathf.Abs(attackOffset.x), attackOffset.y, attackOffset.z);
        }
        else if (horizontalInput > 0.1f)
        {
            // Sposta l'attackPoint a destra
            attackPoint.localPosition = new Vector3(Mathf.Abs(attackOffset.x), attackOffset.y, attackOffset.z);
        }
    }

    void GestisciAttacchi()
    {
        if (Time.time >= nextAttackTime)
        {
            if (Input.GetKeyDown(KeyCode.J))
            {
                Attacca("Attack");
            }
            else if (Input.GetKeyDown(KeyCode.K))
            {
                Attacca("easy_Attack");
            }
        }
    }

    void Attacca(string tipoAttacco)
    {
        isAttacking = true;
        animator.SetTrigger(tipoAttacco);
        nextAttackTime = Time.time + 1f / attackRate;
        // Il danno verrà applicato tramite l'evento di animazione
    }

    /// Chiamata dall'evento di animazione per applicare il danno.
    public void ApplyDamage()
    {
        // Trova i nemici colpiti all'interno del raggio d'attacco
        Collider[] hitEnemies = Physics.OverlapSphere(attackPoint.position, attackRange, enemyLayers);

        // Infligge danno ai nemici colpiti
        foreach (Collider enemy in hitEnemies)
        {
            Enemy enemyComponent = enemy.GetComponent<Enemy>();
            if (enemyComponent != null)
            {
                enemyComponent.TakeDamage(attackDamage);
                Debug.Log("Il giocatore ha inflitto " + attackDamage + " danni al nemico.");
            }
        }
    }

    /// Chiamata dall'evento di animazione alla fine dell'attacco.
    public void EndAttack()
    {
        isAttacking = false;
    }

    void OnDrawGizmosSelected()
    {
        if (attackPoint == null) return;

        // Disegna la sfera che rappresenta l'area d'attacco
        Gizmos.color = Color.red;
        Gizmos.DrawWireSphere(attackPoint.position, attackRange);
    }
}
”,
“using UnityEngine;
using System.Collections;

public class enemySpawner : MonoBehaviour
{
    [System.Serializable]
    public class EnemyType
    {
        public GameObject enemyPrefab;    // Prefab del nemico
        public int numberOfEnemies = 6;   // Numero di nemici da generare
        public float startDelay = 0f;     // Ritardo prima di iniziare a spawnare questo tipo
    }

    public EnemyType[] enemyTypes;        // Array dei tipi di nemici

    // Confini della mappa
    public float minX, maxX, minZ, maxZ;
    public float yPosition = 0f;

    public float minSpawnDelay = 0.5f;
    public float maxSpawnDelay = 2f;

    private void Start()
    {
        int totalEnemies = 0;
        foreach (var enemyType in enemyTypes)
        {
            totalEnemies += enemyType.numberOfEnemies;
        }

        // Aggiunge il numero totale di nemici all'EnemyManager
        EnemyManager.instance.AddEnemies(totalEnemies);

        StartCoroutine(SpawnEnemies());
    }

    private IEnumerator SpawnEnemies()
    {
        foreach (var enemyType in enemyTypes)
        {
            // Attendi il ritardo specificato per questo tipo di nemico
            yield return new WaitForSeconds(enemyType.startDelay);

            // Genera i nemici di questo tipo
            for (int i = 0; i < enemyType.numberOfEnemies; i++)
            {
                SpawnEnemy(enemyType.enemyPrefab);
                yield return new WaitForSeconds(Random.Range(minSpawnDelay, maxSpawnDelay));
            }
        }
    }

    private void SpawnEnemy(GameObject enemyPrefab)
    {
        // Genera una posizione casuale entro i confini
        float randomX = Random.Range(minX, maxX);
        float randomZ = Random.Range(minZ, maxZ);
        Vector3 spawnPosition = new Vector3(randomX, yPosition, randomZ);

        // Instanzia il nemico
        GameObject enemy = Instantiate(enemyPrefab, spawnPosition, Quaternion.identity);

        // Collega il nemico all'EnemyManager
        enemy.GetComponent<Enemy>().OnDeath += EnemyKilled;
    }

    private void EnemyKilled()
    {
        EnemyManager.instance.EnemyKilled();
    }
}
”,
“using UnityEngine;
using UnityEngine.AI;

public class Enemy : MonoBehaviour
{
    protected Audio_Manager audioManager;
    public Animator animator;
    public int maxHealth = 180;
    protected int currentHealth;
    protected NavMeshAgent agent;

    public delegate void EnemyEventHandler();
    public event EnemyEventHandler OnEnemyKilled;
    public event EnemyEventHandler OnDeath;

    protected virtual void Start()
    {
        currentHealth = maxHealth;
        agent = GetComponent<NavMeshAgent>();
        audioManager = FindObjectOfType<Audio_Manager>();
    }

    public virtual void TakeDamage(int damage)
    {
        currentHealth -= damage;
        animator.SetTrigger("Hurt");
        audioManager.PlaySFX(audioManager.hit_enemies);

        if (currentHealth <= 0)
        {
            Die();
        }
    }

    public virtual void MoveTowardsPlayer(Vector3 playerPosition)
    {
        if (agent != null)
        {
            agent.SetDestination(playerPosition);
        }
    }

    protected virtual void Die()
    {
        animator.SetBool("IsDead", true);
        audioManager.PlaySFX(audioManager.death_enemies);

        OnDeath?.Invoke();
        OnEnemyKilled?.Invoke();

        BoxCollider collider = GetComponent<BoxCollider>();
        if (collider != null)
        {
            collider.enabled = false;
        }

        if (agent != null)
        {
            agent.enabled = false;
        }

        Destroy(gameObject, 1f);
    }
}
”,
“using UnityEngine;
using UnityEngine.AI;
using System.Collections;

public class ChaseEnemy : MonoBehaviour
{
    public Transform target;
    protected NavMeshAgent agent;
    protected SpriteRenderer spriteRenderer;
    public Animator animator;

    protected Coroutine attackCoroutine;

    public Transform attackPoint;
    public float attackRange = 2f;
    public LayerMask targetLayers;

    public float chaseRange = 10f;
    public float attackCooldown = 1.5f;
    public int attackDamage = 10;

    protected Vector3 attackPointOffset;

    public float wanderRadius = 5f;
    public float wanderCooldown = 3f;
    protected float wanderTimer = 0f;

    protected virtual void Start()
    {
        agent = GetComponent<NavMeshAgent>();
        spriteRenderer = GetComponent<SpriteRenderer>();
        animator = GetComponent<Animator>();
        attackPointOffset = attackPoint.localPosition;
        wanderTimer = wanderCooldown;
    }

    protected virtual void Update()
    {
        if (target == null) return;

        float distance = Vector3.Distance(transform.position, target.position);

        if (distance <= chaseRange)
        {
            if (distance > attackRange)
            {
                StopAttackCoroutine();
                ChaseTarget();
            }
            else
            {
                agent.ResetPath();
                AttackBehavior();
            }

            FlipSprite();
        }
        else
        {

            Wander();
        }
    }

    protected virtual void ChaseTarget()
    {
        if (agent != null && target != null)
        {
            agent.SetDestination(target.position);
            animator.SetBool("running", true);
        }
    }

    protected virtual void AttackBehavior()
    {
        animator.SetBool("running", false);

        if (attackCoroutine == null)
        {
            attackCoroutine = StartCoroutine(AttackRoutine());
        }
    }

    protected virtual IEnumerator AttackRoutine()
    {
        while (true)
        {
            Attack();
            yield return new WaitForSeconds(attackCooldown);

            float distance = Vector3.Distance(transform.position, target.position);
            if (distance > attackRange || target == null) break;
        }

        attackCoroutine = null;
    }

    protected virtual void Attack()
    {
        animator.SetTrigger("attack");
        // Damage will be applied via ApplyDamage called from an animation event
    }

    // This method will be called from an animation event
    public virtual void ApplyDamage()
    {
        Collider[] hitTargets = Physics.OverlapSphere(attackPoint.position, attackRange, targetLayers);

        foreach (Collider hitTarget in hitTargets)
        {
            if (hitTarget.TryGetComponent<PozzoHealth>(out PozzoHealth targetHealth))
            {
                targetHealth.takeDamage(attackDamage);
            }
        }
    }

    protected virtual void FlipSprite()
    {
        Vector3 direction = target.position - transform.position;
        if (direction.x < 0)
        {
            spriteRenderer.flipX = false;
            attackPoint.localPosition = new Vector3(-Mathf.Abs(attackPointOffset.x), attackPointOffset.y, attackPointOffset.z);
        }
        else if (direction.x > 0)
        {
            spriteRenderer.flipX = true;
            attackPoint.localPosition = new Vector3(Mathf.Abs(attackPointOffset.x), attackPointOffset.y, attackPointOffset.z);
        }
    }

    protected virtual void StopAttackCoroutine()
    {
        if (attackCoroutine != null)
        {
            StopCoroutine(attackCoroutine);
            animator.ResetTrigger("attack");
            attackCoroutine = null;
        }
    }

    protected virtual void Wander()
    {
        wanderTimer += Time.deltaTime;
        if (wanderTimer >= wanderCooldown)
        {
            Vector3 newPos = RandomNavSphere(transform.position, wanderRadius, -1);
            agent.SetDestination(newPos);
            wanderTimer = 0f;
        }

        if (agent.velocity.magnitude > 0.1f)
        {
            animator.SetBool("running", true);
        }
        else
        {
            animator.SetBool("running", false);
        }
    }

    public static Vector3 RandomNavSphere(Vector3 origin, float dist, int layermask)
    {
        Vector3 randomDirection = Random.insideUnitSphere * dist;
        randomDirection += origin;
        NavMesh.SamplePosition(randomDirection, out NavMeshHit navHit, dist, layermask);
        return navHit.position;
    }

    protected virtual void OnDrawGizmosSelected()
    {
        if (attackPoint != null)
        {
            Gizmos.color = Color.red;
            Gizmos.DrawWireSphere(attackPoint.position, attackRange);
        }

        Gizmos.color = Color.blue;
        Gizmos.DrawWireSphere(transform.position, chaseRange);
    }
}
”,
“using System.Collections;
using UnityEngine;

public class Audio_Manager : MonoBehaviour
{
    [SerializeField] private AudioSource musicsource;    // AudioSource per la musica di sottofondo
    [SerializeField] private AudioSource otherSounds;    // AudioSource per altri suoni
    [SerializeField] private AudioSource musicSFX;       // AudioSource per effetti sonori

    public AudioClip background;            // Clip audio per la musica di sottofondo
    public AudioClip background_sounds;     // Clip audio per altri suoni
    public AudioClip corsa;  
    public AudioClip death_enemies;
    public AudioClip death_player;
    public AudioClip hit_enemies;
    public AudioClip hit_player;
    public AudioClip water;
    public AudioClip Versi;

    public float fadeDuration = 2f;  // Durata della dissolvenza in secondi

    public static Audio_Manager Instance { get; private set; }

    void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }

    public void PlayBackgroundMusic()
    {
        if (background != null && background_sounds != null && musicsource != null && otherSounds != null)
        {
            otherSounds.clip = background_sounds;
            musicsource.clip = background;
            musicsource.Play();
            otherSounds.Play();
        }
        else
        {
            Debug.LogWarning("Uno o più clip audio o sorgenti audio non sono assegnati.");
        }
    }

    public void PlaySFX(AudioClip clip)
    {
        if (musicSFX != null && clip != null)
        {
            musicSFX.PlayOneShot(clip);
        }
        else
        {
            Debug.LogWarning("La sorgente audio musicSFX o il clip non è assegnato.");
        }
    }

    public void FadeOutMusic()
    {
        StartCoroutine(FadeOutRoutine());
    }

    private IEnumerator FadeOutRoutine()
    {
        float startVolume = musicsource.volume;

        for (float t = 0; t < fadeDuration; t += Time.deltaTime)
        {
            musicsource.volume = Mathf.Lerp(startVolume, 0, t / fadeDuration);
            yield return null;
        }

        musicsource.volume = 0;
        musicsource.Stop();
    }
}
”,
“using UnityEngine;

public class FollowCamera : MonoBehaviour
{
    public Camera mainCamera;    // La telecamera principale
    public Vector3 offset;       // Offset rispetto alla telecamera
    public float inFrontDistance = 0.1f; // Distanza per essere davanti

    void Start()
    {
        // Trova la telecamera principale se non assegnata
        if (mainCamera == null)
        {
            mainCamera = Camera.main;
        }
    }

    void LateUpdate()
    {
        if (mainCamera != null)
        {
            // Calcola la nuova posizione leggermente davanti alla telecamera con offset
            Vector3 newPos = mainCamera.transform.position + mainCamera.transform.forward * inFrontDistance + mainCamera.transform.TransformVector(offset);
            transform.position = newPos;

            // Mantiene l'oggetto rivolto verso la telecamera
            transform.rotation = Quaternion.LookRotation(transform.position - mainCamera.transform.position);
        }
    }
}
”. Non sono tutti i codici, ma quelli principali.