diff --git a/report.md b/report.md index 91ec484..07396c4 100644 --- a/report.md +++ b/report.md @@ -277,6 +277,321 @@ Inoltre, alla sua creazione si occupa di creare un grafo delle GameTile libere, per permettere loro di inseguire un certo target. ### Bittasi Francesco +#### GraphicsComponent +```mermaid +classDiagram + class GraphicsComponent { + <> + +getColor(): EngineColor + +getPositions(): List + +setColor(color: EngineColor): void + +setPositions(pos: List): void + +getType(): GraphicType + } + class GraphicType { + <> + WORLD + HUD + } + class EngineColor { + <> + +BLUE: EngineColor + +RED: EngineColor + +GREEN: EngineColor + +WHITE: EngineColor + +BLACK: EngineColor + +YELLOW: EngineColor + +getR(): int + +getG(): int + +getB(): int + } + GraphicsComponent --> GraphicType + GraphicsComponent --> EngineColor + + class PolygonGraphicsComponent { + <> + } + PolygonGraphicsComponent --|> GraphicsComponent + + class StatusBarGraphicsComponent { + <> + +setStatusColor(statusColor: EngineColor): void + +setDimension(base: int, height: int): void + +setRange(min : int , max : int) : void + +setStatus(i: int): void + +getStatusColor(): EngineColor + +getBase(): int + +getHeight(): int + +getMax(): int + +getMin(): int + +getStatus(): int + +getPercentage(): double + } + StatusBarGraphicsComponent --|> GraphicsComponent + + class TextGraphicsComponent { + <> + +getText(): String + +setText(text: String): void + +setSize(size: int): void + +getSize(): int + } + TextGraphicsComponent --|> GraphicsComponent +``` +**Problema:** Creare oggetti che rappresentino l'aspetto grafico di elementi del modello del gioco, in maniera indipendente dalla tecnologia della View. + +**Soluzione:** L'interfaccia GraphicsComponent rappresenta un componente grafico generico, questa può essere specializzao in più tipi: Testo, Poligono, Barra di stato, etc. +- Il model dell'applicazione si occupa di creare e aggiornare questi oggetti per dire che aspetto devono assumere gli elementi del gioco. +- Avendo definito tutte queste proprietà, tra cui i colori e che tipo di elemento grafico sono (se parte del mondo di gioco o della HUD), questi componenti grafici sono totalmente indipendenti dalla tecnologia di View adottata. + +```mermaid +classDiagram + class RenderableGraphicComponent { + <> + +getRenderer(): GraphicsComponentRenderer + } + RenderableGraphicComponent --|> GraphicsComponent + + class GraphicsComponentRenderer { + <> + +render(g: Graphics2D, camera: ViewCamera): void + +setComponent(gComp: GraphicsComponent): void + } + RenderableGraphicComponent *-- GraphicsComponentRenderer + GraphicsComponentRenderer *-- GraphicsComponent + class GraphicsComponent { + <> + } +``` +**Problema:** Permettere alla view di leggere questi componenti grafici e rappresentarli a schermo. + +**Soluzione:** Per facilitare la view nell'interpretare questi oggetti è stata definita un'interfaccia che estende GraphicsComponent e che permette di contenere un oggetto che si occupa di renderizzare questi componenti grafici, specifico per la tecnologia usata. +- Il GraphicsComponentRenderer è dunque unico per Swing: è legato ad un solo componente grafico e alla chiamata del metodo `render` lo stampa a schermo avendo il riferimento alla Camera e all'oggetto Graphics2D. +- Per l'implementazione della view alla fine i componenti grafici che le vengono passati si comportano similmente al pattern Command: ottenuto il set di Componenti grafici filtra quelli che è capace di interpretare (chi implementa RenderableGraphicComponent) e li stampa a schermo chiamando il metodo `render`. + +```mermaid +classDiagram + class GraphicsComponentsFactory { + <> + +polygon(color: EngineColor, pos: List, type: GraphicType): PolygonGraphicsComponent + +text(color: EngineColor, pos: Position2D, size: int, text: String, type: GraphicType): TextGraphicsComponent + +statusBar(bgColor: EngineColor, statusColor: EngineColor, pos: Position2D, base: int, height: int, type: GraphicType): StatusBarGraphicsComponent + } + GraphicsComponentsFactory --> TextGraphicsComponent + GraphicsComponentsFactory --> PolygonGraphicsComponent + GraphicsComponentsFactory --> StatusBarGraphicsComponent + GraphicsComponentsFactory --> RenderableGraphicComponent + + class RenderableGraphicComponent { + <> + } + + class PolygonGraphicsComponent { + <> + } + + class StatusBarGraphicsComponent { + <> + } + + class TextGraphicsComponent { + <> + } +``` + +**Problema:** creare facilmente i componenti grafici. + +**Soluzione:** Per permettere al Model dell'applicazione di creare i componenti grafici senza dover sapere dell'associazione di questi coi GraphicsComponentRenderer è stata creata una factory che restituisse il tipo di componente grafico richiesto con già incorporato il renderer della view necessario per permetterne la stampa. + +#### Camera +```mermaid +classDiagram + class ViewCamera { + <> + +getScreenPosition(pos: Position2D, type: GraphicType): Position2D + +getScreenPositions(pos: List, type: GraphicType): List + +setScreenDimension(width: double, height: double): void + +getHoriOffset(): double + +getVertOffset(): double + +getSizeRatio(): double + } + + class WorldCamera { + <> + +moveTo(position: Position2D): void + +getWorldPosition(pos: Position2D): Position2D + +centerOn(position: Position2D): void + +setArea(height: int, width: int): void + +keepInArea(position: Position2D): void + } + + class CameraMover { + <> + +installCamera(camera: WorldCamera): void + } + CameraMover --> WorldCamera + + class CameraViewer { + <> + +installCamera(camera: ViewCamera): void + } + CameraViewer --> ViewCamera + + class Camera { + <> + } + Camera --|> WorldCamera + Camera --|> ViewCamera + + CameraImpl --|> Camera + EngineImpl *-- CameraImpl + + World --|> CameraMover + World *-- CameraImpl + ViewImpl *-- CameraImpl + ViewImpl --|> CameraViewer +``` +**Problema:** Sullo schermo va visualizzata solo una parte del mondo di gioco, deve mantere le proporzioni corrette per qualunque formato di schermo e la porzione visibile deve essere scelta dal Model dell'applicazione. + +**Soluzione:** L'engine si occupa di creare un oggetto (nel progetto la classe "CameraImpl") che si occupa di effettuare i calcoli di conversione da coordinate del mondo a coordinate sullo schermo e viceversa. L'engine darà il riferimento a questo oggetto al World e all'EngineView che lo interfacceranno tramite delle interfacce specifiche per quello che ci possono fare: +- Il world ha accesso ai metodi che gli permettono di spostare la telecamera e di ottenere le coordinate del mondo a partire da quelle sullo schermo (nel caso di quando si riceve input dal puntatore). +- L'EngineView ha accesso ai metodi di conversione delle coordinate da world a schermo, metodi per informare la telecamera delle dimensioni della finestra e per ottenere informazioni sulle proporzioni della schermata di gioco (per poter generare bande nere a bordo schermo se necessario). + - al momento della stampa l'EngineView passa il riferimento della ViewCamera ai GraphicsComponentRenderer che si occupano della effettiva stampa a schermo. + +Non è stato adottato alcun design pattern particolare se non lo Strategy: le diverse interfacce definiscono infatti una classe Camera generica che può avere l'implementazione che vogliamo, quella adottata nel nostro gioco è una implementazione standard, ma potremmo adottarne una che segue una logica diversa andando a distorcere le cose visualizzate a schermo senza dover modificare in alcun modo le classi che usano la telecamera. + +A differenza della view, potrebbero esserci più oggetti che si occupano di muovere la telecamera, per questo motivo è stato definita anche l'interfaccia di chi può farlo (CameraMover) per poter ricevere il riferimento alla telecamera. + +#### Input +```mermaid +classDiagram + class InputEventListener { + <> + +notifyInputEvent(event: InputEvent): void + } + class InputEventProvider { + <> + +setInputEventListener(listener: InputEventListener): void + +setInputEventFactory(factory: InputEventFactory): void + } + ViewImpl *-- EngineImpl + + EngineImpl --|> InputEventListener + ViewImpl --|> InputEventProvider +``` +**Problema:** Notificare il controller delle azioni dell'utente sulle periferiche. + +**Soluzione:** Usando il pattern Observer abbiamo reso l'implementazione del controller un osservatore di "InputEvent". La view è il provider, il creatore di questi InputEvent e si occupa di notificare il controller quando uno di questi avviene. + +```mermaid +classDiagram + ViewImpl *-- InputEventFactoryImpl + ViewImpl --|> InputEventProvider + class InputEventProvider { + <> + } + InputEventProvider --> InputEventFactory + InputEventFactoryImpl --|> InputEventFactory + class InputEventFactory { + <> + +filterKeyValue(key: int): Optional + +pressedValue(value: Value): InputEvent + +releasedValue(value: Value): InputEvent + +mouseDownAtPosition(position: Position2D): InputEvent + +mouseUpAtPosition(position: Position2D): InputEvent + } + InputEventFactory --> InputEvent + + class InputEvent { + <> + +getType(): Type + } + class Type { + <> + ACTIVE + INACTIVE + } + InputEvent *-- Type + + class InputEventPointer { + <> + +getPosition(): Position2D + } + InputEventPointer --|> InputEvent + + class InputEventValue { + <> + +getValue(): Value + } + class Value { + <> + UP + DOWN + LEFT + RIGHT + RESET + PAUSE + } + InputEventValue *-- Value + InputEventValue --|> InputEvent +``` +**Problema:** Creare una classe di oggetti che rappresenti gli eventi di input indipendentemente dalla tecnologia di View adottata. + +**Soluzione:** L'interfaccia InputEvent rappresenta un evento di Input che può essere ricevuto da un InputEventListener. Questa interfaccia viene poi estesa in tipologie secondarie che vanno a specificare un tipo particolare di input come quelle di un puntatore o di un determinato valore. +- Per permettere al provider di generare questi oggetti facilmente abbiamo creato una Factory di eventi che gli viene fornita. +- E' grazie al metodo `filterKeyValue` della factory che i tasti della tastiera vengono convertiti in valori che rappresentano un'azione di gioco. +- Sarà compito del listener filtrare i vari input che riceve per vedere cosa gli interessa e agire di conseguenza. + +#### Weapon +```mermaid +classDiagram + class Weapon { + <> + +setParentEntity(parent: Character): void + +attack(): void + } + + class WeaponFactory { + <> + +pair(weapon: Weapon, character: Character): void + +simpleGun(reloadTime: long, shootSpeed: long, projectileSize: double, damage: int, projectileFactory: EntityFactory): Weapon + +simpleGunPairing(reloadTime: long, shootSpeed: long, projectileSize: double, damage: int, projectileFactory: EntityFactory, character: Character): Weapon + } + WeaponFactory --> Weapon +``` + +**Problema:** Creare un'arma che possa essere facilmente associata ad un unico personaggio. + +**Soluzione:** Una Factory di Weapon fornisce metodi per creare facilmente il tipo di arma desiderato e per associarla ad un personaggio. La armi implementano l'interfaccia Weapon che identifica un tipo di arma generico (pattern Strategy), questo permette di creare armi con comportamenti diversi senza dover modificare il codice che le utilizza (questo non è stato fatto nel progetto solo per questione di tempo): basta creare nuove implementazioni di Weapon e aggiungere i metodi nella factory. + +```mermaid +classDiagram + class EntityFactory { + <> + +createProjectile(team: Character.CharacterType, position: Position2D, direction: Vect, projectileSize: double, damage: int): Projectile + } + EntityFactory --> Projectile + + SimpleGun *-- EntityFactory + SimpleGun --|> Weapon + + class Projectile { + <> + +setDamage(damage: int): void + } + class ActiveEntity { + <> + } + Projectile --|> ActiveEntity + class Weapon { + <> + } +``` + +**Problema:** Creare un'arma che possa sparare dei proiettili. + +**Soluzione:** SimpleGun implementa l'interfaccia Weapon e fa riferimento ad una EntityFactory per generare nuovi proiettili; questi infatti sono delle ActiveEntiy, entità che si possono muovere e collidere con altre. SimpleGun alla creazione accetta parametri per impostare la velocità di sparo, la dimensione dei proiettili, la loro velocità e danno. Dunque solo da SimpleGun si potrebbero creare diversi tipi di armi. + ### Marchi Luca ### Monaco Andrea # Capitolo 3 - Sviluppo @@ -298,6 +613,24 @@ Usati molto frequentemente, due esempi: Un esempio: https://github.com/frabitta/OOP23-gfight/blob/1f46024e7a794b0b54e1317836fbd691eb322ea7/src/main/java/gfight/view/impl/MenuPanel.java#L95 ### Bittasi Francesco +#### Utilizzo di Stream +Adottate frequentemente per la gestione di elenchi di dati: +- https://github.com/frabitta/OOP23-gfight/blob/fb4012549a225ac1774cfe5618e629f0fbe8a238/src/main/java/gfight/engine/input/impl/InputEventFactoryImpl.java#L18 +- paint in canvas +- PolygonGraphicsRenderer + +#### Utilizzo di Optional +Usati per convertire i tasti premuti in possibili Value: +- https://github.com/frabitta/OOP23-gfight/blob/fb4012549a225ac1774cfe5618e629f0fbe8a238/src/main/java/gfight/engine/input/impl/InputEventFactoryImpl.java#L18 + +#### Sincronizzazioni dei thread +nella classe EngineImpl +- Semaphore per input: processInput, notifyInputEvent +- sincronizzazione tramite wait e notify per gli stati dell'engine: holdPageUntilNotified, changeStatus + +#### Game as a lab +Ho preso ispirazione dal codice di Game as a lab per la base dell'engine e della view, in particolare per la scrittura della classe `Canvas`. + ### Marchi Luca ### Monaco Andrea # Capitolo 4 - Commenti finali @@ -309,4 +642,10 @@ tra di noi e ad affrontare i problemi insieme per trovare le migliori soluzioni. la sfera personale, penso di aver fatto il mio meglio ed essermi impegnato per quanto riuscissi e sono soddisfatto di ciò che ho realizzato. Per quanto ci siano delle parti di codice un po' macchinose, come la lettura della mappa da file, o il salvataggio delle statistiche, credo di essere riuscito a sviluppare codice riutilizzabile e facilmente manutenibile. +### Bittasi Francesco +Sono abbastanza soddisfatto di quanto abbiamo prodotto: le scelte di design che abbiamo fatto ci hanno permesso di apportare numerose modifiche in corso d'opera, ma queste aggiunte/cambiamenti sono stati facili da implementare e non hanno mai rotto il resto del codice: segno che il design fatto a priori era efficace per i nostri obiettivi. +L'unione delle diverse parti di progetto svolte individualmente è stata immediata e senza problemi, potremmo anche implementare nuove feature nel gioco senza modificare il resto del codice: armi, tipi di giocatori e nemici diversi, grafica, etc. +Ci sono diversi casi in cui avrei potuto applicare design Pattern che non ho applicato o seguire tecniche di programmazione java più avanzate, ad esempio avrei potuto pensare di seguire il Decorator per creare le sottocategorie di input ed elementi grafici. +Avevo come parte del progetto una componente fondamentale per permetterne un funzionamento basilare: la gestione della grafica e degli input. Sono due elementi che portano molta soddisfazione da ragionare e implementare in quanto danno un immediato riscontro visivo; guardando indietro avrei potuto cedere l'implementazione delle armi ad un altro membro del gruppo per bilanciare meglio il carico di lavoro visto che anche solo quelle due hanno impiegato gran parte del tempo che ho dedicato al progetto. +Non credo svilupperemo ancora questo gioco, ma è possibile che nel tempo libero io prenda l'engine e la view di questo per sviluppare un gioco diverso. ## 4.2 Difficoltà incontrate e commenti per i docenti