Download - Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Transcript
Page 1: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament devideojocs amb UnityAlejandro Santiago, Xavier Belanche

Desenvolupament d’entorns interactiusmultidispositiu

Page 2: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu
Page 3: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu Desenvolupament de videojocs amb Unity

Índex

Introducció 5

Resultats d’aprenentatge 7

1 Desenvolupament de videojocs amb Unity (Part 1) 91.1 El GDD i la fase de producció . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91.2 Consideracions prèvies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11

1.2.1 El codi font . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111.2.2 Optimització de Unity i organització de les escenes . . . . . . . . . . . . . . . . . . . 12

1.3 Configurar el projecte base . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141.4 El disseny del videojoc ’Ellen and the lost IOCStudents’ . . . . . . . . . . . . . . . . . . . . 151.5 ’GameManager’, ’LevelManager’ i classes del tipus ’Singleton’ . . . . . . . . . . . . . . . . . 17

1.5.1 El ’GameManager’: el cicle complet del videojoc . . . . . . . . . . . . . . . . . . . . 171.5.2 El ’Singleton’: accés global i persistent a un objecte . . . . . . . . . . . . . . . . . . 231.5.3 El ’LevelManager’: encapsular tota la lògica de control d’un nivell . . . . . . . . . . 26

1.6 ’Input’ del nostre videojoc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 291.6.1 Configurar l”input’ a Unity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 301.6.2 Configuració dels botons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 331.6.3 El control de l”input’ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35

1.7 Crear les diferent escenes del videojoc (’Splash’ i ’Menú principal’) . . . . . . . . . . . . . . 391.7.1 Elaborar el "Menú principal" . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41

2 Desenvolupament de videojocs amb Unity (Part 2) 472.1 Creació de l’escena de joc o ’Level01’ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 472.2 Creació de l’escena de resum de nivell o ’LevelReport’ . . . . . . . . . . . . . . . . . . . . . 492.3 Els esdeveniments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 512.4 Creació del personatge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56

2.4.1 Gestionar el moviment del personatge amb ’CharacterController’ . . . . . . . . . . . 572.4.2 El ’Player Input’ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 582.4.3 El controlador del jugador o ’PlayerController’ . . . . . . . . . . . . . . . . . . . . . 612.4.4 ’Animator Controller’ del personatge . . . . . . . . . . . . . . . . . . . . . . . . . . 622.4.5 Les funcions ’Reset’, ’Awake’, ’OnEnable’ i ’OnDisable’ . . . . . . . . . . . . . . . . 652.4.6 El ’FixedUpdate’ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 662.4.7 Rebre impactes o ’Damage’ en el nostre jugador . . . . . . . . . . . . . . . . . . . . 672.4.8 Una petita millora: subdividir les habilitats . . . . . . . . . . . . . . . . . . . . . . . 71

2.5 Creació dels enemics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 732.5.1 Els components del ’Chomper’ (enemic) . . . . . . . . . . . . . . . . . . . . . . . . 742.5.2 L”script EnemyController’ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 752.5.3 El subsistema d’UI a Unity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 762.5.4 Dissenyar la UI del videojoc ’Ellen and the lost IOCstudents’ . . . . . . . . . . . . . 78

2.6 Conclusions finals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81

Page 4: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu Desenvolupament de videojocs amb Unity

3 Programació gràfica 3D 833.1 Creació bàsica d’un ’shader’ a Unity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85

3.1.1 De la geometria a la imatge en pantalla . . . . . . . . . . . . . . . . . . . . . . . . . 873.1.2 Anatomia bàsica d’un ’shader’ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91

3.2 Creació de textures procedimentals amb ’shaders’ a Unity . . . . . . . . . . . . . . . . . . . . 1113.2.1 Declaració i inicialització de les propietats . . . . . . . . . . . . . . . . . . . . . . . 1133.2.2 Utilitzar les coordenades UV . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1143.2.3 Desplaçament horitzontal de les files . . . . . . . . . . . . . . . . . . . . . . . . . . 119

3.3 Consideracions finals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122

Page 5: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 5 Desenvolupament de videojocs amb Unity

Introducció

Afrontar el desenvolupament d’un videojoc no es una tasca senzilla ja que moltesvegades és difícil saber per on començar, especialment en projectes grans o quanés dels primers videojocs que desenvolupem. En aquesta unitat intentarem crearuna petita guia de tots els aspectes que cal tenir en compte abans de començar icom anar estructurant el desenvolupament d’un videojoc 3D des de zero.

En el primer apartat, “Desenvolupament de videojocs amb Unity (Part 1)“,estudiarem les parts més importants de la creació d’un videojoc i veureu elsfonaments que utilitzarem durant tot el desenvolupament del nostre videojoc. Espodrien entendre com els fonaments o l’estructura de quan es construeix unacasa. Veurem quins són els elements bàsics d’un GDD o Game Design Document,algunes pautes que seguirem durant el desenvolupament tant del codi font com del’organització de les escenes a Unity. Després crearem el projecte des de zero, pasa pas. Primer veurem alguns plug-ins o assets de l’Asset Store. A continuacióestudiarem tres dels elements més importants a l’hora de crear un videojoc: elGameManager, el LevelManager i la gestió de l’input. Finalment, començaremamb la creació de les diferents escenes del nostre videojoc.

En el segon apartat, “Desenvolupament de videojocs amb Unity (Part 2)“,acabarem amb la creació de les diferents escenes del videojoc. Veurem unúltim element important dels nostres fonaments: els esdeveniments entre classesimplementant el design pattern Observer. A continuació veurem com crearun personatge animat en 3D fent servir l’Animator de Unity. Estudiarem comsubdividir totes les possibles accions del personatge en diferents classes petites,per tal que el nostre codi sigui tan petit com sigui possible i eficient. Tambécrearem un controlador de personatges que ens servirà tant per al nostre heroi comper als enemics que col·locarem al llarg del nivell. Afegirem diferents efectesvisuals i de so. Finalment, crearem la UI del nostre videojoc, i tancarem eldesenvolupament amb l’escena del report del nivell.

Finalment, a l’apartat, “Programació gràfica 3D” explorareu, de manera pràc-tica, els coneixements que l’envolten, perquè us permeti l’obtenció de resultatsimmediats, perquè us animin a continuar en la corba d’aprenentatge, sovint forçaescarpada, de la programació gràfica. Per fer-ho, continuarem investigant elfuncionament i les possibilitats de la plataforma Unity; alhora que també haureude familiaritzar-vos amb coneixements bàsics dels llenguatges de programació. Abanda, aquest apartat assumirà el doble objectiu d’explorar també i obrir-vos laporta d’entrada al desenvolupament de shaders amb Unity, perquè els incorporeuen futurs projectes personals i professionals.

Per assolir els objectius d’aquesta unitat, cal que llegiu els annexos, feu elsexercicis i les activitats proposades. En cas de dubte, podeu preguntar al fòrum del’assignatura, ja que així us podran ajudar els vostres companys o el professor.

Page 6: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu
Page 7: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 7 Desenvolupament de videojocs amb Unity

Resultats d’aprenentatge

En finalitzar aquesta unitat, l’alumne/a:

1. Desenvolupa aplicacions interactives d’entreteniment, permetent la interaccióamb els elements en 3D i la participació de diversos usuaris finals simultàniament.

• Genera entorns interactius en els quals s’integren elements en 3D, i els donainteractivitat.

• Desenvolupa aplicacions interactives d’entreteniment aplicades al sectoreducatiu (solucions d’aprenentatge electrònic (e-learning), serious gamesi TV interactiva, entre d’altres), augmentant la participació de l’usuari enels entorns d’aprenentatge.

• Desenvolupa aplicacions interactives d’entreteniment per a espais i esdeve-niments multimèdia, destinades a la transmissió de continguts dependentsde la interactivitat de l’usuari.

• Crea entorns interactius de videojocs que permetin la interactivitat entre elselements en 3D, responent a models naturals de comportament físic, a partird’esdeveniments desencadenats per l’usuari.

• Posa en producció aplicacions interactives d’entreteniment en diferentsentorns i dispositius, verificant la seva funcionalitat i resolent les incidènciesque puguin sorgir.

Page 8: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu
Page 9: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 9 Desenvolupament de videojocs amb Unity

1. Desenvolupament de videojocs amb Unity (Part 1)

Al llarg d’aquest apartat portarem a terme la creació d’un videojoc. Durant totel procés de creació, farem servir alguns assets que proporciona Unity o d’altresque es troben a l’Asset Store de manera gratuïta. Per exemple, un asset pot ser unplug-in per gestionar tot l’input de teclat i comandaments, una base per al vostreprojecte en 3D o, si esteu creant plataformes, us recomanaria donar un cop d’ullal plug-in anomenat Corgi2D.

A l’hora d’afrontar la creació d’un videojoc des de zero, és molt recomanablefer una recerca prèvia i documentar-se sobre els assets que ja existeixen almercat i que podeu fer servir com a base del vostre projecte; alguns són depagament, però n’hi ha molts gratuïts.

En el nostre cas, en farem servir un que proporciona Unity, per crear videojocsen 3D, i n’extraurem algunes parts que ens seran molt útils. Per exemple, faremservir els assets gràfics per crear un entorn 3D, el personatge principal, o fins i totalguns scripts per a la gestió d’efectes visuals o la música.

Com a tot projecte li haurem de donar un nom; el nostre és Ellen and the lostIOCStudents. Consistirà en un personatge principal anomenat Ellen (com suposoque ja havíeu deduït) que es trobarà en un món en 3D, ple de diferents enemics,i en el qual ens haurem de moure per tal d’arribar a la sala on finalment l’Ellentrobarà els alumenes perduts.

El desenvolupament anirà creixent de manera gradual afegint elements en el nostrevideojoc per, com en un trencaclosques, anar-los posant tots junts, i que al finalpuguem jugar al resultat final. En aquesta primera part, començarem amb elsciments, com ara els sistemes d’input, gestió del nivell o LevelManager, gestiódel videojoc complet o GameManager, entre d’altres.

1.1 El GDD i la fase de producció

Els passos inicials a l’hora de crear un videojoc inclouen l’elaboració del quees coneix com a Game Design Document (GDD) durant la preproducció. Acontinuació, arriba el procés de producció d’un videojoc i les diferents parts enquè es divideix.

El GDD, en català ‘Document de disseny de jocs’, és un document on esdescriuran en detall tots els apartats d’un videojoc i que anirà variant durantel desenvolupament juntament amb la idea inicial. Normalment serà creat imantingut per l’equip de desenvolupament i és el resultat de l’aportació de tots

Input System és el sistemapel qual l’usuari o jugadorcontrolarà el seupersonatge.

Page 10: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 10 Desenvolupament de videojocs amb Unity

Convèncer futurs inversors

En cas que necessiteu fer pitchper a possibles inversors i

publishers, elaborar un GDD usresultarà de molta utilitat. És

recomanable que, per a qualsevolprojecte que vulgueu portar aterme, en tingueu ni que siguiuna versió molt reduïda, que

expliqui les característiques mésimportants del vostre videojoc.

els diferents tipus d’equips o professionals que treballaran en el desenvolupamentdel videojoc (art, programació, disseny...).

Normalment la creació del GDD es produeix durant la fase de preproducció d’unvideojoc, com a part d’un possible pitch per a inversors o publishers. Una vegadael projecte ha estat aprovat, serà ampliat durant tot el desenvolupament, i cada cops’entrarà amb més detall a cada una de les parts del desenvolupament del videojoc.

El contingut d’un GDD pot variar entre imatges, concepts, diagrames, conceptart, vídeos i qualsevol tipus de material que ens ajudi a il·lustrar d’una millormanera com serà el videojoc que estem dissenyant, i ens ajudi a transmetre millorcadascuna de les parts que té.

L’objectiu d’un GDD serà definir, entre d’altres, allò que es coneix com elsselling points (audiència a la qual va dirigit) i el gameplay (que contindràl’art, el disseny de nivells, personatges, assets...).

En resum, un GDD haurà d’incloure informació suficientment en detall pertal de poder desenvolupar totes les parts del nostre videojoc, seguint-lo com areferència. Encara que moltes vegades s’abandona durant el desenvolupamenti no s’actualitza, almenys les parts més critiques s’haurien d’intentar manteniractualitzades. El GDD de la majoria de videojocs haurien d’incloure els següentsapartats com a mínim (o una variació d’aquests):

• Història

• Personatges

• Disseny de nivells i entorns

• Gameplay

• Art

• Música i efectes de so

• Interfície d’usuari

Aquesta llista potser més o menys llarga depenent del tipus de videojoc. Perexemple un joc que tingui una UI molt minimalista potser no necessita estardefinida en el GDD, o un videojoc que no tingui cap tipus d’història tampoc. Enresum, el contingut d’un GDD serà flexible sempre tenint en compte cada casconcret, però sempre haurà de fer la tasca de document de referència durant lacreació d’un videojoc, i per tant haurà de contenir tota la informació que podremnecessitar.

Per altra banda, la fase producció és l’etapa principal de desenvolupament, quanes produeixen els assets i el codi font del videojoc. Les principals àrees dedesenvolupament seran les següents:

Page 11: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 11 Desenvolupament de videojocs amb Unity

• Programació: els programadors incorporen noves funcions exigides peldisseny del joc i corregeixen qualsevol error introduït durant el procés dedesenvolupament.

• Animació i modelatge: tota la producció d’assets d’art, ja siguin personat-ges, entorn, objectes, animacions en 2D i 3D, la interfície gràfica, així comles il·lustracions realitzades per dissenyar personatges, entorns, etc.

• Disseny: durant el desenvolupament, el dissenyador del joc implementa imodifica el disseny per reflectir-ne la visió. Les característiques i els nivellssovint s’eliminen o s’afegeixen durant tota la fase de desenvolupament.

• Efectes de so: l’àudio del joc es pot separar en tres categories: efectes de so,música i veu. La producció d’efectes sonors és la producció de sons, ja siguiajustant una mostra a un efecte desitjat o replicant-la amb objectes reals. Lamúsica es pot sintetitzar o reproduir en directe en casos de projectes ambmés pressupost. Les interpretacions de veu generen interactivitat de joc decaràcters. Així doncs, l’actuació de veu afegeix personalitat als personatgesdel videojoc.

1.2 Consideracions prèvies

Abans de res, cal definir la manera com treballarem durant la resta del desenvo-lupament del nostre videojoc. Es tracta de fer una petita guia d’estil del codi,de com organitzarem les escenes a Unity i definir com treballarem amb diferentselements i funcionalitats del motor gràfic.

Abans de començar el desenvolupament d’un videojoc o qualsevol altre tipusde programari, és recomanable triar una guia d’estil pel que fa al codi font ol’organització del projecte. Un cop fet això, s’hauria de mantenir durant totel desenvolupament, per tal de ser consistents, i afavorir la claredat a l’horade fer-ne el manteniment.

1.2.1 El codi font

En aquest apartat veurem una sèrie de petits consells, que ens ajudaran a mantenirel nostre codi font més ben organitzat, tan clar com sigui possible per entendre’len el futur i que l’entenguin altres programadors, i que s’executi de la maneramés eficient possible seguint les directrius a l’hora d’escriure’l que veurem acontinuació:

• Noms de variables: les que siguin membres d’una classe començaran per‘m_nomVariable’; per a totes les altres farem servir l’estil ‘nomVariable’.

Page 12: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 12 Desenvolupament de videojocs amb Unity

Amb aquesta notació sabrem només mirant el nom de la variable si l’hemdefinit a la funció en què ens trobem, o a la classe.

• No deixarem mai de fer servir les claus ”{}“ en condicionals (if ) o loops(for) entre d’altres. Molt sovint apareixen errors perquè creiem que el nostrecodi dintre del condicional conté només una línia després i a l’afegir méslínies executa línies que no volem que executi. També serà molt més fàcilde llegir per a altres programadors.

• Tot i que es poden fer declaracions implícites de variables, nosaltresintentarem evitar al màxim aquest tipus de declaració, ja que complica al’hora de llegir el codi, especialment quan fa molt de temps que vam crear-lo o per a altres programadors.

• Intentarem evitar al màxim fer servir variables del tipus string per fercomparacions. Serà preferible fer servir el seu codi hash, o d’altre tipus devariable si hem de fer comparacions en clàusules if, exceptuant el cas querealment necessitem fer aquest tipus de comparació (per exemple el nomd’un fitxer, o d’un jugador).

• Totes les classes que creem hauran d’estar dintre d’un namespace. Ambaixò evitarem que els noms de les nostres classes col·lideixin amb algunaque tingui el mateix nom en una llibreria, plug-in o asset. Per posar unexemple molt comú, si definim la nostra classe ‘GameManager’, podriacol·lidir amb la mateixa de l’asset ‘2DGameKit’ de Unity.

• Evitarem tant sí com no fer servir les funcions ‘Invoke’ i ‘SendMessage’.Amb aquestes podem cridar d’altres funcions pel nom com a paràmetre peròtenen un cost molt alt en termes de rendiment, a part de trencar tot el dissenyd’orientació a objectes en programació.

• Si feu servir coroutines, haurem de fer-les servir de manera eficient. Sónmolt útils a l’hora de resoldre molts problemes amb codi que volem ques’executi durant diferents frames. Però d’altra banda són molt difícils dedepurar o fer debug, i per tant es poden convertir en parts del codi gairebéimpossibles de mantenir o entendre. Per això us recomano que siguinsempre petites funcions, amb un objectiu molt definit.

• Feu servir classes del tipus ‘Singleton’ quan necessitem accedir a classesde manera global, i de les quals només en voldrem tenir una sola instànciadurant l’execució del projecte segons com les necessitem però sense abusard’aquest design pattern o patró de disseny. Per a més informació usrecomano donar un cop d’ull al següent enllaç: bit.ly/2QIcmLE.

1.2.2 Optimització de Unity i organització de les escenes

En aquest apartat, veurem una sèrie de consells per tal d’organitzar millor lesnostres escenes i objectes a Unity, i intentar que siguin tan eficients i fàcils de

Page 13: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 13 Desenvolupament de videojocs amb Unity

mantenir com sigui possible. A continuació veurem una sèrie de directrius queseguirem durant la creació del nostre projecte:

• Decidirem una escala dels nostres objectes al principi, i la seguirem duranttot el projecte, o potser haurem de canviar valors de models, assets etc.durant el desenvolupament, amb tota la feina extra que això significaria.Normalment per a videojocs en 3D es farà servir una unitat a Unity, queequivaldrà a 1 metre. En videojocs en 2D en els quals no fem servir físiqueso tècniques complexes d’il·luminació, una unitat a Unity equivaldrà a 1 píxel(a la resolució en la qual estem dissenyant el nostre videojoc). Per la partd’interfície d’usuari o UI, haurem de triar una resolució (per exemple 2xHD)i dissenyarem tots els assets tenint aquesta resolució en ment.

• Quan creem objectes en temps d’execució del nostre videojoc, és a dir, queels crearem via codi, intentarem no deixar-los en l’arrel de la nostra escena.Això ens servirà a l’hora de fer debug d’aquests objectes, i serà més fàcil ferel seguiment.

• Totes les escenes del nostre projecte s’han de poder executar de maneraindependent. Això serà molt útil a l’hora de fer proves durant el desen-volupament, i és molt important que ho tinguem en compte a l’hora dedissenyar els nivells. De vegades pot ser molt complicat, ja que hi hauràobjectes que persisteixen entre escenes (per exemple on s’emmagatzemenles dades del jugador), haurem de carregar fitxers de partides guardades...Per això farem servir ‘Singletons’ en moltes de les classes que es dediquena controlar aquest tipus de funcionalitat.

• Quan vulguem importar assets d’altres proveïdors o de l’Asset Store, perevitar-nos problemes, és molt recomanable primer importar-los en unprojecte buit, per comprovar que la funcionalitat és la que esperem, i uncop fet això els importarem en el nostre projecte.

• En principi, tots els objectes de la nostra escena han de ser objectes prefabs,sense excepcions. Els únics objectes de la nostra escena que no ho han de sersón els que facin la funció de carpetes per organitzar-les. Això ens facilitaràmolt la feina a l’hora de fer canvis en objectes que tinguem en diferentsescenes, i ens estalviarà la feina d’anar modificant-los escena a escena.

• Intentarem evitar posar objectes com a meshes, sprites i altres componentsgràfics en l’arrel dels nostres objectes. És una bona pràctica crear unobjecte fill per contenir aquest tipus d’objecte, ja que si el volem canviarens resultarà més fàcil. També és recomanable posar els scripts en l’arreldel nostre projecte, ja que serà més fàcil inspeccionar els valors o paràmetresd’entrada d’aquests, sense haver de buscar-los en la jerarquia de l’objecte.

• Tots els noms dels nostre directoris començaran per “_”. Amb aixòaconseguirem que es mostrin en la part superior de l’inspector de Unity.Per exemple, el nostre directori de prefabs l’anomenarem “_prefabs” o eldirectori d’art, “_art”. Com que l’inspector de Unity ordena els directorisper ordre alfabètic, amb aquest petit truc tindrem tots els nostres directorisordenats a la part superior de l’inspector i ens seran més fàcils de trobar,

Page 14: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 14 Desenvolupament de videojocs amb Unity

especialment si el nostre projecte creix, i fem servir molts assets o plug-insde tercers.

1.3 Configurar el projecte base

En aquest apartat veurem com configurar el nostre projecte abans de començar:entre d’altres, quina versió de Unity farem servir, o quins assets farem servirdurant el desenvolupament del videojoc.

Visual Studio Community i Monodevelop

Visual Studio Community és, segons la descripció de Microsoft, “un IDE totalment funcional,extensible i gratuït per crear aplicacions modernes per a Android, iOS, Windows”. Unity, desde fa algunes versions, en distribueix una còpia com el seu editor de codi font predeterminat.Podeu trobar més informació al següent enllaç: bit.ly/2NzB14g.

Monodevelop és un IDE multiplataforma per desenvolupar aplicacions en C# i F# entred’altres. En versions més antigues de Unity, era l’editor de codi font de Unity predeterminat.Podeu trobar més informació al següent enllaç: bit.ly/2NumMkl.

Per al desenvolupament d’aquest videojoc farem servir la versió Unity3D en laseva versió 2018.2.20f1 . Us el podeu baixar de la pàgina oficial de Unity o fentservir el Unity Hub des de l’enllaç oficial a unity3d.com/get-unity/download.

No és obligatori fer servir la mateixa versió per seguir el desenvolupamentd’aquest exemple, però sí que és molt recomanable. Si feu servir qualsevolversió superior, han aparegut noves característiques, com la de nestedprefabs, que no estan recollides en aquest manual. Si feu servir qualsevolversió inferior a la 2018.1 hi haurà assets que no seran compatibles.

Amb Unity us podeu baixar com a editor de codi font el Visual Studio Commu-nity 2017 si esteu fent servir un entorn de desenvolupament a Windows, o VisualStudio 2017 for Mac si esteu fent servir un entorn de desenvolupament a Mac OSX.En aquest tutorial jo faré servir un entorn de desenvolupament a Windows amb elVisual Studio Community 2017. Tot i que el editor de codi font és opcional i hiha més opcions com MonoDevelop, per exemple, jo us recomanaria fer servir unentorn de desenvolupament tan semblant com sigui possible al que jo faré servir,per tal de seguir millor els exemples.

Entre els molts recursos que posa a la nostra disposició Unity, hi ha el 3D GameeKit. Segons la definició oficial, és una sèrie de mecàniques, sistemes, assets perpoder desenvolupar videojocs 3D des de zero. Podeu trobar el tutorial complet enel següent enllaç: bit.ly/2JNDFp2.

En el nostre cas, farem servir molts dels assets en 3D, alguns scripts per controlarels efectes visuals, música o efectes sonors. També farem servir la màquinad’estats del personatge principal (Animator), els models en 3D, etc. Us podeubaixar l’asset en el següent enllaç: bit.ly/2ryz12m.

Page 15: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 15 Desenvolupament de videojocs amb Unity

Us recomano importar aquest asset en un projecte buit, ja que sobreescriurà lesopcions o settings del vostre projecte. És força gran (1,9 GB) i trigarà una bonaestona a importar-se a Unity per la gran quantitat de models 3D, fitxers d’àudio itextures que inclou.

Com a Tween Engine en farem servir un de gratuït anomenat LeanTween. Segonsla seva pròpia definició a l’Asset Store, LeanTween és un motor de tween o interac-cions extremadament eficient que ofereix moltes de les mateixes característiquesque els altres motors tween (i més!) amb menys consum de recursos que els seuscompetidors. Us el podeu baixar en el següent enllaç: bit.ly/2MsCedT.

El farem servir principalment per fer petites animacions com moure una platafor-ma entre dues (o més) posicions predeterminades, canviar valors d’una variabledurant el temps, canviar l’alfa d’una imatge i moltes aplicacions més.

En el següent vídeo, podreu veure com importar-ho tot a Unity per tal que seguiuels passos vosaltres també. En cas que vulgueu fer servir el projecte ja configurat,us el podeu baixar del següent enllaç: bit.ly/2C1aSaJ.

https://player.vimeo.com/video/320682810

1.4 El disseny del videojoc ’Ellen and the lost IOCStudents’

Abans d’entrar a desenvolupar el videojoc pròpiament dit, i les seves diferentsparts, és convenient que reflexionem una mica sobre quin game design tindrà.És com si fossin els plànols per construir una casa. Igual que no se’nsacudiria construir una casa sense els plànols, seria igualment erroni desenvoluparun videojoc sense tenir, com a mínim, una estructura bàsica del que volemdesenvolupar. En el cas de videojocs, aquest disseny es fa dintre del GDD o GameDesign Document.

Les parts fonamentals d’un game design o ‘disseny d’un joc’ són quatre:l’estil visual, els objectius del jugador, les seves accions disponibles, i l’estatde final de partida.

En el nostre cas, l’estil visual del videojoc vindrà donat per l’asset 3D Game Kitde Unity, i per tant no tindrem gaire marge de maniobra en aquest apartat. De totesmaneres intentarem dissenyar un entorn bastant gran per tal que el jugador puguiexplorar. Dissenyarem tres zones o àrees diferents:

• Àrea inicial: món obert, amb molt d’espai perquè el jugador pugui adaptar-se al control de la nostra heroïna.

’Tween Engine’

LeamTween i els Tween engineen general són sistemes que enspermeten animar coses a travésdel codi (o visualment) demanera molt senzilla, concisa ide manera eficient.

No tenim espai perdesenvolupar tot el GDD enaquesta unitat, però sí lesidees fonamentals obàsiques.

Page 16: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 16 Desenvolupament de videojocs amb Unity

• Àrea amb enemics: aquesta àrea serà menys rica en entorn, però amb mésenemics.

• Àrea final, els estudiants: en aquesta àrea final, es trobaran els estudiantsque hem de rescatar. El posarem en una zona allunyada de la zona inicial,per tal de maximitzar.

Us recomano que a l’hora de fer el disseny visual no us centreu només en seguirel mateix aspecte visual que dona el 3D Game Kit, i experimenteu amb diferentsshaders i materials per intentar donar un aspecte diferent al vostre projecte. Fins itot podeu provar de canviar els efectes de postprocessament. Veureu que amb unparell de clics de ratolí podeu canviar l’estil visual de la vostra escena.

Pel que fa als objectius del jugador, procurarem que aquests siguin divertits iengrescadors. Els jugadors necessitaran saber quins són, i els hi farem saber demica en mica, a mesura que avancin en el nostre videojoc:

1. Aprendre a moure’s pel món en 3D, i esquivar els diferents perills que té.

2. Aprendre a fer servir les armes, per tal de defensar-se dels enemics.

3. Aprendre matar els enemics; això implica aprendre com actuen.

Així, per donar suport a aquests objectius, en una primera instància no hi hauràenemics, en el nostre món en 3D, i un cop el jugador hagi caminat una estona pelnostre entorn, li deixarem agafar l’arma i començaran a aparèixer enemics per talque la dificultat vaig creixent mica en mica.

En tercer lloc, per accions disponibles per al jugador podem entendre una granvarietat de comportaments que podrà dur a terme el nostre personatge: córrer,saltar, atacar, nedar, penjar-se d’una corda, conduir un vehicle... Establir una llistad’accions ens serà molt útil a l’hora de desenvolupar tot el sistema d’input delvideojoc; concretament, en el nostre videojoc, el jugador podrà fer les següentsaccions:

