Econeer

Tijdens Project Context II werkten we aan een game in opdracht van ROM Utrecht. De opdracht was om een game te ontwikkelen die gebaseerd is op een ecosysteem. Econeer is een survivalgame met een dynamisch ecosysteem, waarin de speler een bioloog speelt die een mysterieus eiland verkent. Het doel is om de unieke dieren van het eiland te ontdekken en te overleven, zonder het ecosysteem drastisch te verstoren. Het eiland reageert op jouw acties… Verzamel data, volg de dierpopulaties en probeer in harmonie met de natuur te overleven!


Project Info:

Teamleden: Jim Dekker, Mitchel Gereadts, Julius Kamphuis, Joey van Gool, Griffin Linckens & Emma Bout
Project tijd: HKU Jaar 2 Periode 3 (10-02-2025 t/m 28-03-2025)
Engine: Unity
Code Languages: C#
Design Patterns: Observer, Singleton & FlyWeight

GOAP (Goal-Oriented Action Planning) is een AI-systeem waarbij een agent een doel krijgt en zelfstandig bepaalt welke acties nodig zijn om dat doel te bereiken. In plaats van vaste gedragsregels, genereert GOAP dynamisch een plan door te kijken welke acties beschikbaar zijn en hoe deze op elkaar aansluiten. De AI begint bij het einddoel en werkt terug om een keten van acties te vormen die op elkaar aansluiten. Elke actie heeft een precondition die vervuld moet zijn (Dit kan met de vorige actie). De planner zoekt de efficiëntste route, maar houd hier rekening met de huidige situatie mee. GOAP is erg flexibel vergeleken met tradionele AI, omdat de agent zich kan aanpassen aan veranderende omstandigheden zonder er handmatig situaties voor te schrijven.

 public Queue<GAction> Plan(List<GAction> actions, Dictionary<string, int> goal, WorldStates beliefstates)
 {
     List<GAction> useableActions = new List<GAction>();
     foreach (GAction a in actions)
     {
         if (a.IsAchievable()) useableActions.Add(a);
     }

     List<Node> leaves = new List<Node>();
     Node start = new Node(null, 0, GWorld.Instance.GetWorld().GetStates(), beliefstates.GetStates(), null);

     bool succes = BuildGraph(start, leaves, useableActions, goal);

     if (!succes) return null;

     Node cheapest = null;

     foreach (Node leaf in leaves)
     {
         if (cheapest == null) cheapest = leaf;
         else
         {
             if (leaf.cost < cheapest.cost) cheapest = leaf;
         }
     }

     List<GAction> result = new List<GAction>();
     Node n = cheapest;

     while (n != null)
     {
         if (n.action != null)
         {
             result.Insert(0, n.action);
         }

         n = n.parent;
     }

     Queue<GAction> queue = new Queue<GAction>();
     result.ForEach(a => queue.Enqueue(a));

     return queue;
 }

Maakt een plan met alle actions die het dier toegewezen heeft gekregen. Deze probeert hij zo goedkoop mogelijk te maken.

Het animals script is de base class voor alle animals. De class beheert dingen zoals: Baby’s laten groeien, Voedsel vermindering en het beheren van zwangerschappen. Dieren zelf hebben nog de GOAP laag er boven op. In hun eigen type dier script worden de goals ingesteld die de verschillende dieren kunnen hebben. Goals zoals: Wander, Fed, FoundMate, HasBred & IsSafe. Er worden ook dynamisch goals toegevoegd als dat nodig is. Zo wordt Panic toegevoegd als een dier damage heeft gekregen.

    private void HandleReproduction()
    {
        timeSinceLastMating = Mathf.Max(timeSinceLastMating - TimeTickSystem.ANIMAL_TICK_RATE, 0);

        if (Sex == Sex.Male) EvaluateReproduction();

        if (IsPregnant) UpdatePregnancy();
    }

    private void EvaluateReproduction()
    {
        beliefs.RemoveState("canReproduce");

        if (IsBaby) return;
        if (timeSinceLastMating > 0) return;
        if (GetHungerPercentage() < 70) return;
        if (inventory.FindItemWithKey("partner") != null) return;
        if (currentAction != null && currentAction.GetType() == typeof(AFlee)) return;

        beliefs.SetState("canReproduce", 1);
    }

Het beheren van de Reproductie. Dit is het mannelijke deel waar hij de belief “canReproduce” krijgt. Nu kan hij via zijn GOAP een request maken aan een vrouwtje

De speler heeft een hotbar en de mogelijkheid voor een backpack. Momenteel is de backpack uitgeschakeld, wat betekent dat de speler alleen items kan opslaan in de hotbar. Het oorspronkelijke systeem was echter ontworpen om automatisch items in de backpack te plaatsen wanneer de hotbar vol is. Wanneer de speler met een object in de wereld interacteert, kan het systeem opvragen welk item de speler op dat moment in de hand heeft. Dit wordt bepaald door de inhoud van de hotbar. Als de speler een actie uitvoert waarvoor een specifiek item nodig is, zal het systeem controleren of dit item in de hotbar aanwezig is.

    public bool AddToInventory(InventoryItemData itemToAdd, int amountToAdd)
    {
        //Checks wheter Item exist in inventory.
        if (ContainsItem(itemToAdd, out List<InventorySlot> invSlot))
        {
            foreach (InventorySlot slot in invSlot)
            {
                if (slot.RoomLeftInStack(amountToAdd))
                {
                    slot.AddToStack(amountToAdd);
                    OnInventorySlotChanged?.Invoke(slot);
                    return true;
                }
            }
        }

        //Gets the first available slot.
        if (HasFreeSlot(out InventorySlot freeSlot))
        {
            freeSlot.UpdateInventorySlot(itemToAdd, amountToAdd);
            OnInventorySlotChanged?.Invoke(freeSlot);
            return true;
        }

        return false;
    }

    public bool ContainsItem(InventoryItemData itemToAdd, out List<InventorySlot> invSlot)
    {
        invSlot = inventorySlots.Where(i => i.ItemData == itemToAdd).ToList();

        return invSlot != null;
    }

    public bool HasFreeSlot(out InventorySlot freeSlot)
    {
        freeSlot = inventorySlots.FirstOrDefault(i => i.ItemData == null);
        return freeSlot != null;
    }

Het toevoegen van nieuwe items en het kijken naar beschikbare ruimte.