Indholdsfortegnelse

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:

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:


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.

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:

Vi behøver altså ikke at skrive dem igen.

Samtidig tilføjer vi to nye egenskaber, der er særlige for våben:

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<Item> _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

  1. Opret Item.cs med navn, beskrivelse og ikon.
  2. Opret WeaponItem.cs, der nedarver fra Item.
  3. Opret PotionItem.cs, der nedarver fra Item.
  4. Opret ArmorItem.cs, der nedarver fra Item.
  5. Kompilér projektet, og kontrollér, at klasserne kan bruges i Godot-editoren.

Opgave B: Opret konkrete items

Opret mindst fire ressourcefiler:

  1. Et sværd.
  2. En kniv.
  3. En healing potion.
  4. 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.

  1. Hvad betyder nedarvning i objektorienteret programmering?
  2. Hvad er forskellen på en basisklasse og en subklasse?
  3. Hvorfor arver WeaponItem fra Item?
  4. Hvilke egenskaber har alle vores items til fælles?
  5. Hvorfor har PotionItem en HealAmount, mens WeaponItem har Damage?
  6. Hvorfor kan en metode, der modtager et Item, også modtage et PotionItem?
  7. 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:

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.