• Caminar: amb aquesta acció el jugador es podrà moure per tot el món en3D que dissenyarem, i ho farà servir tant per trobar els estudiants perduts,com per esquivar perills del nivell o enemics.

• Disparar: podrem disparar una arma senzilla en línia recta des d’on estiguimirant el jugador en aquell moment.

• Saltar: el jugador podrà saltar per poder esquivar diferents perills en elnivell, o per poder anar d’una plataforma a una altra.

L’última part del disseny serà determinar l’estat final del joc, és a dir, comacabarà; hi ha dues opcions o estats:

• La condició de guanyar o victòria és l’objectiu final del nostre joc, i éscom els jugadors podran completar-lo de manera satisfactòria. Consistirà

Page 17: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 17 Desenvolupament de videojocs amb Unity

en descobrir on hi ha els estudiants perduts, sense que cap enemic ens barriel pas.

• La condició de perdre o derrota es produirà si la nostra energia o barrade vida s’esgota per culpa dels atacs dels enemics o els diferents perills delnivell.

1.5 ’GameManager’, ’LevelManager’ i classes del tipus ’Singleton’

Per continuar amb la creació del nostre videojoc, caldrà elaborar tot allò relacionatamb l’esquelet i controladors del nostre projecte. Per un cantó, hi ha el GameMa-nager, que s’encarregarà de controlar tot el que està relacionat amb els estats delvideojoc, en quin nivell o escena ens trobem i les transicions entre uns i altres. Peraltra banda, tenim el LevelManager, que s’encarregarà de gestionar tot allò relatiual nivell; com pot ser el checkpoint actiu, els punts en el nivell, el temps que hajugat el jugador, fer spawn en el punt adient...

Haurem de poder accedir a aquestes dues classes (com la que controla l’inputdel jugador, per exemple), des de qualsevol punt del codi; i només voldrem unainstància de cadascuna d’aquestes classes en el projecte. Aquesta funcionalitat ésconeguda com a design patern o ‘patró de disseny’, anomenat Singleton.

1.5.1 El ’GameManager’: el cicle complet del videojoc

En qualsevol videojoc, sempre hi trobarem que el bucle o loop és molt similar,indiferentment del gènere, el motor gràfic utilitzat o el tipus de nivell o escena queestem executant. De fet, en cada nivell o escena, primer es carregarà tot el que ésnecessari, després hi haurà un bucle principal, i un cop es compleixin una sèrie derequisits, sortirem de l’escena cap a una altra.

En el menú principal, per exemple, hi haurà un estat d’inici on es carreguen elsassets necessaris per mostrar el menú per pantalla, un temps que s’executa fins quel’usuari interactuï de manera que vulgui jugar o continuar una partida, i un estatde sortida, quan comencem a carregar el nivell que volem jugar.

Ara imaginem el bucle dintre d’un nivell del videojoc. Hi haurà un estat inicial decàrrega dels assets necessaris i inicialització dels components. Un cop inicialitzat,el jugador jugarà el nivell, interactuant amb el món virtual, fent pausa al gameplay,reiniciant el nivell... Un cop arribem al final del nivell, o el jugador mori, tornaremal menú principal, o saltarem al següent nivell del nostre videojoc.

En tots aquests possibles estats que acabem de narrar, que són molt comuns entotes les escenes el nostre videojoc, el GameManager s’encarregarà de controlar-los, juntament amb altres esdeveniments del nostre videojoc que faran saltar d’un

Page 18: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 18 Desenvolupament de videojocs amb Unity

estat a un altre. Alguns exemples, entre d’altres, dels possibles estats que tindremen GameManager seran:

• StartGame

• GameOver

• MainMenu

• Pause

• Splash

En un videojoc petit, com l’Ellen and the lost IOCStudents que estem creant, ésrecomanable tenir només una instància del GameManager que controli tots elsesdeveniments principals del nostre videojoc durant l’execució.

El flux del nostre videojoc consistirà en quatre escenes diferents: la pantalla inicialdel logotip o Splash, la del menú principal, la del joc en si mateix, i la del resumdel nivell. Com podeu observar al gràfic de transicions de la figura 3.9, el flux quecontrolarà el nostre GameManager, que quan sigui necessari s’encarregarà de ferles transicions adients, serà el següent:

1. La primera escena del nostre videojoc serà la de l’Splash o logotip; d’aquífarem una transició al menú principal.

2. En el Menú principal esperarem la interacció de l’usuari, i un cop selecci-oni jugar un nivell, farem la transició a l’escena del nivell.

3. Des del Nivell, l’usuari podrà reiniciar-lo, sortir al menú principal o en casque l’acabi (de manera satisfactòria o no, anirà a l’escena de resum delnivell).

4. En aquesta última escena, el Resum de nivell, mostrarem informació al’usuari de com ha jugat el nivell (punts, vides, si ho ha acabat bé o no...).Des d’aquesta escena, podrem torna a jugar un nivell, o anar al menúprincipal.

Figura 1.1. Transicions entre escenes

Page 19: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 19 Desenvolupament de videojocs amb Unity

L’esquelet del nostre ’GameManager’

El primer pas a l’hora d’anar escrivint i ampliant el nostre GameManager, éscrear un petit script buit, amb allò que formarà part de l’esquelet inicial del nostrecontrolador, com podem veure en el següent codi:

1 using System.Collections;2 using UnityEngine;3 using UnityEngine.SceneManagement;4

5 namespace ioc.IOCStudents.Core6 {7 public enum GameState8 {9 splash,

10 mainMenu,11 inGame,12 gameOver,13 exit14 }15

16 public class GameManager : PersistenSingleton<GameManager>17 {18 public void ToManinMenu()19 {20 }21

22 public void ToGame()23 {24 }25 public void ToGameOver()26 {27 }28

29 public void ToExit()30 {31 }32 }33 }

Com podeu veure en el codi, fer-ho no és gens complicat; els mètodes encara estanbuits i, per tant, encara és una classe buida de contingut. A continuació, el quefarem, com sempre, és crear un GameObject buit, a la posició (0,0,0) i afegireml’script que hem creat. Un cop ho tinguem, crearem un prefab, perquè ho puguemreferenciar fàcilment en totes les escenes del nostre videojoc.

Figura 1.2. El ’prefab GameManager’

Page 20: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament dp u b l i c ' entorns interactius multidispositiu 20 Desenvolupament de videojocs amb Unity

C# Enum

La paraula clau enum s’utilitzaper declarar una enumeració, un

tipus diferent de dades queconsisteix en un conjunt de

constants amb nom anomenatllista d’enumerats. Per exemple,

una llista d’enums podria ser{Vermell, Groc, Blau, Vermell}.

Ara és hora de començar a donar una mica de contingut al nostre GameManager.El primer pas serà afegir una mica de lògica per poder canviar d’escenes entresplash, menú principal, joc i final del joc. També afegirem la lògica per carregarles escenes amb un segon pla i mostrar una pantalla de càrrega mentre ho fem(amb un petit efecte de fade in/out).

Per controlar en quin estat o escena estem actualment, i simplificant molt el nostreexemple, farem servir un enum amb els estats que necessitem. Però abans usrecomano donar un cop d’ull al design pattern anomenat State, que ens anirà moltbé en aquest cas, al següent enllaç: bit.ly/2QIcmLE.

En el nostre cas, però, no el farem servir, ja que tot i que és molt útil requereix unamica més d’esforç a l’hora de programar-lo. Així doncs, afegirem transicionsentre tots els estats del nostre videojoc, fent servir un enum per guardar l’estatactual, com podeu veure en el codi següent:

1 using Gamekit3D;2 using System.Collections;3 using UnityEngine;4 using UnityEngine.SceneManagement;5

6 namespace ioc.IOCStudents.Core7 {8 public enum GameState9 {

10 splash,11 mainMenu,12 inGame,13 gameOver,14 exit15 }16

17 public class GameManager : PersistenSingleton<GameManager>18 {19 protected GameState m_gameState;20 protected float m_playedTime;21

22 protected override void Awake()23 {24 m_gameState = GameState.splash;25 m_playedTime = 0.0f;26 base.Awake();27 }28

29 protected void Update()30 {31 if (m_gameState == GameState.inGame)32 {33 m_playedTime += Time.deltaTime;34 }35 }36

37 public void ToManinMenu()38 {39 ChangeState(GameState.mainMenu);40 }41

42 public void ToGame()43 {44 ChangeState(GameState.inGame);45 }46 public void ToGameOver()47 {48 ChangeState(GameState.gameOver);49 }

Page 21: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament dp u b l i c ' entorns interactius multidispositiu 21 Desenvolupament de videojocs amb Unity

50

51 public void ToExit() {52 ChangeState(GameState.exit);53 }54

55 private void ChangeState(GameState newState)56 {57 switch (newState)58 {59 case GameState.splash:60 break;61 case GameState.mainMenu:62 StartCoroutine(LoadGame("MainMenu"));63 break;64 case GameState.inGame:65 StartCoroutine(LoadGame("Level01"));66 break;67 case GameState.gameOver:68 StartCoroutine(LoadGame("EndGame"));69 break;70 case GameState.exit:71 Application.Quit();72 break;73 }74

75 m_gameState = newState;76 }77

78 private IEnumerator LoadGame(string sceneName)79 {80 yield return StartCoroutine(ScreenFader.FadeSceneOut(ScreenFader.

FadeType.Loading));81

82 AsyncOperation asyncOperation = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Single);

83 asyncOperation.allowSceneActivation = false;84 float progress = 0.0f;85

86 while (asyncOperation.progress < 0.9f)87 {88 progress = asyncOperation.progress;89 yield return null;90 }91

92 asyncOperation.allowSceneActivation = true;93

94 yield return StartCoroutine(ScreenFader.FadeSceneIn());95 }96 }97 }

Com podeu veure en el codi anterior, definim un enum, amb el nom de ‘GameState’on definirem totes les possibles escenes o transicions del nostre videojoc: splash,mainMenu, inGame, gameOver, exit.

A continuació, definim la classe ‘GameManager’, que serà del tipus ‘Singleton’persistent, ja que només voldrem tenir una instància d’aquesta en totes les escenesdel nostre videojoc, i que sigui sempre la mateixa, la primera que s’executi.

Després tindrem quatre funcions per gestionar el pas a cada estat; és a dir,es cridaran des del manager o controller de cada escena, per saltar a la següent.Per exemple, des de ‘SplashController’ haurem de saltar al menú principal i, pertant, cridarem a la funció ‘ToMainMenu’. Les signatures de les funcions seran lessegüents:

Page 22: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 22 Desenvolupament de videojocs amb Unity

1 public void ToManinMenu()public void ToGame()2 public void ToGameOver()3 public void ToExit()

Amb aquestes funcions cridarem la funció ‘ChangeState’, amb el nou estat, per talde fer el canvi. De moment, aquesta funció senzillament crearà una coroutine percarregar la següent escena que dependrà de l’estat.

Finalment, la funció ‘LoadGame’ s’encarregarà de fer la càrrega de la novafunció de manera asíncrona. Primer de tot, farem un petit efecte de fade inamb un component del 3D Game Kit anomenat ‘ScreenFader’; el podeu trobara Assets/3DGameKit/Prefabs/SceneControl o buscant directament pel nom delprefab per tal de fer-lo servir a les vostres escenes. Per fer aquest efecte de fade infarem servir el següent codi:

1 StartCoroutine(ScreenFader.FadeSceneOut(ScreenFader.FadeType.Loading));

Després, farem la càrrega de la nova escena en el background per tal de nointerferir amb una possible finestra de “carregant” o loading amb el següent codi:

1 AsyncOperation asyncOperation = SceneManager.LoadSceneAsync(sceneName,LoadSceneMode.Single);

2 asyncOperation.allowSceneActivation = false;

A continuació, esperarem que la càrrega de la nova escena arribi al 90%.Mentrestant anirem guardant el valor del percentatge de càrrega en una variable,per si volem mostrar-lo al jugador, com es pot veure en el codi següent:

1 while (asyncOperation.progress < 0.9f)2 {3 progress = asyncOperation.progress;4 yield return null;5 }

Finalment, activarem la nova escena, i farem el fadeout des de la pantalla decàrrega a la nova escena amb el codi següent:

1 asyncOperation.allowSceneActivation = true;2 yield return StartCoroutine(ScreenFader.FadeSceneIn());

Així doncs, ja tenim una petita classe que gestionarà els diferents estats del nostrevideojoc i les transicions bàsiques entre escenes. Us recomano donar un cop d’ullal següent vídeo per veure com crear el GameManager i veure’l en funcionamententre diferents escenes.

https://player.vimeo.com/video/320148820

Page 23: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d' entorns interactius multidispositiu 23 Desenvolupament de videojocs amb Unity

1.5.2 El ’Singleton’: accés global i persistent a un objecte

Durant el treball amb la nostra classe ‘GameManager’, ens pot sorgir un problema:necessitarem accedir a la classe ‘GameManager’ des de moltes parts diferents delnostre codi i, a més a més, que no es destrueixi cada cop que canviem l’escenaactiva. Com ho podem solucionar? Necessitem un accés global i persistent alnostre codi. Imagineu-vos el següent problema: des del menú principal, en lalògica del botó play haurem de cridar el ‘GameManager’ perquè carregui l’escenaque conté el nostre nivell. Un cop estigui carregada, haurem de canviar l’estat enel ‘GameManager’ a Start Level des d’un altre script, i així successivament. Unaopció seria tenir referències a l’objecte ‘GameManager’ a tots els scripts que honecessitin, però la solució que adoptarem serà implementar un Design Pattern, el‘Singleton’.

‘Singleton’ és un Design Pattern que es fa servir quan ens volem assegurarque una classe només té una instància en tot el nostre codi i, a més a més, livolem donar un punt d’accés global, és a dir, que podrem accedir-hi des dequalsevol punt del nostre codi.

En el nostre videojoc necessitarem dos tipus d’implementacions diferents: unaimplementació que sigui única i persisteixi entre les diferents escenes, i una altraque només estigui disponible en l’escena on tenim l’objecte d’aquest tipus, i esdestrueixi quan sortim. En aquest sentit:

• El component ‘GameManager’ ha de ser únic per a tot el videojoc, i nomésvoldrem tenir-ne una instància.

• I, en canvi, el ‘LevelManager’ només el necessitarem dintre dels diferentsnivells del nostre joc, però un cop sortim, i anem al menú principal perexemple, s’haurà de destruir.

A més a més, de les funcionalitats típiques d’aquest Design Pattern, en laimplementació que veurem a continuació, necessitarem que es pugui fer servir enclasses del tipus ‘MonoBehaviour’, per tal que es pugui accedir als típics mètodes,com poden ser ‘Start’ o ‘Awake’, entre d’altres. A continuació, veureu el codi fontde la implementació d’aquest patró en les seves dues versions, la persistent i la nopersistent, que farem servir durant la creació del nostre videojoc:

• Implementació persistent:

1 using UnityEngine;2 namespace ioc.IOCStudents.Core3 {4 /// <summary>5 /// Persistent Singleton pattern.6 /// </summary>7 /// <typeparam name="T">Instantiation type of this singleton </typeparam>

’Design Pattern’

En el món informàtic, el DesignPattern és el que es coneix comuna solució reusable a unproblema recurrent en el contextdel programari. No serà unaimplementació en codidirectament, sinó una descripcióo base de com resoldre unproblema que apareix en moltessituacions diferents.

Informació sobre’Singleton’

Hi ha un parell de lecturesinteressants sobre aquest DesignPattern: al web GameProgramming Patterns(bit.ly/2ChgZXN), i en lareferència original a l’explicaciódel llibre Design Patterns:Elements of ReusableObject-Oriented Software, elsautors del qual es coneixen comGang of Four (bit.ly/2CfJEfY).

Page 24: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 24 Desenvolupament de videojocs amb Unity

8 public class PersistenSingleton<T> : MonoBehaviour where T :PersistenSingleton<T>

9 {10 /// <summary> /// Singleton design pattern11 /// </summary>12 /// <value>The instance.</value>13 public static T Instance14 {15 get { return ((T)SingletonInstance); }16 set { SingletonInstance = value; }17 }18 private static T m_instance;19 protected bool m_enabled;20

21 protected static PersistenSingleton<T> SingletonInstance22 {23 get24 {25 if (m_instance == null)26 {27 m_instance = FindObjectOfType<T>();28 if (m_instance == null)29 {30 GameObject obj = new GameObject();31 m_instance = obj.AddComponent<T>();32 }33 }34 return m_instance;35 }36

37 set38 {39 m_instance = value as T;40 }41 }42

43 /// <summary>44 /// On awake, we initialize our instance. If you need to use Awake,

make sure to override it, and call base.Awake()45 /// </summary>46 protected virtual void Awake()47 {48 if (!Application.isPlaying)49 {50 return;51 }52

53 if (m_instance == null)54 {55 //If I am the first instance, make me the Singleton56 m_instance = this as T;57 DontDestroyOnLoad(transform.gameObject);58 m_enabled = true;59 }60 else61 {62 //If a Singleton already exists and you find63 //another reference in scene, destroy it!64 if (this != m_instance)65 {66 Destroy(this.gameObject);67 }68 }69 }70 }71 }

Com podeu observar, és una implementació genèrica, és a dir, quan la fem serviren la nostra classe ‘GameManager’, l’haurem de fer servir d’una manera semblant

Page 25: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d} ' entorns interactius multidispositiu 25 Desenvolupament de videojocs amb Unity

a la següent:

1 public class GameManager : PersistenSingleton <GameManager>

En el codi que acabem de veure, creem una instància del nostre design pattern(PersistenSingleton) amb la classe ‘GameManager’. Seguint aquest codi d’exem-ple, podríem fer que qualsevol classe implementés aquest patró. Per exemple, enel codi que controli l’input del videojoc, o les dades guardades... Referenciareml’objecte, amb un codi com el que podeu veure a continuació:

1 GameManager.Instnace.NomFuncioACridar()

Quan ho fem, comprovarem si ja tenim una referència a aquest objecte (variableestàtica). Si ja tenim una referència, voldrà dir que ja hem inicialitzat aquestobjecte. Si no és així, buscarem a l’escena si hi ha un objecte del tipus queestem implementant, en el nostre exemple del tipus ‘GameManager’. Si ja existeix,no farem res; si no existeix, crearem un nou ‘GameObject’ per tal d’afegir-lo al’escena i retornarem el valor d’aquest objecte creat o la referència que ja teníememmagatzemada.

Comentant una mica el codi, en la funció ‘Awake’ primer comprovarem sil’aplicació s’està executant. Si és així, mirarem si la instància (variable estàtica)és igual a ‘null’. Això voldrà dir que és la primera (i única) vegada que executaremla funció ‘Awake’. En aquest cas, guardarem una referència, marcarem l’objecteperquè Unity s’encarregui que no es destrueixi entre escenes, i el marcarem coma actiu o enabled.

• Implementació no persistent:

1 using UnityEngine;2

3 namespace ioc.IOCStudents.Core4 {5 /// <summary>6 /// Singleton pattern.7 /// </summary>8 /// <typeparam name="T">Instantiation type of this singleton </typeparam>9 public class Singleton<T> : MonoBehaviour where T : Component

10 {11 protected static T _instance;12

13 /// <summary>14 /// Singleton design pattern15 /// </summary>16 /// <value>The instance.</value>17 public static T Instance18 {19 get20 {21 if (_instance == null)22 {23 _instance = FindObjectOfType<T>();24 if (_instance == null)25 {26 GameObject obj = new GameObject();27 _instance = obj.AddComponent<T>();28 }29 }

Page 26: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 26 Desenvolupament de videojocs amb Unity

30 return _instance;31 }32 }33 /// <summary>34 /// On awake, we initialize our instance. Make sure to call base.Awake

() in override if you need awake.35 /// </summary>36 protected virtual void Awake()37 {38 if (!Application.isPlaying)39 {40 return;41 }42

43 _instance = this as T;44 }45 }46

47 }

Com podeu veure, el codi és molt similar a l’anterior; la major diferènciaes troba a la part de la funció ‘Awake’. Aquí no indicarem l’objecte com a‘DontDestroyOnLoad’. Així, un cop l’escena es descarregui, el nostre objectetambé ho farà, i perdrem la referència.

1.5.3 El ’LevelManager’: encapsular tota la lògica de control d’unnivell

El LevelManager serà la classe encarregada de controlar en quin moment començael nivell i de quina manera, si el nivell està pausat o no, i quan acaba el nivell perquèel jugador ho ha superat correctament o perquè per algun motiu no ha arribat alfinal. Els estats més típics d’un ‘LevelManager’ seran els següents:

• Level Start: en el moment de començar el nivell haurem d’inicialitzarcomponents com les dades o estadístiques, la UI, preparar el controlador,quin és el checkpoint actiu...

• Gestionar la pausa: quan el jugador pressioni el botó de pausa, hauremde fer pausa a l’execució del nostre videojoc, així com mostrar el menú depausa. El mateix en el moment contrari, quan estant al menú de pausa eljugador vulgui continuar jugant.

• End Level: un cop el jugador ha perdut totes les vides o el temps disponibleper completar el nivell, o quan l’ha completat de manera satisfactòria, seràel moment de fer un efecte de fade out, deixar de llegir l’input del jugador,i cridar al ‘GameManager’ per carregar l’escena d’EndLevel.

• Respawn: com que haurem de fer l’acció de generar o fer spawn del nostreheroi quan comenci a jugar el nivell, o després de perdre una de les vides,haurem de controlar a quin punt s’ha de fer (si és el punt inicial, o qualsevoldels checkpoints) i inicialitzar tot el que calgui en el nostre heroi.

Page 27: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d} ' entorns interactius multidispositiu 27 Desenvolupament de videojocs amb Unity

• Controlar les estadístiques del nivell: entre d’altres coses, ens interessaràsaber el temps que ha jugat en el nivell, tenint en compte quan s’està enpausa i quan no, les vegades que el jugador ha perdut una vida, els puntsacumulats en el nivell... Per mantenir un control de tot això, farem servirun struct de l’anomenat ‘PlayerData’, on emmagatzemarem totes aquestesdades.

Així doncs, a continuació podreu veure el codi del LevelManager, amb algunesfuncionalitats ja implementades, i d’altres que anirem implementant en el futur:

1 using System;2 using UnityEngine;3

4 namespace ioc.IOCStudents.Core5 {6 public class LevelManager : Singleton<LevelManager>7 {8 [Serializable]9 public struct PlayerLevelStatistics

10 {11 public float m_playedTime;12 public int m_deaths;13 public int m_enemiesKilled;14 }15

16 public enum LevelStates17 {18 None,19 Normal,20 ControlledMovement,21 Paused,22 Dead,23 Finished24 }25

26 [SerializeField]27 protected LevelStates m_levelState;28

29 private PlayerLevelStatistics m_levelStatistics;30

31 protected override void Awake()32 {33 base.Awake();34 m_levelState = LevelStates.None;35 }36

37 protected void Start()38 {39 Initialize();40 }41

42 private void Initialize()43 {44 m_levelStatistics = new PlayerLevelStatistics()45 {46 m_deaths = 0,47 m_playedTime = 0.0f,48 m_enemiesKilled = 049 };50 }51

52

53 protected void Update()54 {55 if ( m_levelState == LevelStates.Normal)56 {

C# Struct

El tipus ‘Struct’, és un tipus dedades que normalment s’utilitzaper encapsular petits grups devariables relacionades, com arales coordenades d’un rectangle oles característiques d’un elementd’un inventari.

Page 28: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament dp r i v a t e ' entorns interactius multidispositiu 28 Desenvolupament de videojocs amb Unity

57 m_levelStatistics.m_playedTime += Time.deltaTime;58 } }59

60 private void SpawnCharacter()61 {62

63 }64

65 public void OnPlayerDead()66 {67 m_levelStatistics.m_deaths++;68 }69

70 public void OnPause()71 {72

73 }74 public void OnLevelEnded()75 {76 GameManager.Instance.ToGameOver();77 }78 }79 }

Com podeu observar, la nostra classe ‘LevelManager’ contindrà un ‘enum’ perdefinir els possibles diferents estats en els quals ens trobarem durant l’execuciódel nivell, començant en la funció ‘Awake’ amb estat ‘None’:

1 m_levelState = LevelStates.None;

També crearem un tipus de dades (struct) per tal d’emmagatzemar les estadístiquesdel jugador durant l’execució del nivell; en aquest cas voldrem saber el tempsque porta jugant en el nivell, el nombre de vegades que ha mort, i el nombred’enemics que ha matat. Com us podeu imaginar, es poden guardar qualsevoltipus d’estadística que ens interessi depenent del tipus de joc, els objectius delnivell, les diferents mecàniques...

1 [Serializable]2 public struct PlayerLevelStatistics3 {4 public float m_playedTime;5 public int m_deaths;6 public int m_enemiesKilled;7 }

En la funció ‘Update’ del ‘LevelManager’ (‘MonoBehaviour’), si l’estat actual delnivell és jugant o normal, anirem actualitzant el temps jugat dintre de la variableon emmagatzemen les estadístiques del nivell:

1 protected void Update()2 {3 if ( m_levelState == LevelStates.Normal)4 {5 m_levelStatistics.m_playedTime += Time.deltaTime;6 }7 }

Finalment, podeu observar una sèrie de funcions que estan buides o gairebé buides.Les anirem farcint de contingut a mesura que les anem necessitant. De moment,només deixem la seva definició buida:

Page 29: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 29 Desenvolupament de videojocs amb Unity

1 private void SpawnCharacter()public void OnPlayerDead()2 public void OnPause()3 public void OnLevelEnded()

1.6 ’Input’ del nostre videojoc

Com que estem desenvolupant un videojoc per a PC/Mac/Linux, ens centraremen el típic control d’input per a aquests sistemes operatius i equips, és a dir, enscentrarem a treballar amb teclat i gamepad. Dintre d’aquests perifèrics, trobaremdos tipus diferents d’input:

• Els digitals, com són els botons d’un gamepad o les tecles del teclat/ratolí,i que tenen dos estats, premut i alliberat.

• Els analògics, que poden tenir diferents valors entre 0 i 1 (totalmentalliberat/totalment premut), com els joysticks.

Els botons d’un ’Gamepad’ o lestecles d’un teclat proporcionen un’input’ digital.

Per gestionar l’input tenim dues opcions:

• D’una banda, podem fer-ho dintre de cada classe que necessiti accedira l’input. Per exemple, dintre de les diferents opcions de menú, en unnivell de joc, en cada classe que controli alguna habilitat del jugador, elmenú de pausa... Com us podeu imaginar aquesta manera de treballar no ésgaire eficient, ja que si volem desactivar l’input del teclat, o gestionar quijuga amb teclat i qui ho fa amb un gamepad, volem canviar quina tecla faquina acció, etc., serà molt complicat, i haurem de modificar molts arxiusdiferents.

• Una altra opció, més neta i clara, per gestionar tot l’input del videojoc, seriacrear una classe anomenada ‘InputManager’ que gestionarà la configuraciói detecció de tot l’input de manera centralitzada.

Per altra banda, qualsevol classe que necessiti rebre els inputs de diferents botonso del gamepad, es registrarà a escoltar-los a la classe ‘InputManager’. De maneraque, automàticament, quan es detecti un esdeveniment d’input, s’enviarà a totsaquells que estiguin escoltant aquest esdeveniment concret (vegeu la figura 1.3).

Page 30: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 30 Desenvolupament de videojocs amb Unity

Figura 1.3. Diagrama del funcionament de l”InputManager’

Com podeu veure en la imatge anterior, tant el teclat com el GamePad enviaran elsesdeveniments a la classe ‘InputManager’; aquesta s’encarregarà d’escoltar-los i,en cas que sigui adient, els propagarà a totes les classes que els estiguin escoltant.

1.6.1 Configurar l”input’ a Unity

Per tal de configurar l’input a Unity, haurem d’accedir a les Input Settings, quees poden trobar al menú Edit / Project Settings / Input (vegeu la figura 1.4).

Figura 1.4. ’Input Settings’ a Unity

Page 31: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 31 Desenvolupament de videojocs amb Unity

Aquí podrem definir quins botons del teclat farem servir, el eixos dels joysticks itot l’input en general del nostre projecte.

En aquesta finestra per afegir o reduir la mida dels botons, és tan senzill comcanviar la propietat Size. Com podeu veure, cada botó o input té diferentspropietats, entre les quals destaquem les següents:

