second species

Role: Programmer


Tool: Unity


Team: CiGA Game Jam 2024 (Limited and Limitless), Team of 6


Timeline: 48 hours


Completion Date: July 7, 2024

Role: Programmer


Tool: Unity


Team: CiGA Game Jam 2024 (Limited and Limitless), Team of 6


Timeline: 48 hours


Completion Date: July 7, 2024

Role: Programmer


Tool: Unity


Team: CiGA Game Jam 2024 (Limited and Limitless), Team of 6


Timeline: 48 hours


Completion Date: July 7, 2024

Play ->

Play ->

Play ->

GAME DESIGN

GAME DESIGN

GAME DESIGN

design pillars

design pillars

design pillars

Lifespan as a Resource

Lifespan as a Resource

Lifespan as a Resource

The player's limited lifespan drives strategic decisions, making every second crucial for progress.

The player's limited lifespan drives strategic decisions, making every second crucial for progress.

The player's limited lifespan drives strategic decisions, making every second crucial for progress.

Failure as Experience

Failure as Experience

Failure as Experience

Each death leaves a meaningful impact on the game world, encouraging adaptation and new strategies.

Each death leaves a meaningful impact on the game world, encouraging adaptation and new strategies.

Each death leaves a meaningful impact on the game world, encouraging adaptation and new strategies.

Emergent Problem-Solving

Emergent Problem-Solving

Emergent Problem-Solving

Each death leaves a meaningful impact on the game world, encouraging adaptation and new strategies.

Each death leaves a meaningful impact on the game world, encouraging adaptation and new strategies.

Each death leaves a meaningful impact on the game world, encouraging adaptation and new strategies.

mechanics

mechanics

mechanics

PROGRAMMING

PROGRAMMING

PROGRAMMING

Level-Based Occlusion Culling

Level-Based Occlusion Culling

Level-Based Occlusion Culling

This code manages room visibility and camera focus dynamically in a multi-room environment, ensuring smooth transitions and optimized performance.

  1. Room Visibility Control:

    • The ShowActiveRoom() method activates the visual components (MeshRenderer, VisualEffect, SpriteRenderer, Canvas) of the current room while deactivating them in others.

    • It sets the virtual camera to follow the floor of the active room, keeping the player's focus on the relevant area and reducing distractions.

  2. Room Initialization:

    • The OnSceneLoad() method initializes the room list from the environment and retrieves the virtual camera reference when the scene loads.


There are several advantages to having a custom occlusion culling:

  • Performance: Only the active room is rendered, reducing GPU load.

  • Focus: Keeps the player centered on the active area, avoiding distractions.

  • Dynamic Gameplay: Smooth transitions between rooms maintain immersion.

  • Scalability: Simplifies managing and expanding environments.


Below is the code snippet used for loading in the rooms when the level starts and rendering only the active room during runtime:

This code manages room visibility and camera focus dynamically in a multi-room environment, ensuring smooth transitions and optimized performance.

  1. Room Visibility Control:

    • The ShowActiveRoom() method activates the visual components (MeshRenderer, VisualEffect, SpriteRenderer, Canvas) of the current room while deactivating them in others.

    • It sets the virtual camera to follow the floor of the active room, keeping the player's focus on the relevant area and reducing distractions.

  2. Room Initialization:

    • The OnSceneLoad() method initializes the room list from the environment and retrieves the virtual camera reference when the scene loads.


There are several advantages to having a custom occlusion culling:

  • Performance: Only the active room is rendered, reducing GPU load.

  • Focus: Keeps the player centered on the active area, avoiding distractions.

  • Dynamic Gameplay: Smooth transitions between rooms maintain immersion.

  • Scalability: Simplifies managing and expanding environments.


Below is the code snippet used for loading in the rooms when the level starts and rendering only the active room during runtime:

This code manages room visibility and camera focus dynamically in a multi-room environment, ensuring smooth transitions and optimized performance.

  1. Room Visibility Control:

    • The ShowActiveRoom() method activates the visual components (MeshRenderer, VisualEffect, SpriteRenderer, Canvas) of the current room while deactivating them in others.

    • It sets the virtual camera to follow the floor of the active room, keeping the player's focus on the relevant area and reducing distractions.

  2. Room Initialization:

    • The OnSceneLoad() method initializes the room list from the environment and retrieves the virtual camera reference when the scene loads.


There are several advantages to having a custom occlusion culling:

  • Performance: Only the active room is rendered, reducing GPU load.

  • Focus: Keeps the player centered on the active area, avoiding distractions.

  • Dynamic Gameplay: Smooth transitions between rooms maintain immersion.

  • Scalability: Simplifies managing and expanding environments.


Below is the code snippet used for loading in the rooms when the level starts and rendering only the active room during runtime:

public void ShowActiveRoom(GameObject currentRoom)
{
    foreach (GameObject room in rooms)
    {
        GameObject roomFloor = room.transform.GetChild(0).gameObject;

        if (currentRoom == room)
        {
            virtualCamera.Follow = roomFloor.transform;

            foreach (MeshRenderer mr in room.GetComponentsInChildren<MeshRenderer>())
            {
                mr.enabled = true;
            }

            foreach (VisualEffect vfx in room.GetComponentsInChildren<VisualEffect>())
            {
                vfx.enabled = true;
            }

            foreach (SpriteRenderer sr in room.GetComponentsInChildren<SpriteRenderer>())
            {
                sr.enabled = true;
            }

            foreach (Canvas canvas in room.GetComponentsInChildren<Canvas>())
            {
                canvas.enabled = true;
            }
        }

        else
        {
            foreach (MeshRenderer mr in room.GetComponentsInChildren<MeshRenderer>())
            {
                mr.enabled = false;
            }

            foreach (VisualEffect vfx in room.GetComponentsInChildren<VisualEffect>())
            {
                vfx.enabled = false;
            }

            foreach (SpriteRenderer sr in room.GetComponentsInChildren<SpriteRenderer>())
            {
                sr.enabled = false;
            }

            foreach (Canvas canvas in room.GetComponentsInChildren<Canvas>())
            {
                canvas.enabled = false;
            }
        }

        roomFloor.SetActive(true);
    }
}

