Game Development - Final Project & Portfolio

02/07/2025 - 23/07/2025  / Week 11 - Week 14

Velicia Raquel Dewi Setiawan | 0369188 | Bachelor of Design (Honours) in Creative Media

Game Development 

JUMPLINK:

  1. Lecture
  2. Instruction
  3. Final Project & Portfolio 
  4. Feedback
  5. Reflection

1. LECTURE


2. INSTRUCTION


3. FINAL PROJECT & PORTOFOLIO

Description
1. Final Project – Playable Game – 30 %
Students will integrate their art asset to their game development and produce a fully functional and playable game.

2. E-Portfolio - 10 %
Students describe and reflect on their social competencies within the design studio context, supported by evidence. Reflect on how to empathize with others within group settings, interact positively within a
team and foster stable and harmonious relationships for productive teamwork. The reflective writing is part of the TGCP

Requirements
1. Unity Project files and Folders.
2. Video Capture and Presentation of the gameplay walkthrough
3. Game WebGL Build
4. Blog post for your reflective studies.


1. Process

Folder - Google Drive (where i got the file from gawai but it didnt get the update yet)

I finally managed to open the Unity file Gawai gave a while back. Turns out I just needed to download the same Unity engine version she was using. Without the right version, the background kept turning pink, which confused me at first.

Fig 3.1 Attempt to Open File Week 16 5/08/2025

Now that it's working, I can start helping the others with the parts of the coding that are still missing. This is my attempt to support them the best I can. Between the three of us, I’m the one with the least experience in Unity, so I’m still trying to catch up. I didn’t get to follow the tutorials earlier because I couldn’t even open Unity back then. But now that I can, I’ll do what I can to contribute properly.

I asked them what code function they need right now, and I’ll try to do it in my Unity. I’m posting the whole code since Gawai asked me for the tutorial on how to do it, and so she can copy and paste the code from here.


FOR THE ENEMY CHANGING SKIN AND STOP

Replace the EnemyAI code with this:

using UnityEngine;
using System.Collections;
using Platformer.Mechanics;

public class EnemyAI : MonoBehaviour
{
    [Header("References")]
    public Transform player;
    public Animator anim;

    [Header("Detection Settings")]
    public float detectionRange = 8f;

    [Header("Charge Settings")]
    public float anticipationTime = 0.8f;
    public float chargeSpeed = 10f;
    public float chargeCooldown = 2f;

    [Header("Damage Settings")]
    public int damageAmount = 1;

    [Header("Health Settings")]
    public int maxHealth = 6; // Changed to 6 so 3 hits (2 dmg per hit) = full transform
    private int currentHealth;

    private Rigidbody2D rb;
    private bool isCharging = false;
    private float nextChargeTime = 0f;
    private Collider2D col;

    private bool hasDamagedPlayer = false;
    private bool reachedFinalSkin = false;

    void Start()
    {
        rb = GetComponent<Rigidbody2D>();
        col = GetComponent<Collider2D>();
        currentHealth = maxHealth;

        if (anim != null)
            anim.SetInteger("StateIndex", 0); // Start as Sketch
    }

    void Update()
    {
        if (player == null || currentHealth <= 0 || reachedFinalSkin) return;

        FacePlayer();

        float distance = Vector2.Distance(transform.position, player.position);
        AnimatorStateInfo stateInfo = anim.GetCurrentAnimatorStateInfo(0);
        bool isInFinalIdle = stateInfo.IsName("Final_Idle") || anim.GetInteger("StateIndex") == 3;

        if (distance <= detectionRange && !isCharging && Time.time >= nextChargeTime && !isInFinalIdle)
        {
            StartCoroutine(ChargeSequence());
        }
    }

    void FacePlayer()
    {
        if (player == null) return;

        float scaleX = player.position.x < transform.position.x ? -0.18f : 0.18f;
        transform.localScale = new Vector3(scaleX, 0.18f, 0.18f);
    }

    IEnumerator ChargeSequence()
    {
        isCharging = true;
        hasDamagedPlayer = false;

        anim.SetTrigger("Transform");
        yield return new WaitForSeconds(anticipationTime);

        Vector2 targetPosition = player.position;
        col.isTrigger = true;

        anim.SetTrigger("Charge");

        while (Mathf.Abs(transform.position.x - targetPosition.x) > 0.1f)
        {
            FacePlayer();
            Vector2 newPos = Vector2.MoveTowards(rb.position, new Vector2(targetPosition.x, rb.position.y), chargeSpeed * Time.deltaTime);
            rb.MovePosition(newPos);
            yield return null;
        }

        rb.velocity = Vector2.zero;
        col.isTrigger = false;

        anim.SetTrigger("TransformBack");
        yield return new WaitForSeconds(0.5f);

        nextChargeTime = Time.time + chargeCooldown;
        isCharging = false;
    }

    void OnDrawGizmosSelected()
    {
        Gizmos.color = Color.red;
        Gizmos.DrawWireSphere(transform.position, detectionRange);
    }