• Name: serà el nom al qual ens referirem des del codi del nostre projecte. Perexemple “Jump”, “Fire”, “Dash” o qualsevol altre nom que sigui descriptiude les accions o inputs necessaris per al vostre projecte.

• Negative Button: el nom del botó que envia un valor negatiu a l’eix o axis.

• Positive Button: el nom del botó que registrarà l’input positiu per a aquesteix o axis.

• Dead: el valor pel qual es considerarà que qualsevol input per sota seràigual a zero. És molt útil per a joysticks.

• Sensitivity: per input de teclat; com més alt sigui el valor, més ràpid serà eltemps de resposta que tindrà.

• Type: triar a quin tipus d’input ens estem referint; poden ser: Key / MouseButton, Mouse Movement, Window Movement, Joystick Axis.

Per al nostre projecte, farem servir la configuració d’input que podeu veure a lasegüent imatge (vegeu la figura 1.5) :

Figura 1.5. ’Input Settings’ del projecte

Com podeu observar en la imatge anterior, hi haurà els següents tipus d’input enel nostre projecte:

Opcions de configuració

Per a una definició detallada deles diferents opcions deconfiguració, us recomano donarun cop d’ull a la documentacióoficial de Unity a l’enllaçsegüent: bit.ly/2MBu7M7.

Page 32: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 32 Desenvolupament de videojocs amb Unity

• Horizontal: aquest és un dels inputs per defecte. El deixarem per si femservir els menús UI de Unity i els UnityEvents.

• Vertical: aquest és un dels inputs per defecte. El deixarem per si fem servirels menús UI de Unity i els UnityEvents.

• Player1_Horizontal: amb aquest input capturarem tot el moviment horit-zontal, tant per teclat com per gamepad. Per aquesta raó podeu veure’n dosa la llista. En el nostre cas seran les tecles ‘a’ i ‘d’ a més a més del joystickesquerre del gamepad.

• Player1_SecondaryHorizontal: amb aquest input capturarem tot el movi-ment horitzontal secundari, tant per teclat com per gamepad. Per aquestaraó podeu veure’n dos a la llista. En el nostre cas seran les tecles fletxaesquerra i dreta a més a més del joystick dret del gamepad.

• Player1_Vertical: amb aquest input capturarem tot el moviment vertical,tant per teclat com per gamepad. Per aquesta raó podeu veure’n dos a lallista. En el nostre cas seran les tecles ‘w’ i ‘s’ a més a més del joystickesquerre del gamepad.

• Player1_SecondaryVertical: amb aquest input capturarem tot el movimentvertical secundari, tant per teclat com per gamepad. Per aquesta raó podeuveure’n dos a la llista. En el nostre cas seran les tecles fletxa dalt i baix amés a més del joystick dret del gamepad.

• Player1_Jump: tecla per fer l’acció de salt, serà la tecla d’espai en el teclati el botó X en el comandament (suposant que sigui el de Playstation 4).

• Player1_Shoot: tecla per fer l’acció de disparar, serà la tecla ‘e’ en el teclati el botó quadrat en el comandament (suposant que sigui el de Playstation4).

• Player1_Pause: tecla per fer l’acció de pausar el joc, serà la tecla escapeen el teclat i el botó options en el comandament (suposant que sigui el dePlaystation 4).

Finalment, recordeu que el Game Launcher permetrà al jugador canviar aquestesconfiguracions (en cas que el tinguem activat) abans d’executar el videojoc. Peraquest motiu sempre és millor fer servir eixos o axis en comptes de botons. A lafigura 1.6 podeu veure un exemple del Game Launcher.

Page 33: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 33 Desenvolupament de videojocs amb Unity

Figura 1.6. Game Launcher a Unity

Us recomano donar un cop d’ull al vídeo XXX, per veure com es configura l’inputa Unity, i com fer proves de la funcionalitat que acabem de desenvolupar.

1.6.2 Configuració dels botons

El primer pas serà crear una petita classe per gestionar els tres possibles estatsque tindran els botons del gamepad o les tecles del teclat. Ser els següents:

• ‘ButtonDown’: durant el primer frame en què un botó és pressionat aquestestat serà cert, fals la resta de la estona. Ens servirà per exemple per al saltdel nostre personatge. Voldrem que salti una vegada un cop es pressioni elbotó de saltar, i no ho torni a fer fins que toqui a terra o torni a pressionar elbotó.

• ‘ButtonPressed’: es produirà durant tot el temps en què el botó estiguipressionat, fins que l’usuari el deixi anar. Per exemple, aquest tipus debotons s’utilitzaran quan vulguem fer accions que es poden repetir fins quel’usuari deixi anar el botó, com disparar o córrer, entre d’altres.

• ‘ButtonUp’: es produirà en el frame en què l’usuari deixi anar un botóque tenia prèviament pressionat. El podem fer servir en casos en què no

Page 34: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 34 Desenvolupament de videojocs amb Unity

’Delegate’

El Delegate és un tipus de dadaque representa una referència a

una funció amb uns paràmetres ivalor de retorn concrets. Es

poden associar les dades delsdelegates amb qualsevol mètode

o funció que tingui la mateixasignatura, i es podrà invocar enqualsevol moment en el codi.

vulguem que una acció es faci fins que l’usuari deixi de pressionar el botóen qüestió.

El codi que farem servir per gestionar els diferents estats dels botons que necessi-tem controlar el trobareu a continuació:

1 namespace ioc.IOCStudents.Core2 {3 public class IMButton4 {5 public enum ButtonStates { Off, ButtonDown, ButtonPressed, ButtonUp }6

7 public ButtonStates State { get; set; }8 public string m_buttonID;9

10 public delegate void ButtonDownMethodDelegate();11 public delegate void ButtonPressedMethodDelegate();12 public delegate void ButtonUpMethodDelegate();13

14 public ButtonDownMethodDelegate m_buttonDownMethod;15 public ButtonPressedMethodDelegate m_buttonPressedMethod;16 public ButtonUpMethodDelegate m_buttonUpMethod;17

18 public IMButton(string playerID, string buttonID,ButtonDownMethodDelegate btnDown, ButtonPressedMethodDelegatebtnPressed, ButtonUpMethodDelegate btnUp)

19 {20 m_buttonID = playerID + "_" + buttonID;21 m_buttonDownMethod = btnDown;22 m_buttonUpMethod = btnUp;23 m_buttonPressedMethod = btnPressed;24 State = ButtonStates.Off;25 }26

27 public virtual void TriggerButtonDown()28 {29 m_buttonDownMethod();30 }31

32 public virtual void TriggerButtonPressed()33 {34 m_buttonPressedMethod();35 }36

37 public virtual void TriggerButtonUp()38 {39 m_buttonUpMethod();40 }41 }42 }

Com podeu veure en el codi anterior, aquest només s’encarrega de mantenir l’estatde cada botó (acabat de pressionar, pressionat o alliberat), sense preocupar-se dedetectar els canvis en el hardware. Si volguéssim detectar els canvis, hauríem defer servir la classe ‘InputManager’, per poder controlar l’input. També haurem defer servir el que es coneix com a Delegates, per cridar-los en cas que es compleixiqualsevol dels tres estats del nostre botó.

Page 35: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament dm _ p r i m a r y M o v e m e n t R a w ' entorns interactius multidispositiu 35 Desenvolupament de videojocs amb Unity

1.6.3 El control de l”input’

De manera genèrica, des de Unity, podem controlar l’input per al nostre videojoc;ja sigui un input analògic (joysticks) o digital (botons). La idea serà detectar l’inputdirectament, fent servir en aquest cas la funcionalitat que dona Unity.

Versió per a totes les consoles

Per al nostre videojoc, amb la funcionalitat que dona Unity en tindrem més que suficient;però si ens decidim, per exemple, a fer una versió per a totes les consoles, us recomanodonar un cop d’ull a un parell de plug-ins de pagament, a l’Asset Store: ‘Incontrol’bit.ly/2GHqQeg i ‘Rewired’ bit.ly/2VgbQqG. Especialment el segon, que té suport natiu pera consoles i una interfície gràfica per gestionar tots els mapes dels controls. Fer servir und’aquests plug-ins ens estalviarà molts malts de cap, i feina.

El nostre ‘InputManager’ el distribuirem en dues grans àrees, l’input dels botonsi l’input dels eixos o axis del joystick, de la següent manera:

• Input de botons: aquest tipus d’input és molt comú, i serà qualsevol teclaque premem del teclat o el gamepad. Tindrà tres estats: acabat de pressionar(en el frame actual), pressionat i alliberat. El codi amb el qual es pot feraquesta acció és el següent; com podeu veure, és molt senzill i es tractade mirar, a cada frame o a les funcions ‘Update’ o ‘FixedUpdate’ de Unity,l’estat de les tecles o botons que necessitem:

1 if (Input.GetButton(KEY OR GAMEPAD BUTTON CODE))2 {3 // Code if the button is being pressed4 }5 if (Input.GetButtonDown(KEY OR GAMEPAD BUTTON CODE))6 {7 // Code if the button has been pressed in this frame8 }9 if (Input.GetButtonUp(KEY OR GAMEPAD BUTTON CODE))

10 {11 // Code if the button is released12 }

• Input d’eixos o joysticks: aquests seran una mica diferents dels botons otecles. El valor que tindran variarà de -1 a 1, passant per 0 en el moment queno estigui pressionat. És a dir, suposant un joystick en el moviment esquerra– dreta, -1 serà el valor quan estigui totalment a l’esquerra, 0 en el centre i 1totalment a la dreta. El codi per obtenir el valor d’un axis concret serà el queteniu a continuació. Com podeu observar, hi ha dues maneres d’obtenir elvalor dels eixos: les funcions ‘GetAxis’ i ‘GetAxisRaw’; depenent del tipusde dades que vulguem obtenir: si més suavitzat en la primera (depenent dela configuració del paràmetre smoth), o sense suavitzar els moviments en lasegona.

1 Vector2 m_primaryMovement = new Vector2();2 Vector2 m_ primaryMovementRaw = new Vector2();3 m_primaryMovement.x = Input.GetAxis(m_axisHorizontal);

Page 36: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d} ' entorns interactius multidispositiu 36 Desenvolupament de videojocs amb Unity

4

5 m_primaryMovement.y = Input.GetAxis(m_axisVertical);6

7 m_primaryMovementRaw.x = Input.GetAxisRaw(m_axisHorizontal);m_

primaryMovementRaw.y = Input.GetAxis(m_axisVertical);

El nostre ‘InputManager’, doncs, tindrà una sèrie de botons dels quals voldremsaber en tot moment l’estat, així com el valor dels dos eixos (un per cada joystick)del gamepad. Fent servir la classe ‘IMButton’ per gestionar tots els botons, i unparell de variables del tipus ‘Vector2’ per emmagatzemar el valor dels eixos, ensquedaria una classe de la següent manera:

1 using UnityEngine;2 using System.Collections.Generic;3

4 namespace ioc.IOCStudents.Core5 {6 /// <summary>7 /// IMPORTANT : this script’s Execution Order MUST be −100.8 /// You can define a script’s execution order by clicking on the script’s

file and then clicking on the Execution Order button at the bottomright of the script’s inspector.

9 /// See https://docs.unity3d.com/Manual/class−ScriptExecution.html for moredetails

10 /// </summary>11 public class InputManager : Singleton<InputManager>12 {13 public bool m_inputDetectionActive = true;14 public string m_playerID = "Player1";15

16 /// If set to true, acceleration / deceleration will take place whenmoving / stopping

17 public bool m_smoothMovement = true;18 /// the minimum horizontal and vertical value you need to reach to

trigger movement on an analog controller (joystick for example)19 public Vector2 m_threshold = new Vector2(0.1f, 0.4f);20

21 public IMButton JumpButton { get; protected set; }22 public IMButton ShootButton { get; protected set; }23 public IMButton PauseButton { get; protected set; }24

25 public Vector2 PrimaryMovement { get { return m_primaryMovement; } }26 public Vector2 SecondaryMovement { get { return m_secondaryMovement; }

}27

28 protected List<IMButton> m_buttonList;29 protected Vector2 m_primaryMovement = Vector2.zero;30 protected Vector2 m_secondaryMovement = Vector2.zero;31 protected string m_axisHorizontal;32 protected string m_axisVertical;33 protected string m_axisSecondaryHorizontal;34 protected string m_axisSecondaryVertical;35

36 protected virtual void Start()37 {38 InitializeButtons();39 InitializeAxis();40 }41

42 protected virtual void InitializeButtons()43 {44 m_buttonList = new List<IMButton>();45 m_buttonList.Add(JumpButton = new IMButton(m_playerID, "Jump",

JumpButtonDown, JumpButtonPressed, JumpButtonUp));46 m_buttonList.Add(ShootButton = new IMButton(m_playerID, "Shoot",

ShootButtonDown, ShootButtonPressed, ShootButtonUp));47 m_buttonList.Add(PauseButton = new IMButton(m_playerID, "Pause",

Page 37: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d} ' entorns interactius multidispositiu 37 Desenvolupament de videojocs amb Unity

PauseButtonDown, PauseButtonPressed, PauseButtonUp));48 }49 protected virtual void InitializeAxis()50 {51 m_axisHorizontal = m_playerID + "_Horizontal";52 m_axisVertical = m_playerID + "_Vertical";53 m_axisSecondaryHorizontal = m_playerID + "_SecondaryHorizontal";54 m_axisSecondaryVertical = m_playerID + "_SecondaryVertical";55 }56

57 protected virtual void LateUpdate()58 {59 ProcessButtonStates();60 }61

62 protected virtual void Update()63 {64 SetMovement();65 SetSecondaryMovement();66 GetInputButtons();67 }68

69 protected virtual void GetInputButtons()70 {71 foreach (IMButton button in m_buttonList)72 {73 if (Input.GetButton(button.m_buttonID))74 {75 button.TriggerButtonPressed();76 }77 if (Input.GetButtonDown(button.m_buttonID))78 {79 button.TriggerButtonDown();80 }81 if (Input.GetButtonUp(button.m_buttonID))82 {83 button.TriggerButtonUp();84 }85 }86 }87

88 public virtual void ProcessButtonStates()89 {90 foreach (IMButton button in m_buttonList)91 {92 if (button.State == IMButton.ButtonStates.ButtonDown)93 {94 button.State = IMButton.ButtonStates.ButtonPressed;95 }96 if (button.State == IMButton.ButtonStates.ButtonUp)97 {98 button.State = IMButton.ButtonStates.Off;99 }100 }101 }102

103 public virtual void SetMovement()104 {105 if (m_inputDetectionActive)106 {107 if (m_smoothMovement)108 {109 m_primaryMovement.x = Input.GetAxis(m_axisHorizontal);110 m_primaryMovement.y = Input.GetAxis(m_axisVertical);111 }112 else113 {114 m_primaryMovement.x = Input.GetAxisRaw(m_axisHorizontal);115 m_primaryMovement.y = Input.GetAxisRaw(m_axisVertical);116 }

Page 38: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 38 Desenvolupament de videojocs amb Unity

117 }118 }119 public virtual void SetSecondaryMovement()120 {121 if (m_inputDetectionActive)122 {123 if (m_smoothMovement)124 {125 m_secondaryMovement.x = Input.GetAxis(

m_axisSecondaryHorizontal);126 m_secondaryMovement.y = Input.GetAxis(

m_axisSecondaryVertical);127 }128 else129 {130 m_secondaryMovement.x = Input.GetAxisRaw(

m_axisSecondaryHorizontal);131 m_secondaryMovement.y = Input.GetAxisRaw(

m_axisSecondaryVertical);132 }133 }134 }135

136 public virtual void JumpButtonDown() { JumpButton.State = IMButton.ButtonStates.ButtonDown; }

137 public virtual void JumpButtonPressed() { JumpButton.State= IMButton.ButtonStates.ButtonPressed; }

138 public virtual void JumpButtonUp() { JumpButton.State= IMButton.ButtonStates.ButtonUp; }

139

140 public virtual void ShootButtonDown() { ShootButton.State= IMButton.ButtonStates.ButtonDown; }

141 public virtual void ShootButtonPressed() { ShootButton.State= IMButton.ButtonStates.ButtonPressed; }

142 public virtual void ShootButtonUp() { ShootButton.State= IMButton.ButtonStates.ButtonUp; }

143

144 public virtual void PauseButtonDown() { PauseButton.State = IMButton.ButtonStates.ButtonDown; }

145 public virtual void PauseButtonPressed() { PauseButton.State= IMButton.ButtonStates.ButtonPressed; }

146 public virtual void PauseButtonUp() { PauseButton.State= IMButton.ButtonStates.ButtonUp; }

147 }148 }

En el codi anterior, podem observar una sèrie d’operacions bàsiques; tres opera-cions principals que es faran en ‘Update’, i una de secundària que es realitzarà enel ‘LateUpdate’:

• A les tres funcions principals, processarem si algun dels botons ha canviatd’estat (GetInputButtons();) i els dos eixos o axis (SetMovement(); iSetSecondaryMovement();).

• A la funció del ‘LateUpdate’, actualitzarem l’estat de cadascun dels botonsde la nostra llista i farem una crida als delegates o callbacks que escaigui.

Es recomana donar un cop d’ull ara als següents vídeos, per veure en funcionamenttot el que hem vist en aquest apartat sobre l’input del nostre videojoc:

Page 39: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 39 Desenvolupament de videojocs amb Unity

https://player.vimeo.com/video/320148891

https://player.vimeo.com/video/320148952

1.7 Crear les diferent escenes del videojoc (’Splash’ i ’Menúprincipal’)

Per elaborar l’esquelet de totes les escenes del nostre videojoc, haurem de crearuna escena com Splash o introducció del videojoc; una per al menú principal; unaaltra per al nivell de joc, i una altra per al resum de la partida del jugador o endgame. Normalment, en gairebé qualsevol videojoc que jugueu, el primer queapareix són els diferents logotips del desenvolupador del joc, el publisher i totsels diferents socis o contribuïdors.

Com podeu veure en la figura 1.7, la composició de la nostra escena Splash seràmolt senzilla, contindrà ‘InputManager’ i ‘GameManager’, una càmera per podervisualitzar el logotip i un objecte que contindrà el logotip de l’IOC.

Figura 1.7. Configuració de l’es-cena ’Splash’

Recordeu que una de les característiques de les nostres escenes és que s’hande poder executar de manera autònoma. Per això, totes tindran una còpiadel ‘GameManager’ i ‘InputManager’, tot i que només es mantindrà actiu elde la primera escena que executem, i es destruirà tota la resta.

La nostra pantalla de Splash tindrà el següent comportament:

L’elaboració de lesescenes corresponents alnivell de joc i l’end game,la trobareu a l’apartat“Desenvolupament devideojocs amb Unity (Part2)”.

Page 40: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament dp u b l i c ' entorns interactius multidispositiu 40 Desenvolupament de videojocs amb Unity

1. Començarem amb tota la pantalla negra.

2. A continuació el logotip de la IOC començarà a fer el típic efecte de fade indurant uns dos segons.

3. Un cop estigui completament visible esperarem uns dos segons amb ellogotip a la pantalla.

4. Farem desaparèixer el logotip amb el típic efecte de fade out de la imatgetambé durant uns dos segons.

5. Un cop hagi desaparegut completament el logotip, carregarem la següentescena, en aquest cas el menú principal.

Per tal que la nostra escena faci aquest comportament, inclourem un scriptanomenat ‘SplashController’ en l’objecte de la càmera, com podeu veure a lafigura 1.8.

Figura 1.8. SplashController

El codi que farà les accions necessàries per fer que la nostra escena de Splashtingui el comportament esperat és el següent:

1 using UnityEngine;2 using System.Collections;3 using ioc.IOCStudents.Core;4

Page 41: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 41 Desenvolupament de videojocs amb Unity

5 namespace ioc.IOCStudents.Scenes6 {7 public class SplashController : MonoBehaviour {8 [SerializeField]9 private GameObject m_logo = null;

10

11 private void Start()12 {13 LeanTween.alpha(m_logo, 1.0f, 2.0f).setOnComplete(

OnAlphaInCompleted);14 }15

16 private void OnAlphaInCompleted()17 {18 StartCoroutine(OnStartAlphaOut());19 }20

21 IEnumerator OnStartAlphaOut()22 {23 yield return new WaitForSeconds(2.0f);24 LeanTween.alpha(m_logo, 0.0f, 2.0f).setOnComplete(On2AlphaOutEnded)

;25 }26

27 private void On2AlphaOutEnded()28 {29 GameManager.Instance.ToManinMenu();30 }31 }32 }

Finalment, us recomano veure el següent vídeo per tal de veure com és el procésde crear l’escena de Splash que acabem de descriure:

https://player.vimeo.com/video/320682906

1.7.1 Elaborar el "Menú principal"

El Menú principal és la primera escena del nostre videojoc, on l’usuari hauràde fer alguna acció per tal d’avançar. Normalment en aquest menú a part d’oferirl’opció al jugador de començar una partida nova o sortir del joc, hi ha les opcionsdel joc, com poden ser configurar el so, la resolució de pantalla, el tipus d’inputque farem servir... En el nostre cas, per simplificar una mica, donarem a l’usuarinomés dues opcions: jugar una partida o sortir del videojoc.

Recordeu sempre incloure els managers a totes les escenes, per tal que espuguin executar de manera autònoma. Inclourem, doncs, el ‘GameManager’i ‘InputManager’ per poder executar el menú sense haver d’executar primera Unity l’escena de Splash.

Page 42: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 42 Desenvolupament de videojocs amb Unity

El primer pas serà crear una imatge a mode de fons del nostre menú, per això asota de la càmera crearem un canvas (Unity UI) que contindrà tots els componentsdel nostre menú, com la imatge de fons, els botons, text... (vegeu la figura 1.9).

Figura 1.9. Menú ‘canvas’

Un cop el tinguem enllestit, serà el moment de crear la imatge del fons. Afegiremun nou objecte del tipus ‘Image’ i li assignarem una imatge per ensenyar de fonsdel menú (vegeu la figura 1.10).

Page 43: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 43 Desenvolupament de videojocs amb Unity

Figura 1.10. Imatge de fons

Ara voldrem afegir un petit marc per incloure un text a dintre del tipus Press spaceto play o alguna cosa similar. Per fer-ho, crearem un objecte del tipus ‘Image’ ambel marc, i a sota d’aquest un altre objecte del tipus ‘Text’ per incloure el text quevolem ensenyar al jugador (vegeu la figura 1.11).

Page 44: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament dGameManager ' entorns interactius multidispositiu 44 Desenvolupament de videojocs amb Unity

Figura 1.11. Botó i text

També posarem música al nostre menú perquè no quedi tan pla. En aquest casfarem servir un prefab que ja té el 3D Game Kit, anomenat “Level01Music”. Elpodreu trobar a Assets/3DGameKit/Prefabs/Audio o buscant directament pel nomdel prefab.

Per donar-hi una mica més de vistositat afegirem un component del 3D Game Kitper tal que faci fade out de l’escena per a nosaltres, mostri un menú de càrrega oloading i un cop hagi carregat la nova escena, faci fade in de nou, i per tant lestransicions entre escenes siguin menys brusques. El component és ScreenFader iel podeu trobar a Assets/3DGameKit/Prefabs/SceneControl o buscant directamentpel nom del prefab.

A continuació podreu observar el codi que farà les accions necessàries per fer quela nostra escena de menú principal tingui el comportament esperat:

1 using UnityEngine;2 using ioc.IOCStudents.Core;3

4 namespace ioc.IOCStudents.Scenes5 {6 public class MainMenuController : MonoBehaviour7 {8 [SerializeField]9 protected RectTransform m_fadeButtonFrame = null;

10

11 protected void Start()12 {13 InputManager.Instance.JumpButton.m_buttonDownMethod +=

OnStartLevelPressed;14

15 LeanTween.alpha(m_fadeButtonFrame, 0.0f, 2.0f).setLoopPingPong();16 }17

18 private void OnStartLevelPressed()19 {20 LeanTween.cancel(m_fadeButtonFrame);21 m_fadeButtonFrame.gameObject.SetActive(false);

Page 45: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 45 Desenvolupament de videojocs amb Unity

22

23 GameManager.Instance.ToGame(); }24

25 protected void OnDisable()26 {27 if (InputManager.Instance.JumpButton == null)28 {29 return;30 }31

32 InputManager.Instance.JumpButton.m_buttonDownMethod −=OnStartLevelPressed;

33 }34 }35 }

Com heu pogut veure al codi, en la funció Start farem dues accions: ens posarema “escoltar” l’esdeveniment de pressionar un botó, en aquest cas, el de “salt” (teclaespai), i farem un alpha del botó amb el text Press space to play, per tal que elnostre projecte no quedi tan pla.

Un cop rebem l’esdeveniment que l’usuari ha pressionat la tecla d’espai, can-cel·larem el efecte d’alpha que teníem en el botó, i li direm al ‘GameManager’que volem anar cap a l’escena de joc o nivell.

L’últim tros de codi remarcable és quan l’script està a punt de ser deshabilitat perUnity (abans de destruir completament l’escena); deixarem d’escoltar l’esdeveni-ment de l’input, ja que, si no, podríem tenir problemes de referències en el nostrecodi. A la figura 1.12 podeu veure com quedarà tota la escena.

Figura 1.12. Escena “Menú principal”

Es recomana donar un cop d’ull ara al vídeo, per veure com s’ha creat l’escena delmenú principal des de zero a Unity:

Page 46: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 46 Desenvolupament de videojocs amb Unity

https://player.vimeo.com/video/320682962

Page 47: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 47 Desenvolupament de videojocs amb Unity

2. Desenvolupament de videojocs amb Unity (Part 2)

Continuant amb el desenvolupament bàsic d’un videojoc, primer de tot ens caldràfinalitzar la creació de les escenes restants: la del resum del nivell i l’escena deljoc pròpiament dit. Després, caldrà analitzar una última part de les funcions ‘Core’del nostre codi font, els esdeveniments.

A continuació, haurà arribat l’hora de treballar en el nostre personatge i lesdiferents habilitats que tindrà. Tot seguit, caldrà crear els enemics del nivell, il’objectiu final del nivell, així com la UI, per tal de mostrar a l’usuari informacióimportant.

Finalment, afegirem una sèrie d’efectes sonors i música de fons en el nostre nivell,al menú principal i al menú de final de nivell.

2.1 Creació de l’escena de joc o ’Level01’

En qualsevol videojoc, crear l’escena del joc serà la part central i, per tant, la querequerirà una major inversió en termes de temps i esforç.

Per crear l’escena del joc pròpiament dit heu de fer servir, com a base, una de lesescenes que proporciona el 3D Game Kit; allà haureu de crear un entorn virtualque simularà un planeta alienígena.

Així, farem servir una còpia de l’escena inicial d’exemple enel 3D Game Kit. Navegarem fins a aquesta escena en l’editor:Assets/3DGamekit/Scenes/Gameplay/Level1 i farem una còpia fent CTRL+D aWindows o CMD+D a Mac. Un cop hem fet la còpia, mourem l’escena al nostredirectori _Scenes i li donarem el nom de ‘Level01’.

Un cop hem fet la còpia de l’escena, i la tenim en el directori correcte, farem dobleclic a sobre, i l’obrirem a l’editor de Unity. La gent de Unity ha fet una gran tascaordenant tots els objectes en l’escena.

Organització escena del nivell(Credit: Unity Technologies)

En una escena organitzada d’aquesta manera és fàcil trobar els objectes. Perexemple, si volem buscar on està localitzada la UI, ràpidament trobarem que és alvoltant de l’objecte ”—– UI —–”. Pot semblar molt trivial organitzar correctamentles nostres escenes, especialment al començament del desenvolupament.

En la següent imatge (figura 3.11), podeu veure una escena real d’un videojoc queestà al mercat (Steam, PS4, XB1, WiiU, IOS, Android i properament a Switch).

En aquest apartat,continuarem treballant ambla creació del videojoc Ellenand the lost IOCStudents.

Page 48: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 48 Desenvolupament de videojocs amb Unity

Figura 2.1. Organització escena joc real

Credit: 2Awesome Studio

Com podeu comprovar, és un caos total, i fins i tot la llista d’objectes és tan llargaque no cap en una captura de pantalla senzilla. És un videojoc d’uns trenta nivells,tots ordenats de manera diferent a mesura que s’anaven creant.

