Galdr: Forgotten Songs

Category

Game

Duration


7 weeks

Project Size

13 Team members

Engine

Unity

Languages

C#

This game was a 7-week project made in Unity. My role was being both systems programmer and gameplay programmer on this project, responsible for implementing a system for handling all the interactables that the player can interact with, which includes items and puzzle mechanics.

private void Update() {
    if (this.interactablesInRange.Count > 0) {
        GameObject latestGameObject = this.interactablesInRange[^1];
        bool activeGameObject = true;

        if (this.hasActivatedOrderedButtons) {
            this.ShowInteractPrompt(InteractionType.BUTTON);
            this.hasActivatedOrderedButtons = false;
        }

        if (this.hasDeactivatedOrderedButtons) {
            Interactable latestAvailableInteractable = this.GetLastInteractableBySkippingType(
                typeof(Puzzle_ActivateButton));
            if (latestAvailableInteractable != null) {
                this.ShowInteractPrompt(latestAvailableInteractable);
            } else {  InteractPrompt.Instance.HideInteractPrompt();
            }
            this.hasDeactivatedOrderedButtons = false;
        }

        if (this.hasActivatedOneOrderedButton) {
            activeGameObject = false;
            this.hasActivatedOneOrderedButton = false;
        }

        if (this.hasActivatedClickButton) {
            activeGameObject = false;
            InteractPrompt.Instance.HideInteractPrompt();
            this.hasActivatedClickButton = false;
        }

        if (this.hasPickedUp) {
            this.hasPickedUp = false;
            activeGameObject = false;
            this.toBeDestroyed = true;
        }

        if (latestGameObject == null) {
            activeGameObject = false;
        }

        if (!activeGameObject) {
            this.interactablesInRange.RemoveAt(this.interactablesInRange.Count-1);
            if (this.toBeDestroyed) {
                Destroy(latestGameObject);
            }

            if (this.interactablesInRange.Count != 0) {
                latestGameObject = this.interactablesInRange[^1];

                if (latestGameObject != null) {
                    Interactable latestInteractable = this.GetLastInteractable();
                    if (latestInteractable.GetType() == typeof(Puzzle_ActivateButton) &&
                            ((Puzzle_ActivateButton)latestInteractable).
                            GetButtonInteractionType() == InteractionType.BUTTON &&
                            ((Puzzle_ActivateButton)latestInteractable).IsButtonActive()) {
                        this.ShowInteractPrompt(InteractionType.BUTTON);
                    } else if (latestInteractable.GetType() == typeof(Puzzle_ClickSendPuzzleEvent) &&
                            ((Puzzle_ClickSendPuzzleEvent)latestInteractable).
                            GetButtonInteractionType() == InteractionType.BUTTON &&
                            !((Puzzle_ClickSendPuzzleEvent)latestInteractable).IsButtonActive()) {
                        this.ShowInteractPrompt(InteractionType.BUTTON);
                    } else if (latestInteractable.GetInteractionType() is InteractionType type && (
                            type == InteractionType.VERBAL ||
                            type == InteractionType.ITEM ||
                            type == InteractionType.SPELL_ATTRIBUTE)) {
                        this.ShowInteractPrompt(type);
                    }
                }
            } else { InteractPrompt.Instance.HideInteractPrompt(); }
        }
    }
}

private void OnTriggerEnter(Collider other) {
    if (other.gameObject.TryGetComponent(out Interactable interactable)) {
        bool activeInteractable = true;

        if (interactable.GetType() == typeof(Puzzle_ClickSendPuzzleEvent)) {
            if (!((Puzzle_ClickSendPuzzleEvent)interactable).IsButtonActive()) {
                this.interactablesInRange.Add(other.gameObject);
            } else {
                activeInteractable = false;
            }
        } else {
            this.interactablesInRange.Add(other.gameObject);
        }

        if (activeInteractable) {
            this.ShowInteractPrompt(interactable);
        }
    }
}

private void OnTriggerExit(Collider other) {
    if (other.gameObject.TryGetComponent(out Interactable interactable)) {
        this.interactablesInRange.Remove(other.gameObject);

        if (this.interactablesInRange.Count != 0) {
            Interactable newInteractableInRange = this.GetLastInteractable();
            this.ShowInteractPrompt(newInteractableInRange);
        } else { InteractPrompt.Instance.HideInteractPrompt

Collision: Interactable Handling

Whenever the player is able to reach an interactable, it is placed in a list. The system then determines whether the player can actually interact with the interactable (f.e. if a puzzle mechanism has already been activated, then the interactable is removed from the list) and is then presented in the UI with an icon, or not. The system also makes sure that the player always prioritizes the latest interactable that the player has found and is still reachable.

Interaction: Player Input

Handles the latest interactable that the player has detected and wants to interact with. Data from a ScriptableObject that represents a specific interactable is returned, in case the player object needs to handle that data and change something in the game (opening the journal, picking up an item, activate a puzzle mechanism etc).

public void OnInteract(InputAction.CallbackContext obj) {
    if (isPlayingMusic || Player.Instance.isOutOfBody) return;

    Interactable interactable = interactZone.GetLastInteractable();
    if (interactable != null) {

        SO_InteractableData result = interactable.Interact();

        if (result != null) {
            switch (interactable) {
                case InteractableArtifact artifact:
                    Inventory.TryCollectItem(artifact);
                    artifact.GetGameObject().GetComponent<Collider>().enabled = false;
                    this.interactZone.PickedUpInteractable();
                    JournalManager.Instance.OpenJournalToPage(0);
                    break;
                case InteractableSignPost signPost:
                    break;
                case InteractableNPC npc:
                    Inventory.TryUnlockNpc(npc); 
                    break;
                case InteractableTransition transition:
                    break;
                case InteractableDoor door:
                    break;
                case InteractableSpellAttribute spellAttribute:
                    Inventory.TryCollectItem(spellAttribute);
                    spellAttribute.GetGameObject().GetComponent<Collider>().enabled = false;
                    this.interactZone.PickedUpInteractable();
                    JournalManager.Instance.OpenJournalToPage(2);
                    break;
                case Puzzle_ActivateButton puzzleActivateButton:
                    break;
                case Puzzle_ClickSendPuzzleEvent clickPuzzleEvent:
                    if (clickPuzzleEvent.IsButtonActive()) {
                        this.interactZone.ActivatedClickButton();
                    }
                    break;
                default: // If nothing matches
                    Debug.Log(interactable.GetType().Name);
                    break;
            }
        } else {
            if (interactable.GetType() == typeof(Puzzle_ClickSendPuzzleEvent)) {
                if (((Puzzle_ClickSendPuzzleEvent)interactable).IsButtonActive()) {
                    this.interactZone.ActivatedClickButton();
                }
            }
        }
    } else {
        Debug.Log("No interactable found"

© 2026 Sebastian Valck. All rights reserved.

Create a free website with Framer, the website builder loved by startups, designers and agencies.