Com muntar SFTP/SSH al Finder de MacOSX

Haurem d’utilitzar homebrew

brew cask install osxfuse
brew install sshfs

Una vegada instal·lat, escriurem:

sshfs username@hostname:/remote/directory/path /local/mount/point -ovolname=NAME

Si no tenim homebrew instal·lat, a un terminal del Mac farem:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"

Per més info sobre homebrew: https://brew.sh/index_ca

Odoo 11 Tutorial Desenvolupament d’una aplicació

Desenvolupament de la nostre primera aplicació de Odoo – Una visió general pràctica

Desenvolupar en Odoo la major part del temps significa crear els nostres propis mòduls. En aquest capítol, crearem la nostra primera aplicació Odoo i aprendrem els passos necessaris per a posar-la a disposició de Odoo i instal·lar-la. 

Crearem una senzilla aplicació de coses per fer. Ha de permetre afegir noves tasques, marcar-les com completades i, finalment, esborrar la llista de tasques de totes les tasques ja completades. 

Començarem aprenent els conceptes bàsics del flux de treball de desenvolupament: configurarem una nova instància per al seu treball, crearem i instal·larem un nou mòdul i el actualitzarem per aplicar els canvis que realitzem juntament amb les iteracions de desenvolupament.

Odoo segueix una arquitectura similar a MVC, i revisarem les capes durant la nostra implementació de l’aplicació “ToDo”: 

  • La capa de model, que defineix l’estructura de les dades de l’aplicació
  • La capa de vista, que descriu la interfície d’usuari
  • La capa de controlador, que admet la lògica de negocis de l’aplicació

A continuació, aprendrem com configurar la seguretat del control d’accés i, finalment, afegirem algunes descripcions i informació de personalització de marca al mòdul.

Amb aquest enfocament, seràs capaç d’aprendre gradualment sobre els blocs de construcció bàsics que componen una aplicació i experimentar el procés iteratiu de la construcció d’un mòdul Odoo des de zero.

Creació d’un nou mòdul de complements

Un mòdul addon és un directori que conté els fitxers que implementen algunes característiques de Odoo. Podeu afegir noves característiques o modificar les existents. 

El directori addon ha de contenir un manifest, o arxiu descriptor, denominat __manifest__.py, a més dels altres arxius de mòdul.

Alguns complements de mòdul es presenten com aplicacions. Aquests representen aplicacions disponibles per Odoo i normalment afegeixen el seu propi element de menú de nivell superior. Proporcionen els elements principals per a una àrea funcional, com CRM o HR. A causa d’això, es resalten en el menú Aplicacions de Odoo. D’altra banda, s’espera que els complements de mòdul que no siguin aplicables agreguin característiques a aquestes aplicacions.

Si el mòdul afegeix una funcionalitat nova o principal a Odoo, probablement hauria de ser una aplicació. Si el mòdul simplement realitza canvis en la funcionalitat d’una aplicació existent, probablement hauria de ser un mòdul de complement normal. T’ho mostrarem en la següent secció.

Creació de l’esquelet bàsic

Odoo inclou una ordre scaffold per crear automàticament un nou directori de mòdul, amb una estructura bàsica. Podeu obtenir més informació amb la següent comanda:

odoo-bin scaffold –help

És possible que vulgueu tenir-ho en compte i utilitzar-ho quan comenceu a treballar en el vostre pròxim mòdul, però ara no ho utilitzarem, ja que ara mateix és millor alterar manualment l’estructura del nostre mòdul.

Un mòdul addon d’Odoo és un directori que conté un arxiu descriptor __manifest__.py. També ha de ser importable de Python, de manera que també ha de tenir un arxiu __init__.py. 

El nom del directori és el seu nom tècnic. Farem servir perfer_app. El nom tècnic ha de ser un identificador de Python vàlid i per tant, ha de començar amb una lletra i només pot contenir lletres, números i el caràcter de subratllat.

Si utilitzes la línia d’ordres, podem inicialitzar el nostre directori de mòdul, amb un arxiu __init__.py buit, amb aquestes comandes: 

$ mkdir -p ~/odoo-dev/custom-addons/perfer_app
$ touch ~/odoo-dev/custom-addons/perfer_app/__init__.py

A continuació, ens cal afegir el fitxer de manifest. Ha de contenir només un diccionari de Python, amb aproximadament una dotzena d’atributs possibles. D’ells, només es requereix l’atribut name, però description i author també són molt recomanables. 

Ara, crea l’arxiu __manifest__.py, junt a l’arxiu __init__.py, amb el següent contingut:

{
'name': 'Aplicació de tasques pendent',
'description': 'Gestio tasques personals pendents',
'author': 'Xavier Sastre',
'depends': ['base'],
'application': True,
}

L’atribut depends pot tenir una llista d’altres mòduls que són necessaris. Odoo els instal·larà automàticament quan s’instal·li aquest mòdul. No és un atribut obligatori, però es recomana tenir-lo sempre. Si no es necessiten dependències concretes, hem de dependre del core base principal. 

Heu de tenir cura d’assegurar-vos que totes les dependències s’estableixen explícitament aquí; o d’altra manera, el mòdul pot no instal·lar-se en una base de dades neta (a causa de les dependències que falten) o tenir errors de càrrega si per casualitat els altres mòduls requerits es carreguen després. 

Per a la nostra aplicació, no necessitem cap dependència específica, de manera que depenem només de base

Per ser concisos, només hem utilitzat alguns dels descriptors essencials:

  • name és una cadena amb el títol del addon. 
  • description és un text llarg amb la descripció de les característiques, generalment en format RST.
  • author és el nom de l’autor del mòdul. És un string, però pot contenir una llista de noms separats per comes.  
  • depends és una llista dels mòduls addon dels quals depèn. S’instal·laran automàticament abans que aquest mòdul. 
  • application és un indicador booleà, que declara si el mòdul addon ha d’aparèixer com una aplicació en la llista Aplicacions.

Des d’Odoo 8.0, en lloc de description podem utilitzar un fitxer README.rst o README.md en el directori superior del mòdul. Si tots dos existeixen, s’utilitza la descripció del manifest. 