Imagineu-vos que els han creat persones diferents, i després heu de crear unaactualització en algun d’aquests nivells, i vosaltres no el vau crear. Per posar unexemple, si heu de modificar una part del terra, o un model en concret, us portaràuna estona llarga trobar-lo abans de començar amb l’edició.

Organitzeu les vostres escenes des del començament del projecte, i noespereu a tenir el projecte en fases molt avançades, ja que en aquest puntserà massa tard, i complicarà molt l’edició d’escenes i el desenvolupament.

A continuació, haurem d’afegir els nostres managers per tal de tenir el GameMa-nager i el LevelManager. Finalment, doneu un cop d’ull als diferents componentsa l’escena. A continuació veurem l’explicació dels més rellevants:

Page 49: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 49 Desenvolupament de videojocs amb Unity

• System: en aquest grup trobarem tots els objectes que s’encarreguen de lagestió de l’escena, les transicions entre aquestes, els efectes de postproces-sament, la música i els efectes de so de l’escena.

• UI: aquí es trobaran tots els elements de la UI, des del menú de pausa, finsals diferents cartells amb informació que aniran sortint al llarg de l’escena,l’ScreenFader, i el HealthCanvas amb els cors que indicaran la vida deljugador.

• PlayerAssets: en aquesta àrea hi haurà el nostre jugador, els enemics i lescàmeres del joc (ja que es veuen afectades pel moviment del jugador).

• GamePlay: aquí hi haurà tots els assets que tenen alguna influència en elgameplay, com poden ser els objectes que es poden destruir, les diferentsportes en el nivell, checkpoints, interruptors...

• LevelAssets: tots els elements de disseny del nivell, que són meramentdecoratius.

• Lighting: tots els punts de llum del nivell, així com ReflectionProbes iLightProbes.

• SpawnedPrefabs: tots els prefabs nous en què anirem fent spawn en el nivellen el temps d’execució.

Finalment, us recomano donar un cop d’ull al vídeo L’escena ‘Level01’, on es potveure com es crea aquesta escena a partir de la còpia de l’original, i es fan algunesproves.

https://player.vimeo.com/video/323459741

2.2 Creació de l’escena de resum de nivell o ’LevelReport’

Quant a l’escena de resum o level report, allà es veuran les estadístiques del nivellque farem que es mostrin, un cop l’usuari acabi el nivell de manera satisfactòria.Aquesta escena serà molt senzilla, ja que només haurem de mostrar informació al’usuari de com ha jugat el nivell anterior (estadístiques) i esperar el seu input pertal de continuar amb el següent nivell, o saltar al menú principal.

És important, al finalitzar cada nivell o àrea del videojoc, donar informació al’usuari, per tal que vegi com està avançant en la nostra aventura virtual. És adir, el jugador ha de saber com està progressant en l’aventura, si el seu resultaten el nivell ha estat bo, regular o dolent.

Page 50: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 50 Desenvolupament de videojocs amb Unity

Depenent del tipus de videojoc pot ser interessant crear classificacions en línia orankings, per tal que els jugadors competeixen entre ells per veure qui treu mésbona puntuació en cada nivell. En altres casos, els interessa saber quant tempshan necessitat per completar un nivell en concret, ja que molts competeixen en elque es coneix com a speed run, que consisteix en com de ràpid pots completar elvideojoc.

Totes aquestes consideracions i altres s’han de tenir en compte a l’hora de dissenyarel videojoc (a quin públic objectiu va dirigit) i guiaran moltes de les nostresdecisions a l’hora de crear escenes com la d’aquest apartat. No serà el mateixdissenyar una escena de resum de nivell per a un videojoc de mòbil, PC, consoles,dirigit a públic casual o més hardcore.

En el nostre cas voldrem mostrar a l’usuari una sèrie d’estadístiques de com hajugat el nivell. Seran les següents:

• Played Time: el temps que ha trigat a completar el nivell (des del primerintent, fins que ha arribat al punt final).

• Enemies Killed: quants enemics ha matat en el nivell. En aquest punthaurem d’anar amb compte a l’hora d’enregistrar aquesta estadística, i enshem de preguntar: els enemics tornen a aparèixer cada cop que el jugadormor i ha de començar de nou? Comptarem només els enemics que mati enl’últim intent o tots?

• Points: punts aconseguits durant el nivell.

Com us podeu imaginar, podem mostrar tantes estadístiques com creguem neces-sari, nomes haurem de tenir-ho en compte a l’hora de programar els managers delnostre videojoc per tal que vagi enregistrant aquestes estadístiques.

És hora dedissenyar la UI o finestra que mostrarà les dades de resum denivell. Quan es dissenyen canvas complexos, o que tenen molts elements dintred’elements és recomanable fer un petit esbós (en paper o digital) per tal que siguimés fàcil la seva creació a Unity. En el nostre cas, la UI tindrà una aparençasemblant a la que podeu veure en l’esbós de la imatge següent (vegeu la figura3.20).

Figura 2.2. Esbós ’UI Level Report’

Com podeu comprovar, la distribució de la UI serà la següent:

Page 51: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 51 Desenvolupament de videojocs amb Unity

• Hi haurà un canvas que englobarà tots els components.

• A la columna de l’esquerra hi haurà uns components de text a sobre d’unaimatge o rectangle com un contenidor. Cadascun d’aquests rectangles tambéseran un objecte del tipus canvas, que inclourà la imatge i el text (PlayedTime, Enemies Killed i Points).

• La columna de la dreta seran unes caixes de text on hi haurà el valor decadascuna de les estadístiques que volem mostrar.

• La part en gris serà un text que farà de títol.

El resultat final serà el que podeu observar en la imatge següent (figura 3.21).

Figura 2.3. Escena de ’Level Report’

Us recomano donar un cop d’ull al vídeo L’escena ‘LevelReport’, on veureu comes crea aquesta escena des de zero amb tots els seus components.

https://player.vimeo.com/video/323460478

2.3 Els esdeveniments

Normalment, durant el desenvolupament d’un videojoc, ens trobarem amb lasegüent problemàtica, que està present moltes vegades en el nostre codi:

• La classe A conté informació sobre els punts del jugador.

• La classe B s’encarrega de la gestió de la UI del nostre videojoc.

• Com que el jugador ha recollit un element, els punts del jugador s’incremen-ten en 10 punts (per posar un exemple).

Page 52: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament dp u b l i c ' entorns interactius multidispositiu 52 Desenvolupament de videojocs amb Unity

Aleshores, ens trobem amb la necessitat de notificar a la classe B que ha ocorregutaquest esdeveniment, per tal que reflecteixi a la UI aquestes dades. Doncs bé, perresoldre la problemàtica anterior hi ha diferents opcions:

• Una seria fer servir una classe del tipus Singleton.

• També podríem crear la classe de gestió de la UI com a estàtica (no gairerecomanable mesclar classes d’aquest tipus i MonoBehaviours de Unity).

• Una altra opció seria tenir una referència a la classe A dintre de classe A,i cridar un mètode d’aquesta per tal d’actualitzar els punts.

Com us podeu imaginar, cap de les solucions anteriors seria gaire eficient, i moltesd’elles trencarien completament el desenvolupament orientat a objectes. A més amés, el nostre codi es convertiria en molt difícil de mantenir, i donaria pas a moltsbugs o errors.

Una possible solució és el que es coneix com el patró de disseny Observer.En el diagrama de la imatge figura 2.4 podeu observar el seu funcionament, onl’EventManager és el subjecte del patró Observer i les classes B, C i D són elsobservadors. Aleshores, la classe A notifica a la classe EventManager qualsevolcanvi, i aquesta notifica la resta dels canvis notificats per la classe A.

Figura 2.4. Diagrama d’esdeveniments

Patró de disseny ‘Observer’

Observer és un patró de disseny de programari en què un objecte, anomenatsubjecte, manté una llista dels seus dependents, anomenats observadors, i els notificaautomàticament qualsevol canvi d’estat, generalment trucant a un dels seus mètodes.Trobareu més informació a: bit.ly/2BN9mZK.

En el següent codi, podrem veure una implementació de ‘EventManager’, la classecentral per gestionar tots els esdeveniments del nostre videojoc:

1 using System;2 using UnityEngine;3 using UnityEngine.Events;4 using System.Collections;5 using System.Collections.Generic;6

Page 53: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament dL i s t ' entorns interactius multidispositiu 53 Desenvolupament de videojocs amb Unity

7 namespace ioc.IOCStudents.Core8 {9 public static class EventManager {

10 private static Dictionary<Type, List<MMEventListenerBase>>_subscribersList;

11

12 static MMEventManager()13 {14 _subscribersList = new Dictionary<Type, List<MMEventListenerBase>>();15 }16

17 public static void AddListener<MMEvent>( MMEventListener<MMEvent>listener ) where MMEvent : struct

18 {19 Type eventType = typeof( MMEvent );20

21 if( !_subscribersList.ContainsKey( eventType ) )22 _subscribersList[eventType] = new List<MMEventListenerBase>();23

24 if( !SubscriptionExists( eventType, listener ) )25 _subscribersList[eventType].Add( listener );26 }27

28 public static void RemoveListener<MMEvent>( MMEventListener<MMEvent>listener ) where MMEvent : struct

29 {30 Type eventType = typeof( MMEvent );31

32 if( !_subscribersList.ContainsKey( eventType ) )33 {34 return;35 }36

37 List<MMEventListenerBase> subscriberList = _subscribersList[eventType];38 bool listenerFound;39 listenerFound = false;40

41 for (int i = 0; i<subscriberList.Count; i++)42 {43 if( subscriberList[i] == listener )44 {45 subscriberList.Remove( subscriberList[i] );46 listenerFound = true;47

48 if( subscriberList.Count == 0 )49 _subscribersList.Remove( eventType );50

51 return;52 }53 }54

55 }56

57 public static void TriggerEvent<MMEvent>( MMEvent newEvent ) whereMMEvent : struct

58 {59 List<MMEventListenerBase> list;60 if( !_subscribersList.TryGetValue( typeof( MMEvent ), out list ) )61 {62 return;63 }64

65 for (int i=0; i<list.Count; i++)66 {67 ( list[i] as MMEventListener<MMEvent> ).OnMMEvent( newEvent );68 }69 }70

71 private static bool SubscriptionExists( Type type, MMEventListenerBasereceiver )

Page 54: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 54 Desenvolupament de videojocs amb Unity

72 {73 List<MMEventListenerBase> receivers;74 if( !_subscribersList.TryGetValue( type, out receivers ) ) return

false;75

76 bool exists = false;77

78 for (int i=0; i<receivers.Count; i++)79 {80 if( receivers[i] == receiver )81 {82 exists = true;83 break;84 }85 }86

87 return exists;88 }89 }90

91 public static class EventRegister92 {93 public delegate void Delegate<T>( T eventType );94

95 public static void MMEventStartListening<EventType>( this MMEventListener<EventType> caller ) where EventType : struct

96 {97 MMEventManager.AddListener<EventType>( caller );98 }99

100 public static void MMEventStopListening<EventType>( this MMEventListener<EventType> caller ) where EventType : struct

101 {102 MMEventManager.RemoveListener<EventType>( caller );103 }104 }105

106 public interface MMEventListenerBase { };107

108 public interface MMEventListener<T> : MMEventListenerBase109 {110 void OnMMEvent( T eventType );111 }112 }

Com podeu observar, hi ha quatre mètodes principals en la classe ‘EventManager’:

• ‘AddListener’: amb aquesta funció registrarem un nou subscriptor oescoltador per a un determinat esdeveniment.

• ‘RemoveListener’: similar a l’anterior funció però per fer la funció opo-sada, un cop ja no necessitem continuar subscrits a un esdeveniment, ensesborrarem de la llista de classes subscrites.

• ‘TriggerEvent’: amb aquesta funció podem executar un esdevenimentconcret, i tots els seus subscriptors rebran la notificació pertinent.

• ‘SubscriptionExists’: aquesta és una funcionalitat extra per tal de compro-var si un esdeveniment concret té subscriptors associats, és a dir, si algunaclasse s’ha registrat com a escoltador o subscriptor d’aquest esdeveniment.

Per tal d’escoltar esdeveniments, en qualsevol classe del nostre projecte, hauremd’incloure les següents funcions o declaracions per tal d’indicar a l’ ‘EventMa-nager’ que volem escoltar un esdeveniment concret (o deixar de fer-ho) així com

Page 55: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 55 Desenvolupament de videojocs amb Unity

indicar quina funció haurà de cridar en la nostra classe en cas que hi hagi canvisen l’esdeveniment.

’C# Interfaces’

Una interfície conté definicions per a un grup de funcionalitats que una classe o unaestructura poden implementar. Mitjançant l’ús d’interfícies, podeu, per exemple, incloureel comportament de diverses fonts d’una classe. Aquesta capacitat és important en C #,perquè aquest llenguatge de programació no admet diverses herències de classes.

Primer de tot, en la definició de la nostra classe, haurem d’afegir una declaracióper tal d’indicar que volem implementar la interfície ‘EventListener’, com podeuveure en el codi d’exemple següent:

1 public class GUIManager : Singleton<GUIManager>, EventListener<GameEvent>

En la funció ‘OnEnable’ començarem a escoltar l’esdeveniment o esdevenimentsque vulguem en aquesta classe, com en l’exemple següent amb un del tipus‘GameEvent’:

1 void OnEnable()2 {3 this.EventStartListening<GameEvent>();4 }

En la funció ‘OnDisable’ deixarem d’escoltar els esdeveniments que estiguemescoltant i que hem registrat en la funció ‘OnEnable’. En l’exemple següentdeixem d’escoltar l’esdeveniment del tipus ‘GameEvent’:

1 void OnDisable()2 {3 this.EventStopListening<GameEvent>();4 }

Haurem d’afegir una funció a la nostra classe per tal d’escoltar l’esdevenimentque volem escoltar. És la funció que cridarà la classe ‘EventManager’ quan enshagi de notificar algun canvi en l’esdeveniment:

1 public void OnMMEvent(MMGameEvent gameEvent)2 {3 if (gameEvent.eventName == "NomDelEsdeveniment")4 {5 }6 }

Finalment, us recomano donar un cop d’ull al vídeo “El patró de disseny ‘Ob-server”’, on es pot veure com funciona la classe EventManager, com es creenesdeveniments i com fer-la servir a part d’alguns tests.

Page 56: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 56 Desenvolupament de videojocs amb Unity

https://player.vimeo.com/video/323460834

2.4 Creació del personatge

Per tal de crear el nostre personatge o heroïna, l’Ellen, farem servir moltes deles funcionalitats que ens dona per defecte el 3D Game Kit; com poden ser elmodel 3D, alguns dels scripts de control, l’Animator Controller... Quan estemdesenvolupant un videojoc, amb un personatge complex, com sol ser el personatgeprincipal que controlarà el jugador, és important tenir en compte que el controld’aquest tipus de personatge sol ser molt complex, i que portarà bastantes líniesde codi.

A més a més, haurem de controlar les seves animacions, les transicions entreestats, quan el personatge rep un impacte... Per això sempre m’agrada fer algunesrecomanacions abans de començar a desenvolupar el codi que controlarà el nostrepersonatge:

• Recordeu que serà la manera d’interacció del jugador amb el nostre mónvirtual.

• Serà l’objecte amb més línies de codi en el nostre videojoc. Penseu en lafrase: “dividiu i vencereu”. Intenteu dividir en petits scripts cada apartat,cada habilitat...

• Serà el més complex, amb molta diferència, així que recordeu: “dividiu ivencereu”.

• Algun altre programador/desenvolupador ha creat un personatge similaral vostre. Feu una recerca per internet de possibles personatges similars,i comenceu amb el seu codi com a base. Per exemple per controlar unpersonatge humanoide en 3D, totes les funcionalitats del 3D Game Kit, elmateix per a 2D amb el 2D Game Kit, el Corgi...

• El sistema d’animacions de Unity, tot i que és molt potent, està pensat perfuncionar d’una manera, només. Fixeu-vos en com realitzen les animacionsi les seves transicions al 3D Game Kit.

Page 57: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 57 Desenvolupament de videojocs amb Unity

2.4.1 Gestionar el moviment del personatge amb’CharacterController’

A l’hora de gestionar el moviment del personatge a Unity tenim dues opcions:podem fer servir un RigidBody o, en unes altres paraules, el motor de físiques deUnity, o bé una eina que ens proporciona Unity anomenada CharacterController.

’CharacterController’ o ’RigidBody’?

El CharacterController és un component (script) de Unity que ens permet fer fàcilment elmoviment d’un objecte, restringit per col·lisions i sense haver de fer servir un RigidBody ofísiques en el moviment de l’objecte esmentat. Vegeu: bit.ly/2C6JEiQ.

El RigidBody és un component de Unity que ens ajudarà a controlar un objecte mitjançantla simulació de les físiques, emulant el seu comportament en el món real. Un cop hàgimafegit aquest tipus de component, fins i tot sense afegir cap codi o script, l’objecte es veuràafectat per les físiques del món que l’envolta.

Us recomano donar un cop d’ull a aquest article en línia, on s’expliquen més en profunditatles diferències entre fer servir el CharacterController i un RigidBody, així com preparar unpersonatge amb les dues opcions: bit.ly/2wetY8T.

En el cas de l’exemple del 3D Game Kit, van triar l’opció de fer servir elCharacterController, i no el Rigidbody. Com sempre, hi ha opinions per a tot,a favor i en contra. A continuació enumerarem alguns dels pros i contres de lesdues opcions:

• El CharacterController, com el seu nom indica, està orientat a fer-se servirper controlar personatges.

• El control d’un personatge que no vulgui simular 100% les físiques del mónreal amb un RigidBody és força complicat, ja que s’han de forçar valors deforces a l’objecte. Per exemple, si volem parar de sobte el personatge (perqualsevol motiu) hauríem de reduir la força a 0, i la velocitat també a 0. Encanvi, amb el CharacterController, senzillament deixem de cridar la funcióMove.

• En jocs on les físiques tenen una part molt important, és molt recomanablefer servir el motor de físiques amb RigidBody. Per exemple, un joc sobre unapilota que cau, serà molt més fàcil de crear amb les físiques que manualmento amb un CharacterController.

• És molt més fàcil d’utilitzar i intuïtiu el CharacterController que el motorde físiques.

• En canvi, el seu disseny està tancat i, per tant, tot i que podem estendre laclasse, pot resultar difícil d’aconseguir el resultat que estem buscant per almoviment del jugador.

En la configuració del personatge de l’Ellen tenim els valors que podeu observara la següent imatge (figura 3.31):

Page 58: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 58 Desenvolupament de videojocs amb Unity

Figura 2.5. CharacterController

Com podeu observar, tindrem els següents valors en l’inspector per a l’Ellen, i laseva explicació:

• Slope Limit:: el límit de pendent del personatge en graus (a l’hora demoure’s per rampes o superfícies que no siguin planes).

• Step Offseet: la distància de cada pas del personatge en metres. Aquestvalor dependrà de l’animació del personatge.

• Skin Width: la mida de la càpsula de col·lisió (amplada).

• Center: el centre del personatge, o de la càpsula que l’envolta per tal de ferels càlculs de les col·lisions.

• Radius: el radis de la càpsula de col·lisió.

• Height: l’alçada de la càpsula de col·lisió.

Per tal d’entendre millor el funcionament dels valors, us recomano queexecuteu l’escena amb la qual estem treballant, i modifiqueu els valors delCharacterController, per tal de veure el seu efecte en el moviment, canvis,col·lisions...

2.4.2 El ’Player Input’

El control del moviment del jugador el farem en un script separat. Recordeu quesempre hem de crear scripts amb un objectiu clar i definit, i evitar afegir mésfuncionalitats de les estrictament necessàries. El jugador, doncs, haurà d’estaratent als següents tipus d’input:

• Horitzontal i vertical: llegirem l’input del joystick primari del gamepad ode les tecles wasd. Amb aquest input mourem la nostra heroïna pel nivell.

• Horitzontal i vertical secundari: en el nostre cas aquest input el llegiremdel ratolí o el joystick secundari en un gamepad. Amb aquest input mouremla càmera, per tal de mirar al nostre voltant i observar el món que ens envolta.

• Salt: quan el jugador pressioni la tecla de salt, saltarà.

• Pausa: quan es pressioni aquesta tecla, el joc entrarà o sortirà del modepausa, mostrant un menú especial de pausa.

Page 59: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d

v o i d ' entorns interactius multidispositiu 59 Desenvolupament de videojocs amb Unity

• Disparar: quan el jugador pressioni aquesta tecla, la nostra heroïna dispa-rarà si va equipada amb una arma.

El codi de la classe PlayerInput serà la següent (una petita modificació del queens proporciona el 3D Game Kit per treballar amb l’InputManager):

1 using UnityEngine;2 using System.Collections;3 using ioc.IOCStudents.Core;4

5 public class PlayerInput : Singleton<PlayerInput>6 {7 [HideInInspector]8 public bool playerControllerInputBlocked;9

10 protected Vector2 m_Movement;11 protected Vector2 m_Camera;12 protected bool m_Jump;13 protected bool m_Attack;14 protected bool m_Pause;15 protected bool m_ExternalInputBlocked;16

17 public Vector2 MoveInput18 {19 get20 {21 if(playerControllerInputBlocked || m_ExternalInputBlocked)22 return Vector2.zero;23 return m_Movement;24 }25 }26

27 public Vector2 CameraInput28 {29 get30 {31 if(playerControllerInputBlocked || m_ExternalInputBlocked)32 return Vector2.zero;33 return m_Camera;34 }35 }36

37 public bool JumpInput38 {39 get { return m_Jump && !playerControllerInputBlocked && !

m_ExternalInputBlocked; }40 }41

42 public bool Attack43 {44 get { return m_Attack && !playerControllerInputBlocked && !

m_ExternalInputBlocked; }45 }46

47 public bool Pause48 {49 get { return m_Pause; }50 }51

52 WaitForSeconds m_AttackInputWait;53 Coroutine m_AttackWaitCoroutine;54

55 const float k_AttackInputDuration = 0.03f;56

57 protected override void Awake()58 {59 base.Awake();60 m_AttackInputWait = new WaitForSeconds(k_AttackInputDuration);

Page 60: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 60 Desenvolupament de videojocs amb Unity

61 }62 void Update() {63 m_Movement = InputManager.Instance.PrimaryMovement;64 m_Camera = InputManager.Instance.SecondaryMovement;65 m_Jump = InputManager.Instance.JumpButton.State == IMButton.

ButtonStates.ButtonDown;66 m_Pause = InputManager.Instance.PauseButton.State == IMButton.

ButtonStates.ButtonDown;67 if (InputManager.Instance.ShootButton.State == IMButton.ButtonStates.

ButtonDown)68 {69 if (m_AttackWaitCoroutine != null)70 StopCoroutine(m_AttackWaitCoroutine);71

72 m_AttackWaitCoroutine = StartCoroutine(AttackWait());73 }74 }75

76 IEnumerator AttackWait()77 {78 m_Attack = true;79

80 yield return m_AttackInputWait;81

82 m_Attack = false;83 }84

85 public bool HaveControl()86 {87 return !m_ExternalInputBlocked;88 }89

90 public void ReleaseControl()91 {92 m_ExternalInputBlocked = true;93 }94

95 public void GainControl()96 {97 m_ExternalInputBlocked = false;98 }99 }

Com podeu observar en el codi, el primer que caldrà serà definir unes variableson emmagatzemarem a cada Update el valor de cadascuna:

1 protected Vector2 m_Movement;2 protected Vector2 m_Camera;3 protected bool m_Jump;4 protected bool m_Attack;5 protected bool m_Pause;6 protected bool m_ExternalInputBlocked;

El moviment, tant del jugador com de la càmera, l’emmagatzemarem en duesvariables del tipus Vector2 (x i y). Les accions de salt i atac seran una variable deltipus bool, depenent de si està activada o no. El mateix farem per a la funció de‘pausa’.

En la funció ‘Awake’, farem algunes inicialitzacions necessàries de les variables.I en la funció ‘Update’ anirem actualitzant el valor de les variables tant delmoviment com de les accions del jugador.

Com podeu veure, anirem cridant la nostra funció d’InputManager que havíemcreat en la primera part dels materials. Podríem llegir directament de l’input

Page 61: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament dr e a d o n l y ' entorns interactius multidispositiu 61 Desenvolupament de videojocs amb Unity

de Unity, però per encapsular les accions del Player hem modificat la classePlayerInput del 3DGameKit.

1 void Update()2 {3 m_Movement = InputManager.Instance.PrimaryMovement;4 m_Camera = InputManager.Instance.SecondaryMovement;5 m_Jump = InputManager.Instance.JumpButton.State == IMButton.ButtonStates.

ButtonDown;6 m_Pause = InputManager.Instance.PauseButton.State == IMButton.

ButtonStates.ButtonDown;7 if (InputManager.Instance.ShootButton.State == IMButton.ButtonStates.

ButtonDown)8 {9 if (m_AttackWaitCoroutine != null)

10 StopCoroutine(m_AttackWaitCoroutine);11

12 m_AttackWaitCoroutine = StartCoroutine(AttackWait());13 }14 }

2.4.3 El controlador del jugador o ’PlayerController’

Farem servir la classe PlayerController del 3DGameKit . És una classe forçaextensa, i serà millor ampliar-la amb noves funcionalitats que començar de zero.Anirem comentant les parts més importants de com controlar el nostre jugador.

Si examinem la classe PlayerController, el primer que observarem seran lesvariables. Tindrem, per configurar el moviment del jugador, els diferents àudiosque farem servir depenent de l’acció a realitzar o realitzada, i altres que depenende l’estat mateix de components com l’Animator, el PlayerInput (que hem vist enl’apartat anterior).

També tindrem algunes constants que farem servir durant el moviment, salt...Finalment, a la part de les variables o propietats de l’objecte, trobarem les quefan referència a la part de l’Animator o les animacions, com podeu veure en elfragment de codi següent:

1 // Parameters2 readonly int m_HashAirborneVerticalSpeed = Animator.StringToHash("

AirborneVerticalSpeed");3 readonly int m_HashForwardSpeed = Animator.StringToHash("ForwardSpeed");4 readonly int m_HashAngleDeltaRad = Animator.StringToHash("AngleDeltaRad");5 readonly int m_HashTimeoutToIdle = Animator.StringToHash("TimeoutToIdle");6 readonly int m_HashGrounded = Animator.StringToHash("Grounded");7 readonly int m_HashInputDetected = Animator.StringToHash("InputDetected");8 readonly int m_HashMeleeAttack = Animator.StringToHash("MeleeAttack");9 readonly int m_HashHurt = Animator.StringToHash("Hurt");

10 readonly int m_HashDeath = Animator.StringToHash("Death");11 readonly int m_HashRespawn = Animator.StringToHash("Respawn");12 readonly int m_HashHurtFromX = Animator.StringToHash("HurtFromX");13 readonly int m_HashHurtFromY = Animator.StringToHash("HurtFromY");14 readonly int m_HashStateTime = Animator.StringToHash("StateTime");15 readonly int m_HashFootFall = Animator.StringToHash("FootFall");16

17 // States18 readonly int m_HashLocomotion = Animator.StringToHash("Locomotion");19 readonly int m_HashAirborne = Animator.StringToHash("Airborne");

Page 62: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 62 Desenvolupament de videojocs amb Unity

’Animator Controller’

Els Animator Controllers sónmàquines d’estat que determinen

quines animacions s’estanreproduint actualment i

s’encarreguen que les transicionsentre animacions siguin de

manera suau (blend).

20 readonly int m_HashLanding = Animator.StringToHash("Landing"); // Also aparameter.

21 readonly int m_HashEllenCombo1 = Animator.StringToHash("EllenCombo1");readonlyint m_HashEllenCombo2 = Animator.StringToHash("EllenCombo2");

22 readonly int m_HashEllenCombo3 = Animator.StringToHash("EllenCombo3");23 readonly int m_HashEllenCombo4 = Animator.StringToHash("EllenCombo4");24 readonly int m_HashEllenDeath = Animator.StringToHash("EllenDeath");

Com podeu veure en el codi anterior, estan dividides en dues parts: les variablesque emmagatzemen els paràmetres de l’Animator i les que ho fan per diferentsestats o states.

2.4.4 ’Animator Controller’ del personatge