    void OnTriggerEnter2D(Collider2D other)
    {
        bool isInFinalIdle = anim.GetInteger("StateIndex") == 3;

        if (other.CompareTag("Player") && isCharging && !hasDamagedPlayer && !isInFinalIdle && !reachedFinalSkin)
        {
            hasDamagedPlayer = true;

            var playerHealth = other.GetComponent<Platformer.Mechanics.Health>();
            if (playerHealth != null && playerHealth.IsAlive)
            {
                for (int i = 0; i < damageAmount; i++)
                {
                    playerHealth.Decrement();
                }
            }

            var playerAnimator = other.GetComponent<Animator>();
            if (playerAnimator != null)
            {
                playerAnimator.SetTrigger("hit");
            }

            var playerController = other.GetComponent<PlayerController>();
            if (playerController != null)
            {
                playerController.TakeHit(0.5f);
            }
        }
    }

    public void TakeDamage(int damage)
    {
        currentHealth -= damage;

        if (anim != null)
        {
            anim.SetTrigger("Hurt");

            // Change: Transition every 2 damage, not 3
            int hitsTaken = Mathf.Clamp((maxHealth - currentHealth) / 2, 0, 3);
            int currentState = anim.GetInteger("StateIndex");

            if (hitsTaken != currentState)
            {
                anim.SetInteger("StateIndex", hitsTaken);

                string[] attackStates = { "Sketch_Attack", "Line_Attack", "Colour_Attack", "Final_Idle" };
                if (hitsTaken < attackStates.Length)
                {
                    anim.Play(attackStates[hitsTaken]);
                }
            }

            if (hitsTaken >= 3)
            {
                reachedFinalSkin = true;
                anim.SetInteger("StateIndex", 3);
                anim.Play("Final_Idle");
            }
        }

        if (currentHealth <= 0)
        {
            Die();
        }
    }

    private void Die()
    {
        if (anim != null)
            anim.SetTrigger("Die");

        GetComponent<Collider2D>().enabled = false;
        this.enabled = false;

        if (rb != null)
            rb.velocity = Vector2.zero;

        Destroy(gameObject, 1f);
    }
}



After you save this, you’ll probably see a pop-up below in Unity:
(DO NOT CLICK YES OR NO, CLICK THE X BUTTON ON TOP INSTEAD. Every time this shows again, click X. If you click Yes or No, the project cannot be played—it will break.)

Pop-up message:
Some of this project's source files refer to API that has changed. These can be automatically updated. It is recommended to have a backup of the project before updating. Do you want these files to be updated?
Assets/ForgotenProject/Enemies/Enemy 1/EnemyAI.cs


For the Animator scene, you need to set it up as below and put in the appropriate animation:


Fig 3.2 Enemy Animator Scene Week 16 5/08/2025

  • Add in the parameter: top left click the +, pick "int", rename it to "StateIndex", keep it at 0.
  • Add another parameter: top left click the +, pick "bool", rename it to "reachedFinalSkin", leave it unchecked.

Fig 3.3 Enemy Animator Scene Week 16 5/08/2025
  • Click the transition from sketch idle to line idle. In the Inspector bar on the right, under Condition, click +, then click the transform that turns into a dropdown, pick "StateIndex", and put in 1.
  • For the rest of the transitions that use line animation, also add "StateIndex" = 1.

Fig 3.4 Enemy Animator Scene Week 16 5/08/2025
  • For the transition from line idle to color idle, add "StateIndex" = 2.
  • For the rest of the transitions that have color animation, also add "StateIndex" = 2.

Fig 3.5 Enemy Animator Scene Week 16 5/08/2025
  • Add an empty for the final idle as the "stop". Add "StateIndex" = 3.

Fig 3.6 Enemy Animator Scene Week 16 5/08/2025

This is the result:

Enemy Changing Skin: Video Link

Fig 3.7 Enemy Changing Skin Week 16 5/08/2025

Enemy Changing Skin: Video Link 

Fig 3.8 Enemy Changing Skin Week 16 5/08/2025

Also, the changing skin is still very, very wonky. But Gawai told me it’s okay for now—it’s probably just the skin animation issue, and she said she can fix it on her side later.




FOR THE NPC QUEST GIVER

  • Add objective bg, I get this from Drive Tracy UI Art.
  • Right click the player UI - Add UI - Legacy - (Text), not the Text (Mesh Pro), rename "QuestlogText", change color, position, size and stuff. I fill with "talk to poelpe".
  • Add Text (Mesh Pro), rename "Interaction Text". I filled it with "press E to interact", make it not visible (uncheck the thing beside the name in Inspector bar).

Fig 3.9 Player Ui Week 16 6/08/2025

  • Add the NPC sprite in scene.
  • Add component Rigidbody2D - set Body Type = Kinematic.
  • Add component BoxCollider2D - set Is Trigger = checked.
  • Add component NpcQuestGiver script - add the Interaction Text.
  • Make SCRIPT
  • Rename "NpcQuestGiver", copy paste below.

using UnityEngine;

public class NPCQuestGiver : MonoBehaviour
{
    public GameObject interactionPrompt; // Optional UI like "Press E to talk"

