====== Bruger af Resources & Nedarvning ======
===== Introduktion =====
I Part 1 lærte du at bruge Resources til at beskrive forskellige våben. Vi oprettede en klasse, ''WeaponData'', og brugte den til at lave et sværd, en kniv og et spyd.
Men hvad sker der, når vores spil får flere typer genstande?
Forestil dig, at spilleren skal kunne finde og opbevare:
* Våben: sværd, knive og spyd.
* Potions: healing potions og mana potions.
* Rustninger: hjelme og brystplader.
* Andre genstande: nøgler, mønter og quest items.
Alle disse genstande er forskellige, __men de har også noget til fælles__. De kan eksempelvis alle have et navn, et ikon og en beskrivelse.
Hvordan kan vi organisere vores kode, så vi ikke skal gentage de samme egenskaber i alle vores klasser?
Her skal vi bruge **nedarvning**.
Når du er færdig med denne guide, skal du kunne:
* Forklare, hvad nedarvning er.
* Oprette en fælles ''Item''-klasse.
* Oprette specialiserede item-klasser, der nedarver fra ''Item''.
* Oprette forskellige Items som Godot Resources.
* Forklare, hvorfor nedarvning gør det lettere at lave et inventory.
----
===== 1. Problemet: Forskellige typer items =====
Forestil dig, at vi opretter en klasse til hver type item.
WeaponItem
WeaponName
Icon
Description
Damage
Range
PotionItem
PotionName
Icon
Description
HealAmount
ArmorItem
ArmorName
Icon
Description
Defense
Der er en del gentagelser. Alle tre klasser har eksempelvis et navn, et ikon og en beskrivelse.
Hvis vi senere vil tilføje en vægt eller en værdi i guld, skal vi ændre flere klasser.
Det er uhensigtsmæssigt, fordi vi gentager den samme struktur flere steder.
Vi vil i stedet samle de fælles egenskaber i én klasse og lade de specialiserede klasser arve dem.
----
===== 2. Hvad er nedarvning? =====
Nedarvning er et princip inden for objektorienteret programmering (OOP).
En klasse kan arve egenskaber og metoder fra en anden klasse.
Vi kalder klassen, der nedarves fra, for **basisklassen** eller **superklassen**. Klassen, der arver, kaldes en **subklasse**.
I vores spil skal ''Item'' være basisklassen.
Item
|
+-------+-------+
| | |
Weapon Potion Armor
Alle tre subklasser arver de fælles egenskaber fra ''Item''.
Derudover kan hver subklasse have sine egne egenskaber.
* ''WeaponItem'' har eksempelvis skade og rækkevidde.
* ''PotionItem'' har eksempelvis en mængde healing.
* ''ArmorItem'' har eksempelvis en forsvarsværdi.
Det betyder, at vi kun behøver at definere de fælles egenskaber ét sted.
----
===== 3. Opret basisklassen ItemData =====
Vi skal stadig bruge Godot Resources, ligesom vi gjorde i Part 1.
Opret et C#-script med navnet ''Item.cs''.
using Godot;
[GlobalClass]
public partial class Item : Resource
{
[Export]
public string ItemName { get; set; } = "New Item";
[Export(PropertyHint.MultilineText)]
public string Description { get; set; } = "";
[Export]
public Texture2D Icon { get; set; }
}
----
==== Hvad indeholder Item? ====
Klassen indeholder tre egenskaber, som alle vores items har brug for:
^ Egenskab ^ Formål ^
| ''ItemName'' | Genstandens navn. |
| ''Description'' | En beskrivelse af genstanden. |
| ''Icon'' | Et ikon, der kan vises i inventory. |
Bemærk, at ''Item'' arver fra ''Resource''. Det betyder, at vi kan oprette og gemme konkrete items som ''.tres''-filer.
Vi har nu defineret de fælles egenskaber for alle items i spillet.
**Vigtigt:** ''Item'' er vores fælles basisklasse. Den beskriver, hvad alle items har til fælles, men ikke hvad der gør et våben forskelligt fra en potion.
----
===== 4. Opret WeaponItem =====
Nu skal vi oprette en specialiseret klasse til våben.
Opret et nyt C#-script med navnet ''WeaponItem.cs''.
using Godot;
[GlobalClass]
public partial class WeaponItem : Item
{
[Export]
public int Damage { get; set; } = 10;
[Export]
public int Range { get; set; } = 40;
}
Læg mærke til denne linje:
public partial class WeaponItem : Item
Her fortæller vi, at ''WeaponItem'' nedarver fra ''Item''.
Det betyder, at ''WeaponItem'' automatisk får adgang til de egenskaber, vi har defineret i basisklassen:
* ''ItemName''
* ''Description''
* ''Icon''
Vi behøver altså ikke at skrive dem igen.
Samtidig tilføjer vi to nye egenskaber, der er særlige for våben:
* ''Damage''
* ''Range''
Vi har nu en klasse, der beskriver både de generelle egenskaber ved et item og de særlige egenskaber ved et våben.
----
===== 5. Opret PotionItem =====
Nu skal vi oprette en klasse til potions.
Opret et nyt C#-script med navnet ''PotionItem.cs''.
using Godot;
[GlobalClass]
public partial class PotionItem : Item
{
[Export]
public int HealAmount { get; set; } = 25;
}
Denne klasse arver også fra ''Item''.
En potion har derfor automatisk et navn, en beskrivelse og et ikon.
Derudover har den sin egen egenskab:
public int HealAmount { get; set; } = 25;
''HealAmount'' beskriver, hvor mange HP potionen kan genoprette.
Vi behøver ikke at tilføje ''Damage'' eller ''Range'', fordi de egenskaber hører til våben og ikke til potions.
----
===== 6. Opret ArmorItem =====
Lad os også oprette en klasse til rustninger.
Opret ''ArmorItem.cs''.
using Godot;
[GlobalClass]
public partial class ArmorItem : Item
{
[Export]
public int Defense { get; set; } = 5;
}
''ArmorItem'' arver de fælles egenskaber fra ''Item'' og tilføjer sin egen egenskab:
public int Defense { get; set; } = 5;
Nu har vi tre specialiserede item-klasser, der alle bygger på den samme basisklasse.
----
===== 7. Opret konkrete Items i Godot =====
Nu skal vi bruge klasserne til at oprette konkrete items.
Kompilér projektet, så Godot registrerer de nye klasser.
Opret derefter følgende Resources i Godot-editoren:
^ Ressourcefil ^ Type ^ Navn ^ Særlig egenskab ^
| ''Sword.tres'' | ''WeaponItem'' | Sword | Damage: 20 |
| ''Knife.tres'' | ''WeaponItem'' | Knife | Damage: 8 |
| ''HealthPotion.tres'' | ''PotionItem'' | Health Potion | HealAmount: 25 |
| ''IronArmor.tres'' | ''ArmorItem'' | Iron Armor | Defense: 10 |
Når du opretter en ressource af typen ''WeaponItem'', kan du både se de fælles egenskaber fra ''Item'' og de særlige egenskaber fra ''WeaponItem'' i Inspector.
For ''PotionItem'' får du i stedet adgang til ''HealAmount''.
Det er nedarvning i praksis: Subklassen genbruger basisklassens egenskaber og tilføjer sine egne.
----
===== 8. Hvorfor er det smart for et inventory? =====
Forestil dig, at du skal lave et inventory, der kan indeholde alle fire ressourcefiler.
Hvis inventory kun accepterede ''WeaponItem'', ville du få problemer, når spilleren også skulle kunne opbevare potions og rustninger.
Men alle vores klasser nedarver fra ''Item''.
Derfor kan vi arbejde med dem gennem den fælles type ''Item''.
Eksempelvis kan vi have en liste med Items:
using Godot;
using System.Collections.Generic;
public partial class InventoryExample : Node
{
private readonly List- _items = new();
public void AddItem(Item item)
{
_items.Add(item);
GD.Print($"Tilføjet: {item.ItemName}");
}
}
Denne klasse er kun et lille eksempel på, hvordan et inventory kan modtage items. Den er ikke et komplet inventorysystem.
Metoden ''AddItem'' modtager en parameter af typen ''Item''.
Det betyder, at metoden kan modtage:
AddItem(sword);
AddItem(healthPotion);
AddItem(ironArmor);
Forudsat at variablerne refererer til de tilsvarende ressourceobjekter, accepteres alle tre typer, fordi de er subklasser af ''Item''.
Vi behøver ikke en separat metode til våben, potions og rustninger.
**Det er en af de store fordele ved nedarvning:** Vi kan behandle forskellige objekter ens, når de har en fælles basisklasse.
----
===== 9. Øvelse: Byg dit eget Item-system =====
Nu skal du selv udvide systemet.
==== Opgave A: Opret klassestrukturen ====
- Opret ''Item.cs'' med navn, beskrivelse og ikon.
- Opret ''WeaponItem.cs'', der nedarver fra ''Item''.
- Opret ''PotionItem.cs'', der nedarver fra ''Item''.
- Opret ''ArmorItem.cs'', der nedarver fra ''Item''.
- Kompilér projektet, og kontrollér, at klasserne kan bruges i Godot-editoren.
==== Opgave B: Opret konkrete items ====
Opret mindst fire ressourcefiler:
- Et sværd.
- En kniv.
- En healing potion.
- En rustning.
Giv hvert item et navn, en beskrivelse og et ikon. Udfyld også de særlige egenskaber for den relevante type.
----
==== Opgave C: Brug den fælles type ====
Tag udgangspunkt i ''InventoryExample'' fra afsnit 8.
Opret en metode, der kan modtage et ''Item'' og udskrive dets navn.
Undersøg, om du kan sende både et ''WeaponItem'', et ''PotionItem'' og et ''ArmorItem'' til metoden.
Du skal ikke lave et grafisk inventory endnu. Fokus er på klassestrukturen og nedarvning.
----
==== Opgave D: Udvid systemet ====
Opret en ny klasse, ''QuestItem'', der nedarver fra ''Item''.
Giv den en særlig egenskab, der passer til quest items.
Opret derefter en ressourcefil til en nøgle, som spilleren kan finde i spillet.
Du skal ikke ændre basisklassen ''Item'' for at tilføje de særlige egenskaber til ''QuestItem''.
----
===== 10. Kontrolspørgsmål =====
Besvar spørgsmålene med dine egne ord.
- Hvad betyder nedarvning i objektorienteret programmering?
- Hvad er forskellen på en basisklasse og en subklasse?
- Hvorfor arver ''WeaponItem'' fra ''Item''?
- Hvilke egenskaber har alle vores items til fælles?
- Hvorfor har ''PotionItem'' en ''HealAmount'', mens ''WeaponItem'' har ''Damage''?
- Hvorfor kan en metode, der modtager et ''Item'', også modtage et ''PotionItem''?
- Hvilken fordel giver denne struktur, når vi senere skal udvikle et inventory?
----
===== Opsummering =====
I Part 1 lærte du at bruge Resources til at beskrive forskellige våben.
I Part 2 har du lært at bruge nedarvning til at organisere flere typer items.
Du har nu:
* Oprettet en fælles basisklasse, ''Item''.
* Oprettet subklasserne ''WeaponItem'', ''PotionItem'' og ''ArmorItem''.
* Genbrugt fælles egenskaber gennem nedarvning.
* Tilføjet særlige egenskaber til de enkelte item-typer.
* Set, hvordan et inventory kan modtage forskellige items gennem den fælles type ''Item''.
Det vigtigste er, at vi **samler fælles funktionalitet i basisklassen og placerer specialiserede egenskaber i subklasserne**.
Det giver os en struktur, som vi senere kan bygge videre på, når vi udvikler et rigtigt inventory med slots, stakke, opsamling og brug af items.