Abans de continuar amb l’explicació de la classe PlayerController (i com, entrealtres, controla les diferents animacions del personatge), caldrà que parem atencióa la configuració de l’Animator Controller de l’Ellen. A continuació, veuremcom està dissenyat l’Animator Controller, els diferents estats de què consta, iaprofundirem en l’estat de Locomotion i quins paràmetres farà servir per a lesanimacions de fer caminar el personatge. A la imatge següent (figura 3.41) veureucom és l’Animator de l’Ellen per defecte en el 3D Game Kit:

Figura 2.6. ’Animator’ Ellen

Com podeu veure a la imatge, es tracta d’un Animator força complex. Podemobservar els següents estats:

• IdleSM: és l’estat en repòs de l’Ellen, és a dir, quan no està reben cap inputper part del jugador ni cap hit. Reproduirà una petita animació per tal queno doni un mal efecte de model estàtic.

• Hurt: serà l’estat on acabarem de rebre un impacte, i s’estarà reproduintuna animació d’impacte, per donar feedback al jugador.

Page 63: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 63 Desenvolupament de videojocs amb Unity

• EllenDeath: si el nostre personatge no pot absorbir tot l’impacte, morirà iper tant haurem de mostrar/reproduir una animació de mort.

• BeginRespawn: un cop hem mort o és el moment de generació inicial(quan apareix l’Ellen), haurem de reproduir una animació a aquest efecte,en aquest cas l’animació de Spwan.

• AirboneSM: aquest serà un estat pare que contindrà una sèrie de subestats,tots ells relacionats amb el moment que l’Ellen està realitzant un salt finsque aterra i per tant farà una transició a l’estat LandingSM.

• LandingSM: aquest estat s’encarregarà de controlar les animacions del’estat quan l’Ellen està aterrant o trepitjant de nou terra ferma després d’unsalt.

• LocomotionSM: aquest serà un estat d’estats o estat pare que contindràuna sèrie de subestats. Tots ells estaran relacionats amb el moviment del’Ellen quan es troba a terra. És a dir, quan camina. Dintre d’aquest estatpare hi haurà diferents estats fills, com poden ser el de caminar, girar cap al’esquerra o dreta i Idle o en repòs.

• MeleeSM: aquest serà un estat pare que controlarà tot el moviment durantels atacs de l’Ellen.

Quan ens trobem en un estat pare, si volem accedir als subestats o estats fills,hauríem de fer doble clic en l’estat pare. En aquest cas, per exemple, farem clic enl’estat de LocomotionSM i veurem una State Machine com la de la imatge següent(figura 3.42):

Figura 2.7. ’State Machine LocomotionSM’

Com observeu en la imatge, hi haurà els següents subestats dintre de l’estatLocomotionSM:

’Unity State Machine’

Proporcionen una manera deveure tots els clips d’animaciód’un Animator en concretrelacionant els diferents estatspossibles entre si per activardiferents animacions, perexemple seguint l’input del’usuari.

Page 64: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 64 Desenvolupament de videojocs amb Unity

• Idle: estat en repòs de l’Ellen, és a dir, quan no està fent cap altra acció.

• Locomotion: estat en moviment de l’Ellen, quan està caminant o correntpel món virtual, sense estar saltant, disparant o rebent un impacte.

• QuickTurnLeft: quan l’Ellen fa un gir brusc cap a la dreta, animació de gir.

• QuickTurnRight: quan l’Ellen fa un gir brusc cap a l’esquerra, animacióde gir.

Com observeu a la imatge sobre l’State Machine de LocomotionSM (vegeu lafigura 3.42), hi ha diferents transicions entre els diferents estats.

A continuació, els analitzarem per tal d’entendre com es fan les transicions entreels estats (si voleu veure els valors a l’inspector, heu de seleccionar la fletxa decada transició). També veureu els diferents valors de les variables d’animació oAnimation Parameters:

Locomotion a Idle Parameters

Idle a Locomotion Parameters

Locomotion a Exit Parameters

• Locomotion a Idle: en el moment que el personatge no s’estigui movent, ésa dir, la VerticalSpeed i la HorizontalSpeed estiguin molt properes a 0.

• Idle Locomotion: en el moment que el personatge es comença a moure, laseva velocitat horitzontal o HorizontalSpeed passarà a incrementar el seuvalor fins arribar a la velocitat màxima. En el moment que aquesta superael llindar de 0,1, començarem a reproduir l’animació de caminar o córrer.

• Locomotion a Exit: hi haurà casos que deixarem d’estar caminant o enl’estat Locomotion. Com podeu veure en la imatge, hi ha dues possibilitatsde sortida, si el següent estat és en l’aire (‘airbone’) o en atac (‘melee’).

• Locomotion a QuickTurnLeft: en el moment que girem cap a l’esquerra(en aquest cas) o molt similar a la dreta (estat QuickTurnright) es donaràquan ens estiguem movent a una velocitat superior a 2 i l’angle de rotaciósigui inferior a -2,5 radians, com podeu veure en la figura 2.8.

Figura 2.8. Transició ’Locomotion’ a ’QuickTurnLeft’

Page 65: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament dm _ D a m a g e a b l e ' entorns interactius multidispositiu 65 Desenvolupament de videojocs amb Unity

• QuickTurnLeft a Locomotion: aquest estat és una mica diferent delsanteriors. Aquesta transició es dona per temps i no a través d’una condició.És a dir, un cop s’estigui executant l’animació (de girar en aquest cas), enalgun moment durant l’execució farem la transició al següent estat, en aquestcas Locomotion. Com podeu veure en la imatge, és possible triar en quinmoment comença la transició).

Transició ’QuickTurnLeft’ a’Locomotion’

2.4.5 Les funcions ’Reset’, ’Awake’, ’OnEnable’ i ’OnDisable’

La funció Reset té com a objectiu inicialitzar alguns components de l’escena, perexemple l’objecte que anirà seguint la càmera, els reproductors d’àudio... Pera més informació sobre l’ordre d’execució de la funció Reset com a part delMonobehaviour.

A la funció Awake ens encarregarem d’obtenir una sèrie de components delGameObject del jugador com poden ser el PlayerInput, l’Animator i el Character-Controller a banda de dir-li a l’arma qui serà el propietari i guardar una referènciaestàtica a l’objecte actual:

1 void Awake()2 {3 m_Input = GetComponent<PlayerInput>();4 m_Animator = GetComponent<Animator>();5 m_CharCtrl = GetComponent<CharacterController>();6

7 meleeWeapon.SetOwner(gameObject);8

9 s_Instance = this;10 }

A la funció OnEnable inicialitzarem el controlador de les animacions, obtindremel component Damageable i l’inicialitzarem. Finalment, obtindrem una referènciaa tots els Renderers dels objectes fill de l’actual (el nostre jugador):

1 void OnEnable()2 {3 SceneLinkedSMB<PlayerController>.Initialise(m_Animator, this);4

5 m_Damageable = GetComponent<Damageable>();6 m_Damageable.onDamageMessageReceivers.Add(this);7

8 m_Damageable.isInvulnerable = true;9

10 EquipMeleeWeapon(false);11

12 m_Renderers = GetComponentsInChildren<Renderer>();13 }

A la funció OnDisable, que s’executarà abans de destruir l’objecte actual, trauremtotes les referències a objectes en el Damageable, i deixarem tots els Renderersde l’objecte del jugador activats:

’Animation Parameters’

Variables definides dins d’unAnimator Controller: s’hi potaccedir i assignar-hi valors desde scripts. Així, un script potcontrolar o afectar el flux de lamàquina d’estats de l’AnimatorController.

’MonoBehaviour.Reset()’

Us recomano donar un cop d’ulla la documentació oficial deUnity, en el següent enllaç:bit.ly/2CbV5WF.

Page 66: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d

P l a y A u d i o ' entorns interactius multidispositiu 66 Desenvolupament de videojocs amb Unity

1 void OnDisable()2 {3 m_Damageable.onDamageMessageReceivers.Remove(this);4 for (int i = 0; i < m_Renderers.Length; ++i)5 {6 m_Renderers[i].enabled = true;7 }8 }

2.4.6 El ’FixedUpdate’

En la funció ‘FixedUpdate’ processarem tots els diferents estats del nostrepersonatge. És a dir, des d’actualitzar les animacions, actualitzar les variablesde l’Animator Controller, calcular el moviment (tant horitzontal com a vertical),la rotació del cos de l’Ellen, si hem de reproduir algun àudio en concret, i fins itot comprovar si és a terra o no.

Característiques de la funció ’FixedUpdate’

MonoBehaviour.FixedUpdate s’executa amb la freqüència del sistema de físiques; escridarà cada frame depenent del frame-rate. Els càlculs del sistema de físiques (PhysicsSystem) es fan després de la crida al FixedUpdate. 0,02 segons (50 trucades per segon)és el temps predeterminat entre les diferents crides a aquesta funció. Heu de fer servirTime.fixedDeltaTime per accedir a aquest valor.

Recordeu que és recomanable fer servir la funció ‘FixedUpdate’ especialmentquan els càlculs que volem fer a cada frame depenen del motor de físiques. Ésa dir, si l’objecte té un component del tipus RigidBody o a RigidBody2D.

Encara que pugui semblar que queden totalment fora del vostre abast aquest tipusde consideracions, a l’hora de fer moviment basat en físiques (incloent el salt), oque les animacions també estiguin basades en aquest tipus d’animació, és moltimportant actualitzar l’estat i posició del nostre personatge (incloent animacions).

A continuació, podeu veure el codi de la funció FixedUpdate, com podeu veure facrides a moltes funcions en el codi:

1 void FixedUpdate()2 {3 CacheAnimatorState();4

5 UpdateInputBlocking();6

7 EquipMeleeWeapon(IsWeaponEquiped());8

9 m_Animator.SetFloat(m_HashStateTime, Mathf.Repeat(m_Animator.GetCurrentAnimatorStateInfo(0).normalizedTime, 1f));

10 m_Animator.ResetTrigger(m_HashMeleeAttack);11

12 if (m_Input.Attack && canAttack)13 m_Animator.SetTrigger(m_HashMeleeAttack);14

15 CalculateForwardMovement();16 CalculateVerticalMovement();17

18 SetTargetRotation();19

20 if (IsOrientationUpdated() && IsMoveInput)

Page 67: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 67 Desenvolupament de videojocs amb Unity

21 UpdateOrientation();22 PlayAudio();23 TimeoutToIdle();24

25 m_PreviouslyGrounded = m_IsGrounded;26 }

Ara comentarem algunes de les crides més destacables de la funció ‘FixedUpda-te’:

1 CacheAnimatorState();

En aquesta funció emmagatzemem l’estat d’alguns paràmetres de les animacionsper tal de fer comprovacions després de la funció FixedUpdate.

1 CalculateForwardMovement();2 CalculateVerticalMovement();

En ambdues funcions calcularem el moviment cap endavant o cap amunt fentservir el motor de físiques. En el cas de la velocitat vertical, també tindrem encompte si estem a terra, ja que, si és així, haurem d’aplicar una petita força de lagravetat i, si no, haurem de calcular l’arc del salt.

2.4.7 Rebre impactes o ’Damage’ en el nostre jugador

A qualsevol videojoc d’acció, com el nostre, el jugador haurà de rebre danysdepenent d’una sèrie de condicionants. Per exemple, si ens ataca un enemic, ocaiem per un precipici, o fem un salt massa gran.

Els moments on el nostre jugador rebrà dany dependran una mica del disseny quefem. Així, hi ha videojocs on el jugador no en rep mai, ja que l’experiènciade gameplay no ho requereix, com poden ser videojocs d’esports (FIFA, F1,MotoGP...).

Així doncs, en el nostre cas voldrem que l’Ellen rebi impactes o dany (com a mí-nim) en el cas de caure per un precipici, els atacs dels enemics, i si cau l’aigua/àcid.Per fer aquesta tasca farem servir l’script que ens proporciona el 3D Game Kitanomenat Damageable; el podeu trobar a Assets/3DGamekit/Scripts/Game.

És una classe força intuïtiva, on es podrà configurar quin tipus de dany rebrà eljugador, enemic o objecte de l’escena on estiguin afegits.

Si reflexionem en el procés de rebre impactes/dany del jugador, enemics i objectesdestructibles que es trobin en el nostre nivell, serà molt similar al diagrama següent(vegeu la figura 2.9):

Jocs centrats en el ’journey’

Alguns jocs es centren més enl’experiència del camí, encomptes de l’acció, com pot serel cas de Journey. Trobareu mésinformació a: bit.ly/1NZa1Hl.

Page 68: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 68 Desenvolupament de videojocs amb Unity

Figura 2.9. Diagrama de dany

Com podeu veure, tots els objectes que puguin rebre dany estaran en un estatde repòs o Idle (en allò relacionat al dany, poden estar realitzant altres accionsal mateix temps). Un cop rebem un impacte o hit haurem de decidir si podemabsorbir-lo, és a dir, si tenim prou escut o vida per fer-ho.

Si és així, absorbirem el dany, mostrarem un efecte a l’usuari per tal de donar-lifeedback i que vegi que està passant alguna cosa, i després d’un temps de cooldowntornarem a l’estat inicial. En cas que no puguem absorbir el dany, l’objecte esdestruirà / morirà (depenent de si és un objecte de l’entorn, o un enemic/jugador).

A continuació estudiarem les parts més importants de la classe Damageable, pertal d’entendre una mica millor el seu funcionament. A la imatge a continuaciópodeu veure la seva configuració en l’editor de Unity (vegeu la figura 2.10).

Figura 2.10. Configuració ’Damageable’ jugador

Page 69: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 69 Desenvolupament de videojocs amb Unity

Com veieu en la imatge, podrem configurar els punts d’impacte, el temps d’invul-nerabilitat després de rebre un impacte, l’angle en què podem rebre impactes i laseva rotació.

Un altre punt important a l’hora de configurar aquesta classe serà una llista demètodes de diferents classes que haurem de notificar en cas d’impacte. Per això,definirem una llista de MonoBehaviour i la forçarem a ser del tipus IMessageRe-ceiver; és a dir que implementin una funció específica per rebre la notificació del’impacte. La definició la podeu trobar al codi següent:

1 [EnforceType(typeof(Message.IMessageReceiver))]2 public List<MonoBehaviour> onDamageMessageReceivers;

Això ens permetrà definir una llista a l’editor de Unity on arrossegarem els objectesque contindran scripts que implementin la Interface IMessageReceiver, com heuvist a la imatge de la captura de l’objecte Damageable (vegeu la figura 2.9).Entre d’altres, podeu observar que fa referència a l’script HealthUI de l’objecteHealthCanvas, el qual, entre altres mètodes, haurà d’implementar la interfaceanomenada IMessageReceiver, com es veu en el codi següent:

1 public void ChangeHitPointUI(Damageable damageable)

Com podeu veure, el mètode ChangeHitPointUI rep com a paràmetre la referènciaa l’objecte Damageable que farà la crida. És a dir, quan rebem un hit o impacte,la classe Damageable que hi ha a l’Ellen ho detectarà i farà la crida a tots elsobjectes de la llista d’objectes que hem de notificar. Un d’aquests objectes serà laUI, i actualitzarà la UI amb la nova informació.

Un altre mètode interessant de la classe Damageable serà la funció Update. Enaquesta realitzarem el cooldown entre diferents impactes. El codi és força senzilli va sumant el temps entre els diferents frames al temps des de l’últim impacte. Siel temps ha superat el de cooldown , reinicialitzarem les variables i deixarem deser invulnerables. A continuació podeu veure el codi de la funció Update en laclasse Damage:

1 void Update()2 {3 if (isInvulnerable)4 {5 m_timeSinceLastHit += Time.deltaTime;6 if (m_timeSinceLastHit > invulnerabiltyTime)7 {8 m_timeSinceLastHit = 0.0f;9 isInvulnerable = false;

10 OnBecomeVulnerable.Invoke();11 }12 }13 }

Finalment, veurem el mètode per aplicar el dany o ApplyDamage. Els passos aseguir en el procés d’aplicar el dany seran els següents:

1. El primer pas serà comprovar si encara tenim punts de hit o no. En casnegatiu, sortirem de la funció, ja que no podem aplicar més dany un cophem arribat a 0.

Page 70: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 70 Desenvolupament de videojocs amb Unity

2. El següent pas serà comprovar si estem en mode invulnerable; si és així,acabarem l’aplicació del dany i tornarem.

3. Com que ja estem segurs que podrem absorbir l’impacte i no som invulnera-bles, farem els càlculs per tal de projectar l’objecte (el personatge en aquestcas) cap endarrere tenint en compte la direcció de l’impacte.

4. Restarem els punts de hit depenent del dany que fa l’objecte que ens haimpactat, i deixarem l’objecte en mode invulnerable.

5. Comprovarem si hem arribat a 0 en els punts de hit i per tant hem de destruirl’objecte, o encara els podem absorbir i farem un efecte (diferent en cadaobjecte).

6. Finalment, enviarem el missatge a tots els “escoltadors” conforme hem rebutun impacte. Per exemple, en aquest cas, a la UI, perquè mostri la novainformació de la nostra salut o Health.

A continuació, podeu veure el codi de la funció ApplyDamage de la classeDamage:

1 public void ApplyDamage(DamageMessage data)2 {3 if (currentHitPoints <= 0)4 {//ignore damage if already dead. TODO : may have to change that if we want

to detect hit on death...5 return;6 }7

8 if (isInvulnerable)9 {

10 OnHitWhileInvulnerable.Invoke();11 return;12 }13

14 Vector3 forward = transform.forward;15 forward = Quaternion.AngleAxis(hitForwardRotation, transform.up) * forward;16

17 //we project the direction to damager to the plane formed by the directionof damage

18 Vector3 positionToDamager = data.damageSource − transform.position;19 positionToDamager −= transform.up * Vector3.Dot(transform.up,

positionToDamager);20

21 if (Vector3.Angle(forward, positionToDamager) > hitAngle * 0.5f)22 return;23

24 isInvulnerable = true;25 currentHitPoints −= data.amount;26

27 if (currentHitPoints <= 0)28 schedule += OnDeath.Invoke; //This avoid race condition when objects

kill each other.29 else30 OnReceiveDamage.Invoke();31

32 var messageType = currentHitPoints <= 0 ? MessageType.DEAD : MessageType.DAMAGED;

33

34 for (var i = 0; i < onDamageMessageReceivers.Count; ++i)35 {36 var receiver = onDamageMessageReceivers[i] as IMessageReceiver;37 receiver.OnReceiveMessage(messageType, this, data);

Page 71: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 71 Desenvolupament de videojocs amb Unity

38 }39 }

Finalment, us recomano donar un cop d’ull al vídeo Modificació del personatgeprincipal, l’Ellen, on es mostra com funciona el personatge de l’Ellen i comcanviar-ne diferents configuracions.

https://player.vimeo.com/video/323461130

2.4.8 Una petita millora: subdividir les habilitats

Recordeu que a l’hora de dissenyar els controladors d’un personatge, normalmentens sortiran classes molt complexes, amb moltes línies de codi. Com hem vist,la classe PlayerController és força extensa i difícil de seguir segons com. Unamanera de millorar la implementació d’aquest controlador seria fer servir elprincipi de “divideix i venceràs”. És a dir, hauríem de crear classes més petites pergestionar tot el tema de les habilitats i treure aquest codi del controlador principalo PlayerController.

El primer pas serà crear una classe base per a totes les nostres habilitats. Totesles funcions i variables que creiem que seran comunes a totes les habilitats lesdefinirem en aquesta classe amb el nom Character.cs. Com us podeu imaginar,és complicat definir-les totes per endavant, ja que moltes vegades aniran apareixentnoves necessitats a mesura que anem creant noves habilitats.

En aquest apartat, estudiarem l’estructura de la classe Character com a base deles nostres habilitats, que seran la de saltar, el moviment del personatge i la dedisparar una arma. Com podeu veure en la imatge següent (vegeu la figura 3.10):,serà la de la classe Character com a base, i la resta com a fills.

Figura 2.11. Diagrama habilitats

En resum, la classe Character serà la classe central que unirà totes les altres percontrolar un personatge. No fa gaire cosa en si mateixa, sinó que és més comun punt central o esquelet per a l’estructura de la resta de classes. Aquí és on es

Herència entre classes

En la programació orientada aobjectes, l’herència permet queels objectes nous assumeixin lespropietats i funcions delsobjectes existents. Una classeque s’utilitza com a base per al’herència es denomina classesuperclasse o base. Una classeque hereta d’una superclasses’anomena classe subclasse oderivada.

Trobareu més informació ala unitat “Programaciód’aplicacionsmultidispositiu”, d’aquestmateix mòdul.

Page 72: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 72 Desenvolupament de videojocs amb Unity

defineix si el jugador és controlat per un IA o controlat pel jugador, si es basa enun model 3D, si disposa d’un animador de Unity (Animator).

Així mateix, és la classe que controlarà totes les habilitats del personatge en tempsd’execució (tasca que haurem de fer posteriorment).

També tindrem una llista de les habilitats de personatge que farem servir en aquestobjecte. I s’encarregarà d’executar aquestes habilitats (funció ‘Update’).

De moment, crearem només tres habilitats (moviment, saltar i disparar); però,per descomptat, també podeu crear les vostres pròpies habilitats per expandirfàcilment el gameplay del videojoc.

A continuació observeu el codi d’una primera implementació de la classe Charac-ter:

1 public class CharacterAbility : MonoBehaviour2 {3 protected virtual void Start()4 {5 Initialization();6 }7

8

9 protected virtual void Initialization()10 {11 // Do ability initialization here12 }13 protected virtual void BindAnimator()14 {15 }16 protected virtual void InitializeAnimatorParameters()17 {18 }19 protected virtual void InternalHandleInput()20 {21 }22 protected virtual void HandleInput()23 {24 }25 public virtual void EarlyProcessAbility()26 {27 }28 public virtual void ProcessAbility()29 {30 }31 public virtual void LateProcessAbility()32 {33 }34 public virtual void UpdateAnimator()35 {36 }37 }

Com podeu observar, és tan sols un esquelet buit, però ja permet entendre comvoldríem gestionar les habilitats. A continuació, vegeu un petit resum de cadafunció i les seves característiques i funcionalitats més importants:

• Start i Initialization: inicialització genèrica i específica de l’habilitat.

• BindAnimator: obtenir l’Animator del GameObject actual (o algun delsseus fills).

Page 73: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 73 Desenvolupament de videojocs amb Unity

• InitializeAnimatorParameters: inicialitza tots els AnimatorParamaters.Aquí inclouríem coses com obtenir-ne el codi hash, entre altres.

• InternalHandleInput: obtenir els valors de l’input en el frame actual.

• HandleInput: processar l’input del jugador durant el frame actual.

• EarlyProcessAbility: semblant a l’EarlyUpdate de Unity, serà el primer delstres passos que tindrà cada habilitat. Es pot no fer servir.

• ProcessAbility: semblant a l’Update de Unity, serà el moment central onprocessarem les habilitats.

• LateProcessAbility: semblant al LateUpdate de Unity, serà l’últim dels trespassos que tindrà cada habilitat. Es pot no fer servir.

• UpdateAnimator: en aquesta funció actualitzarem l’Animator, amb elsAnimation Parameters necessaris, canvis d’estats, transicions... per tal dereflectir el nou estat de l’animació.

Moviment, saltar i disparar

Moviment horitzontal de caràcters: aquest component controla el moviment bàsicd’esquerra/dreta, fricció i detecció de cops al sòl. En el seu inspector es pot definirla velocitat de moviment estàndard, la velocitat de la marxa i els efectes que calutilitzar quan el personatge toca a terra després d’un salt o caiguda.

Aquest component també controla els salts. En el seu inspector es pot definirl’alçada del salt, tant si és proporcional a la longitud de la premsa com si no, eltemps d’aire mínim (quant de temps un personatge ha d’estar en l’aire abans depoder baixar si el jugador ha alliberat el botó de salt), les restriccions de salt, quantssalts pot fer el personatge sense tocar el terra altre cop, i quant de temps han dedesactivar-se les col·lisions quan surti de plataformes d’un sentit o de plataformesmòbils.

Finalment, aquest component permetrà al personatge recollir i utilitzar armes.El que farà l’arma es defineix a les classes ‘Arma’. Això només descriu elcomportament de la “mà” que sosté l’arma, no l’arma mateixa.

2.5 Creació dels enemics

Un cop tinguem creat el nostre personatge protagonista, el següent pas serà crearuna sèrie d’enemics per tal d’afegir una mica de dificultat al nostre videojoc icol·locar-los al llarg de l’escena.

En el nostre cas seran una sèrie d’animalets petits, de color rosa, que estarantranquil·lament esperant al llarg del nivell, i un cop arribem, o siguem a prop,ens atacaran carregant contra nosaltres i, si ens impacten, ens causaran 1 de dany.

Page 74: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 74 Desenvolupament de videojocs amb Unity

Com a model, en farem servir un que ja ens proporciona el 3D Ga-me Kit anomenat Chomper; del qual podeu trobar el prefab a As-sets/3DGamekit/Prefabs/Characters/Enemies/Chomper.

2.5.1 Els components del ’Chomper’ (enemic)

A continuació, analitzarem els components més importants del prefab Chomper,les seves funcionalitats, i entendrem millor com crear enemics d’aquest tipus. Ala figura 2.12 hi ha els components típics del prefab. Com veieu, trobarem elssegüents components:

• Animator: el component que controlarà les animacions del model en 3D.

• ChomperBehaviour: una rèplica del component de Unity CharacterCon-troller, però per controlar els objectes de tipus Chomper en comptes d’unjugador.

• Damageable: com hem vist en l’apartat del jugador, s’encarregarà decontrolar el dany infligit al Chomper.

• ReplaceWithDagRoll: en el moment de morir, després de rebre l’impac-te de l’arma del jugador, s’executarà un efecte de dissoldre l’enemic oChomper que es troba en un altre prefab per separat. Aquest script faràla substitució d’un per l’altre just abans de començar a reproduir l’efecte.

• Box Collider: la caixa de col·lisions per tal de detectar quan es col·lideixamb altres objectes de l’escena, com el terra, el jugador (atac), l’arma deljugador (rebre dany).

• EnemyController: un petit script per controlar l’enemic.

• Nav Mesh: el component del sistema de navegació de Unity que enspermetrà crear una ruta per als enemics i que es vagin movent pel nostremón. Per a més informació sobre aquesta funcionalitat us recomano donarun cop d’ull a la documentació oficial de Unity.

Page 75: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 75 Desenvolupament de videojocs amb Unity

Figura 2.12. El ’prefab Chomper’

2.5.2 L”script EnemyController’

L’objectiu del controlador de l’enemic o EnemyController serà tenir l’enemic enestat de repòs, amb un moviment per l’escenari o atacant el jugador. Molt semblantal PlayerController que vam veure en el punt dedicat al control del jugador.

A continuació, comentarem les funcions més rellevants d’aquest script:

1 void OnEnable()2 {3 m_NavMeshAgent = GetComponent<NavMeshAgent>();4 m_Animator = GetComponent<Animator>();5 m_Animator.updateMode = AnimatorUpdateMode.AnimatePhysics;6

7 m_NavMeshAgent.updatePosition = false;8

9 m_Rigidbody = GetComponentInChildren<Rigidbody>();10 if (m_Rigidbody == null)11 m_Rigidbody = gameObject.AddComponent<Rigidbody>();12

13 m_Rigidbody.isKinematic = true;14 m_Rigidbody.useGravity = false;15 m_Rigidbody.collisionDetectionMode = CollisionDetectionMode.Continuous;16 m_Rigidbody.interpolation = RigidbodyInterpolation.Interpolate;17

18 m_FollowNavmeshAgent = true;19 }

El més rellevant del codi anterior és que si no detecta un component del tipusRigidBody, qualsevol dels fills de l’objecte o enemic en el qual està inclòs aquestscript, en crearà un en temps d’execució. A més a més, guardarà referències aobjectes com l’Animator, el NavMeshAgent.

Finalment, fixeu-vos que, en aquest cas, tant l’animació com el moviment estaranbasats en físiques, al contrari que en el cas del personatge principal, on vam ferservir el CharacterController, justament per evitar fer servir les físiques.

A continuació, veurem la funció FixedUpdate:

Unity ’NavMesh’

El sistema de navegació de Unityo NaveMesh és un componentper als GameObjects que permetcrear personatges que podennavegar pel món o les escenesdel joc. Ofereix la possibilitatque entenguin que necessitenpujar escales per arribar al segonpis, o saltar per superar unprecipici.

Page 76: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 76 Desenvolupament de videojocs amb Unity

Unity ’Canvas’

El Canvas és l’àrea on hi had’haver tots els elements de la

UI. El Canvas és un GameObjectamb un component del tipus

Canvas i tots els elements de lainterfície d’usuari han de ser fills

d’aquest GameObject.

1 private void FixedUpdate()2 {3 animator.speed = PlayerInput.Instance != null && PlayerInput.Instance.

HaveControl() ? 1.0f : 0.0f;4

5 CheckGrounded();6

7 if (m_UnderExternalForce)8 ForceMovement();9 }

Com podeu observar, és una funció força senzilla. Primer, depenent de si estemjugant o estem en mode pausa, executarem les animacions adients, o les posaremen pausa per complet. Si estem a terra i acabem de rebre un impacte, forçarem elmoviment per tal de simular una mica de retrocés.

Amb el següent codi, indicarem al NavMesh quin és l’objectiu a seguir, en cas queestiguem a prop del jugador. El NavMesh s’encarregarà de seguir el jugador perl’escena:

1 public void SetTarget(Vector3 position)2 {3 m_NavMeshAgent.destination = position;4 }

L’enemic tindrà dues maneres d’executar el control sobre si mateix:

• La primera, gràcies al NavMesh, quan estigui seguint una ruta predeter-minada, o perseguint el jugador.

• En cas que estigui atacant el jugador o rebent un impacte per part d’ell,desactivarem el NaveMesh durant l’estona que dura aquesta acció i deixaremque sigui el mateix Animator o les físiques qui s’encarregui del movimentdel jugador.

Finalment, us recomano donar un cop d’ull al vídeo Els enemics en el nostrevideojoc, on podreu veure en funcionament com un enemic ataca el jugador, iquines són les funcions implicades.

https://player.vimeo.com/video/323461719

2.5.3 El subsistema d’UI a Unity

El sistema UI de Unity va ser redissenyat a les últimes versions de Unity 5,per fer-lo més potent i fàcil d’utilitzar. Potser el concepte més important que

Page 77: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 77 Desenvolupament de videojocs amb Unity

s’ha d’entendre és l’objecte Canvas. Tots els elements de la interfície d’usuariestan continguts en objecte d’aquest tipus Canvas. Una escena pot tenir més d’unobjecte Canvas. Podeu pensar en un Canvas com a contenidor per als elements dela interfície d’usuari. Per crear un Canvas, simplement navegueu i seleccioneuel menú GameObject / UI / Canvas. Com veieu en el menú, hi ha diferentselements d’UI. A continuació trobareu una llista, així com una petita descripciódels components més importants del subsistema d’UI:

• Canvas: el GameObject contenidor de tots els elements d’UI.

• Panel: un objecte de marc o contenidor (frame).

• Button: un botó estàndard amb la propietat de poder fer clic a sobre.

• Text: un camp de text amb les propietats estàndard de format.

• Image: imatges que poden ser simple, sliced, tiled i filled.

• Raw Image: un fitxer de textura en mode en brut.

• Slider: un control lliscant amb valor mínim i màxim.

• Scrollbar: barra de desplaçament amb valors entre 0 i 1.

• Toggle: caixa de verificació (checkbox) estàndard.

• Dropdown: un menú desplegable amb una llista d’opcions per triar.

• Input: caixa d’entrada de text.

• Event system: sistema d’esdeveniments especial dels elements d’UI. Enspermet fer crides a funcions depenent de la interacció amb l’usuari.

Mode de renderització

Hi ha alguns paràmetres a la vista de l’inspector que s’han de tenir en compte enrelació amb el vostre objecte de joc de llenç. El primer paràmetre és el Mode derenderització. Hi ha tres configuracions: Screen Space - Overlay, Screen Space -Camera i World Space, com veieu a la següent imatge (figura 2.13):

Figura 2.13. El mode de renderització

El primer tipus de representació serà l’anomenat Screen Space - Overlay. Aquestmode de representació situa els elements de la interfície d’usuari a la pantalla,per sobre de la renderització de l’escena. Si la pantalla es canvia de mida o deresolució, el Canvas canviarà automàticament la mida per adaptar-s’hi.

Page 78: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 78 Desenvolupament de videojocs amb Unity

El segon mode de representació, Screen Space – Camera, és similar a ScreenSpace - Overlay, però amb la diferència que en aquest mode el Canvas es col·locaa una distància determinada davant la càmera que seleccionem. Els elements dela UI són representats per aquesta càmera, i això significa que la configuració dela càmera afecta l’aspecte de la interfície d’usuari.

El tercer mode de representació és World Space. Aquest mode proporciona méscontrol sobre com es col·loquen els objectes i es pot manipular com qualsevol altreobjecte del videojoc. És a dir, fa servir un sistema de coordenades com qualsevolaltre GameObject, i les coordenades són amb relació al món o world, i no ambrelació a la càmera.

Per entendre millor aquests tres tipus de representació, la meva recomanacióes fer proves per tal de veure visualment com canvia, depenent del tipus derepresentació, el mateix objecte.

2.5.4 Dissenyar la UI del videojoc ’Ellen and the lost IOCstudents’

El disseny del la nostra UI serà força senzill. Voldrem mostrar a l’usuari elnivells de vida que té disponibles el personatge principal. Per fer-ho, mostraremuna sèrie d’icones amb el dibuix d’un cor.

Tindrem dos tipus d’icones, una d’omplerta i una altra de buida. Farem servirels buits per indicar a l’usuari quin és el nivell màxim de vida, i els omplerts perindicar-li quin és el seu nivell de vida actual, com podeu veure en la següent imatge(vegeu la figura 2.14):

Figura 2.14. La UI

Com heu observat, el nombre d’icones serà de cinc: a la imatge se’n mostrenquatre amb la icona omplerta i una amb la icona buida. Això indica a l’usuarique el nivell màxim de vida que pot tenir tindrà un valor de cinc, però que en lasituació actual, per algun motiu, n’ha perdut un de vida.

Per crear aquesta UI s’ha de crear un objecte amb els components que podeu veureen la imatge següent (figura 2.15):

Page 79: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament dp r o t e c t e d ' entorns interactius multidispositiu 79 Desenvolupament de videojocs amb Unity

Figura 2.15. El ’GameObject’ de la UI

Hi ha quatre components: el Canvas i el CanvasScaler, que ambdós s’encar-regaran de fer la funció de marc dels objectes (o imatges) de la nostra UI;també inclourem un GraphicsRaycaster i l’script, que controlarà la nostra UI, elHealthUI.

Com veieu en la imatge, l’script té una referència a un altre GameObject ambel nom de ‘HealthIcon’. Com us podeu imaginar pel nom, aquest GameObjectcontindrà una referència de la icona del cor, amb els dos cors, l’omplert i el buit.

A continuació estudiarem el codi que controlarà la UI, l’script ‘HealthUI’:

1 using System.Collections;2 using UnityEngine;3

4 namespace Gamekit3D5 {6 public class HealthUI : MonoBehaviour7 {8 public Damageable representedDamageable;9 public GameObject healthIconPrefab;

10

11 protected Animator[] m_HealthIconAnimators;12

13 protected readonly int m_HashActivePara = Animator.StringToHash("Active");

’Graphic Raycaster’

S’utilitza per tal de fer un raycasten un objecte del tipus Canvas.És a dir, comprovarà tots elsobjectes (gràfics visuals) d’unCanvas i determinarà siqualsevol d’ells ha sigut impactat(raycast) per altres objectes.

Page 80: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 80 Desenvolupament de videojocs amb Unity

14 protected readonly int m_HashInactiveState = Animator.StringToHash("Inactive");

15 protected const float k_HeartIconAnchorWidth = 0.041f;16 IEnumerator Start()17 {18 if (representedDamageable == null)19 yield break;20

21 yield return null;22

23 m_HealthIconAnimators = new Animator[representedDamageable.maxHitPoints];

24

25 for (int i = 0; i < representedDamageable.maxHitPoints; i++)26 {27 GameObject healthIcon = Instantiate(healthIconPrefab);28 healthIcon.transform.SetParent(transform);29 RectTransform healthIconRect = healthIcon.transform as

RectTransform;30 healthIconRect.anchoredPosition = Vector2.zero;31 healthIconRect.sizeDelta = Vector2.zero;32 healthIconRect.anchorMin += new Vector2(k_HeartIconAnchorWidth,

0f) * i;33 healthIconRect.anchorMax += new Vector2(k_HeartIconAnchorWidth,

0f) * i;34 m_HealthIconAnimators[i] = healthIcon.GetComponent<Animator>();35

36 if (representedDamageable.currentHitPoints < i + 1)37 {38 m_HealthIconAnimators[i].Play(m_HashInactiveState);39 m_HealthIconAnimators[i].SetBool(m_HashActivePara, false);40 }41 }42 }43

44 public void ChangeHitPointUI(Damageable damageable)45 {46 if (m_HealthIconAnimators == null)47 return;48

49 for (int i = 0; i < m_HealthIconAnimators.Length; i++)50 {51 m_HealthIconAnimators[i].SetBool(m_HashActivePara, damageable.

currentHitPoints >= i + 1);52 }53 }54 }55 }

Com podeu veure hi ha tres seccions ben diferenciades:

• Variables: hi haurà només dues variables públiques, una referència al’objecte Damageable del qual farem el seguiment a la UI, i una referènciaa la icona dels cors. Com que cada icona del cor tindrà un Animator per ferles animacions de guanyar o perdre un cor, també guardarem els Animatorsper a futures referències, així com el nom dels Animator Parameters.

• Funció Start: la funció Start tindrà la peculiaritat que serà una Coroutine.Algunes funcions del MonoBehaviour podran ser Coroutines. Amb aquestafuncionalitat podrem executar el contingut d’aquesta funció al llarg d’algunsframes. Com podeu observar en el codi, primer farà comprovacions respectea que té la referència que necessita a l’objecte Damageable, i després crearào instanciarà tantes icones com tingui com a màxim de vida aquest objecte

Page 81: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 81 Desenvolupament de videojocs amb Unity

Damageable, i inicialitzarà cadascuna d’aquestes icones, amb el seu estatinicial, la seva posició...

• Funció ChangeHitPointUI: aquesta funció es cridarà des de dintre delnostre script Damageable en l’objecte del personatge principal. És a dir, uncop rebem un impacte o recarreguem la nostra vida, l’script que s’encarregade la gestió de la vida del personatge cridarà aquesta funció de la UI (entred’altres) per notificar-nos el nou estat i, per tant, perquè actualitzem l’estatde totes les icones dels cors amb el nou estat.

Finalment, us recomano donar un cop d’ull al vídeo El sistema UI del nostrevideojoc, on es mostra com funciona la UI i com crear-hi nous valors o objectes.

https://player.vimeo.com/video/324130611

2.6 Conclusions finals

En aquests dos apartats hem vist com crear un videojoc a Unity des de zero.Algunes parts les hem creat nosaltres i per a altres hem fet servir Asset o codide tercers, tot això en el motor gràfic de Unity. Aquest procés de posar en comúel nostre coneixement i habilitats amb les de tercers en un producte es podriadefinir com a desenvolupament de videojocs. Durant el procés de desenvoluparun videojocs tindrem necessitats i ens trobarem amb problemes que molts altresdesenvolupadors, artistes, dissenyadors... han tingut abans.

En desenvolupament (de videojocs en el nostre cas) no és necessarireinventar la roda, és millor fer servir les eines que ja existeixen per crearun millor producte, que no pas perdre el temps creant una roda nova.

Així, en el procés de la creació del nostre videojoc:

1. En el primer apartat hem estudiat molts dels objectes genèrics, com sónel GameManager, LevelManager i d’altres classes genèriques que ensajudaran durant el desenvolupament del videojoc.

2. En el segon apartat hem creat el nostre entorn virtual, hem donat vida a lanostra heroïna, així com a alguns enemics i la UI del nostre videojoc.

Page 82: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu
Page 83: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 83 Desenvolupament de videojocs amb Unity

3. Programació gràfica 3D

“Sembla que totes aquestes API gràfiques, el nombre considerable de motors de jocdisponibles de forma gratuïta, la multitud d’entorns i llibreries gràfiques i videojocs queaugmenten la barra de progrés en el camp de la programació gràfica, acaben per intimidarqui gosa iniciar-se en el tema. Dit d’una altra manera: resulta difícil saber per on començar.

La programació gràfica és una àrea de treball multidisciplinària, però, al mateix temps,per aquest motiu, la programació gràfica permet ser abordada des de diferents punts devista, en diferents etapes i graus de dificultat perquè qualsevol s’adapti a l’experiència iconeixement del qui vol dedicar-se a ella.”

Kostas Anagnostou, “How to start learning graphics programming?” (2018)

No podem estar més d’acord amb la reflexió de Kostas Anagnostou en relacióamb l’estudi i pràctica de la programació gràfica. Fins i tot, podríem afegir quela programació gràfica resulta sovint tan críptica com intimidant; però al mateixtemps resulta lògic preguntar-se com es generen els gràfics en pantalla, és a dir,com la informació geomètrica dels models acaba representant-se (render) d’unamanera determinada.

Per endinsar-vos en el món de la programació gràfica, us caldrà una plataformaque us permeti desenvolupar-hi els aspectes bàsics; en aquest sentit, la tecnolo-gia Unity (unity3d.com) servirà perfectament per a aquest propòsit. A banda,també haureu de familiaritzar-vos amb coneixements bàsics dels llenguatges deprogramació, matemàtiques, física i altres qüestions que, recuperant de nou la citad’Anagnostou, només representen la porta oberta a un horitzó tan inabastable comla mateixa imaginació.

Abans de començar, necessitareu també un objecte o tema d’estudi que englobiel màxim possible aquests aspectes bàsics de la programació gràfica. Un delstemes estrella és la programació d’un ray tracing, és a dir, un tècnica de generaciód’imatges fotorealistes que simula el comportament dels rajos de llum. El seudesenvolupament aglutina tot allò que involucra la disciplina de la programaciógràfica (llenguatge de programació, estudi del color, llum, ombres, transparències,textures, intersecció entre rajos de llum i geometria, projecció dels models enpantalla...).

Altres enfocaments són més generalistes i impliquen l’aprenentatge i ús d’una APIgràfica, com OpenGL o Direct3D, amb la finalitat d’assolir els coneixementssuficients per desenvolupar un render pipeline o graphics pipeline (en català espodria traduir com a flux de processos d’un sistema gràfic) personalitzat o unprogramari orientat a la creació de gràfics en 3D; qüestions que han estat, des defa dècades, motiu de redacció d’un nombre elevat de literatura sobre divulgaciótècnica. En aquest sentit, prendrem com a referència el llibre, ja clàssic, Texturing& Modeling. A Procedural Approach (figura 3.1).

’Render pipeline’

Segons la Wikipedia anglesa, elrender pipeline és un model quedescriu les passes necessàriesperquè un sistema gràfic mostrien pantalla una escena en 3D.

Page 84: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 84 Desenvolupament de videojocs amb Unity

Figura 3.1. Llibre de capçalera de la programació gràfica

L’aproximació que s’elaborarà en aquest apartat, sobre la programació gràfica,serà des de la vessant pràctica de creació de shaders amb la tecnologia Unity.Malauradament, molts autors coincideixen en l’embolcall críptic o de misteri quesembla envoltar la teoria i pràctica de creació de shaders, fet que la situen en lafase d’aprenentatge de nivell mitjà o alt.

Un shader és un programa que s’executa en el graphics pipeline i determinael valor final de color de cada píxel en pantalla dels models en 3D de l’escena.El perquè del nom tècnic de shaders és, bàsicament, perquè controlen elsefectes de llum i ombrejat (shading), tot i que acaben gestionant altres tipusd’efectes visuals que es mostren en pantalla.

Doncs bé, perquè deixi de semblar-vos tan críptic i comenceu a introduir-vos enaquest món, us guiarem en la creació d’un shader molt especial; es tractarà d’unmaterial simple que mostri en pantalla la textura procedimental d’un mur de maons(figura 3.2).

Figura 3.2. Exemple de textura de mur de maons (’brick texture’en anglès)

Font: ’Texturing & Modeling. A Procedural Approach’

La creació bàsica d’aquest material us ha d’ajudar a entreveure les possibilitatsvisuals que us ofereixen els shaders, com també comprendre quin són els concep-

Page 85: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 85 Desenvolupament de videojocs amb Unity

tes de la programació gràfica que intervenen en el seu funcionament, conceptesque estan presents no només en l’arquitectura del sistema gràfic de la tecnologiaUnity, sinó en qualsevol tecnologia gràfica actual de disseny i creació professionalde gràfics en 3D.

Programaris; versions i compatibilitats

Abans de continuar, cal que tingueu en compte les versions de les eines amb les quals esdesenvoluparà la part pràctica d’aquest apartat, pensant en termes de compatibilitat ambversions anteriors o posteriors d’aquestes. La versió de Unity és la 2018.3.0f2 Personal.Per a l’edició dels shaders i scripts es farà servir la versió 15.9.6 del Microsoft Visual Studio2017; no és problema que, en lloc d’aquest editor en feu servir un altre, com, per exemple, elVisual Studio Code (code.visualstudio.com/) o el Sublime Text 3 (www.sublimetext.com/).

3.1 Creació bàsica d’un ’shader’ a Unity

Quan creeu un projecte nou a Unity, per defecte l’escena inclou una càmera i unallum de tipus direccional. Si afegiu un pla (GameObject/ 3D Object/ Quad) al’escena, veureu la següent imatge (figura 3.3).

Figura 3.3. Creació bàsica d’una escena a Unity

És una escena bàsica, similar a la que podríem trobar en altres programaris dedisseny i creació de gràfics en 3D. Però aquesta escena, tan trivial i simple per al’usuari, amaga un procés certament complex que es podria resumir amb la següentpregunta: com tradueix el programari en 3D (Unity, en aquest cas) la informaciógeomètrica/matemàtica del pla (Quad) i el projecta d’acord amb els paràmetres dela càmera i la influència del punt de llum en la pantalla en 2D en la forma de píxelsde color? Per respondre aquesta pregunta, abans caldrà descartar aquella part que,per defecte, Unity inclou per fer la representació bàsica de l’escena bàsica siguivisualment interessant.

En primer lloc, obriu les opcions d’il·luminació de l’escena (Window /Rendering/Lighting Settings) i elimineu el valor de Default-Skybox del paràmetre SkyboxMaterial. Modifiqueu l’entrada del paràmetre de Color dins de la secció Envi-ronment Lighting i seleccioneu el color negre com a valor del paràmetre Ambient

Page 86: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 86 Desenvolupament de videojocs amb Unity

Color. Finalment, desactiveu les opcions de Realtime Global Illumination dins delpanell Realtime Lighting i Baked Global Illumination del panell Mixed Lighting,tal com podeu veure en la figura 3.4.

Figura 3.4. Opcions d’il·luminació de l’escena a Unity

Amb les diferents opcions relacionades amb les propietats d’il·luminació generaldesactivades, el color de fons i la llum d’ambient de l’escena han canviat a uncolor sòlid (blau fosc) i el color del pla (Quad) s’ha tornat gris. La representació del’escena és, ara mateix, menys sofisticada visualment, però encara faltaria resoldreun parell de qüestions més: per què el fons és de color blau fosc i el pla és de colorgris? (figura 3.5).

Figura 3.5. Vista de càmera de l’escena bàsica amb les opcions de propietats d’il·luminació generaldesactivades

Page 87: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 87 Desenvolupament de videojocs amb Unity

El color de fons ve determinat per la càmera, present a l’escena bàsica. Sensel’opció activa de representar l’Skybox, només pot representar el fons (Background)com a color sòlid, tal com podeu identificar en el tauler d’inspecció (Inspector)de les propietats de la càmera (figura 3.6).

Figura 3.6. Propietats de la càmera activa de l’escena

Ja només falta saber per què el color del pla és d’una tonalitat grisa. L’origend’aquest valor és la font de llum present a l’escena. Elimineu la llum de l’escena i,finalment, us trobareu amb una representació (render) simple de l’escena (sempredes de la visió de la càmera activa de l’escena): un fons de color sòlid i la siluetadel pla amb la tonalitat del color ambient (figura 3.7).

Figura 3.7. L’escena bàsica sense cap font de llum o altres opcions d’il·luminació general quel’“enriquien” visualment

3.1.1 De la geometria a la imatge en pantalla

Aprofitant la simplicitat de l’escena, us descriurem de manera general el procés derepresentació gràfica o graphics pipeline; és a dir, transformar la informació delsmodels geomètrics de l’escena (per exemple, el pla) en una imatge en pantalla.

Page 88: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 88 Desenvolupament de videojocs amb Unity

Vegeu, en primer lloc, el diagrama següent (figura 3.8):

Figura 3.8. Diagrama del procés de representació gràfica o ’graphic pipeline’

Òbviament, és un diagrama força general del que realment passa internamentperquè veieu en pantalla l’escena en 3D, però us ajudarà a identificar el conjuntd’etapes que van de la geometria a la imatge en pantalla; que, com veureu mésendavant, mostren una relació directa amb la programació de shaders o, dit d’unaaltra manera, amb la programació gràfica actual. Vegeu, a continuació, unadescripció de cada fase en relació amb l’escena bàsica de Unity:

1. En principi, quan incorporeu i manipuleu els objectes 3D de l’escena,aquests estan subjectes a un sistema de coordenades cartesià anomenat espaidel món (world space) o espai de l’objecte on les unitats de mesura esdecideixen per convenció (cada unitat pot ser un centímetre, un metre, unquilòmetre...).

2. El programari (Unity o qualsevol altra tecnologia de creació i disseny3D actual) no en sap res, de figures o models que identifiquem com apersonatges reals o ficticis, elements naturals com pedres o muntanyeso coses com una cadira o un vehicle. L’aplicació només veu punts ovèrtexs que haurà de traslladar del sistema de coordenades world spaceo object space a un altre sistema de coordenades conegut com a sistemade coordenades homogènies (homogeneous clip space). Dit d’una altramanera, l’aplicació projecta els vèrtexs dels models geomètrics sobre unapantalla. Aquest procés de projecció implica aquest canvi de sistema decoordenades i és el que es produeix en la fase de processament dels vèrtexso vertex shader.

3. La fase de tramar (Rasterization, en anglès) ve a continuació de la delprocessament dels vèrtexs, ja que si el programari ja disposa dels vèrtexsprojectats sobre una pantalla, és lògic que els transformi en fragments(encara no són píxels en pantalla, però són quelcom similar). O expressatd’una altra forma: transforma la informació dels vèrtexs projectats enpantalla en triangles que, la GPU, transformarà en fragments.

4. A la fase següent, la de processament dels píxels/fragments (fragmentshader), el programari treballa només amb els fragments que tindran asso-ciats una sèrie d’atributs que determinaran el càlcul o valor de color. És enaquesta fase quan s’apliquen les textures, la incidència de les fonts de llumactives a l’escena, els filtres d’antialiàsing o efectes de postprocessament dela imatge.

Page 89: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 89 Desenvolupament de videojocs amb Unity

5. Finalment, aquesta informació es desa en el framebuffer o memòria dequadre per mostrar en pantalla.

Ara que disposeu d’una idea general del procediment de treball de la representaciógràfica (graphics pipeline), recupereu l’escena bàsica de Unity; aquella sense lesfonts de llum o altres opcions d’il·luminació general que venien per defecte. El pla(de color negre) és una geometria que Unity incorpora en el seu graphics pipeline.Això és possible pel fet que el pla conté, a banda dels valors bàsics de posició,orientació i escala de qualsevol model geomètric, una propietat, Mesh Renderer,que està activa (figura 3.9).

Figura 3.9. Paràmetres de la propietat “Mesh Renderer” dels objectes

Sense entrar en la necessitat d’explicar un per un els diferents paràmetres dela propietat Mesh Renderer, ara mateix només cal tenir present que, tots elsobjectes d’una escena que hagin de participar del graphics pipeline de Unityhan de tenir activada aquesta propietat, però també contemplar un altre factorigualment necessari i important: estar situats dins del camp de visió o ViewFrustum, determinat pel valor de clipping (‘retallar’, en català) de la càmera activade l’escena (figura 3.10).

Figura 3.10. Camp de visió o ’View frustum’ de la càmera

Com que l’escena és simple (només conté un pla), és fàcil pensar que, perquè Unitydeterminarà si el pla està o no dins del camp de visió de la càmera, realitzarà un

Page 90: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 90 Desenvolupament de videojocs amb Unity

La GPU

GPU és l’acrònim de GraphicsProcessing Unit o ‘Unitat de

Procés Gràfic’, és elcoprocessador dedicat a lageneració de gràfics per a

dispositius com ordinadorspersonals, tauletes, mòbils,

estacions de treball o consoles devideojocs.

càlcul per a cada vèrtex del pla (quatre en total). Però, i si en lloc d’un únic plasón nombrosos models geomètrics formats per un nombre elevat de polígons? SiUnity hagués de calcular si tots els vèrtexs dels polígons de cada model geomètricde l’escena estan dins del camp de visió de la càmera, per determinar si el model éso no visible, el temps de representació de cada imatge seria insostenible en termesde “temps real”. Per aquest motiu, una operació prèvia és calcular si el boundingbox de cada model de l’escena interseca amb el camp de visió de la càmera.

Què és el ’Bounding Box’?

Si agafeu qualsevol objecte en una escena, imagineu quin seria la caixa de dimensionsmés petites en la que podria entrar. Aquesta és la bounding box. Considereu, per tant, lesbounding boxes com simples aproximacions d’un volum ocupat per un objecte geomètric.Si no pots veure la caixa, realment no pots veure l’objecte que conté.

Finalment, quan l’objecte de l’escena (el pla) és visible per la càmera i té activadal’opció de ser inclòs en el graphics pipeline, la GPU rep l’encàrrec de generar laimatge en funció de les propietats del material-shader que els models de l’escenatenen assignat. Per defecte, Unity assigna un material que utilitza un shaderestàndard (figura 3.11).

Figura 3.11. Panell del material assignat a l’objecte.

La vostra tasca, a continuació, serà escriure un shader que reemplaci l’estàndardque Unity assigna per defecte, el qual fareu des de zero perquè us permetiidentificar quina és la seva estructura i elements fonamentals.

Page 91: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 91 Desenvolupament de videojocs amb Unity

3.1.2 Anatomia bàsica d’un ’shader’

Creeu un nou shader a través de l’opció del menú Assets/ Create/ Shader/ UnlitShader. Unity inclourà, dins del panell d’Assets, un nou fitxer. Anomeneu-lomiPrimerShader. Cliqueu dues vegades sobre la icona del nou fitxer (se us obriràl’editor que teniu associat a Unity) i esborreu tot el seu contingut perquè, d’aquestamanera, podeu començar a editar el shader des del principi.

Un shader ve definit, en primer lloc, per la paraula clau Shader i, a continuació, peruna paraula o expressió entre cometes, que serà el nom o identificador del shadera Unity per a quan el vulgueu seleccionar. Aquesta expressió no necessàriamentha de coincidir amb el nom de l’arxiu del shader. Finalment, obriu i tanqueuclaus. Dins d’aquest espai entre claus d’obertura i tancament, desenvolupareu elscontinguts del shader:

1 Shader "A3D/El meu primer Shader" {2

3 }

Un cop deseu els canvis, Unity mostrarà automàticament un missatge d’adverti-ment al panell de l’Inspector (Shader warning in ‘A3D/El meu primer Shader’:Shader is not supported on this GPU (none of subshaders/fallbacks are suitable))on se us informa que el shader que acabeu de crear no està admès per la GPU del’ordinador en el qual esteu treballant, ja que encara no heu introduït cap instruccióo SubShaders dins del shader. Dit d’una altra manera: Unity es queixa que elshader està buit (figura 3.12).

Figura 3.12. Missatge d’advertiment de Unity al panell de l’Inspector del ’shader’

Encara que el shader no sigui funcional, el podeu assignar a un material. Creeu unmaterial nou a través de l’opció Assets/ Create/ Material amb el nom miMateriali seleccioneu el vostre shader des del menú desplegable de shaders del material.Com que encara no és funcional, Unity l’ubicarà dins de l’entrada Not supported(figura 3.13).

Recordeu que, tal compodeu llegir al principid’aquest apartat, l’editor quees farà servir per editar elsshaders serà el MicrosoftVisual Studio 2017.

Page 92: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d

} ' entorns interactius multidispositiu 92 Desenvolupament de videojocs amb Unity

Figura 3.13. Assigneu el vostre primer ’shader’ al material nou que acabeu de crear

Assigneu, a continuació, al pla de l’escena, el nou material que acabeu de crear i,per tant, reemplaçareu el material estàndard que Unity li havia assignat per defecte.Veureu que el color del pla es tornarà d’una tonalitat violeta o magenta. És lamanera en què Unity recorda a l’usuari que el shader presenta alguna errada oproblema (figura 3.14).

Figura 3.14. Unity, tenim un problemaamb el ’shader’

Tal com ja apuntàvem abans, Unity espera la definició d’un SubShader o mésdins dels límits de les claus del shader. La finalitat que Unity permeti un omés SubShaders definits a un shader és, bàsicament, per la seva naturalessamultiplataforma. És a dir, podeu definir un SubShader per a ordinadors desobretaula i un altre per a mòbils dins del mateix shader. Afegiu, llavors, unSubShader, tal com se us mostra en el següent codi :

1 Shader "A3D/El meu primer Shader" {2

Page 93: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 93 Desenvolupament de videojocs amb Unity

3 SubShader {4 }5 }

Malauradament, Unity continua mostrant l’advertiment anterior, però a més inclouun error de sintaxi: Shader error in ‘miPrimerShader’: Parse error: syntaxerror, unexpected ’}’ at line 5. Per corregir aquest error i solucionar el problemapersistent de l’advertiment, cal que el SubShader inclogui una secció anomenadaPass:

1 Shader "A3D/El meu primer Shader" {2

3 SubShader {4

5 Pass {6

7 }8 }9 }

El pla es presentarà ara de color blanc i, per fi, Unity ja no mostrarà mésadvertències o errors en el panell d’inspecció del shader (figura 3.15). La finalitatde la secció Pass del Subshader és, simplement, agrupar els programes del shaderque afectaran la representació de l’objecte en pantalla. És possible afegir mésd’una secció Pass, tot i que, en aquest cas, només us caldrà el mínim necessari.

Figura 3.15. Un ’shader’ buit, però completament funcional

A partir d’aquest moment, si recupereu el panell d’inspecció del material nou queheu creat expressament per assignar-li el vostre shader, aquest ja no estarà associatal submenú Not supported, sinó que tindrà el seu propi submenú A3D.

Page 94: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 94 Desenvolupament de videojocs amb Unity

Recupereu el diagrama dela figura 3.8 amb la

finalitat d’establir la relacióentre les fases del

graphics pipeline i els dosprogrames, vertex i

fragment, dels shaders.

Figura 3.16. Menú desplegable ’Shader’ del material

Els programes ’vertex’ i ’fragment’

Si bé el shader és funcional (el pla no es mostra amb la tonalitat violeta), encarafalten més instruccions dins de la secció Pass que us permetin modificar larepresentació en pantalla del pla. Aquestes instruccions corresponen al llenguatgede shading de Unity, que és una variant del llenguatge HLSL i CG Shading. Perfer-ho, cal indicar on comença i acaba aquesta part del codi del shader, és a dir,utilitzeu les següents paraules clau: CGPROGRAM per a l’inici i ENDCG perdesignar el final.

1 Pass {2 CGPROGRAM3

4 ENDCG5 }

Un cop heu inclòs aquestes dues paraules claus, de nou Unity mostrarà en tonalitatvioleta el pla i, per tant, us caldrà revisar quin nou advertiment o avís d’errorafecta el comportament del shader: l’inspector us informa que heu d’incloureels programes vertex i fragment (Shader warning in ‘A3D/El meu primer Shader’:Both vertex and fragment programs must be present in a shader snippet. Excludingit from compilation.).

Un shader consisteix sempre en dos programes: vertex i fragment. Elprograma vertex és el responsable de processar els vèrtexs de la geometriadel model. Això inclou la conversió de l’espai de l’objecte o world spacea l’espai de projecció de coordenades homogènies. El programa fragmentés el responsable d’assignar els colors a cada píxel (o fragments) visible delmodel geomètric en la seva projecció en pantalla.

Page 95: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 95 Desenvolupament de videojocs amb Unity

Cal, per tant, que informeu de com s’anomenaran els dos programes (vertex ifragment) que definireu dins de la secció Pass del SubShader. Per a aquestobjectiu, fareu servir la directiva pragma. D’altra banda, els programes vertexi fragment es declaren com a simples mètodes, de manera molt similar a la sintaxidel llenguatge de programació C, tot i que en general es refereixen habitualmenta funcions:

1 CGPROGRAM2

3 #pragma vertex miVertexPrograma4 #pragma fragment miFragmentPrograma5

6

7 void miVertexPrograma(){8

9 }10

11 void miFragmentPrograma(){12

13 }14

15 ENDCG

Comprovareu que l’avís d’error anterior ha desaparegut però, sorpresa, tambéel pla de l’escena! Lògicament, el fet que els dos programes vertex i fragmentestiguin buits afecta la representació en pantalla del pla. Però abans de continuaren la seva compleció, cal que tingueu en compte la inclusió d’unes llibreriesexternes al codi del shader.

Llibreries externes

Encara que, fins al moment, el codi font del shader és gairebé mínim, necessitareuun boilerplate code que us ajudi a resoldre determinades qüestions; altrament,la creació d’un shader funcional seria un procés tediós i gens eficient. Aquestboilerplate code acostuma a ser una llibreria externa on trobareu definicionscomunes, funcions i altres opcions de codi que us seran de gran ajut en eldesenvolupament funcional dels vostres shaders.

Per incloure una llibreria externa cal que utilitzeu la directiva #include i el nomdel fitxer que voleu incloure en el codi font del shader. Un exemple típic de fitxerque s’inclou com a llibreria externa és UnityCG.cginc:

1 CGPROGRAM2 #pragma vertex miVertexPrograma3 #pragma fragment miFragmentPrograma4

5 #include "UnityCG.cginc"6

7 void miVertexPrograma() {8

9

10 }11

12 void miFragmentPrograma() {13

14 }15

16 ENDCG

Què significa "pragma"?

El terme pragma, en el contextdels llenguatges de programació,fa referència a una directiva,ordre o opció que el compiladorha de tenir present en la fase decompilació del codi font.

Què significa ’boilerplatecode’?

Segons el següent fil dediscussió a la web d’StackOverflow (goo.gl/HoZnX6),l’expressió boilerplate code fareferència a aquell codi reusablei necessari per a obtenir unresultat que, d’una altra manera,us obligaria a gairebé reinventarla roda des de zero.

Page 96: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 96 Desenvolupament de videojocs amb Unity

’float’

El tipus de valor float ve del’anglès floating-point (en català,‘coma flotant’ o ‘punt flotant’) i

és un sistema de notacióutilitzada en el camp de la

informàtica per representareficientment els nombres realsextremadament grans o petits,

amb els quals es poden feroperacions i càlculs on la

precissió del resultat és cabdal.

L’arxiu UnityCG.cginc és, en realitat, una de les llibreries associades al desenvo-lupament de shaders que Unity inclou per defecte. Aquesta llibreria crida a unesaltres llibreries amb un propòsit més específic, tal com se us enumera a continuació(figura 3.17):

• UnityShaderVariables.cginc defineix tot un conjunt de variables neces-sàries per a determinades operaciones, com la transformació de vèrtex,manipulació de la càmera o de les fonts de llum disponibles en l’escenaactiva.

• HLSLSupport.cginc activa determinades funcionalitats dels shaders per-què us permeti utilitzar el mateix codi font del shader independent de laplataforma a la qual va destinat el videojoc o aplicació interactiva..

• UnityInstancing.cginc és, tal com ja apunta el nom de l’arxiu, per donarsuport al procés d’instanciació, que és específic de les tècniques de repre-sentació (rendering) per a reduir la crida de dibuix en pantalla (draw calls).

Figura 3.17. Jerarquia de llibreries exter-nes amb relació a ’UnityCG’

Tingueu present que el codi d’aquestes llibreries s’incorporarà en la fase decompilació del codi font del shader, ja que el compilador reemplaçarà la directiva#include pel contingut de les llibreries. Això succeeix, en concret, durant la faseconeguda com a pre-processing, fase on el compilador gestiona totes les directivesque comencen amb el quadradet (#), com #include i #pragma.

Retorn i semàntica dels valors d’entrada i sortida

Fins ara, les dues declaracions dels programes del shader, voidmiVertexPrograma() i void miFragmentPrograma(), continuen buidesi, tal com estan declarats, no retornen cap valor (void). Si recupereu el diagramadel graphic pipeline (figura 3.8), es dedueix que la informació del modelgeomètric de l’escena que entra en el flux del graphic pipeline per ser representaten pantalla ha de passar d’una fase a l’altra. O, dit d’una altra manera, hi had’haver una forma en la qual el programa vertex es comuniqui amb el programafragment perquè transformi els vèrtexs del model geomètric en píxels de coloren pantalla.

Comenceu pel programa vertex (void miVertexPrograma()). Aquest ha derebre la informació dels vèrtexs del model geomètric en l’espai de coordenades del’objecte (world space) i que els retorni en el sistema de coordenades de projeccióo homogènies. Independentment del sistema de coordenades, sabeu que un vèrtexen l’espai tridimensional està format per tres valors numèrics: X, Y i Z.

Page 97: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 97 Desenvolupament de videojocs amb Unity

Unity (i la resta de programari de disseny i creació 3D) afegeix un quart valor (1)i, per tant, obteniu un vector de quatre valors numèrics (X, Y, Z, 1) que és el que,finalment, el programa vertex esperarà rebre i, al mateix temps, el que retornaràperquè, a continuació, aquest valor el processi el programa fragment.