    private bool isPlayerNearby = false;
    private bool questAccepted = false; // Prevents re-accepting the quest

    void OnTriggerEnter2D(Collider2D other)
    {
        if (other.CompareTag("Player"))
        {
            isPlayerNearby = true;

            if (interactionPrompt != null && !questAccepted)
                interactionPrompt.SetActive(true);
        }
    }

    void OnTriggerExit2D(Collider2D other)
    {
        if (other.CompareTag("Player"))
        {
            isPlayerNearby = false;

            if (interactionPrompt != null)
                interactionPrompt.SetActive(false);
        }
    }

    void Update()
    {
        if (isPlayerNearby && !questAccepted && Input.GetKeyDown(KeyCode.E))
        {
            Debug.Log("Quest accepted!");
            QuestManager.Instance.AcceptQuest();
            questAccepted = true;

            if (interactionPrompt != null)
                interactionPrompt.SetActive(false);
        }
    }
}



Make empty GameObject
Rename "QuestManager" (add it GameObject), copy paste below.

Fig 3.10 QuestManager GameObject Week 16 6/08/2025

using UnityEngine;
using UnityEngine.UI;

public class QuestManager : MonoBehaviour
{
    public static QuestManager Instance; // Singleton instance

    public int enemy1Kills = 0;
    public int enemy2Kills = 0;

    public int enemy1Target = 10;
    public int enemy2Target = 8;

    public Text questLogText; // Assign this in the Inspector
    private bool questAccepted = false;

    void Awake()
    {
        if (Instance == null)
            Instance = this;
        else
            Destroy(gameObject);
    }

    public void AcceptQuest()
    {
        questAccepted = true;
        UpdateQuestLog();
    }

    public void AddKill(string enemyTag)
    {
        if (!questAccepted) return;

        bool updated = false;

        if (enemyTag == "Enemy1" && enemy1Kills < enemy1Target)
        {
            enemy1Kills++;
            updated = true;
        }
        else if (enemyTag == "Enemy2" && enemy2Kills < enemy2Target)
        {
            enemy2Kills++;
            updated = true;
        }

        if (updated)
            UpdateQuestLog();
    }

    public void EnemyKilled(string enemyTag)
    {
        if (enemyTag == "Enemy1")
        {
            AddKill("Enemy1");
        }
        else if (enemyTag == "Enemy2")
        {
            AddKill("Enemy2");
        }
    }

    void UpdateQuestLog()
    {
        if (!questLogText) return;

        string questText = $"📝 Quest:\n" +
                           $"• Kill Enemy 1: {enemy1Kills}/{enemy1Target}\n" +
                           $"• Kill Enemy 2: {enemy2Kills}/{enemy2Target}\n";

        if (enemy1Kills >= enemy1Target && enemy2Kills >= enemy2Target)
        {
            questText += "\n✅ Quest Complete!";
        }

        questLogText.text = questText;
    }
}


I still can’t get the quest to update after killing the enemy. I’ve tried to make it work, but it still doesn’t count the kill properly. At least the base system is kind of there already. 


Npc Quest Giver: Video Link 

Fig 3.11 Npc Quest Giver Week 16 6/08/2025





2. Final Submision

Fig 3.12 Gameplay by Tracy Week 16 7/08/2025

By Me:

Drive Link: Task 3 - Google Drive (My Contribution)

Game Walkthrough:

Npc Quest Giver: Video Link 

Fig 3.11 Npc Quest Giver Week 16 6/08/2025

Enemy Changing Skin: Video Link

Fig 3.7 Enemy Changing Skin Week 16 5/08/2025

Enemy Changing Skin: Video Link 

Fig 3.8 Enemy Changing Skin Week 16 5/08/2025



Final Game



Game Link:  

Gameplay by Gawai:

https://youtu.be/C2U0wIARng0

Fig 3.13 Gameplay by Gawai Week 17 13/08/2025





4. Feedback

Week 4: 

Specific Feedback: 

  • Make the information simpler.

5. Reflection

1. Experience 

This week, I finally managed to open the Unity file Gawai gave me. I realized the pink background issue was because I didn’t install the correct Unity version. After fixing that, I was able to help more with the project. I tested the enemy AI, added the NPC quest system, and tried putting both into the scene. Even though I’m not that experienced, I still tried my best to follow tutorials and understand how it all worked.

2. Observation

I noticed many of my bugs came from using the wrong Unity version or missing references. Once I fixed that, things started working better. Talking to Gawai and Tracy helped a lot—I understood more about how animations and quest systems worked. The enemy skin-change is still a bit broken, but Gawai said it’s okay and she can fix it later. That made me feel more at ease.

3. Findings

I found that testing directly in Unity helped me learn faster than just watching tutorials. I understood how animations work using StateIndex and how to detect player input for quests. Even though the quest doesn’t update when killing enemies yet, the base system is there. 


Comments

Popular posts from this blog

Information Design - Exercise 1&2

Advanced Interactive Design - Final Project

Advanced Interactive Design - Task 2