void OnSceneLoad(Scene scene, LoadSceneMode mode)
{
    GameObject environment = GameObject.Find("ENVIRONMENT");
    if (environment == null)
    {
        Debug.LogWarning("No environment found in scene: " + scene.name + " maybe this is not the game scene. Returning.");
        return;
    }

    rooms.Clear();

    for (int i = 0; i < environment.transform.childCount; i++)
    {
        rooms.Add(environment.transform.GetChild(i).gameObject);
    }

    virtualCamera = GameObject.Find("Virtual Camera").GetComponent<CinemachineVirtualCamera

public void ShowActiveRoom(GameObject currentRoom)
{
    foreach (GameObject room in rooms)
    {
        GameObject roomFloor = room.transform.GetChild(0).gameObject;

        if (currentRoom == room)
        {
            virtualCamera.Follow = roomFloor.transform;

            foreach (MeshRenderer mr in room.GetComponentsInChildren<MeshRenderer>())
            {
                mr.enabled = true;
            }

            foreach (VisualEffect vfx in room.GetComponentsInChildren<VisualEffect>())
            {
                vfx.enabled = true;
            }

            foreach (SpriteRenderer sr in room.GetComponentsInChildren<SpriteRenderer>())
            {
                sr.enabled = true;
            }

            foreach (Canvas canvas in room.GetComponentsInChildren<Canvas>())
            {
                canvas.enabled = true;
            }
        }

        else
        {
            foreach (MeshRenderer mr in room.GetComponentsInChildren<MeshRenderer>())
            {
                mr.enabled = false;
            }

            foreach (VisualEffect vfx in room.GetComponentsInChildren<VisualEffect>())
            {
                vfx.enabled = false;
            }

            foreach (SpriteRenderer sr in room.GetComponentsInChildren<SpriteRenderer>())
            {
                sr.enabled = false;
            }

            foreach (Canvas canvas in room.GetComponentsInChildren<Canvas>())
            {
                canvas.enabled = false;
            }
        }

        roomFloor.SetActive(true);
    }
}

void OnSceneLoad(Scene scene, LoadSceneMode mode)
{
    GameObject environment = GameObject.Find("ENVIRONMENT");
    if (environment == null)
    {
        Debug.LogWarning("No environment found in scene: " + scene.name + " maybe this is not the game scene. Returning.");
        return;
    }

    rooms.Clear();

    for (int i = 0; i < environment.transform.childCount; i++)
    {
        rooms.Add(environment.transform.GetChild(i).gameObject);
    }

    virtualCamera = GameObject.Find("Virtual Camera").GetComponent<CinemachineVirtualCamera

Dialogue Controller

Dialogue Controller

Dialogue Controller

The DialogueController class manages NPC interactions and dialogue using Unity and the Ink storytelling system, developed by Inkle. When the player presses "Q," nearby NPCs are detected via a spherical overlap, and the closest one is selected. If dialogue is initiated, the system enters dialogue mode, displaying the NPC's name and dialogue while pausing gameplay and stopping the game timer.

The EnterDialogueMode() method initializes the Ink story from the NPC's script and updates the dialogue panel. Conversations progress through ContinueStory(), fetching the next segment of dialogue, and conclude when no further text is available, triggering ExitDialogueMode(). This restores gameplay elements and clears the panel, ensuring a smooth and immersive interaction system.

The DialogueController class manages NPC interactions and dialogue using Unity and the Ink storytelling system, developed by Inkle. When the player presses "Q," nearby NPCs are detected via a spherical overlap, and the closest one is selected. If dialogue is initiated, the system enters dialogue mode, displaying the NPC's name and dialogue while pausing gameplay and stopping the game timer.

The EnterDialogueMode() method initializes the Ink story from the NPC's script and updates the dialogue panel. Conversations progress through ContinueStory(), fetching the next segment of dialogue, and conclude when no further text is available, triggering ExitDialogueMode(). This restores gameplay elements and clears the panel, ensuring a smooth and immersive interaction system.

The DialogueController class manages NPC interactions and dialogue using Unity and the Ink storytelling system, developed by Inkle. When the player presses "Q," nearby NPCs are detected via a spherical overlap, and the closest one is selected. If dialogue is initiated, the system enters dialogue mode, displaying the NPC's name and dialogue while pausing gameplay and stopping the game timer.

The EnterDialogueMode() method initializes the Ink story from the NPC's script and updates the dialogue panel. Conversations progress through ContinueStory(), fetching the next segment of dialogue, and conclude when no further text is available, triggering ExitDialogueMode(). This restores gameplay elements and clears the panel, ensuring a smooth and immersive interaction system.

using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Ink.Runtime;
using TMPro;
using UnityEngine;

public class DialogueController : MonoBehaviour
{
    public GameObject player;

    [Header("Dialogue")]
    public GameObject dialoguePanel;
    public TMP_Text npcText, dialogueText;

    Story currentStory;

    public TextMeshProUGUI TimeText;

    void Start()
    {
        dialoguePanel.SetActive(false);
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Q))
        {
            GameObject[] NPCs = Physics.OverlapSphere(player.transform.position, 1f, LayerMask.GetMask("NPC")).Select(collider => collider.gameObject).ToArray();

            if (NPCs.Length == 0) {
                return;
            }

            float closestDistance = Mathf.Infinity;
            GameObject closestNPC = null;

            foreach (GameObject npc in NPCs) {
                float currentDistance = Vector3.Distance(player.transform.position, npc.transform.position);
                if (currentDistance < closestDistance) {
                    closestDistance = currentDistance;
                    closestNPC = npc;
                }
            }

            if (!dialoguePanel.activeSelf)
            {
                EnterDialogueMode(closestNPC.name, closestNPC.GetComponent<NPCDialogue>().inkJson);
            }

            else if (dialoguePanel.activeSelf
            && currentStory.currentChoices.Count == 0)
            {
                ContinueStory();
            }
        }
    }

    void EnterDialogueMode(string npcName, TextAsset inkJson)
    {
        currentStory = new Story(inkJson.text);

        dialoguePanel.SetActive(true);
        player.GetComponent<PlayerController>().enabled = false;

        npcText.text = npcName;

        TimeText.GetComponent<TimeTextUpdate>().StopTimer();

        ContinueStory();
    }

    void ExitDialogueMode()
    {
        dialoguePanel.SetActive(false);
        player.GetComponent<PlayerController>().enabled = true;

        dialogueText.text = "";

        TimeText.GetComponent<TimeTextUpdate>().ResumeTimer();
    }

    void ContinueStory()
    {
        if (currentStory.canContinue)
        {
            dialogueText.text = currentStory.Continue();
        }

        else
        {
            ExitDialogueMode

using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Ink.Runtime;
using TMPro;
using UnityEngine;

public class DialogueController : MonoBehaviour
{
    public GameObject player;

    [Header("Dialogue")]
    public GameObject dialoguePanel;
    public TMP_Text npcText, dialogueText;

    Story currentStory;

    public TextMeshProUGUI TimeText;

    void Start()
    {
        dialoguePanel.SetActive(false);
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Q))
        {
            GameObject[] NPCs = Physics.OverlapSphere(player.transform.position, 1f, LayerMask.GetMask("NPC")).Select(collider => collider.gameObject).ToArray();

            if (NPCs.Length == 0) {
                return;
            }

            float closestDistance = Mathf.Infinity;
            GameObject closestNPC = null;

            foreach (GameObject npc in NPCs) {
                float currentDistance = Vector3.Distance(player.transform.position, npc.transform.position);
                if (currentDistance < closestDistance) {
                    closestDistance = currentDistance;
                    closestNPC = npc;
                }
            }

            if (!dialoguePanel.activeSelf)
            {
                EnterDialogueMode(closestNPC.name, closestNPC.GetComponent<NPCDialogue>().inkJson);
            }

            else if (dialoguePanel.activeSelf
            && currentStory.currentChoices.Count == 0)
            {
                ContinueStory();
            }
        }
    }

    void EnterDialogueMode(string npcName, TextAsset inkJson)
    {
        currentStory = new Story(inkJson.text);

        dialoguePanel.SetActive(true);
        player.GetComponent<PlayerController>().enabled = false;

        npcText.text = npcName;

        TimeText.GetComponent<TimeTextUpdate>().StopTimer();

        ContinueStory();
    }

    void ExitDialogueMode()
    {
        dialoguePanel.SetActive(false);
        player.GetComponent<PlayerController>().enabled = true;

        dialogueText.text = "";

        TimeText.GetComponent<TimeTextUpdate>().ResumeTimer();
    }

    void ContinueStory()
    {
        if (currentStory.canContinue)
        {
            dialogueText.text = currentStory.Continue();
        }

        else
        {
            ExitDialogueMode

Copyright © 2024, Andy Pang. All rights reserved.

Copyright © 2024, Andy Pang. All rights reserved.

Copyright © 2024, Andy Pang. All rights reserved.