Dit això, cal que modifiqueu el tipus de retorn de la funció de void a float4,en primer lloc, del programa vertex (recordeu que el nom que li heu assignat enla directiva #pragma és miVertexPrograma). El tipus float4 és simplement laforma amb la qual Unity identifica el vector de quatre valors numèrics de tipus float.D’altra banda, afegiu dins de les claus del programa void miVertexPrograma()la instrucció return 0:

1 float4 miVertexPrograma() {2

3 return 0;4 }

En el moment que deseu els canvis, veureu de nou el pla, però amb el fatídic colorvioleta: l’inspector de Unity us informarà del següent error: function return valuemissing semantics. Què significa? Bàsicament el compilador es “queixa” pel fetque espera un vector de quatre valors floats que representin una posició en l’espai.Dit d’una altra manera: la GPU no sap què fer amb aquesta valor que li esteuretornant perquè sigui operatiu en el graphics pipeline. Per tant, heu d’especificarquin és el valor semàntic que retorna el programa.

Si el programa vertex manipula i retorna els vèrtexs del model de l’escena quees vol representar en pantalla, cal que afegiu la següent expressió semàntica alprograma vertex: SV_POSITION, que significa System Value i POSITION.

1 float4 miVertexProgram() : SV_POSITION {2

3 return 0;4 }

En canvi, segons el graphic pipeline, el programa fragment ha de retornar un valorde color per a cada píxel. El format de color serà l’RGBA i, per tant, esperaràquatre valors en la forma de float4. És a dir, si retorneu 0 produirà un negresòlid.

1 float4 miFragmentPrograma() {2 return 0;3 }

Tornareu a ser alertats per l’inspector del shader de l’error anterior. Cal, per tant,que incloeu la semàntica del valor de retorn. En aquest cas, heu d’indicar on esdesarà el color definitiu, és a dir, el que finalment veureu en pantalla. UtilitzareuSV_TARGET, que significa que, segons el graphic pipeline, és la memòria delquadre (framebuffer) que la GPU mostrarà en pantalla.

1 float4 miFragmentPrograma() : SV_TARGET {2 return 0;3 }

Valors semàntics a Unity

Per a més informació sobre elsvalors semàntics a Unity, visiteula documentació oficial:goo.gl/PnT64w.

Page 98: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 98 Desenvolupament de videojocs amb Unity

No importa el nom queassigneu al paràmetre

d’entrada (a l’exemple, vert).El més important és indicar

al compilador la semàntica.

Fixeu-vos que esteuutilitzant de nou el nom de

la variable vert com aparàmetre d’entrada, idèntic

al valor d’entrada delprograma fragment. No

passa res, ja que aquestavariable només és operativai visible (scope) dins de lesclaus del programa vertex.

Quin és el propòsit de lamatriu MVP?

Sobre aquesta qüestió, usrecomanem la lectura d’aquest fil

al servei web de preguntes irespostes, Stack Overflow:

goo.gl/E1YNi2.

Heu actualitzat els dos programes perquè retornin els valors correctes, però encarano hi ha cap comunicació entre el retorn del programa vertex i l’entrada delprograma fragment. Per aquesta raó, el programa fragment tindrà un paràmetred’entrada que coincideixi amb la semàntica del valor de retorn del programavertex.

1 float4 miFragmentPrograma( float4 vert : SV_POSITION) : SV_TARGET {2 return 0;3 }

Com deveu haver notat, el shader compila sense errors, però el pla continuasense representar-se en pantalla. Però segurament heu advertit que, ara mateix,el programa vertex col·lapsa tots els vèrtexs del pla en un únic punt de l’espai:(0, 0, 0, 1).

Transformar els vèrtexs

Si els vèrtexs del pla, en el codi anterior, es situaven tots en un únic punt de l’espai(0, 0, 0), és el moment de recuperar la posició original de cadascun d’ells. Perfer-ho, necessiteu informar el programa vertex de la posició original dels vèrtexsde l’objecte. Això és possible a través de la inclusió del paràmetre d’entrada desemàntica POSITION en el programa vertex:

1 float4 miVertexPrograma(float4 vert : POSITION) : SV_POSITION {2

3 return vert;4 }

Encara el pla no sigui visible, amb la finalitat d’explicar el que està passantara mateix, us convidem a incorporar una esfera a l’escena i, a continuació,assigneu-li el vostre material (miMaterial). Sorprenentment, l’esfera es mostraràen pantalla de color negre, però completament distorsionada! (figura 3.18).

Figura 3.18. L’esfera es mostra distorsionada i de color negre amb l’assignació del material “miMaterial”

El fet que l’esfera es mostri distorsionada respon a una simple qüestió de sistemesde coordenades. Recordeu que els vèrtexs dels models de l’escena que entren en

Page 99: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament df l o a t 4 ' entorns interactius multidispositiu 99 Desenvolupament de videojocs amb Unity

el graphics pipeline formen part del sistema de coordenades de l’objecte o món(world space). Cal, per tant, transformar el sistema de coordenades de l’objectedels vèrtexs pel sistema de coordenades projectiu. Per fer-ho, multipliqueu(gràcies a la funció mul) els vèrtexs del model per la matriu MVP (Model ViewProjection) de Unity, UNITY_MATRIX_MVP:

1 float4 miVertexPrograma( float4 vert : POSITION ) : SV_POSITION {2

3 vert = mul(UNITY_MATRIX_MVP, vert);4 return vert;5 }

Tot d’una, en el moment de desar els canvis comprovareu, en primer lloc, que:sorpresa!, tant el pla com l’esfera es mostren correctament en pantalla (figura3.19) i, en segon lloc, l’editor us avisarà d’actualitzar el codi font del shaderper requeriment del mateix Unity: accepteu i comproveu que Unity, per motiusde compatibilitat, ha reemplaçat la funció mul(UNITY_MATRIX_MVP, vert) perUnityObjectToClipPos(vert):

1 float4 miVertexPrograma( float4 vert : POSITION ) : SV_POSITION {2

3 vert = UnityObjectToClipPos(vert); // Upgrade NOTE: replaced ’mul(UNITY_MATRIX_MVP,*)’ with ’UnityObjectToClipPos(*)’

4 return vert;5 }

Figura 3.19. Gràcies a la transformació del sistema de coordenades dels vèrtexs, els models esmostren correctament.

Tot i això, encara veieu el pla i l’esfera de color negre. És el moment, llavors,que actualitzeu el programa fragment perquè el pla i l’esfera es mostrin amb unatonalitat de color diferent.

Acolorir els píxels

Ara que ja tenim la projecció correcta del pla i l’esfera, assigneu-los un color en elprograma fragment. La forma més simple és un color constant com, per exemple,el color groc:

Page 100: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 100 Desenvolupament de videojocs amb Unity

1 float4 miFragmentPrograma( float4 vert : SV_POSITION ) : SV_TARGET {2 float4 groc = float4(1.0, 1.0, 0.0, 0.0);3 return groc;4 }

Comprovareu que, per fi, el shader “pinta” de color groc el pla i l’esfera de l’escena(i qualsevol altre model al qual assigneu el material miMaterial), tal com podeuapreciar en la figura 3.20.

Figura 3.20. El ’shader’ per fi “pinta” els models de l’escena, encara que, ara mateix, sigui d’una tonalitatgroga

Naturalment, l’interès dels shaders va més enllà de pintar els models de l’escenad’un únic color. Resulta més interessant si, en lloc d’escriure el color de retornen el programa fragment, permeteu que l’usuari assigni el color a través d’unafinestra de selecció de color al tauler de propietats del material. Això és possiblegràcies a la definició de propietats en el codi font del shader.

Propietats del shader

Les propietats del shader es declaren en un bloc separat al bloc Subshader:

1 Shader "A3D/El meu primer Shader" {2

3 Properties {4

5 }6

7 SubShader {8 ...

Escriviu una propietat nova anomenada _Color a dins del bloc Properties. Podeudir-li qualsevol altre nom, però la convenció és sempre iniciar-lo amb un “unders-core” seguit d’una lletra capital i la resta, en minúscules. La idea és que nomésutilitzi aquesta convenció, cosa que us assegura la duplicació de noms per accident:

1 Properties {2 _Color3 }

Page 101: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament dS u b S h a d e r ' entorns interactius multidispositiu 101 Desenvolupament de videojocs amb Unity

Aquesta propietat es defineix per una cadena de caràcters (string) i un tipus devalor, dins de parèntesi. La cadena de caràcters s’utilitza per etiquetar la propietat i,per tant, correspondrà al nom que apareixerà en el panell d’inspecció del material.Pel que fa al tipus de valor, aquest serà Color:

1 Properties {2 _Color ("Color de base", Color)3 }

L’última part que faltarà és assignar un valor per defecte a la propietat. Assigneu-li,per exemple, el color blanc:

1 Properties {2 _Color ("Color de base", Color) = (1, 1, 1, 1)3 }

Fet això, verifiqueu si la propietat “Color de base” és visible en el panell depropietats del material miMaterial, tal com es mostra en la figura 3.21. Pel fetque la propietat és del tipus Color, estarà acompanyada d’un selector de colorque, per defecte, tal com l’heu definit al codi del shader, és de color blanc.

Figura 3.21. Propietat “Color de base” funcional i accessible en el panell depropietats del material ’miMaterial’

Accedir a les propietats des del "SubShader"

Naturalment, perquè aquesta propietat del shader sigui completament funcional,heu de permetre que el SubShader la pugui llegir; és a dir, que sigui accessibledes dels programes vertex i fragment del shader. Perquè sigui possible, cal quedefiniu la variable amb el mateix nom que heu definit a la secció Properties, ésa dir:

A diferència de llenguatges de programació com C#, l’ordre o la seqüènciade lectura de les instruccions en la programació dels shaders sí queimporta, ja que el compilador llegeix de principi a final. Si definíssiu lavariable _Color després de la funció float4 miFragmentPrograma(*),el compilador us notificaria un error de compilació.

1 Shader "A3D/El meu primer Shader" {2

3 Properties{4 _Color("Color de base", Color) = (1, 1, 1, 1)5 }

Page 102: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 102 Desenvolupament de videojocs amb Unity

6

7 SubShader{8 Pass {9

10 CGPROGRAM11 #pragma vertex miVertexPrograma12 #pragma fragment miFragmentPrograma13

14 #include "UnityCG.cginc"15

16 float4 _Color;17

18 float4 miVertexPrograma( float4 vert : POSITION ) : SV_POSITION {19

20 vert = UnityObjectToClipPos(vert); // Upgrade NOTE: replaced ’mul(UNITY_MATRIX_MVP,*)’ with ’UnityObjectToClipPos(*)’

21 return vert;22 }23

24 float4 miFragmentPrograma( float4 vert : SV_POSITION ) : SV_TARGET {25

26 return _Color;27 }28

29 ENDCG30 }31 }32 }

Finalment, tal com ja se us avança en el codi font anterior, es reemplaça el retorndel programa fragment pel valor de la propietat _Color. Ara ja és possiblemodificar a temps real, tant des de l’editor com des l’àrea de joc (Game) de Unity,el color del pla i de l’esfera (figura 3.22).

Figura 3.22. El valor de la propietat “_Color” assigna de manera interactiva el color del pla i l’esfera

Triangulització, rasterització i interpolació

El shader només us permet assignar un únic color a tots els píxels del pla i l’esfera(i de qualsevol altre model al qual assigneu el material que té associat aquestshader) representat en pantalla. Òbviament, aquesta utilitat gràfica del shader ésforça limitada (i avorrida), però necessària per comprendre la funció i importància

Page 103: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 103 Desenvolupament de videojocs amb Unity

dels programes vertex i fragment en la creació del shader.

El següent diagrama (figura 3.23) resumeix el procés o flux d’entrada i sortida deles dades entre els dos programes, procés que coincideix amb el graphic pipelinede la figura 3.8.

Figura 3.23. Els programes “vertex” i “fragment”

Hi ha una qüestió que apareix en el diagrama anterior (figura 3.23) de la qual, si béheu pogut sentir abans alguna referència, és ara el moment de fer-ne una explicaciómés pautada. Per fer-ho, citarem un text de la documentació oficial de Unity sobrel’aprenentatge dels shaders:

“Els programes vertex i fragment dels shaders treballen de manera gairebé idèntica a comla GPU representa (render ) els triangles que defineixen un model geomètric de l’escena,sense cap funció integrada de com interactuen les possibles fonts de llum que participen enl’escena. La geometria del model és processada, en primer lloc, per una funció anomenada“vert” (vertex), la qual pot alterar els seus vèrtexs. A continuació, els triangles del modelgeomètric són processats gràcies a una altra funció coneguda com “frag” (fragment), quedecideix el color en format RGB (Red, Green, Blue) de cada píxel.”

Alan Zucconi, “A Gentle Introduction to Shaders” (2015).

Si la part del programa vertex processa els vèrtexs de la geometria dels modelsque es representen en l’escena (el pla i l’esfera), Zucconi es pregunta: per quèés necessari, llavors, que el programa “fragment” hagi de processar la geometriatriangularitzada del model? No processava el programa fragment els vèrtexs delmodel, que han estat prèviament processats pel programa vertex (transformant-losd’un sistema de coordenades de l’objecte a un sistema de coordenades projectiu)per, després, traslladar-los al programa fragment perquè els assigni un valor decolor?

La resposta necessita la introducció d’una fase intermèdia que involucra unprocediment, la interpolació, prèviament a l’execució del programa fragment(figura 3.24).

Page 104: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 104 Desenvolupament de videojocs amb Unity

Figura 3.24. Interpolació dels vèrtexs transformatsprèviament pel programa “vertex”

Quan el programa vertex retorna els valors dels vèrtexs (transformats a l’espaiprojectiu) gràcies al programa vertex, la GPU fa la tasca de triangulització, ésa dir, connecta els vèrtexs projectats per formar triangles. La creació d’aqueststriangles per la GPU acostuma a seguir tres estratègies diferents, sempre en termesd’eficiència i optmització (vegeu la figura 3.25):

• Crear un triangle independent a partir de la connexió de tres vèrtexs.

• Crear una tira de triangles (triangle strip), descartant els dos últims vèrtexsde cada triangle com els primers dos vèrtexs del següent.

• Crear un fan de triangles (triangle fan), connectant el primer element pera cada subsegüent parell d’elements.

Figura 3.25. Tècniques de trian-gularització de la GPU

Un cop finalitza la fase de la triangulització, la GPU els rasteritza, és a dir, la GPUprocessa cada triangle en unitats discretes anomenades fragments i, com podeuveure en la figura 3.26, determina quines d’aquestes unitats discretes continuaranen el procés de graphic pipeline i quines, finalment, es descartaran per situar-sefora o gairebé fora de l’interior del triangle.

Page 105: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 105 Desenvolupament de videojocs amb Unity

Figura 3.26. Procés de rasterització dels triangles

La rasterització també determina els valors de cada fragment al programa delgraphic pipeline: el programa fragment del shader. Però, quins valors? Si elprograma shader assigna un valor de color a cada vèrtex, després de rasteritzarels triangles, la GPU també efectuarà una operació d’interpolació entre els valorsdels tres vèrtexs de cada triangle tal com podeu veure en la figura 3.27.

Figura 3.27. Interpolació dels triangles.

És a dir, la sortida del programa vertex no és la que servirà d’entrada alprograma shader, tal com se us avançava en la figura 3.24. El procedimentd’interpolació se situa al mig dels dos programes del shader. És, en aquestsentit, la semàntica SV_POSITION que rep aquests valors interpolats per laGPU en la fase intermèdia de la rasterització/interpolació.

Per comprendre millor aquest procés d’interpolació, us proposem una ampliaciódel shader en què s’aplicarà una operació d’interpolació de valors de color a partirde les dades que es deriven de la posició local dels vèrtexs dels objectes. Usanticipem, en primer lloc, el resultat visual del shader (figura 3.28).

Page 106: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 106 Desenvolupament de videojocs amb Unity

Figura 3.28. Obtenció gràfica del’shader’ a partir d’una operació d’inter-polació personalitzada

Si es vol accedir a la posició local dels vèrtexs dels objectes de l’escena, cal pensaren la manera en la qual, des del programa fragment es pot accedir a aquestes dades.Aquestes dades, com a posicions a l’espai de l’objecte, han de tenir únicament trescomponents, x, y i z i, per tant, un tipus de valor float3.

Però, pel fet que el programa fragment retorna un valor de color en la forma dequatre components (RGBA), caldrà complementar amb un quart valor, unitari, elscomponents de la posició local dels vèrtexs de l’objecte:

1 float4 miFragmentPrograma( float4 position : SV_POSITION, float3posicioLocalVertex ) : SV_TARGET {

2

3 return float4(posicioLocalVertex, 1);4 }

Com era d’esperar, el compilador us informa d’un error ja conegut: Shadererror in ‘A3D/El meu primer Shader’: ‘miFragmentPrograma’: input parameter‘posicioLocalVertex’ missing semantics. Cal que informeu el compilador del tipusde valor semàntic (semantics) de la variable d’entrada posicioLocalVertexperquè la interpreti correctament. Utilitzeu el tipus de semantics TEXCOORD0:

1 float4 miFragmentPrograma(float4 position : SV_POSITION, float3posicioLocalVertex : TEXCOORD0) : SV_TARGET{

2

3 return float4(posicioLocalVertex, 1);4 }

Adquisició de les dades interpolades de la GPU mitjançant la semàntica decoordenades

Us preguntareu, amb raó, el perquè d’aquest valor semàntic per a la variableposicioLocalVertex i no un altre. En certa manera, pot resultar, en un primer moment,una mica confosa, arbitrària o, fins i tot, capritxosa l’elecció dels valors semàntics de lesvariables d’entrada i sortida entre els programes vertex i fragment, sobretot quan no s’estàutilitzant cap textura en la definició del material associat al shader i, tot i així, assigneu lasemàntica TEXCOORD0.

Doncs, bé, la resposta és terriblement simple: no hi ha cap opció semàntica per accedir ales dades interpolades de la GPU i, per tant, la majoria de desenvolupadors han de recórrera la semàntica de les coordenades de textures com a forma d’adquirir-les.

El compilador ja no us informa de l’error, però el resultat visual del shader aplicatal models (el pla i l’esfera) és un perfecte color negre, és a dir, el programafragment està retornant el valor (0, 0, 0, 0) per a cada fragment dels models.

Page 107: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament dv e r t ' entorns interactius multidispositiu 107 Desenvolupament de videojocs amb Unity

Dit d’una altra manera, la variable posicioLocalVertex no aporta cap valor alprograma fragment, ja que aquesta no extreu les posicions locals dels vèrtexs delmodel al programa vertex del shader. Això és possible si afegiu un paràmetre desortida al programa vertex que tingui la mateixa semàntica que la del programafragment, és a dir:

1 float4 miVertexPrograma( float4 vert : POSITION, out float3 posicioLocalVertex: TEXCOORD0 ) : SV_POSITION {

2

3 vert = UnityObjectToClipPos(vert);4 return vert;5 }6

7 float4 miFragmentPrograma(float4 position : SV_POSITION, float3posicioLocalVertex : TEXCOORD0) : SV_TARGET{

8

9 return float4(posicioLocalVertex, 1);10 }

Què significa ’.xyz’ associat a un valor ’float3’ o ’float4’?

Coneguda com a operació swizzle, aquest mètode us permet accedir a un o diferentscomponents d’un vector d’una manera flexible. És un mètode que permet filtrar,reorganitzar i repetir components dels tipus float. Per exemple: .x retorna el primer valordel vector, .xy retorna un nou vector de dos valors, .yx retorna un vector de dos valors ons’ha alterat l’ordre del vector original, .xxx retorna un vector de tres valors que són idènticsal primer valor del vector original. Accedir als quatre components representaria el mètode.xyzw. També és possible fer servir el noms convencionals del color, és a dir, .rgba.

Ara mateix, el compilador us advertirà d’un problema: la variableposicioLocalVertex no està inicialitzada (Output value ‘posicioLocalVertex’is not completely initialized), és a dir, és una variable de sortida que encara noheu creat dins del programa vertex. Inicialitzeu-la de la següent manera:

1 float4 miVertexPrograma( float4 vert : POSITION, out float3 posicioLocalVertex: TEXCOORD0 ) : SV_POSITION {

2

3 posicioLocalVertex = _Color.xyz;4 vert = UnityObjectToClipPos(vert);5 return vert;6 }7

8 float4 miFragmentPrograma(float4 position : SV_POSITION, float3posicioLocalVertex : TEXCOORD0) : SV_TARGET{

9

10 return float4(posicioLocalVertex, 1);11 }

I el resultat no és gaire diferent del que s’obtenia quan la propietat _Colordeterminava el color final del pla i l’esfera (figura 3.22). El més important,ara mateix, és verificar que aquest valor (posicioLocalVertex) es comunicaamb el programa fragment correctament, tot i que, en aquest cas, la semànticaTEXCOORD0 no té cap efecte.

Llavors, per passar la informació de la posició local dels vèrtexs dels modelsnomés cal que assigneu els valors de la variable d’entrada position a la variableposicioLocalVertex:

1 float4 miVertexPrograma( float4 vert : POSITION, out float3 posicioLocalVertex: TEXCOORD0 ) : SV_POSITION {

Page 108: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 108 Desenvolupament de videojocs amb Unity

2 posicioLocalVertex = vert.xyz;3 vert = UnityObjectToClipPos(vert); return vert;4 }5

6 float4 miFragmentPrograma(float4 position : SV_POSITION, float3posicioLocalVertex : TEXCOORD0) : SV_TARGET {

7

8 return float4(posicioLocalVertex, 1);9 }

El resultat és ara el que esperàveu, tal com es mostra a la figura 3.29.

Figura 3.29. Interpolació en clau de color de les posicions locals dels vèrtexs dels models

Pel fet que la semàntica TEXCOORD0 es mou en un rang entre 0 i 1, qualsevol valornegatiu es resoldrà amb el valor 0, així com qualsevol valor que superi el valorpositiu d’1 es limitarà a 1. Per aquest motiu, tant l’esfera com el pla mostrenuna tonalitat fosca, ja que alguns dels valors dels vèrtexs locals de l’objecte sónnegatius.

Perquè aquests valors negatius passin a un valor que formi part del rang 0-1, sumeuun valor de 0,5 a tots els canals de la variable posicioLocalVertex:

1 float4 miFragmentPrograma(float4 position : SV_POSITION, float3posicioLocalVertex : TEXCOORD0) : SV_TARGET {

2

3 return float4(posicioLocalVertex + 0.5, 1);4 }

S’aconsegueix, tal com veieu en la figura 3.30, un resultat més aproximat al de lafigura 3.29.

Page 109: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 109 Desenvolupament de videojocs amb Unity

Figura 3.30. Sumeu un valor de 0,5 al tots els canals de la variable ’posicioLocalVertex’

Per finalitzar la pràctica, recupereu la propietat per influir, de manera interactiva,en el resultat gràfic de la interpolació de color del shader:

1 float4 miFragmentPrograma(float4 position : SV_POSITION, float3posicioLocalVertex : TEXCOORD0) : SV_TARGET {

2

3 return float4(posicioLocalVertex + 0.5, 1) * _Color;4 }

Endreçar el codi font del ’shader’ amb l’ús d’estructures de dades

Acabarem aquesta secció de l’apartat dedicada a l’anatomia bàsica dels shaders aUnity amb la incorporació d’estructures de dades perquè us permetin una millorlectura i manteniment dels diferents components que intervenen en el flux decomunicació d’entrada i sortida entre els programes vertex i fragment.

Com segurament deveu haver detectat, la incorporació d’un nou component enla llista de paràmetres dels programes obre la porta a que, més endavant, perexigència del resultat gràfic del shader, hi intervinguin un nombre elevat decomponents. Per exemple:

1 float4 miVertexPrograma( float4 vert : POSITION, out float3posicioLocalVertex0 : TEXCOORD0, posicioLocalVertex1 : TEXCOORD1,posicioLocalVertex2 : TEXCOORD2, posicioLocalVertex3 : TEXCOORD3,posicioLocalVertex4 : TEXCOORD4, posicioLocalVertex5 : TEXCOORD05) :SV_POSITION {

2 ...

Aquesta és la raó per la qual s’utilitza un tipus de dades compostes (originaridel llenguatge de programació C) anomenat struct. El tipus struct estableix unaestructura de dades o, dit d’una altra manera, agrupa un conjunt de components dediferents tipus de valors amb la finalitat d’organitzar i gestionar de manera eficientles diferents variables d’un programa. La sintaxi general per declarar un struct ésla següent:

1 struct nom_etiqueta {2 tipus component0;3 tipus component1;

Page 110: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d# ' entorns interactius multidispositiu 110 Desenvolupament de videojocs amb Unity

4 ...5 };

Llavors, és possible agrupar les variables d’entrada del programa fragment en unstruct, tal com es mostra a continuació, en el qual declareu l’estructura amb elnom interpol_lacio:

1 struct interpol_lacio {2 float4 position : SV_POSITION;3 float3 posicioLocalVertex : TEXCOORD0;4 };

A continuació cal que la inicieu perquè aquesta sigui efectiva. Per fer-ho, creeuuna variable del tipus d’estructura interpol_lacio dins del programa vertex:

1 interpol_lacio i;

L’accés als membres o components de l’estructura interpol_lacio és forçasimple:

1 interpol_lacio i;2 i.posicioLocalVertex = vert.xyz;3 i.position= UnityObjectToClipPos(vert);

Seguidament, cal que declareu que el programa vertex retornarà l’estructurainterpol_lacio perquè aquesta traslladi les dades al programa fragment. Enaquest sentit, com que es fa servir l’estructura de dades amb la seva semànticaassignada, ja no caldrà incloure la semàntica del valor de retorn:

1 interpol_lacio miVertexPrograma( float4 vert : POSITION) {2 interpol_lacio i;3 i.posicioLocalVertex = vert.xyz;4 i.position= UnityObjectToClipPos(vert);5 return i;6 }

Finalment, modifiqueu els paràmatres d’entrada del programa fragment així comtambé el valor del seu retorn:

1 float4 miFragmentPrograma(interpol_lacio i) : SV_TARGET{2 return float4(i.posicioLocalVertex + 0.5, 1) * _Color;3 }

Utilitzar estructures us permet obtenir una estructura més endreçada i compactadel codi font del shader:

1 Shader "A3D/El meu primer Shader" {2

3 Properties{4 _Color("Color de base", Color) = (1, 1, 1, 1)5 }6

7 SubShader{8

9 Pass {10

11 CGPROGRAM12 #pragma vertex miVertexPrograma

Page 111: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 111 Desenvolupament de videojocs amb Unity

13 #pragma fragment miFragmentPrograma14

15 #include "UnityCG.cginc"16 float4 _Color;17

18 struct interpol_lacio {19 float4 position : SV_POSITION;20 float3 posicioLocalVertex : TEXCOORD0;21 };22

23 interpol_lacio miVertexPrograma( float4 vert : POSITION) {24 interpol_lacio i;25 i.posicioLocalVertex = vert.xyz;26 i.position= UnityObjectToClipPos(vert);27 return i;28 }29

30 float4 miFragmentPrograma(interpol_lacio i) : SV_TARGET{31 return float4(i.posicioLocalVertex + 0.5, 1) * _Color;32 }33

34 ENDCG35

36 }37 }38 }

3.2 Creació de textures procedimentals amb ’shaders’ a Unity

Un cop que ja esteu familiaritzats amb l’estructura, conceptes i funcionament bàsicdels shaders amb Unity, és el moment de fer un pas endavant i que intenteu crearuna textura procedimental basada en el patró d’un mur de maons, tal com se usinformava al principi d’aquest apartat (figura 3.2).

L’interès d’aquesta proposta és variat, però es podria resumir en l’aprofundimenten el coneixement de la programació gràfica actual a través de les possibilitatsvisuals (materials, efectes, postprocessament de la imatge) gràcies a l’ús delsshaders.

El patró del mur de maons consisteix en una fila de maons en què cada fila estàdesplaçada una distància equivalent a la meitat de la mida d’amplada del maó. Elsmaons estan separats per un morter que té un color diferent que el dels maons. Lafigura 3.31 us mostra un exemple real del mur de maons partint d’una fotografia.

Page 112: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 112 Desenvolupament de videojocs amb Unity

Figura 3.31. Patró del mur de maons

Creeu un nou shader (Assets/ Create/ Shader/ Unlit Shader) i anomeneu-lomurMaons. Cliqueu dues vegades sobre la icona del nou shader i esborreucompletament el contingut del shader estàndard que, per defecte, inclou Unityi, finalment, afegiu aquest contingut bàsic, però funcional:

1 Shader "A3D/Patró mur de maons" {2

3 Properties{4

5 }6

7 SubShader{8

9 Pass {10

11 CGPROGRAM12 #pragma vertex vert13 #pragma fragment frag14

15 #include "UnityCG.cginc"16

17 struct vertexEntrada {18 float4 posicio : POSITION;19 };20

21 struct vertexSortida {22 float4 posicio : SV_POSITION;23 };24

25 vertexSortida vert(vertexEntrada entrada) {26 vertexSortida s;27 s.posicio = UnityObjectToClipPos(entrada.posicio);28 return s;29 }30

31 float4 frag(vertexSortida s) : SV_TARGET {32 return float4(1, 1, 1, 1);33 }34

35 ENDCG36

37 }38 }39 }

I assigneu el nou shader al material miMaterial (figura 3.32). Veureu que elmodels del pla i l’esfera es tornaran de color blanc.

Page 113: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament df l o a t 4 ' entorns interactius multidispositiu 113 Desenvolupament de videojocs amb Unity

Figura 3.32. Assignació del ’shader’ “Patró mur de maons” al material ’miMaterial’

3.2.1 Declaració i inicialització de les propietats

Una de les característiques més importants dels shaders en el context de creació aUnity és la modificació a temps real de les variables o paràmetres que determinenel resultat gràfic. El desenvolupament del shader de generació de la textura delmur de maons no serà una excepció.

A banda de facilitar l’accés a aquelles variables responsables de la definiciógràfica (color i dimensions) dels maons i el morter, us presentem noves opcionsde configuració que podeu utilitzar en la creació dels vostres shaders:

1 Properties{2 _ColorMao("Color del maó", Color) = (0.5, 0.15, 0.14)3 _ColorMorter("Color del morter", Color) = (0.5, 0.5, 0.5)4 _MidesMao("Mides del maó", Vector) = (0.3, 0.15, 0, 0)5 _MidesMorter("Amplada del morter", Vector) = (0.1, 0.15, 0, 0)6 _Densitat("Densitat", Range(2, 50)) = 37 }

Un cop introduït en el codi del shader, podreu tenir accés a les propietats des delmaterial al qual heu assignat el shader (figura 3.33).

Figura 3.33. Accés de les propietats del ’shader’ al material “miMaterial”

Finalment, no oblideu inicialitzar les variables de les propietats perquè siguinvisibles/accesibles des del programa vertex i fragment, ja que si no el compiladorus informaria d’un error:

1 float _Densitat;2 float2 _MidesMao;

Page 114: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 114 Desenvolupament de videojocs amb Unity

3 float2 _MidesMorter;4 float4 _ColorMao;5 float4 _ColorMorter;

3.2.2 Utilitzar les coordenades UV

Quan necessiteu un aspecte visual dels models 3D rics en varietat de detalls, siguid’estil realista o cartoon, utilitzeu textures per evitar incrementar el nombre detriangles del model. El patró del mur de maons seria un bon exemple: abansde modelar blocs i blocs de geometria per representar els maons, projecteu unaimatge (com la de la figura 3.31) sobre un pla per aconseguir el mateix efecte.

La part interessant de l’ús de les textures en aquest apartat és com les textureses projecten sobre la geometria del model. Per fer-ho utilitzen un sistema decoordenades especial conegut com a coordenades UV. Aquestes coordenades sóncoordenades de dues dimensions que situen les dimensions de la imatge en unaàrea quadrada d’una unitat (vegeu la figura 3.31 i la figura 3.34) sense tenir encompte la relació d’aspecte (aspect ratio) de la textura.

S’utilitza la convenció U i V per evitar confusions amb les coordenades cartesianesX i Y ; la coordenada horitzontal es coneix com a U i la vertical com a V. Per aixòes parla de coordenades UV.

Figura 3.34. Sistema de coordenades UV cobrint laimatge “Patró del mur de maons”

La coordenada U incrementa el seu valor de 0 a 1 d’esquerra a dreta. Lacoordenada V es comporta de la mateixa manera, però en vertical, és a dir, debaix a dalt, execepte en el cas d’utilitzar Direct3D, que va en direcció contrària(de dalt a baix).

En qualsevol model geomètric (sigui un pla, una esfera o un model 3D compleximportat des d’un altre programari), Unity assigna unes coordenades UV perdefecte. És possible accedir als valors d’aquest sistema de coordenades UV des delprograma vertex a través de la semàntica TEXCOORD0, que heu vist a la subsecció

Page 115: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 115 Desenvolupament de videojocs amb Unity

sobre la fase de rasterització i interporlació del graphics pipeline en el context delsshaders.

Per fer-ho, cal que incorporeu un nou membre en l’estructura vertexEntradaetiquetat com a uv i de semàntica TEXCOORD0:

1 struct vertexEntrada {2 float4 posicio : POSITION;3 float2 uv : TEXCOORD0;4 };

És clar que, si voleu utilitzar les dades UV del model en el programa fragment, uscaldrà incloure també aquest mateix component en l’estructura vertexSortida:

1 struct vertexSortida {2 float4 posicio : SV_POSITION;3 float2 uv : TEXCOORD0;4 };

És dins del programa vertex on el component uv de l’estructura vertexSortidacaptura el valor uv de l’estructura vertexEntrada:

1 vertexSortida vert(vertexEntrada entrada) {2 vertexSortida s;3 s.posicio = UnityObjectToClipPos(entrada.posicio);4 s.uv = entrada.uv;5 return s;6 }

Finalment, si interpretem en clau de color els valors de les coordenades UV, noméscal que modifiquem el retorn del programa fragment:

1 float4 frag(vertexSortida s) : SV_TARGET {2 return float4(s.uv.xy, 0, 1);3 }

El resultat és el que veieu a la figura 3.35 Un resultat idèntic al de la figura 3.28!

Figura 3.35. Traducció en clau de valor de color del sistema de coordenades UV

La importància de l’ús de la variable uv : TEXCOORD0 és, com podeu deduir,enorme, particularment pel següent motiu: gràcies a ella podeu controlar les

Page 116: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 116 Desenvolupament de videojocs amb Unity

Què és i que fa la funciófrac(x)?

Segons la documentació oficiald’HLSL de Microsoft

(goo.gl/RsxT4A), la funciófrac(x) retorna la part

fraccional (o decimal) del valorx; la qual sempre serà més gran o

igual a 0 i menys que 1. Perexemple, el valor de frac(3,14)

seria 0,14.

posicions i valors dels fragments que acabaran mostrant-se en pantalla. Ditd’una altra manera, en clau pràctica: si volguéssiu pintar un quadrat negre sobrefons blanc sobre la superfície del pla i l’esfera (figura 3.36), només us caldriadeterminar un condicional en el programa fragment:

1 float4 frag(vertexSortida s) : SV_TARGET {2 float2 posicio = s.uv;3

4 if (posicio.y > 0.25 &&5 posicio.x > 0.25 &&6 posicio.y < 0.75 &&7 posicio.x < 0.75) {8 return float4(0, 0 , 0, 1); // color negre9 }

10 else {11 return float4(1, 1 , 1, 1); // color blanc12 }13 }

Figura 3.36. Quadrat negre sobre blanc

Recupereu la versió anterior del programa fragment, aquella en què el shadermostrava els valors en clau de color de les coordenades UV (figura 3.35) peròsense descartar l’expressió float2 posicio = s.uv:

1 float4 frag(vertexSortida s) : SV_TARGET{2

3 float2 posicio = s.uv;4

5 return float4(posicio, 0, 1);6 }

Introduïu, a continuació, la funció frac(x). Com que la variable float2posicio està normalitzada (els seus valors es situen sempre del 0 a l’1), no tindriagaire sentit incloure-la de la següent manera, ja que el resultat seria redundant:

1 float4 frag(vertexSortida s) : SV_TARGET{2

3 float2 posicio = s.uv;4

5 posicio = frac(posicio);6

7 return float4(posicio, 0, 1);8 }

Page 117: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d

f l o a t 2 ' entorns interactius multidispositiu 117 Desenvolupament de videojocs amb Unity

Però què passa si escaleu el sistema normalitzat de coordenades UV, per exemple,per tres?

1 float4 frag(vertexSortida s) : SV_TARGET{2

3 float2 posicio = s.uv * 3;4

5 posicio = frac(posicio);6

7 return float4(posicio, 0, 1);8 }

Obtindreu una seqüència de tres interpolacions lineals entre 0 i 1 (els valors delsistema de coordenades UV): el primer inclouria aquells valors del tipus floatsituats entre el 0 i l’1; el segon entre l’1 i el 2 i, finalment, el tercer entre el 2 i el3. Dit d’una altra manera més gràfica, esteu creant un simple patró visual (figura3.37).

Figura 3.37. Un simple patró visual basat en la interpolació lineal en clau de color del sistema decoordenades UV

Si reemplaceu el valor del 3 per la propietat _Densitat, tindreu l’opció demodificar, des de les propietats del material, el grau de densitat del patró visual(figura 3.38):

Figura 3.38. Densitat variable i a temps real del patró visual

Page 118: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 118 Desenvolupament de videojocs amb Unity

1 float4 frag(vertexSortida s) : SV_TARGET{2 float2 posicio = s.uv * _Densitat; posicio = frac(posicio);3 return float4(posicio, 0, 1);4 }

Per acabar, vegeu el que passa si fraccionem el valor de la variable posicio perles dimensions del maó, és a dir, pel valor de la propietat __MidesMao:

1 float4 frag(vertexSortida s) : SV_TARGET{2

3

4 float2 posicio = ( s.uv * _Densitat ) / _MidesMao ;5 posicio = frac(posicio);6

7 return float4(posicio, 0, 1);8 }

La forma quadrada original del patró, definida pel sistema unitari de coordenadesUV, adopta la forma de les dimensions del maó, tal com podeu apreciar en la figura3.39.

Figura 3.39. Alteració de la forma quadrada original per la forma de les dimensions del maó

Fet això, us trobeu davant d’un escenari favorable per mostrar els maons i el mortercom a sortida del shader. Introduïu un simple condicional que retorna el color delmaó o del morter segons el valor de posicio:

1 float4 frag(vertexSortida s) : SV_TARGET{2

3 float2 posicio = ( s.uv * _Densitat ) / _MidesMao ;4 posicio = frac(posicio);5

6 if (posicio.x < _MidesMorter.x / 2 ||7 posicio.x > (1.0 − _MidesMorter.x / 2) ||8 posicio.y < _MidesMorter.y / 2 ||9 posicio.y > (1.0 − _MidesMorter.y / 2))

10 {11 return _ColorMorter;12 }13 else {14 return _ColorMao;15 }16 }

Page 119: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 119 Desenvolupament de videojocs amb Unity

El resultat és el que veieu a la figura 3.40. És, gairebé, la textura del patró demaons... Però faltaria quelcom que us permeti desplaçar alternadament les filesdels maons perquè llavors sigui el resultat que esteu perseguint.

Figura 3.40. Primer intent fallit de ’shader’ en generar una textura de mur de maons

3.2.3 Desplaçament horitzontal de les files

En primer lloc, recordeu que la mida del maó sigui el mòdul unitari de la texturai, per tant, el valor 1.0 significarà el límit del maó. Llavors, és fàcil considerarque caldrà aplicar el desplaçament dels píxels/fragments horitzontals si el valor ysupera aquest límit unitari:

1 float2 posicio = ( s.uv * _Densitat ) / _MidesMao ;2

3 if (posicio.y > 1.0) {4 posicio.x += 0.5;5 }6 posicio = frac(posicio);

Podeu comprovar que “funciona”, però no del tot: el desplaçament només afectala primera fila, tal com podeu veure en la figura 3.41.

Figura 3.41. Primer intent fallit de ’shader’ en generar una textura de mur de maons

Page 120: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d_ M i d e s M a o ' entorns interactius multidispositiu 120 Desenvolupament de videojocs amb Unity

Òbviament, només afecta la primera fila, perquè heu escalat el sistema decoordenades UV (s.uv * _Densitat) i, per tant, en realitat heu desplaçat totesles files excepte la inferior. Cal que penseu en una estratègia alternativa. Fixeu-vos en la taula 3.1 en què s’estableixen els intervals de files de maons:

Taula 3.1. Relació de files de maons ivalor posició vertical

Ordre de les files de maons posicio.y

primera 0 - 0.9

segona 1.0 - 1.9

tercera 2.0 - 2.9

quarta 3.0 - 3.9

...

Què caldria fer o aplicar perquè la instrucció posicio.x += 0.5 només afectésles files parells? De nou, la funció frac(x) us serà de gran ajut. Si apliqueu lafunció frac(x) al valor posicio.y, comprovareu un interval idèntic per a totesles files, reflectit en la taula 3.2, com a columna nova de la taula 3.1.

Taula 3.2. Relació de files de maons i intervalde posició vertical

Ordre de lesfiles de maons

posicio.y frac(posicio.y)

primera 0 - 0.9 0 - 0.9

segona 1.0 - 1.9 0 - 0.9

tercera 2.0 - 2.9 0 - 0.9

quarta 3.0 - 3.9 0 - 0.9

...

En canvi, si dividim el valor de posicio.y (posicio * 0.5) obtindreu unsvalors que, en el moment que apliqueu la funció frac(x) al seu resultat, uspermetrà aconseguir el resultat desitjat (taula 3.3).

Taula 3.3. Visió de conjunt de les diferents operacions prèvies fins arribar al resultat desitjat

Ordre de les filesde maons

posicio.y frac(posicio.y) posicio.y * 0.5 frac(posicio.y *0.5)

primera 0 - 0.9 0 - 0.9 0 - 0.45 0 - 0.45

segona 1.0 - 1.9 0 - 0.9 0.5 - 0.95 0.5 - 0.95

tercera 2.0 - 2.9 0 - 0.9 1.0 - 1.45 0 - 0.45

quarta 3.0 - 3.9 0 - 0.9 1.5 - 1.95 0.5 - 0.95

...

La condició que aplicareu, per tant, serà la següent:

1 if (frac(posicio.y * 0.5) >= 0.5)2 {3 posicio.x += 0.5;4 }

I el resultat (figura 3.42) del shader serà la mateixa textura procedimental del murde maons que apareix a la il·lustració del llibre Texturing & Modeling (figura 3.2).El codi font del shader serà el següent:

1 Shader "A3D/Patró mur de maons" {2

3 Properties{4 _ColorMao("Color del maó", Color) = (0.5, 0.15, 0.14)

Page 121: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 121 Desenvolupament de videojocs amb Unity

5 _ColorMorter("Color del morter", Color) = (0.5, 0.5, 0.5)6 _MidesMao("Mides del maó", Vector) = (0.3, 0.15, 0, 0) _MidesMorter("Mides

del morter", Vector) = (0.1, 0.15, 0, 0)7 _Densitat("Densitat", Range(2, 50)) = 38 }9

10 SubShader{11

12 Pass {13

14 CGPROGRAM15 #pragma vertex vert16 #pragma fragment frag17

18 #include "UnityCG.cginc"19

20 struct vertexEntrada {21 float4 posicio : POSITION;22 float2 uv : TEXCOORD0;23 };24

25 struct vertexSortida {26 float4 posicio : SV_POSITION;27 float2 uv : TEXCOORD0;28 };29

30 float _Densitat;31 float2 _MidesMao;32 float2 _MidesMorter;33 float4 _ColorMao;34 float4 _ColorMorter;35

36

37 vertexSortida vert(vertexEntrada entrada) {38 vertexSortida s;39 s.posicio = UnityObjectToClipPos(entrada.posicio);40 s.uv = entrada.uv;41 return s;42 }43

44

45 float4 frag(vertexSortida s) : SV_TARGET{46

47

48 float2 posicio = ( s.uv * _Densitat ) / _MidesMao ;49

50 if (frac(posicio.y * 0.5) >= 0.5)51 {52 posicio.x += 0.5;53 }54

55 posicio = frac(posicio);56

57 if (posicio.x < _MidesMorter.x / 2 ||58 posicio.x > (1.0 − _MidesMorter.x / 2) ||59 posicio.y < _MidesMorter.y / 2 ||60 posicio.y > (1.0 − _MidesMorter.y / 2))61 {62 return _ColorMorter;63 }64 else {65 return _ColorMao;66 }67 }68

69 ENDCG70 }71 }72 }

Page 122: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 122 Desenvolupament de videojocs amb Unity

Figura 3.42. Resultat gràfic del ’shader’ que genera la textura de mur de maons.

Finalment, proveu de modificar els valors de les propietats del shader a través delpanell del material assignat als models (pla i esfera), tal com podeu apreciar en lasegüent captura de pantalla (figura 3.43). Aquesta última funcionalitat (permetrela màxima personalització de la textura final) és la que acaba per convènçer-vosde les possibilitats gràfiques i potència visual dels shaders, però també de lacomplexitat de la programació gràfica en 3D actual.

Figura 3.43. Interacció amb les propietats del ’shader’

3.3 Consideracions finals

Moltes qüestions i aspectes propis de la programació gràfica en 3D han quedatfora per falta d’espai i temps; com, per exemple: ús de llums, projecció d’ombres,transparència, reflexió, refracció o l’aplicació de textures, combinació de materialscomplexos, il·luminació global... La majoria d’aquestes tècniques demanen unabase forta de coneixements de matemàtica (particularment àlgebra lineal) queescapen de l’abast d’aquest apartat, tot i que us animem que exploreu els diferentsrecursos disponibles a Internet.

Un bon punt de partida és la pàgina dedicada al llibre Real Time Rendering(www.realtimerendering.com/), d’Eric Haines. Allà trobareu una gran quantitat

Page 123: Desenvolupament de videojocs amb Unity€¦ · Desenvolupament de videojocs amb Unity Alejandro Santiago, Xavier Belanche Desenvolupament d’entorns interactius multidispositiu

Desenvolupament d’entorns interactius multidispositiu 123 Desenvolupament de videojocs amb Unity

de recursos i referències per avançar en el desenvolupament dels vostres shaders;sobre personalitzar el graphics pipeline de Unity (goo.gl/SvwdjN) o, fins i tot,per animar-vos a utilitzar llibreries de representació lliures (render), com aral’Irrlicht (irrlicht.sourceforge.net), bgfx (github.com/bkaradzic/bgfx) o OGRE(www.ogre3d.org/), per crear el vostre motor gràfic.

Això sí, tingueu sempre present que mai superareu el graphics pipeline del motorde videojocs del programari Microsoft Excel (goo.gl/Ce5xuJ).