En un escenari real, es recomana que també utilitzeu les claus addicionals, ja que són rellevants per a la botiga d’aplicacions de Odoo: 

  • summary és una cadena que es mostra com un subtítol per al mòdul. 
  • version, per defecte, és 1.0. Ha de seguir les regles de control de versions (consulteu http://semver.org/per obtenir més informació). És una bona pràctica utilitzar la versió de Odoo abans de la nostra versió de mòdul, per exemple: 11.0.1.0. 
  • license, per defecte considerat com LGPL-3.
  • website és un URL per trobar més informació sobre el mòdul. Això pot ajudar a trobar més documentació.
  • category és una cadena amb la categoria funcional del mòdul, el valor predeterminat és Uncategorized. La llista de categories existents es pot trobar en el formulari Grups de seguretat (a Configuració | Usuari | Grups), a la llista desplegable Application.

Aquestes altres claus també estan disponibles: 

  • installable és per defecte True, però es pot establir en False per desactivar un mòdul.
  • auto_install, si s’estableix en True, s’instal·larà automàticament tan aviat com totes les seves dependències ja estiguin instal·lades. S’utilitza per mòduls de “coa”, connectant dues característiques juntes tan aviat com les dues s’instal·len en la mateixa instància.

Addició d’una icona

Per fer això, només ens cal afegir un arxiu a static/description/icon.png amb la icona que s’utilitzarà.

Anem a reutilitzar la icona de l’aplicació del Notes existent, pel que hem de copiar el fitxer Odoo/addons/note/static/description/icon.png en el directori custom-addons/todo_app/static/description. 

Ho podrem fer amb aquestes ordres:

$ cd ~/odoo-dev
$ mkdir -p ./custom-addons/todo_app/static/description
$ cp /opt/bitnami/apps/odoo/lib/odoo-11.0.post20190218-py3.7.egg/odoo/addons/note/static/description/icon.png ./custom-addons/perfer_app/static/description/.

Més endavant podrem veure la nova icona. Però encara ens queden coses per fer.

Sobre les llicències

Triar una llicència per al treball és molt important, i heu de considerar acuradament quina és la millor opció, i les seves implicacions. Les llicències més utilitzades per als mòduls de Odoo són la LGPL v3 (LGPLv3) i la Affero General Public License v3 (AGPLv3). La LGPL és més permissiva i permet modificacions comercials, sense necessitat de compartir el codi font corresponent. La AGPL és una llicència de codi obert més forta, i requereix treball derivat i allotjament de serveis per compartir el codi font. Més informació sobre les llicències GNU a https://www.gnu.org/licenses/.

Descobrir i instal·lar nous mòduls

Ara tenim un mòdul addon amb una estructura mínima, però es pot instal·lar a la nostra instància de Odoo. Abans que puguem fer això, el nostre servidor Odoo necessita saber sobre aquest nou directori que estem utilitzant per als nostres mòduls personalitzats.

Això és el que farem a continuació.

Addició de la ruta dels complements

Per tenir nous mòduls disponibles a Odoo, necessitem assegurar-nos que el directori que conté el mòdul està en la ruta dels complements, després actualitzar la llista de mòduls de Odoo. 

Per això aturarem la instància d’Odoo i la reiniciarem.

Al nostre cas per aturar la instància d’Odoo hem de fer el següent:

bitnami@debian:~$ sudo /opt/bitnami/ctlscript.sh stop odoo_gevent
bitnami@debian:~$ sudo /opt/bitnami/ctlscript.sh stop odoo_background_worker

Ara afegirem el nostre custom-addons path al fitxer de configuració d’Odoo.

bitnami@debian:~$ vi /home/bitnami/apps/odoo/conf/odoo-server.conf

i modificarem la línia que comença per addons_path per a que quedi així:

addons_path = /home/bitname/odoo-dev/custom-addons,/opt/bitnami/apps/odoo/lib/od
oo-11.0.post20190218-py3.7.egg/odoo/addons

guardam el fitxer i reiniciam el servidor fent:

bitnami@debian:~$ sudo /opt/bitnami/ctlscript.sh start odoo_gevent
bitnami@debian:~$ sudo /opt/bitnami/ctlscript.sh start odoo_background_worker

Recordeu també incloure qualsevol altre directori de complements que feu servir. Per exemple, si també teniu un directori ./odoo-dev/extra que conté mòduls addicionals que es van a utilitzar, és possible que vulgueu incloure també, utilitzant –addons-path option: 

--addons-path = "custom-addons,extra..."

Ara necessitem que la instància de Odoo reconegui el nou mòdul que acabem d’afegir.

Addicionalment ens convé modificar els permisos de qualcun directori d’Odoo

cd /opt/bitnami/apps/odoo/data/
sudo usermod -a -G daemon bitnami
sudo chmod -Rf g+w sessions
sudo chown -Rf daemon:bitnami sessions

Instal·lació del nou mòdul

Abans que es pugui instal·lar un nou mòdul addon, la llista de mòduls s’ha d’actualitzar. 

Això es pot fer des del menú superior Aplicacions i l’opció Actualitzar llista d’aplicacions. Actualitza la llista de mòduls, afegint els mòduls que s’hagin afegit des de l’última actualització de la llista. Recordeu que necessitem el mode de desenvolupador habilitat perquè aquesta opció sigui visible.

L’opció de menú Aplicacions ens mostra la llista de mòduls disponibles. Per defecte, mostra només els mòduls d’aplicació. Ja que hem creat un mòdul d’aplicació, no cal treure aquest filtre per veure-ho. 

Ara feu clic al botó Instal·lar del mòdul.

Actualització d’un mòdul

El desenvolupament d’un mòdul és un procés iteratiu, i voldrà que els canvis realitzats en els arxius d’origen s’apliquin i facin visibles en Odoo.

En molts casos, això es fa mitjançant l’actualització del mòdul: buscar el mòdul a la llista d’aplicacions i una vegada que ja està instal·lat, tindrà un botó d’actualització disponible.

No obstant això, quan els canvis només són al codi de Python, és possible que l’actualització no tingui un efecte. En lloc d’una actualització de mòdul, es necessita un reinici de servidor d’aplicacions. Atès que Odoo càrrega codi Python només una vegada, qualsevol canvi posterior en el codi requereix que s’apliqui un reinici de servidor. 

En alguns casos, si els canvis del mòdul es van realitzar tant en arxius de dades com en codi de Python, és possible que necessiti dues operacions. Aquesta és confusió comú per als nous desenvolupadors de Odoo. 

Quan necessiteu aplicar el treball realitzat en mòduls, la forma més segura és reiniciar la instància de Odoo. Degut a la particularitat de la instància d’Odoo que feu servir, haurem d’utilitzar unes ordres específiques per aturar en aquest moment l’instància:

bitnami@debian:~$ sudo /opt/bitnami/ctlscript.sh stop odoo_gevent
bitnami@debian:~$ sudo /opt/bitnami/ctlscript.sh stop odoo_background_worker

D’aquesta manera aturam Odoo i passarem a utilitzar la línia d’ordres per arrancar i aturar Odoo d’una altra manera

bitnami@debian:~$ odoo-bin -c /opt/bitnami/apps/odoo/conf/odoo-server.conf -d perfer -u perfer_app

Penseu que aquí fem servir les opcions -d i -u per actualitzar el mòdul. Habitualment no serà necessari fer-ho.

Per altra banda la opció -c

Quan vulguem aturar la instància, simplement pitjarem CTRL+C a la finestra de la línia d’ordres activa.

La capa model

Ara que Odoo coneix el nostre nou mòdul, comencem afegint un model simple. 

Els models descriuen objectes de negoci, com una oportunitat, una comanda de vendes o un soci (client, proveïdor, etc.). Un model té una llista d’atributs i també podeu definir els vostres propis. 

Els models s’implementen mitjançant una classe Python derivada d’una classe de plantilla de Odoo. Es tradueixen directament als objectes de base de dades, i Odoo s’encarrega automàticament d’això a l’instal·lar o actualitzar el mòdul. El component responsable d’això és l’assignació relacional d’objectes (ORM).

El nostre mòdul serà una aplicació molt senzilla per mantenir les tasques pendents. Aquestes tasques tindran un únic camp de text per a la descripció i una casella de verificació per a marcar-les com completes. Més endavant hem d’afegir un botó per eliminar les tasques antigues i completades de la llista de tasques pendents. 

Creació del model de dades

Les directrius de desenvolupament de Odoo estableixen que els arxius Python per als models han de col·locar-se dins d’un subdirectori de models. Per tant, crearem un arxiu models/todo_task_model.py al directori principal del mòdul todo_app. 

Les directrius oficials de codificació de Odoo es poden trobar a http://www.odoo.com/documentation/11.0/reference/guidelines.html. Un altre document de normes de codificació és el de les directrius de codificació de la Associació Comunitària de Odoo a odoo-community.org/CONTRIBUTING.rst at master · OCA/odoo-community.org.

Abans d’això, ens cal fer saber a Python que el directori models ha de ser utilitzat (importat). Per a això, editeu el fitxer __init__.py principal del mòdul de la següent manera:  

from . import models

També necessitem afegir un arxiu models/__ init__.py, important l’arxiu de codi Python que s’utilitzarà:  

from . import todo_task_model

Ara podem crear l’arxiu models/todo_task_model.py amb el següent contingut:

from odoo import api, fields, models
 
class PerferTasca(models.Model):
    _name = 'perfer.tasca'
    _description = 'Tasca Per Fer'
    name = fields.Char('Descripcio', required=True)
    is_done = fields.Boolean('Fet?')
    active = fields.Boolean('Actiu?', default=True)
    user_id = fields.Many2one(
         'res.users',
         string='Responsable',
         default=lambda self: self.env.user)
    team_ids = fields.Many2many('res.partner', string='Equip')

La primera línia és un marcador especial que indica a l’intèrpret de Python que aquest arxiu té UTF-8 perquè pugui esperar i controlar caràcters que no siguin ASCII. No farem servir cap, però és una bona pràctica tenir-lo de totes maneres.

La segona línia és una instrucció d’importació de codi Python, fent que els objectes model i camp del nucli de Odoo estiguin disponibles. 

La tercera línia declara el nostre nou model. És una classe derivada de models.Model.

La següent línia l’atribut _name, defineix l’identificador que es farà servir en tot Odoo per fer referència a aquest model. Recordeu que el nom real de la classe Python, TodoTask en aquest cas, no té sentit per a altres mòduls de Odoo. El valor _name és el que es farà servir com a identificador. 

Observa que aquesta i les següents línies tenen sangria. Si no està familiaritzat amb Python, ha de saber que això és important: la sagnia defineix un bloc de codi niat, de manera que aquestes quatre línies han de tenir la mateixa sagnia.

A continuació, tenim l’atribut de model _description. No és obligatori, però proporciona un nom fàcil d’utilitzar per als registres de model, que es pot utilitzar per a millors missatges d’usuari.

Les línies restants defineixen els camps del model. Val la pena assenyalar que el name i active són noms de camp especials. Per defecte, Odoo usarà el camp name com a títol de registre al fer referència a ell des d’altres models. El camp active s’utilitza per activar registres i, per defecte, només es mostraran els registres actius. El farem servir per esborrar les tasques completades sense eliminar-les de la base de dades.

També podem veure exemples de com agregar camps amb relacions a altres models. El camp user_id permet seleccionar el propietari de l’usuari de la tasca. Hem afegit un valor predeterminat perquè l’usuari actual s’estableixi automàticament com a responsable de la tasca.

Finalment, team_ids ens permet seleccionar una llista de Partners involucrats en la Tasca. És una relació de diversos a diversos, on cada tasca pot tenir molts membres de l’equip, i cada soci pot participar en moltes tasques.

Això és tot. Perquè el nostre codi Python s’actualitzi, el mòdul s’ha d’actualitzar, desencadenant la creació dels objectes corresponents a la base de dades. 

No veurem cap opció de menú per accedir a aquest nou model ja que encara no les hem afegit. Tot i així, podem inspeccionar el model acabat de crear usant el menú Tècnic. Al menú superior Settings, aneu a General Settings Technical, Database Structure, Models, cerqueu el model perfer.task a la llista i feu clic per veure la seva definició

Si tot surt bé, podem veure el model i els camps s’han creat correctament. Si no ho podeu veure, intenteu reiniciar el servei com s’ha vist abans.

També podem veure alguns camps addicionals que no hem declarat. Aquests són camps reservats que Odoo afegeix automàticament a cada nou model. Són els següents:

  • id és un identificador numèric únic per a cada registre d’el model.
  • create_date i create_uid especifiquen quan es va crear el registre i qui el va crear, respectivament.
  • write_date i write_uid confirma quan es va modificar per última vegada el registre i qui el va modificar, respectivament.
  • __last_update és una aplicació auxiliar que no s’emmagatzema realment a la base de dades. S’utilitza per a comprovacions de simultaneïtat. 

Ampliació dels models existents

Els nous models es defineixen a través de classes de Python. L’ampliació de models també es realitza a través de classes de Python, amb l’ajuda d’un mecanisme d’herència específic de Odoo, l’atribut de classe _inherit. Aquest atribut de classe _inherit identifica el model que es va a estendre. La nova classe hereta totes les característiques del model primari de Odoo i només necessitem declarar les modificacions que s’han d’introduir. 

En el nostre model perfer.tasca, tenim un camp many to many per a l’equip de persones (Partners) que col·laborarà amb nosaltres en aquesta tasca. Ara afegirem al model de Partners la relació inversa, perquè puguem veure, per a cada soci, totes les Tasques en les que estan involucrats. 

Les directrius d’estil de codificació recomanen tenir un arxiu Python per a cada model. Així que afegirem un arxiu models/res_partner_model.py per implementar les nostres extensions a Partners.

Editeu el fitxer models/__ init__. pi per importar també aquest nou arxiu de codi. El contingut hauria de tenir aquest aspecte:

from . import perfer_tasca_model
from . import res_partner_model

Ara crea el models/res_partner_model.py amb el següent codi:

from odoo import fields, models
class ResPartner(models.Model):
   _inherit = 'res.partner'
   perfer_ids= fields.Many2many(
       'perfer.tasca',
       string="Equips Per fer")

Fem servir l’atribut de classe _inherit per declarar el model que s’estendrà. Recordeu que no fem servir cap altre atribut de classe, ni tan sols el _name.Això no cal, llevat que vulguem fer canvis en qualsevol d’ells.

Podeu pensar en això com obtenir una referència a una definició de model que viu en un registre central i realitzar canvis in situ en ell. Poden ser agregar camps, modificar camps existents, modificar atributs de classe de model o fins i tot mètodes amb lògica de negocis. 

En el nostre cas, afegim un nou camp, perfer_ids, amb la inversa de la relació entre Tasques i Partners, definida en el model Tasca. Hi ha alguns detalls aquí que estan sent manejats automàticament pel ORM, però no són rellevants per a nosaltres en aquest moment. 

Estendre els camps existents segueix una lògica similar: només necessitem usar els atributs que es van a modificar. Tots els atributs omesos mantindran el valor original. 

Per tenir els nous camps de model agregats a les taules de base de dades, ara hem de realitzar una actualització de mòdul. Si tot surt com s’esperava, el camp recent agregat ha de ser vist a l’inspeccionar el model res.partner, al menú tècnic que hem mirat abans.

La capa de vista

La capa de vista descriu la interfície d’usuari. Les vistes es defineixen mitjançant XML, que utilitza el marc de treball de el client web per generar vistes HTML amb reconeixement de dades. 

Tenim elements de menú que poden activar accions que poden representar vistes. Per exemple, l’element de menú Usuaris processa una acció també anomenada Usuaris, que al seu torn representa una sèrie de vistes. Hi ha diversos tipus de vista disponibles, com la llista (de vegades anomenada arbre per motius històrics), els formulari, i el filtre opcions disponibles al quadre de cerca superior dret també es defineixen mitjançant un tipus determinat de vista, la vista de cerca. 

Les directrius de desenvolupament de Odoo estableixen que els arxius XML que defineixen la interfície d’usuari han de col·locar-se dins d’un subdirectori views/. 

Comencem a crear la interfície d’usuari per a la nostra aplicació.

En les següents seccions, realitzarem millores graduals i actualitzacions freqüents de mòduls perquè aquests canvis estiguin disponibles. És possible que també vulgueu provar l’opció –dev=all servidor, que ens estalvia de les actualitzacions de mòduls durant el desenvolupament. Amb ell, les definicions de vista es llegeixen directament des dels arxius XML perquè els canvis puguin estar immediatament disponibles per Odoo sense necessitat d’una actualització de mòdul.

Addició d’elements de menú

Ara que tenim els llocs per emmagatzemar les nostres dades, volem tenir-los disponibles en la interfície d’usuari. El primer que cal fer ha de ser afegir les opcions de menú corresponents. 

Creu el fitxer views/perfer_menu.xml per definir un element de menú i l’acció realitzada per ell:

<?xml version="1.0"?>
<odoo>
   <!-- Action to open To-do Task list -->
   <act_window id="accio_perfer_tasca"
       name="Tasca Per Fer"
       res_model="perfer.tasca"
       view_mode="tree,form"
   />
   <!-- Menu item to open To-do Task list -->
   <menuitem id="menu_perfer_tasca"
       name="Tots"
       action="accio_perfer_tasca"
   />
</odoo>

La interfície d’usuari, incloses les opcions de menú i les accions, s’emmagatzema en taules de base de dades. L’arxiu XML és un arxiu de dades que s’utilitza per carregar aquestes definicions a la base de dades quan s’instal·la o actualitza el mòdul addon. El codi anterior és un arxiu de dades de Odoo, que descriu dos registres per afegir a Odoo:

  • L’element <act_window> defineix una acció de finestra de la banda client que obrirà el model perfer.tasca amb les vistes d’arbre i formulari habilitades, en aquest ordre.
  • El <menuitem> defineix un element de menú superior que crida a l’acció accio_perfer_tasca, que hem definit abans.

Ambdós elements inclouen un atribut id. Aquest atribut id, denominat XML ID, és molt important; s’utilitza per a identificar de forma única cada element de dades dins del mòdul i és el mètode que altres elements poden usar per fer referència a ell. En aquest cas, l’element <menuitem> ha de fer referència a l’acció que es va a processar, pel que ha de fer servir l’identificador XML <act_window> per a això. 

El nostre mòdul encara no sap sobre aquest nou arxiu de dades XML. Perquè això succeeixi, ha de declarar-se en l’arxiu __manifest__.py, utilitzant l’atribut data. És una llista dels arxius de dades que ha de carregar el mòdul després de la instal·lació o actualització.

Ara afegiu aquest atribut al diccionari del manifest: 

       'data': ['views/perfer_menu.xml'],

Necessitem actualitzar el mòdul de nou perquè aquests canvis tinguin efecte. Aneu a del menú superior de Tots i haurieu de veure la nostra nova opció de menú disponible. Al fer clic en ell es motra una vista de llista bàsica, i tindrem disponible una vista de formulari generada automàticament:

Encara que no hem definit la vista d’interfície d’usuari, les vistes de llista i de formulari generades automàticament són funcionals i ens permeten començar a editar dades de forma immediata.

Creació de la vista de formulari

Totes les vistes s’emmagatzemen a la base de dades, en el model ir.ui.view. Per afegir una vista a un mòdul, declaram un element <record> que descriu la vista en un arxiu XML, que es carregarà a la base de dades quan s’instal·li el mòdul. 

Afegiu aquest nou arxiu views/perfer_vista.xml per definir la vista de formulari: 

<?xml version="1.0"?>
<odoo>
   <record id="vista_formulari_perfer_tasca" model="ir.ui.view">
       <field name="name">Formulari Tasca Per Fer</field>
       <field name="model">perfer.tasca</field>
       <field name="arch" type="xml">
           <form string="Tasca Per fer">
               <group>
                   <field name="name"/>
                   <field name="is_done"/>
                   <field name="active" readonly="1"/>
               </group>
           </form>
       </field>
   </record>
</odoo>

El registre ir.ui.view té valors per tres camps: name, model, i arch. Un altre element important és l’identificador de registre id. Defineix un ID XML que es pot utilitzar perquè altres registres facin referència a ell.

La vista és pel model perfer.tasca i es denomina Formulari Tasca Per Fer. El  nomb és només a títol informatiu; no té perquè ser únic, però ha de permetre que qualsevol identifiqui fàcilment a quin registre es refereix. De fet, el nom es pot ometre; en aquest cas, es generarà automàticament a partir del nom del model i del tipus de vista.

El camp més important es campo más importante és arch, ja que conté la definició de la vista, resaltada en el codi XML anterior. L’etiqueta <form> defineix el tipus de vista i, en aquest cas, conté tres camps. Afegim l’atribut define el tipo de vista y, en este caso, contiene tres campos. Agregamos el atributo readonly al camp actiu, perquè sigui de només lectura en l’interfície d’usuari. 

Recordeu afegir aquest nou arxiu a l’arxiu __manifers__.py; en cas contrari, el nostre mòdul no carregarà la vista:

{
       'name': 'Aplicació de tasques pendent',
       'description': 'Gestio tasques personals pendents',
       'author': 'Xavier Sastre',
       'depends': ['base'],
       'application': True,
       'data': [
               'views/perfer_menu.xml',
               'views/perfer_vista.xml',
       ],
}

Recordeu que perquè els canvis es carreguin a la base de dades d’Odoo, es necessita una actualització del mòdul. Per veure els canvis en el navegador, el formulari s’ha de tornar carregar. 

Vista de formulari 

La secció anterior ens ha proporcionat una vista de formulari bàsica, però podem realitzar millores sobre ella. Pels models de documents, Odoo té un estil de presentació que imita una pàgina de paper. Aquest formulari conté dos elements: <header> per disposar-hi botons i <sheet> per disposar-hi els camps de dades. 

Ara podem reemplaçar el <form> bàsic definit en la secció anterior amb aquest altre:

           <form>
               <header>
               <!-- Buttons go here-->
               </header>
               <sheet>
                   <!-- Content goes here: -->
                   <group>
                       <field name="name"/>
                       <field name="is_done"/>
                       <field name="active" readonly="1"/>
                   </group>
               </sheet>
           </form>

Afegim botons

Els formularis poden tenir botons per realitzar accions. Aquests botons poden executar accions, com obrir un altre formulari o executar funcions de Python definides en el model.

Es poden col·locar en qualsevol lloc dins d’un formulari, però pels formularis que tractem ara, el lloc recomanat és la secció <header>. 

Per la nostra aplicació, afegirem un botó per esborrar una tasca feta. Aquest botó executarà el mètode d’objecte esborrar_fet: 

               <header>
                   <!-- Buttons go here-->
                   <button name="esborrar_fet" type="object"
                   string="Esborrar fet" />
               </header>

Els atributs bàsics d’un botó consten dels següents elements:

  • string amb el text que es mostrarà en el botó
  • type d’acció que realitza
  • name és l’identificador per aquesta acció
  • class és un atribut opcional per aplicar estils CSS, com a un HTML 

Ús de grups per organitzar els formularis

L’etiqueta <group> permet organitzar el contingut del formulari. Col·locar elements <group> dins d’un element <group> crea un disseny de dues columnes dins del grup extern. Es recomana que els elements de grup tinguin un atribut name perquè sigui més fàcil ampliar-los per altres mòduls.. 

Utilitzarem això per organitzar millor el nostre contingut. Canviem el contingut de <sheet> amb el següent codi:

               <sheet>
                   <group name="grup_extern">
                       <group name="grup_esquerra">
                           <field name="name" />
                           <field name="user_id" />
                           <field name="is_done" />
                       </group>
                       <group name="grup_dreta">
                           <field name="date_deadline" />
                           <field name="team_ids" widget="many2many_tags" />
                           <field name="active" readonly="1" />
                       </group>
                   </group>
               </sheet>

La vista completa del formulari

En aquest punt, el nostre formulari perfer.tasca hauria de tenir aquest aspecte:

           <form>
               <header>
                   <!-- Buttons go here-->
                   <button name="Esborrar_fet" type="object"
                   string="Esborrar fet" />
               </header>
               <sheet>
                   <group name="grup_extern">
                       <group name="grup_esquerra">
                           <field name="name" />
                           <field name="user_id" />
                           <field name="is_done" />
                       </group>
                       <group name="grup_dreta">
                           <field name="date_deadline" />
                           <field name="team_ids" widget="many2many_tags" />
                           <field name="active" readonly="1" />
                       </group>
                   </group>
               </sheet>
           </form>

Els botons encara no funcionaran ja que falta afegir la seva lògica de negoci.

Afegint llistes i vistes de cerca

Quan visualitzem un model en mode de llista, s’utilitza una vista <tree>. Les vistes tree són capaces de mostrar línies organitzades en jerarquies, però la majoria de vegades s’utlitzen per mostrar llistes sense format o planes. 

Podem afegir la següent definició de vista <tree> a perfer_vista.xml:

   <record id="vista_arbre_perfer_tasca" model="ir.ui.view">
       <field name="name">Formulari Tasca Per Fer</field>
       <field name="model">perfer.tasca</field>
       <field name="arch" type="xml">
           <tree colors="decoration-muted:is_done==True">
               <field name="name"/>
               <field name="is_done"/>
           </tree>
       </field>
   </record>

Això defineix una llista amb només dues columnes: name i is_done. També hem afegit una línia per les tasques realitzades (is_done==True) que es mostrarà com atenuada. Això es fa aplicant la classe muted Bootstrap. Consulteu http://getbootstrap.com/docs/3.3/css/#helper-classes-colors per obtenir més informació sobre Bootstrap i els seus colors contextuals.

Al cantó superior dret de la llista, Odoo mostra un quadre de cerca. Els camps en els que cerca i els filtres disponibles es defineixen mitjançant una vista <search>. 

Com hem fet abans, afegirem això a Como antes, añadiremos esto a perfer_vista.xml:

   <record id="vista_filtre" model="ir.ui.view">
       <field name="name">Filtre Tasca Per Fer</field>
       <field name="model">perfer.tasca</field>
       <field name="arch" type="xml">
           <search>
               <field name="user_id"/>
               <filter string="No està fet"
                   domain="[('is_done','=',False)]"/>
               <filter string="Fet"
                   domain="[('is_done','!=',False)]"/>
           </search>
       </field>
   </record>

Els elements <field> defineixen els camps que també es cerquen quan escrivim en el quadre de cerca. Hem afegit user_id per suggerir automàticamen la cerca en el camp Responsable.

Ampliació de vistes

Els formularis, les llistes i les vistes de cerca es defineixen mitjançant les estructures arch XML. Per ampliar les vistes, ens cal una forma de modificar aquest XML. Això vol dir localitzar elements XML i, a continuació, introduir modificacions en aquests punts.

El registre XML per l’herència de vistes és igual que el de les vistes normals, però també l’atribut inherit_id, amb una referència a la vista que es vol estendre.

Com a exemple d’això, ampliarem la vista Partner perquè el camp perfer_ids sigui visible, mostrant totes les tasques en què està involucrada la persona.

El primer que cal fer és trobar l’identificador XML de la vista que volem estendre. Ho podem trobar mirant a Settings, Technical | User Interface | Views. El ID XML pel formulari Partner és base.view_partner_form. Aquí mateix també hem de trobar l’element XML on volem inserir els nostres canvis. Anem a afegir la llista de col·laboradors a la fi de les columnes dretes, després del camp Idioma. Normalment identifiquem l’element a utilitzar pel seu atribut name. En aquest cas, tenim <field name=”lang” />.

Afegirem un arxiu XML per les extensions fetes a les vistes de Partner, views/res_partner_view.xml, amb aquest contingut:

<?xml version="1.0"?>
<odoo>
   <record id="vista_heretada" model="ir.ui.view">
       <field name="name">Afegeix Per Fers al formulari Partner</field>
       <field name="model">res.partner</field>
       <field name="inherit_id" ref="base.view_partner_form"/>
       <field name="arch" type="xml">
           <field name="lang" position="after">
               <field name="todo_ids" />
           </field>
       </field>
   </record>
</odoo>

El campo inherit_id identifica la vista que es vol ampliar fent referència al seu identificador extern mitjançant l’atribut especial ref. 

Normalment utilitzam XPath per localitzar elements XML. Odoo també utilitza  XPath per això, però també té disponible la forma més simple que acabam d’usar.

Els altres tipus de vista, com les vistes de llista i de cerca, també tenen un camp arch i es pot ampliar de la mateixa manera que les vistes de formulari.

La capa de lògica de negoci

Ara afegirem la lògica pel nostre botó. Això es fa amb codi Python, utilitzant els mètodes de la classe Python del model.

Afegint lògica de negoci

Hem d’editar l’arxiu perfer_tasca_model.py per afegir els mètodes cridats o invocats pel botó. En primer lloc, necessitem importar la nova API, així que afegiu-la a la instrucció import en la part superior de l’arxiu:

from odoo import api, fields, models

La lògica del botó Esborrar Fet és bastant simple. Simplement establir el flag “Actiu?” a falso. Això aprofita una característica integrada d’ORM: els registres amb una marca active posada a false, per defecte es filtres i no es mostraran a l’usuari. 

A l’arxiu models/perfer_tasca_model.py afegiu el següent codi a la classe regue lo siguiente a la clase  PerferTasca: 

    @api.multi
    def esborrar_fet(self):
         for task in self:
              task.active = False
         return True

Per la lògica dels registres, utilitzem el decorator @api.multi. Aquí, self representarà un conjunt de registres, i després farem un recorregut en bucle per cada registre. De fet, aquest és el valor predeterminat pels mètodes Model, per tant en aquest cas el decorator @api.multi es pot ometre amb seguretat. En qualsevol cas, millor deixar-lo per una major claretat.

El codi recorre tots els registres de tasques pendents i, per cada un, assigna el valor False al camp actiu. 

El mètode no necessita retornar res, però hem de menester al menys un return True. El motiu per això és que no totes les implementacions de client del protocal XMLRPC admeten valors None/Null i poden generar errors quan un mètode torna aquest valor.

Ampliació dels mètodes de Python

Els mètodes de Python també es poden ampliar per un lògica de negoci adicional. Això es fa agafant el mecanisme que Python ja proporciona perquè els objectes heretats facin una extensió dels seu comportament de classe primària: super().

Com exemple, farem què la class PerferTasca ampliï el mètode write() de ORM per afegir-li una mica de lògica: quan escrigui en un registre no actiu, el reactivarà automàticament.. 

Afegiu aquest mètode addicional a l’arxiu models/perfer_tasca_model.py: 

    @api.multi
    def write(self, values):
         if 'active' not in values:
              values['active'] = True
         super().write(values)

El mètode write() espera un paràmetre addicional, amb un “diccionari de valors” per escriure en el registre. A no ser que s’estableixi un valor en el camp actiu, per defecte l’establim a True en el cas en que estem escrivint en un registre inactiu. A continuació, utilitzem super() per cridar al mètode pare write() utilitzant els valors modificats. 

Resum

Hem creat un nou mòdul completament des de zero, utilitzant la instància d’Odoo proporcionada en exercicis anteriors. En aquesta aplicació hem cobert els elements més utilitzats en un mòdul: models, els tres tipus de vistes bàsics (formulari, lllista i cerca) i lògica de negoci en els mètodes del model.

Durant tot el procés hem vist com és el procés de desenvolupament del mòdul, que implica actualitzacions de mòduls i reinicis del servidor perquè els canvis graduals siguin efectius a Odoo.

Recordeu que sempre quan afegim canvis de model, es necessita una actualització del mòdul a Odoo. Quan canviem codi Python, inclòs el manifest, es necessita un reinici de la instància. En cas de dubte, reinicieu la instància i actualitzeu els mòduls.

Aquí teniu un arxiu amb tot el codi

Introducció a Unity

Pas Zero: El disseny

Primer, pensem en les peces i parts de Pong: la “mecànica” individual (les regles i les característiques del joc) que haurem de programar.

  1. Teniu un fons per jugar
  2. Teniu un conjunt de paletes que pugen i baixen
  3. Teniu una bolla que rebota de parets i paletes
  4. Teniu un conjunt de parets laterals (dreta i esquerra) a les que heu d’arribar amb la bolla per poder anotar
  5. Teniu una puntuació per mostrar i actualitzar
  6. Teniu un botó de restabliment per poder tornar a començar la partida.

Comencem!

Primer pas: La configuració

Primer, descarregueu aquest fitxer. Conté imatges i altres recursos que utilitzarem en aquest tutorial.

Ara podem començar a configurar el nostre projecte. Obriu Unity i seguiu aquestes passes:

  1. Des de la pantalla de benvinguda, feu clic a Projectes. (Si l’editor Unity ja està obert, feu clic a Fitxer> Projecte nou.)
  2. Feu clic a “Nou”. Llavors, hauríeu de veure alguna cosa així:
  1. Poseu un nom al projecte, per exemple Pong.
  1. Assegureu-vos de triar la plantilla 2D.
  2. Feu clic a Create

Un cop creat el projecte, haureu de veure una graella en 2D a la vista Escena.

Configurar Unity en mode 2D fa diverses coses. En primer lloc, configura la nostra càmera de jocs perquè tot es vegi des d’una perspectiva 2D. També diu a Unity que importi imatges com Sprites en lloc de Textures. 

Recordeu aquell fitxer que hem descarregat? Ara és un bon moment per descomprimir aquest fitxer. Un cop ho facis, veuràs una carpeta anomenada unitspong-assets, que contindrà un conjunt d’imatges, tipus de lletra i altres actius que utilitzarem avui. Seleccioneu tots aquests fitxers i, a continuació, feu clic + arrossegueu-los al plafó Projecte de la part inferior de la finestra Unity. Ara hauria de sortir una cosa així:

Fes clic + arrosega la imatge Background des del panell Project al panell Hierarchy, just davall Main Camera. Haura d’aparèixer a la vista Scene, centrat per defecte. Si no està centrat, utilitza el panell Inspector per posar Transform Position a (0,0,0).

Selecciona el fons, ara al panell Hierarchy, i s’hauria de veure al panell Inspector de la següent manera:

Primer, a la part superior de l’inspector de Transform, canvieu la seva escala per (0.75, 1, 1). Això farà que es vegi millor en el nostre joc. Ara mireu el component Sprite Renderer. Volem assegurar-nos que el fons es mostra darrere dels altres sprites i no apareix accidentalment al damunt. Vés a Sorting Layer i fes clic a Add Sorting Layer. El plafó Inspector ara hauria de quedar així:

Feu clic a la icona + per afegir la nova capa. Canvieu el nom de “New Layer” a “Background”, després feu clic i arrossegueu la nova capa per sobre de la capa predeterminada. És important que col·loqueu la nova capa damunt la capa Default, ja que d’aquesta manera, qualsevol cosa de la capa d’ordenació de fons apareixerà darrere de qualsevol cosa de la capa predeterminada, que és exactament el que volem.

Ara torneu a seleccionar Background al plafó Hierarchy per tal que es mostri de nou a l’Inspector. Al component Sprite Renderer, feu clic al menú desplegable per ordenar la capa i escolliu la nova capa de fons. El vostre inspector hauria de quedar així:

A continuació, hem de fer alguns canvis a la càmera principal perquè el nostre joc quedi més agradable.

Seleccioneu càmera principal al plafó de Hierarchy. L’objecte de la càmera controla com veiem el món del joc (o “Escena”, en termes d’Unity). Quan juguem al nostre joc Pong, és possible que el nostre fons no sigui prou grans per cobrir tota la pantalla. Hem de fer dos petits canvis a la càmera principal per tenir-ne cura.

A l’Inspector, al component Càmera, canvieu la Mida a 3. Això augmentarà lleugerament sobre el fons. A continuació, feu clic a la fons propietat dede la càmera. Això ens permet canviar el color que serà visible a les vores de la pantalla si el fons no és prou gran. Trieu el color negre amb el selector de colors amb valors RGBA de (0,0,0,0). Aleshores, el component de la càmera a l’Inspector hauria de quedar així: 

Seria bo en aquest punt guardar els canvis pitjant CTRL+S

Passa 2: Les paletes

El següent pas és fer les nostres paletes. Cerqueu la imatge anomenada PongPaddle al plafó del projecte.

Ara feu clic i arrossegueu la imatge de la paleta a l’escena de la vista View. Un objecte PongPaddle hauria d’aparèixer al menú Hierarchy.

Feu clic a PongPaddle a la jerarquia. Al inspector, canvieu el nom a Paddle1 i feu clic al menú desplegable Tag i seleccioneu Player. (Més informació sobre això més endavant.) Definiu la posició a (X, Y, Z) = (-4, 0, 0) i Escala a (0,5, 1,5, 1). S’hauria de mostrar així a l’Inspector:

El que fem aquí és assegurar-nos que la paleta estigui situada on el volem. La capa d’ordenació i altres paràmetres no importen aquesta vegada, perquè volem que aquest objecte es dibuixi a la part superior, a la qual es defineix per defecte Unity.

A continuació, afegirem dos components a l’objecte Player. Feu clic al botó Add Component, a continuació, a Physics 2D. Aquí afegiu un Box Collider 2D i un Rigidbody 2D. El Box Collider 2D servirà per assegurar-se que la pilota rebotarà de la seva paleta i que el Rigidbody 2D perquè puguem moure la paleta.

Nota: és important utilitzar Physics, Box Collider i Rigidbody 2D aquí, ja que existeixen versions en 3D, no és el que volem en el nostre joc en 2D.

Ara, farem alguns canvis al component Rigidbody 2D. Unity té un gran sistema integrat de física realista que calcula els efectes de la gravetat, la fricció i altres forces sobre qualsevol objecte que tingui un component 2D Rigidbody. Sovint, aquest sistema és molt útil, però no el volem utilitzar per a les nostres paletes. Les nostres paletes no són exactament realistes, només són una mena de flotació a l’espai i només es mouen quan les expliquem. Per sort, Unity ens proporciona una manera de dir que el nostre Rigidbody 2D només es mogui quan ho expliquem. Només hem de fer clic al desplegable Body Type i seleccionar Kinematic.

Ara començarem la part interessant: afegir un script pel moviment de les nostres paletes.

Per afegir un script, seleccioneu Paddle1 al plafó de Jerarquia i, a continuació, aneu a Add Component i afegiu un nou script. Li podeu posar de nom PlayerControls, i a continuació, feu clic a Create and Add. L’script ara hauria d’aparèixer com a component i al panell de projecte 

Podeu fer-li clic a sobre i s’obrirà amb l’editor predeterminat segons el vostre sistema (Visual Studio, MonoDevelop, etc.)

Descripció del codi

El primer que apareix quan s’obri l’editor de codi són tres línies que corresponen a uns import en Java.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

Just a continuació ve la definició de la classe PlayerControls que es correspon amb el nom que acabam de posar a l’script.

public class PlayerControls : MonoBehaviour {

A continuació de la definició de la classe ja haurem de posar unes línies que són les variables que utilitzarem

public KeyCode moveUp = KeyCode.W;
public KeyCode moveDown = KeyCode.S;
public float speed = 10.0f;
public float boundY = 2.25f;
private Rigidbody2D rb2d;

Les dues primeres com podeu intuir, corresponen a la definició de les tecles que volem utilitzar per la paleta. La següent és la velocitat de desplaçament de la paleta. La variable boundY és la posició més alta a la qual podrà arribar la paleta. Això evitarà que segueixi movent-se per amunt més enllà dels límits de la pantalla. La darrera variable referida al Rigidbody2D l’utilitzarem més endavant.

Start() és la funció que s’executará quan iniciem el joc. L’utilitzarem per fer qualcunes configuracions inicials, com per exempla al Rigidbody2D:

void Start () {
   rb2d = GetComponent<Rigidbody2D>();
}

Update() és una funciós que s’executarà una vegada per frame. L’utilitzarem per saber quin botó s’ha pitjant i moure la paleta en consonància, o, si no s’ha pitjat cap botó mantenir la paleta al seu lloc. Finalment limitarem la paleta verticalment entre +boundY and -boundY, la qual cosa ens la mantindrà dintre de la pantalla del joc en tot moment.

    void Update()
    {
        var vel = rb2d.velocity;
        if (Input.GetKey(moveUp))
        {
            vel.y = speed;
        }
        else if (Input.GetKey(moveDown))
        {
            vel.y = -speed;
        }
        else if (!Input.anyKey)
        {
            vel.y = 0;
        }
        rb2d.velocity = vel;

        var pos = transform.position;
        if (pos.y > boundY)
        {
            pos.y = boundY;
        }
        else if (pos.y < -boundY)
        {
            pos.y = -boundY;
        }
        transform.position = pos;
    }

Aquí teniu el codi complet per descarregar PlayerControls.cs

Ara assegura’t de gravar els canvis a l’editor.

En aquest punt ja podreu provar que efectivament la paleta es pot moure. 

És important destacar que durant el mode de joc, podeu veure (a mode de debug) com es van modificant el paràmetres a través del Inspector i fins i tot modificar-los a la vostre conveniència, però tot allò que faceu durant el mode de joc no es quedarà guardat després.

Ara tornam pitjar el botó de Play per abandonar el mode de joc.

Crearem la segona paleta. Per això podrem duplicar l’objecte Paddle1 i posar-li de nom Paddle2. Ho farem al plafó Hierarchy fent clic amb el botó dret a sobre de Paddle1 i a continuació Duplicate.

Hem de canviar les tecles de moviment i la seva posició inicial. Podem posar, per exemple ‘Up Arrow’ per anar cap amunt i ‘Down Arrow’ per anar cap abaix. La posició serà (4, 0, 0), recordeu que això ho feim al plafó Inspector havent seleccionat Paddle2 a Hierarchy.

Ara hauríem de tenir una cosa semblant a això:

Podem tornar al mode de joc per comprovar que tot funciona tal i com esperam.

Passa 3: La bolla

La bolla és una mica més complicat que les paletes però no gaire més. Pensem que la bolla ha d’anar rebotant per la pantalla, detectar la puntuació i interaccionar amb les paletes.

Per començar, localitzarem la imatge Ball al panell Project abaix de la pantalla (és la icona blava). La seleccionem i l’arrosseguem cap al plafó de jerarquia. Ara tindrem un nou objecte anomenat Ball que el seleccionarem i anirem a modificar els seus atributs al panell Inspector.

Primer crearem una etiqueta. Les etiquetes són la manera d’identificar objectes de joc únics d’Unity i és realment senzill utilitzar-les per trobar objecte. A la part superior de l’Inspector, feu clic a Tag > Add Tag… i l’Inspector canviarà per mostrar les etiquetes i les capes. 

Ara, hem de fer clic a  + per afegir una nova etiqueta. Anomenem-ho Ball. Guardam i en tornar en el desplegable d’etiquetes seleccionarem la nova etiqueta que hem creat.

Canvia l’escala de la nostra bolla a (0,5, 0,5, 1). Una pilota més petita ajuda a fer que el joc se senti més ràpid i divertit.

A continuació, hem d’afegir components similars als que hem fet a la paleta. Feu clic al botó Add Component i afegeix un component Physics 2D, en aquest cas seleccionarem Circle Collider 2D i, per descomptat, Rigidbody 2D. Al Collider de cercles, canvia el radi a 0,23. D’aquesta manera el col·lisionador s’envolta molt més a la pilota.

També volem aplicar un material de física 2D. Seleccioneu BallBounce del plafó del projecte i mireu l’Inspector. Veureu que el valor de fricció és 0 i el factor de rebot (bounce factor) és 1. D’aquesta manera la nostra bola no experimenta fregaments de res, incloses les nostres paletes i parets. El factor de rebot significa que tampoc perd cap velocitat. Rebota amb la mateixa velocitat exacta amb la que va assolir l’objecte.

Per aplicar el material, seleccioneu Ball a l’Inspector i, a continuació, feu clic i arrossegueu el BallBounce material al quadre Circle Collider 2D. També necessitem ajustar diversos paràmetres a Rigidbody 2D per aconseguir el nostre comportament desitjat. Ens hauria de quedar així al final:

Però, per descomptat, per aconseguir que la bola es mogui, necessitem codi. Amb la pilota encara seleccionada a la vostra jerarquia, feu clic a Afegeix un component> Nou script. Aquesta vegada anomenarem a l’script BallControl

Descripció del codi

Primer, com sempre, importem els nostres paquets i confirmem que el nostre nom de classe coincideix amb el nostre nom de fitxer (i script).

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BallControl : MonoBehaviour
{

Hem de declarar una variable que després utilitzarem.

private Rigidbody2D rb2d;

També necessitem una funcio GoBall(), que escollirà una direcció aleatòria (a l’esquerra o a la dreta), per després fer que la bola comenci a moure’s.

    void GoBall()
    {
        float rand = Random.Range(0, 2);
        if (rand < 1)
        {
            rb2d.AddForce(new Vector2(20, -15));
        }
        else
        {
            rb2d.AddForce(new Vector2(-20, -15));
        }
    }

Amb start, inicialitzarem la nostra variable rb2d. A continuació, invocarem la funció GoBall(), mitjançant Invoke(), que ens permet esperar abans de l’execució. Es farà esperar dos segons per donar temps als jugadors per preparar-se abans que la pilota comenci a moure’s.

    void Start()
    {
        rb2d = GetComponent<Rigidbody2D>();
        Invoke("GoBall", 2);
    }

ResetBall() i RestartGame() són dues funcions utilitzades per altres scripts que més endavant escriurem. ResetBall() s’utilitza quan es compleix la condició de victòria. Atura la pilota i restableix la seva posició al centre del tauler.

    void ResetBall()
    {
        rb2d.velocity = new Vector2(0, 0);
        transform.position = Vector2.zero;
    }

RestartGame() s’utilitza quan es prem el nostre botó de reinici. Més endavant afegirem aquest botó. Aquesta funció utilitza ResetBall() per centrar la pilota sobre el tauler. Aleshores s’utilitza Invoke() per esperar 1 segon, i després torna a començar la bola movent-se.

    void RestartGame()
    {
        ResetBall();
        Invoke("GoBall", 1);
    }

OnCollisionEnter2D() espera fins que xoquem amb una paleta, i després ajusta la velocitat adequadament utilitzant tant la velocitat de la bola com la de la paleta.

    void OnCollisionEnter2D(Collision2D coll)
    {
        if (coll.collider.CompareTag("Player"))
        {
            Vector2 vel;
            vel.x = rb2d.velocity.x;
            vel.y = (rb2d.velocity.y / 2.0f) + (coll.collider.attachedRigidbody.velocity.y / 3.0f);
            rb2d.velocity = vel;
        }
    }

Resumint, ara tenim dues paletes que funcionen, i ara una bola que rebotarà de forma realista. L’script complet que acabem d’afegir hauria de ser així: 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BallControl : MonoBehaviour
{

    private Rigidbody2D rb2d;

    void GoBall()
    {
        float rand = Random.Range(0, 2);
        if (rand < 1)
        {
            rb2d.AddForce(new Vector2(20, -15));
        }
        else
        {
            rb2d.AddForce(new Vector2(-20, -15));
        }
    }

    // Use this for initialization
    void Start()
    {
        rb2d = GetComponent<Rigidbody2D>();
        Invoke("GoBall", 2);
    }

    void ResetBall()
    {
        rb2d.velocity = new Vector2(0, 0);
        transform.position = Vector2.zero;
    }

    void RestartGame()
    {
        ResetBall();
        Invoke("GoBall", 1);
    }

    void OnCollisionEnter2D(Collision2D coll)
    {
        if (coll.collider.CompareTag("Player"))
        {
            Vector2 vel;
            vel.x = rb2d.velocity.x;
            vel.y = (rb2d.velocity.y / 2.0f) + (coll.collider.attachedRigidbody.velocity.y / 3.0f);
            rb2d.velocity = vel;
        }
    }

}

Aquí teniu el codi sencer per descarregar BallControl.cs

Passa 4: Les parets

Potser ja us heu adonat que la vostra bolla pot volar per la pantalla, però hem de fer unes parets.

Primer fem un objecte de joc que contingui les nostres parets. Assegureu-vos que no s’hagi seleccionat res al plafó de Jerarquia i, a continuació, feu clic amb el botó dret a un espai buit de la Jerarquia i trieu Create Empty. Es crearà un objecte de joc buit simple, que anomenarem Walls. No hem de canviar res, excepte per assegurar-nos que tingui una posició de (0,0,0).

Ara a fer un mur real. Al plafó de Jerarquia, feu clic botó dret a sobre de l’objecte Walls que acabem de fer i escollim Create Empty. Això farà que un objecte de joc nou sigui un “fill” de l’objecte Walls. Per als nostres propòsits, tenir les parets com a fills de “Walls” ens ajudarà a mantenir la jerarquia agradable i clara, ja que podem fer clic a la petita fletxa al costat de Walls sempre que no vulguem veure cada objecte de paret individual. La idea d’objectes de joc fills és realment molt potent i fa molt més que simplement organitzar coses, però no hi aprofundirem gaire en aquest tutorial.

En qualsevol cas, anomeneu a aquest nou objecte secundari RightWall. Assegureu-vos també que aneu a Afegir component i afegiu un Box Collider 2D. Així la pilota rebotarà de l’objecte de la paret, tal com volem.

Ara dupliqueu el nou objecte de paret tres vegades. Anomeneu a aquests duplicats LeftWall, TopWall i BottomWall. Ara necessitem moure i dimensionar les parets perquè estiguin al lloc correcte de la vista del joc. Ara mateix, són només punts que queden al centre de la taula. Hem de fer que semblin parets.

Per al RightWall, configureu la posició (5.8, 0, 0) i la seva escala a (1, 6, 1). Això ho farà agradable i alt. El LeftWall és el mateix, excepte la posició X que és -5.8. (Els números poden ser diferents al vostre ordinador, bàsicament, només assegureu-vos que la pantalla tingui la imatge de pantalla següent, amb les parets dels extrems de cada costat de la vista de la càmera.)

TopWall s’ha de situar a (0, 3.5, 0) amb una escala de (10.7, 1, 1), i BottomWall té la mateixa escala però amb una posició Y de -3,5. Si feu clic a l’objecte “Walls” hauríeu de veure-ho així a la vista Escena:

En aquest punt la pilota rebota però no és el que volíem. És un bon moment per explicar la física que aporta Unity. 

Ara mateix Unity està simulant una bolla amb una massa de 1, una rotació angular de 0.05 i una escala de gravetat de 1. Per això veieu una cosa semblant a això:

En definitiva està simulant una bolla que rebota simulant la vida real. Com que per aquest joc no ho volem així haurem de modificar el paràmetres de manera posem massa a 0.1, angular drag a 0 i Gravity Scale a 0

Si ara tornem a prova el mode de joc veurem com si ja surt com volíem.

A continuació ve una part important, però lleugerament més dura. Hem de fer que aquest sigui un joc real, no només una pilota rebotant. Necessitem un sistema de puntuació, una manera de mostrar la puntuació, alguna condició de victòria i un botó de restabliment.

Passa 5: La interfície d’usuari de puntuació

Primer el que necessitem per a la nostra HUD (Heads-up display) és un objecte de joc. Feu clic amb el botó dret sobre un espai buit al plafó Jerarquia (no a l’objecte del joc de parets!) I trieu Crear buit. Anomeneu al nou objecte HUD, centra la seva posició a (0, 0, 0) i afegiu un nou script a l’objecte HUD anomenat GameManager.

Fixeu-vos que ara l’script té una icona diferent. Serà un script bastant llarg, però és important per al nostre joc.

Descripció del codi

Primer, com sempre, importem els nostres paquets i declarem la nostra classe.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GameManager : MonoBehaviour {

A continuació, fem quatre variables. Les dues primeres variables són només nombres enters per fer un seguiment de les puntuacions dels dos jugadors. El següent és un objecte GUI (interfície gràfica d’usuari). Aquest objecte serà l’encarregat de mostrar tots els nostres diferents botons i gràfics. Crearem aquest objecte de tipus Skin a Unity. L’última variable és una referència al nostre objecte de bolla.

    public static int PlayerScore1 = 0;
    public static int PlayerScore2 = 0;

    public GUISkin layout;

    GameObject theBall;

A continuació ve la funció Start() que com ja hem dit abans, farem servir quan comença el joc per primera vegada.

    void Start () {
        theBall = GameObject.FindGameObjectWithTag ("Ball");
    }

A continuació es mostra la funció Score (). Cridarà un altre script que farem tot d’una, que detecta quan la pilota colpeja a les parets laterals.

    public static void Score(string wallID) {
        if (wallID == "RightWall") {
            PlayerScore1++;
        } else {
            PlayerScore2++;
        }
    }

La funció OnGUI () s’ocupa de mostrar la puntuació i la funcionalitat del botó de restabliment. També comprova si cada vegada que succeeix alguna cosa, si algú ja ha guanyat i en aquest cas invoca la funció  ResetBall ().

    void OnGUI() {
        GUI.skin = layout;
        GUI.Label (new Rect (Screen.width / 2 - 150 - 12, 20, 100, 100), "" + PlayerScore1);
        GUI.Label (new Rect (Screen.width / 2 + 150 + 12, 20, 100, 100), "" + PlayerScore2);

        if (GUI.Button (new Rect (Screen.width / 2 - 60, 35, 120, 53), "RESTART")) {
            PlayerScore1 = 0;
            PlayerScore2 = 0;
            theBall.SendMessage ("RestartGame", 0.5f, SendMessageOptions.RequireReceiver);
        }

        if (PlayerScore1 == 10) {
            GUI.Label (new Rect (Screen.width / 2 - 150, 200, 2000, 1000), "PLAYER ONE WINS");
            theBall.SendMessage ("ResetBall", null, SendMessageOptions.RequireReceiver);
        } else if (PlayerScore2 == 10) {
            GUI.Label (new Rect (Screen.width / 2 - 150, 200, 2000, 1000), "PLAYER TWO WINS");
            theBall.SendMessage ("ResetBall", null, SendMessageOptions.RequireReceiver);
        }
    }

“SendMessage” és una cosa que hem estat utilitzant molt en aquest fragment de codi. Aquesta invocació el que farà serà activarà qualsevol funció que coincideixi amb el nom que li enviem en una classe que especifiquem. Així doncs, quan diem theBall.SendMessage (“ResetBall”), diem al programa que tingui accés a la classe “BallControl” i desencadeni el mètode ResetBall (). Aquí teniu l’script complet

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GameManager : MonoBehaviour {

    public static int PlayerScore1 = 0;
    public static int PlayerScore2 = 0;

    public GUISkin layout;

    GameObject theBall;

    // Use this for initialization
    void Start () {
        theBall = GameObject.FindGameObjectWithTag ("Ball");
    }

    public static void Score(string wallID) {
        if (wallID == "RightWall") {
            PlayerScore1++;
        } else {
            PlayerScore2++;
        }
    }

    void OnGUI() {
        GUI.skin = layout;
        GUI.Label (new Rect (Screen.width / 2 - 150 - 12, 20, 100, 100), "" + PlayerScore1);
        GUI.Label (new Rect (Screen.width / 2 + 150 + 12, 20, 100, 100), "" + PlayerScore2);

        if (GUI.Button (new Rect (Screen.width / 2 - 60, 35, 120, 53), "RESTART")) {
            PlayerScore1 = 0;
            PlayerScore2 = 0;
            theBall.SendMessage ("RestartGame", 0.5f, SendMessageOptions.RequireReceiver);
        }

        if (PlayerScore1 == 10) {
            GUI.Label (new Rect (Screen.width / 2 - 150, 200, 2000, 1000), "PLAYER ONE WINS");
            theBall.SendMessage ("ResetBall", null, SendMessageOptions.RequireReceiver);
        } else if (PlayerScore2 == 10) {
            GUI.Label (new Rect (Screen.width / 2 - 150, 200, 2000, 1000), "PLAYER TWO WINS");
            theBall.SendMessage ("ResetBall", null, SendMessageOptions.RequireReceiver);
        }
    }

}

Aquí teniu el code sencer per descarregar GameManager.cs

Si miram al HUD, veurem una variable que no hem utilitzat es tracta del skin. Però ara hem de fer-ho a Unity. Si busqueu la carpeta d’actius, haureu de veure un fitxer que hem descarregat que és un tipus de lletra especial anomenat “6809 Chargen”.

Al plafó del projecte, feu clic amb el botó dret i creeu una GUI Skin anomenada ScoreSkin. Feu clic a aquest skin i hauríeu de veure un camp variable anomenat “Font” a la part superior del quadre de l’Inspector. Feu clic a “+” i arrossegueu el nostre tipus de lletra a aquesta ranura variable.

Si es desplaça cap avall i es mira els menús desplegables de “Etiqueta” i “Botó”, també es pot canviar la mida del text, etc. Juga al voltant amb la mida fins que quedi bé. Vaig definir les mides del tipus de lletra per a la meva etiqueta i botó a 42 i 20, respectivament. 

També haurem de seleccionar HUD a la jerarquia per modificar el layout dins del panell Inspector. Hem de seleccionar ScoreSkin.

Al final, el HUD’s Game Manager (Script) es veu així:

Ara ens assegurem que el joc sap quan marquem un punt. Per fer-ho, hem d’afegir un script als LeftWall i RightWall al desplegable HUD. És el mateix script per la qual cosa hauria de ser bastant fàcil de fer. Primer, anirem a Add Component> New script al LeftWall i posarem el nom d’aquest nou script SideWalls.

Descripció del codi

Després d’importar els nostres paquets, només haurem d’escriure una funció. Aquesta funció detecta quan alguna cosa xoca amb les nostres parets esquerra o dreta. Si es tracta de la pilota, cridem al mètode de puntuació a GameManager i restablim la pilota al centre.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SideWalls : MonoBehaviour
{

  void OnTriggerEnter2D(Collider2D hitInfo)
  {
      if (hitInfo.name == "Ball")
      {
          string wallName = transform.name;
          GameManager.Score(wallName);
          hitInfo.gameObject.SendMessage("RestartGame", 1, SendMessageOptions.RequireReceiver);
      }
  }
}

Aquí teniu el code sencer per descarregar SideWalls.cs

Ja heu afegit el script a LeftWall, i ara, ja que està escrit, aneu a Add Component> New script al RightWall. Seleccioneu l’script que acabem d’escriure. 

Ara, per tal que Unity invoqui al nostre mètode OnTriggerEnter2D, ens hem d’assegurar que tant a LeftWall com a RightWall hi hagi marcada la casella Is Trigger als seus Colliders Box en el panell Inspector. Això vol dir que Unity no tractarà aquestes parets com a parets físiques, sinó que “desencadenen” alguna cosa en el joc (en aquest cas, donen un punt al jugador).

Ara és el moment de prova si tot funciona.

Últim pas: Construir el joc

Ara, només hem de fer que el nostre joc es pugui jugar fora d’Unity. Per fer-ho, aneu a Fitxer al menú de la part superior. Vés a Build Settings i, a continuació, tria Mac, PC, Linux Standalone. Això farà que un fitxer executable (reproduïble) aparegui al nostre escriptori. És possible que hagueu de seleccionar Add Open Scenes per assegurar-vos que el main s’inclogui al Build.

Ara, feu clic a Player Settings. Aquí és on heu de posar el vostre nom al projecte, escollir una icona (he escollit el sprite de bolla) i seleccionar les desmarcar Default Is Full Screen. Demana l’amplada i l’altura de la pantalla per defecte. Podeu posar qualsevol cosa.

Introducció a Unity per Xavier Sastre

   CC BY-NC-SA 4.0          

Document basat, traduït i adaptat d’aquest