Abbiamo già visto come instanziare un oggetto (prefab) alla pressione di un tasto sull’articolo riguardante i prefabs.
using UnityEngine;
public class Player : MonoBehaviour {
public GameObject PrefabOriginale;
void Spara () {
//Istanzia l'oggetto proiettile, creando una copia del PrefabOriginale impostando la posizione e la rotazione
GameObject proiettile = GameObject.Instantiate(PrefabOriginale,transform.position,Quaternion.identity);
}
void Update()
{
// Alla pressione di Fire1, esegui il metodo Spara()
if (Input.GetButtonDown("Fire1"))
{
Spara();
}
}
}
Questa funzione ci può tornare utile per creare un sistema di “fuoco” di un’arma. Il codice visto in precedenza (sull’articolo riguardante i prefabs) è però troppo generico e rudimentale perché possa essere considerato realmente utilizzabile se non per un tipo di sparo singolo.
Ora creeremo un sistema che istanzierà un proiettile alla pressione del tasto “Fire1″ e faremo in modo che tenendo premuto il tasto la nostra arma sparerà di continuo, colpo dopo colpo, con una cedenza da noi stabilita.
Ma non solo… faremo in modo anche che il nostro caricatore abbia un numero limitato di colpi e ogni raffica effettuata tenendo premuto “Fire1” sia così contenuta entro un certo numero di proiettili dopo i quali bisognerà rilasciare il pulsante per rendere possibile la raffica successiva. Simuleremo così il riscaldamento dell’arma o se preferite un effetto “UZI” a raffica controllata.
Potremmo poi cambiare i valori a nostro piacimento per simulare qualsiasi tipo di arma, ad un colpo singolo, a raffica controllata o a raffica continua.
Teniamo sempre presente la differenza tra GetButton e GetButtonDown.
Iniziamo creando lo script che gestisce le munizioni dell’arma che chiamerò ArmaControl.
Sarebbe buona usanza usare sempre nomi di variabili in inglese, ma noi, visto che siamo qui per imparare, solo per scopo didattico e per una comprensione più immediata, useremo anche qualche parola in italiano.
Questo script è da posizionarsi su una singola arma, così da poter creare anche diverse tipologie di armi, ognuna con un fire-rate differente.
using UnityEngine;
public class ArmaControl : MonoBehaviour
{
public float fireRate = 0.5f; //Tempo tra un colpo e l'altro
public bool recargeOnMouseUp=false; //Se true, quando si rilascia il tasto di fuoco ricarica la raffica senza attesa
public int maxAmmoRaffica = 5; //Colpi di una singola raffica
public float pauseTime; //Tempo di attesa tra una raffica e l'altra
public int ammoQuantity = 20; //Quantità di munizioni totali
int ammoRaffica; //Munizioni attuali
float nextFire; //Prossimo colpo pronto
float pauseTimer; //Il timer della pausa
float cooldown; //Cantatore di colpi
bool pause =false; //In pausa
void Start()
{
ammoRaffica = maxAmmoRaffica; //All'inizio il caricatore è pieno
}
void Update()
{
if (!pause && ammoQuantity>0) //Se è tra una raffica e l'altra non sparare
{
if (fireRate == 0 && Input.GetButtonDown("Fire1"))
{ //Il primo colpo lo effettua senza dover attendere nulla
Shoot();
}
else
{
if (Input.GetButton("Fire1") && Time.time > nextFire && fireRate > 0) //Pronto per sparare
{
//Colpi successivi, tenendo premuto il pulsante
if (ammoRaffica > 0)
{ //Se ci sono munizioni
nextFire = Time.time + fireRate;
Shoot(); //Esegue la funzione di "sparo"
}
if (ammoRaffica == 0)
{ //Se non ci sono più munizioni
if (cooldown > Time.time)
{ //Se non è passato il tempo giusto
cooldown = Time.time + fireRate;
}
}
}
}
if (Time.time > cooldown && ammoRaffica == 0)
{ //Se il tempo di recupero (cooldown) è finito e le munizioni sono terminate
pause = true;
}
}
//Se si è scelto di far ricaricare al rilascio del tasto
if(recargeOnMouseUp && Input.GetButtonUp("Fire1")){
pauseTimer = 0;
pause = false;
if (ammoQuantity >= maxAmmoRaffica)//Se ci sono abbastanza i proiettili
ammoRaffica = maxAmmoRaffica; //Ricarico per la prossima raffica
else
ammoRaffica = ammoQuantity; //Se non sono i proiettili metti quelli disponibili
}
//Se in pausa, fai il conteggio del tempo tra una raffica e l'altra
if (pause)
ShootPause();
} //Fine di Update()
//Metodo che fa il conteggio del tempo tra una scarica e l'altra
void ShootPause()
{
pauseTimer += Time.deltaTime;
if (pauseTimer >= pauseTime)
{
pauseTimer = 0;
pause = false;
if(ammoQuantity>= maxAmmoRaffica)//Se ci sono abbastanza i proiettili
ammoRaffica = maxAmmoRaffica; //Ricarico per la prossima raffica
else
ammoRaffica = ammoQuantity; //Se non sono i proiettili metti quelli disponibili
}
}
void Shoot() {
ammoRaffica--; //Rimuove un colpo della raffica ad ogni sparo
ammoQuantity--; //Rimuove un colpo dalla quantità in possesso
print("Shooooot");//Spara!
}
} //Chiusura classe
A questo punto abbiamo uno script che ci permette di settare diversi parametri.
Se per esempio non vogliamo il “sistema a scariche”, basterà impostare su 0 il pauseTime.
Abbiamo inserito anche un parametro (ammoQuantity) per gestire il numero di munizioni a disposizione del giocatore, terminate le quali non sarà più possibile sparare.
Fino a qui abbiamo creato la gestione dei colpi con cui poter creare diversi tipi di “reazione” alla pressione del nostro “grilletto”.
Il metodo Spara() fin’ora non fa altro che scrivere in console la stringa Shooooot che potremmo contare per verificare che tutto funzioni correttamente.
Ora lavoriamo sul metodo Spara() per creare il nostro proiettile ed anche un effetto “fiammata” sul punto d’uscita della canna della nostra arma.
Per fare le cose per bene andremo anche ad inserire un suono che si esegue al momento dello sparo.
Dovremmo prima di tutto inserire la variabile del prefab che identifica il proiettile, poi dovremo identificare il punto di creazione del proiettile che sarà lo stesso del punto di creazione dell’ effetto fiamma e del suono.
Per fare tutto questo si potrebbero percorrere tante strade differenti, noi useremo quella che secondo me è quella più utile a scopo didattico.
1. Creiamo un arma (Arma) , nel nostro esempio un semplice parallelepipedo.
2. Creaimo un oggetto che funga da proiettile (Proiettile) , nel nostro caso un semplice cubo. Il gameObject Proiettile non deve essere presente nella scena, ma solo in una cartella del progetto, servirà come prefab da istanziare nel momento dello sparo.
3. Creiamo un gameObject vuoto (ShotPoint) e lo mettiamo come children al gameObject dell’arma, posizionandolo là dove vorremmo far “uscire” il proiettile, in cima alla “canna” dell’arma. ShotPoint sarà anche il punto in cui verrà visualizzato l’effetto “lampo/fiammata” dell’arma.
Se vogliamo aggiungere anche un effetto “lampo/fiammata” allo sparo, creiamo un Particle System e usiamolo come prefab da generare insieme al proiettile.
Ricordiamoci di impostare su la sua Stop Action su “destroy” in modo che l’effetto sia automaticamente distrutto una volta completato (le impoostazioni del Particle System le potete vedere scaricando il package del tutorial).
NOTA SULLE PRESTAZIONI: Non solo l’istanzaimento del Particle System potrebbe essere evitato… ma persino quello dei proiettili! |
using UnityEngine;
public class ArmaControl : MonoBehaviour
{
//Variabili pubbliche
public float fireRate = 0.5f; //Tempo tra un colpo e l'altro
public bool recargeOnMouseUp=false; //Se true, quando si rilascia il tasto di fuoco ricarica la raffica senza attesa
public int maxAmmoRaffica = 5; //Colpi di una singola raffica
public float pauseTime; //Tempo di attesa tra una raffica e l'altra
public int ammoQuantity = 20; //Quantità di munizioni totali
public GameObject proiettilePrefab;//Il prefab del proiettile
public GameObject flashEffectPrefab;//Il prefab dell'effetto flash/fiammata
public Transform shotPoint; //Il trasform che identifica il punto di creazione dei proiettili
public AudioClip shotSound; //Il suono dello sparo
//Variabili private
int ammoRaffica; //Munizioni attuali
float nextFire; //Prossimo colpo pronto
float pauseTimer; //Il timer della pausa
float cooldown; //Cantatore di colpi
bool pause =false; //In pausa
AudioSource audioSource; //L'audioSource che eseguirà il suono
void Start()
{
AudioSourceCreation();//Crea l'AudioSource (se non presente)
ammoRaffica = maxAmmoRaffica; //All'inizio il caricatore è pieno
}
//Creiamo l'AudioSource a runtime, senza doverlo inserire manualmente
void AudioSourceCreation() {
//Se non è stato creato manualmente
if (!GetComponent())
{
audioSource = gameObject.AddComponent(); //Crea l'AdudioSource
}
else
{
audioSource = GetComponent(); //Se già esisteva usa quello esistente
}
//Notare che si può creare l'audioSource anche manualmente, in modo da impostare i parametri
//i parametri (come il volume, il pitch ed altri) direttamente da Inspector
//In alternativa potete anche impostarli qui, nel momento della creazione, scegliendo i parametri
//a runtime (audioSource.volume=1, audioSource.pitch=1, ecc.)
}
void Update()
{
if (!pause && ammoQuantity>0) //Se è tra una raffica e l'altra non sparare
{
if (fireRate == 0 && Input.GetButtonDown("Fire1"))
{ //Il primo colpo lo effettua senza dover attendere nulla
Shoot();
}
else
{
if (Input.GetButton("Fire1") && Time.time > nextFire && fireRate > 0) //Pronto per sparare
{
//Colpi successivi, tenendo premuto il pulsante
if (ammoRaffica > 0)
{ //Se ci sono munizioni
nextFire = Time.time + fireRate;
Shoot(); //Esegue la funzione di "sparo"
}
if (ammoRaffica == 0)
{ //Se non ci sono più munizioni
if (cooldown > Time.time)
{ //Se non è passato il tempo giusto
cooldown = Time.time + fireRate;
}
}
}
}
if (Time.time > cooldown && ammoRaffica == 0)
{ //Se il tempo di recupero (cooldown) è finito e le munizioni sono terminate
pause = true;
}
}
//Se si è scelto di far ricaricare al rilascio del tasto
if(recargeOnMouseUp && Input.GetButtonUp("Fire1")){
pauseTimer = 0;
pause = false;
if (ammoQuantity >= maxAmmoRaffica)//Se ci sono abbastanza i proiettili
ammoRaffica = maxAmmoRaffica; //Ricarico per la prossima raffica
else
ammoRaffica = ammoQuantity; //Se non sono i proiettili metti quelli disponibili
}
//Se in pausa, fai il conteggio del tempo tra una raffica e l'altra
if (pause)
ShootPause();
} //Fine di Update()
//Metodo che fa il conteggio del tempo tra una raffica e l'altra
void ShootPause()
{
pauseTimer += Time.deltaTime;
if (pauseTimer >= pauseTime)
{
pauseTimer = 0;
pause = false;
if(ammoQuantity>= maxAmmoRaffica)//Se ci sono abbastanza i proiettili
ammoRaffica = maxAmmoRaffica; //Ricarico per la prossima raffica
else
ammoRaffica = ammoQuantity; //Se non sono i proiettili metti quelli disponibili
}
}
void Shoot() {
ammoRaffica--; //Rimuove un colpo della raffica ad ogni sparo
ammoQuantity--; //Rimuove un colpo dalla quantità in possesso
//Istanzia l'oggetto proiettile, creando una copia del proiettilePrefab impostando la posizione e la rotazione
GameObject proiettile = Instantiate(proiettilePrefab, shotPoint.position, shotPoint.rotation);
//Notare che impostiamo la posizione di origine e la rotazione inziale uguali a quelle del trasform shotPoint
//Secondo istanziamento di un gameObject, necessario per l'effetto "fiammata"
GameObject flashEffect = Instantiate(flashEffectPrefab, shotPoint.position, shotPoint.rotation);
//Notare che si potrebbe usare anche un'altra tecnica, meno dispendiosa in termini di prestazioni
//che vedremo in seguito
if (shotSound)//Assicuriamoci che c'è un suono scelto per il rumore dello sparo
audioSource.PlayOneShot(shotSound); //Esegui il suono
print("Shooooot");//Spara!
}
} //Chiusura classe
A questo punto sta a voi dare dare velocità al proiettile, altrimenti tutti i proiettili saranno generati nel punto “shotPoint” senza muoversi, accumulandosi in cima alla canno dell’arma.
Per fare questo dovrete dotare il prefab proiettile di uno script apposito, che lo faccia muovere appena generato e che lo faccia autodistruggere dopo un tot di tempo, altre che ovviamente ad un sistema di collisione che rilevi quando il proiettile va a sengno con un nemico o altro.
Potremmo dare forza al proiettile anche direttamente dallo script dell’arma, ma è buona norma dividere le cose in modo da poter gestire i proiettili individualmente.
Questo argomento è influenzato da molti aspetti soggettivi che dipendono da come si vuol far interagire i proiettili con in mondo circostante e con i nemici. Esistono diverse tecniche per muovere gli oggetti, dotandoli di rigidBody o muovendoli da Transform.
using UnityEngine;
public class Proiettile : MonoBehaviour {
public Rigidbody rig;
public float speed=100f;
void Start () {
}
void Update () {
rig.AddForce(transform.forward* speed);
}
}
Nel package del tutorial troverete un sistema molto semplice per muovere i proiettili, a voi modificarlo ed espanderlo a vostro piacimento.
Tutorial Package: Arma Tutorial.