Hello world - Unity - Code guidelines

Di seguito alcune considerazioni piu’ specifiche sul codice, unity e l’organizzazione del lavoro.

Stile, convenzioni indentazione

Legenda:

Da non fare assolutamente
Forse, ma meglio persarci bene
Ok

Pattern and practices


  • Variabili pubbliche (escluse readonly e const)
  • Iteratori a frame time (sono piu’ lenti e allocano, usate i for a frame time, possibilmente)
  • Over-Engineering (Unity e’ un framework ad altissimo livello. E’ molto raro che vi troviate a scrivere architetture complesse di componenti, almeno in una prima fase. Se sentite la necessita’ di usare tante classi da subito e strutture complesse, con buona probabilita’ esiste una soluzione piu’ semplice, magari gia’ implementata nel framework)
  • Statement e modificatori esotici: GOTO, ref & out parameters, tutte cose che rompono incapsulamento e OOP. Evitare!
  • Reflection: macigno sulle performance, inutile nel 99% dei casi (salvo che scriviate un IDE o un sistema di IoC). Vietatissima
  • [De]Allocare memoria a frame time (es. dentro update)
  • Confronti di stringhe usati come id: male male male. Laddove non indispensabile(tag di unity) usare enum o hashare le stringhe in interi.
  • Ereditarieta’: ci sono svariate ragioni per starci alla larga (o almeno non abusarne), in particolare (problemi serializzazione, male integrate in un sistema component based). Se sentite l’esigenza, cercate di capire le motivazioni alla base.
  • Interfacce: ottimo programmare contro le interfacce, ma molta attenzione perche’ unity non serializza. Ottimo usarle in contesti dove la serializzazione non e’ necessaria, altrimenti servono le dovute precauzioni.
  • Pattern Creazionali: per tutto quello che e’ in scena esistono i prefab. Spesso usare AbstractFactory o Builder e’ una complicazione inutile. I prefab sono Prototype. Può avere senso in determinati contesti, ma va dimostrato il reale vantaggio.
  • Linq: aumenta molto semplicità e la leggibilità del codice, MA alloca (see. immutable data structure). Usare con cautela a frame time, si in fase di inizializzazione oggetti
  • Delegates ed eventi: bellissimi usiamoli al posto di SendMessage, ma ricordatevi di deregistrarli. Attenzione quando entrano in gioco varianza e covarianza dei parametri, assicuratevi di avere capito bene.
  • Prefisso I davanti interfacce IQuestaEUnInterfaccia
  • Qualche commento (non troppi eventualmente Doxygen) dove vengono fatte scelte particolari non direttamente evidenti dal codice, TODO, ...
  • Nomi funzioni e classi significativi e autoesplicativi (meglio di troppi commenti)
  • Nomi funzioni, classi commenti in inglese
  • Property C# per getter/setter
  • Principle of Lest privilege
  • KISS

Unity Specific


  • Usare SendMessage(inefficente, no type safe)
  • Aggiornare la propria versione di unity prima di una decisione collettiva e relativi test
  • GameObject.Find a frame time
  • Destroy/Instantiate a frame time (privilegiate pool di oggetti pre-instanziati, attivate/disattivate)
  • Usare la cartella Resources senza valide ragioni
  • Proliferazione di branch nelle callback (Update/LateUpdate/FixedUpdate): per condizioni temporanee o sporadiche usare FSM o meglio coroutines.
  • Valori di ritorno di una coroutine che causano boxing: se non state scrivendo un sistema di estensioni delle coroutine dovreste avere solo yield return null o yield break.
  • Custom Inspector: decisamente si quando servono, MA attenzione a implementarli correttamente (controllate sempre Undo/Redo e che la serializzazione avvenga corretamente)
  • Prefab: assolutamente si quando servono, ma considerate che non esistono i nested prefab.
  • Serializzare reference ad altri oggetti della stessa scena (evita di scrivere codice in Awake/Start)
  • Caching di reference ad altri components o GameObject durante inizializzazione (Awake/Start): ottimo se non si puo’ serializzare la reference o in particolare contesti dove è più comodo
  • Modularizzazione componenti: Unity è component based. Il fatto che l’update dei vari behavior non abbiano un ordine prestabilito è una scelta di design precisa. Sottolinea come i vari oggetti devono essere per lo più indipendenti. Cercate di minimizzare le reference tra componenti/oggetti in maniera che ogni entità sia testabile il più possibile isolata dal contesto.
  • Scene di test: laddove unit testing siano improbabili (vd.gameplay) è comunque utile avere un modo semplice per verificare che un oggetto funzioni, testandolo in un contesto isolato. Es. creo un nuovo nemico e una scena di test semplificata che ne illustra comportamento. E’ utile agli altri a capire come funziona, a trovare bug, ad assicurarsi che dopo modifiche al codice funzioni ancora tutto. Non vale la pena per tutto ma certe volte fa comodo.

Ottimizzazioni


In linea di principio vale la regola di non ottimizzare se non sapete esattamente cosa state facendo. Quindi prima (e dopo)  interventi estremi, profilare prima di eventuali assunzioni ardite su cosa sia più o meno performante.

Alcune cose generiche di buon senso:

  • Minimizzare numero collider
  • Semplificare forme collider (sfere se non c’e’ ragione significativa per usare altro)
  • Minimizzare uso della fisica: per un moto rettilineo uniforme (o accelerato) senza collisioni scrivetevi il codice.
  • Ponderare il numero di GameObject
  • Semplificare matrice layer collisioni
  • Minimizzare raycast
  • Profilare  su device dopo ogni aggiunta/modifica non banale
  • Usare callback per operazioni saltuarie:
  • Branch negli shader
  • Allocazioni gratuite: specialmente a frame time. Evitate di dare lavoro al GC.

Usare Property getter all’interno della classe che dichiara il campo: una property equivale a una chiamata a metodo, se sono all’interno della stessa classe preferire l’accesso in lettura tramite la variabile.

Commenti

Post popolari in questo blog

Indovinello: Soluzioni!!!

Gli indovinelli che ci avete mandato 10