Skip to content

ErigoLee/game-dev-spring2025

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

81 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Game Development I

These are the projects I created during the Game Development I graduate course.

Introduction of Projects

image

  1. Description
  • This project is a custom game based on the classic Breakout concept.
  • Players control a paddle using the arrow keys to bounce a ball and break walls.
  1. Gameplay Rules
  • Players have 5 lives. If the ball falls 5 times, the game is over.
  • The game ends successfully once all walls are destroyed.
  1. Levels
  • The game consists of 3 levels.
  • Players can select and start from their desired level.
  • Level 3 (Extension of Level 2)
    Level 3 reuses the basic structure of Level 2, but adds a control feature: pressing W / A / S / D changes the ball’s movement direction.
  1. User Behavior Insight
  • Some players preferred skipping Level 1 and jumping straight to Level 2.
  • Based on this, Level 1 and Level 2 were separated and exposed via a selection screen.
  1. Input Handling Fix (Debounce)
  • Arrow keys were being registered as continuous inputs on the selection screen, which sometimes prevented choosing the intended level.
  • A debounce interval (timeBetweenInputs) was introduced to prevent rapid repeats and ensure accurate level selection.
  1. Code

(1) GameManager.cs

  • The following function is part of the GameManager code.
  • It implements page navigation using the Up and Down arrow keys.
  • To prevent continuous key inputs from being registered too quickly, an input delay is applied with the variable timeBetweenInputs.
void pageUpDown() {
    if (checkingTime == false) {
        if (Input.GetKeyDown(KeyCode.UpArrow)) {
            page--;
            if (page > page_max)
                page = page_min;
            checkingTime = true;
            lastInputTime = 0.0f;  // Reset the timer whenever an input occurs
        }
        if (Input.GetKeyDown(KeyCode.DownArrow)) {
            page++;
            if (page < page_min)
                page = page_max;
            checkingTime = true;
            lastInputTime = 0.0f;  // Reset the timer whenever an input occurs
        }
    }
    else {
        if (lastInputTime >= timeBetweenInputs) {
            checkingTime = false;  // Allow input again after the delay time has passed
        }
        else {
            lastInputTime += Time.deltaTime;  // Increase elapsed time every frame
        }
    }
}

(2) BallScript.cs

  • When the ball collides with a brick, its velocity changes based on the brick’s color.

  • Red(-10, 20, 0)

  • Orange(9, -18, 0)

  • Yellow(-8, 16, 0)

  • Green(7, -14, 0)

  • Blue (default)(-6, 12, 0)

void OnCollisionEnter(Collision collision)
{
    // Only react to bricks
    if (collision.gameObject.CompareTag("brick"))
    {
        // Read the brick's color
        Color color = collision.gameObject
                               .GetComponent<Renderer>()
                               .material.color;

        // Change ball velocity based on brick color
        if (color == red)
        {
            rb.linearVelocity = new Vector3(-10, 20, 0);
            print("red");
        }
        else if (color == orange)
        {
            rb.linearVelocity = new Vector3(9, -18, 0);
            print("orange");
        }
        else if (color == yellow)
        {
            rb.linearVelocity = new Vector3(-8, 16, 0);
            print("yellow");
        }
        else if (color == green)
        {
            rb.linearVelocity = new Vector3(7, -14, 0);
            print("green");
        }
        else
        {
            rb.linearVelocity = new Vector3(-6, 12, 0);
            print("blue");
        }
    }
}

game screenshot

  1. Description - Grid and Cell System
  • When the game starts, the GridManager4.cs script generates a grid of 20 × 20 cells (total 400 cells) without overlap.
  • Each cell is randomly assigned one of the following attributes: Water, Fire, Grass, or Road.
  • After the grid is created, clicking on any cell with the mouse displays the cell’s information in the corresponding panel UI image.
  1. Code

(1) GridManager4.cs

  • The nested loops place each cell at (x * cellSpacing, 0, y * cellSpacing), creating a uniform gridSize × gridSize layout with consistent spacing in both X (columns) and Z (rows) directions.
  • On instantiation, each cell receives one of four materials — Water, Fire, Grass, Road — chosen uniformly at random (25% each) so that every playthrough yields a different grid composition.
  • The cell’s type is determined by its material (e.g., Water, Fire, Grass, Road).
for (int x = 0; x < gridSize; x++)
{
    for (int y = 0; y < gridSize; y++)
    {
        Vector3 position = new Vector3(x * cellSpacing, 0, y * cellSpacing);
        GameObject cell = Instantiate(cellPrefab, position, Quaternion.identity, transform);

        Cell cellScript = cell.AddComponent<Cell>();
        cellScript.SetTypeMaterials(waterMaterial, fireMaterial, grassMaterial, roadMaterial);

        // Apply either water or fire material randomly (for testing purposes)
        Material chosenMat = Random.value > 0.75f ? waterMaterial : Random.value > 0.5f ? fireMaterial :  Random.value > 0.25f ? grassMaterial : roadMaterial;
        cellScript.SetMaterial(chosenMat);

        cell.name = "Cell_" + x + "_" + y;
        cells[x, y] = cellScript;
    }
}
  • When the player clicks with the mouse, a raycast is used to detect which cell was clicked.
  • When the player clicks on a cell, the hit point is converted into grid coordinates (x, y).
  • If the coordinates are valid, the corresponding Cell object is retrieved.
  • The panel text is updated only when a cell is clicked, showing contextual information about that cell.
    Example: - Clicking a Water cell displays:
    “A calm and stable environment. Reduces fire risk and slows unit movement.”
if (Physics.Raycast(ray, out hit))
{
    Vector3 hitPoint = hit.point;
    Vector2Int gridCoord = new Vector2Int(Mathf.RoundToInt(hitPoint.x / cellSpacing), Mathf.RoundToInt(hitPoint.z / cellSpacing));

    if (gridCoord.x >= 0 && gridCoord.x < gridSize && gridCoord.y >= 0 && gridCoord.y < gridSize)
    {
        Cell clickedCell = cells[gridCoord.x, gridCoord.y];

        string type = clickedCell.GetCellTypeByMaterial();
        Debug.Log($"Clicked Cell Type: {type}");
        string explainStr = "";
        if(type == "Water")
        {
            explainStr = "A calm and stable environment. \nReduces fire risk and slows unit movement.";
        }
    }
}

game screenshot

  1. Description
  • This project was built using the ADialogueSystem.unitypackage provided by the professor.
  • All dialogue content was organized and stored in a Google Sheet:
    Dialogue Google Sheet
  • However, since the professor’s server has been shut down, the dialogue system can no longer be executed in its current form.
  • As a result, the project demonstrates the dialogue structure and data setup, but the live dialogue progression is unavailable.
  1. Code

(1) Dialogue Manager.cs

  • The following code connects and retrieves the dialogue content from the Google Sheet.
if (useGoogleSheet) {
    // This will start the asyncronous calls to Google Sheets, and eventually
    // it will give a value to scc, and also call LoadInitialHistory().
    GoogleSheetSimpleConditionalConversation gs_ssc = gameObject.AddComponent<GoogleSheetSimpleConditionalConversation>();
    gs_ssc.googleSheetDocID = googleSheetDocID;
} else {
    scc = new SimpleConditionalConversation("data");
    LoadInitialSCCState();
}

Description of the Doctor dialogue within multiple dialogues

  • Press D to advance the conversation with the Doctor.
  • Stops automatically when countingTalkD reaches maxCountingTalkD (TalkingDoctorEnd()).
  • On the first line (countingTalkD == 0), runs SettingDefault() to init UI.
  • Fetches the next line via DialogueManager.scc.getSCCLine("C").
  • Alternates speakers each press:
    • Even countingTalkD → Man speaks (show left portrait, hide right).
    • Odd countingTalkD → Doctor speaks (show right portrait, hide left).
  • Updates dialogueText and increments countingTalkD every time.
public void TalkingDoctorContinue(){
		
    //First dialogue
    if (Input.GetKeyDown(KeyCode.D)){
        if (countingTalkD >= maxCountingTalkD){
            TalkingDoctorEnd();
        }
        else{
            if(countingTalkD == 0){
            SettingDefault();
            }
            string line = DialogueManager.scc.getSCCLine("C");
            if(countingTalkD % 2 == 0){
                textObj.SetActive(true);
                left.SetActive(true);
                right.SetActive(false);
                //dialoguePanel.material = dialoguePanelImageLeft;
                Debug.Log("Man says: " + line);
                dialogueText.text = line;
                countingTalkD++;
            }
            else{
                textObj.SetActive(true);
                left.SetActive(false);
                right.SetActive(true);
                //dialoguePanel.material = dialoguePanelImageRight;
                Debug.Log("Doctor says: " + line);
                dialogueText.text = line;
                countingTalkD++;
            }
        }
			
    }

}

(2) MainCharacterInteraction.cs

  • The system checks the distance between the player and each NPC.
  • If the player is within the defined interaction distance, the NPC becomes clickable.
  • When the player clicks with the mouse while inside this range, a raycast is fired from the main camera.
  • If the ray hits the correct NPC, the corresponding dialogue function (e.g., DialogTalkPolice()) is triggered.

This ensures that players can only start a conversation with an NPC when they are close enough and explicitly click on them.

private void CheckDistance(Transform target, string targetName, int idx)
{
    if (target == null) return;

    float distance = Vector3.Distance(male.position, target.position);
    //Debug.Log($"{targetName} : {distance}");
    if (distance <= interactionDistance)
    {
         //Debug.Log("inside distance!");
        if (Input.GetMouseButtonDown(0))
        {
            Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;

            if (Physics.Raycast(ray, out hit))
            {
                if (hit.transform == target)
                {
                    Debug.Log($"[SUCCESS] You clicked on {targetName} within {interactionDistance}m! (Actual Distance: {distance:F2})");
                        
                    switch(idx){
                        case 1:
                            gameManager.DialogTalkPolice();
                    }
                }
            }
        }
    }
}
  1. Platformer Game Overview
  • This project is a platformer-style game where the player advances by moving a character through the stage.
  1. Core Mechanics
  • Movement: Use WASD to move the player character.
  • Jumping: Press Spacebar to jump. Double jump is available.
  • Combat: Press X to shoot bullets and defeat monsters.
  • Key Collection: After defeating all monsters, collect the key to finish the level.
  • Monster AI: Using an FSM (Finite State Machine), enemies can shoot, chase, or patrol the player.
  • NPC Dialogue: A dialogue system allows the player to interact with NPCs.
  1. In-Game screens

Start Menu

  • At launch, you can choose between Tutorial and Game scenes.
  • Selection is done via mouse click.
    game screenshot

Tutorial Scene

  • Learn the basic controls and interactions required to play.
    game screenshot

Game Scene

  • Defeat all enemies to obtain a key.
  • Once the key is obtained, the game ends automatically.
    game screenshot
  1. Code

(1) PlatformerPlayerController.cs

  • Camera Controls
    • Right Mouse Button (Hold): When the right mouse button is held down, moving the mouse allows the camera to rotate:
      • Mouse X (Horizontal movement): Rotates the camera left and right.
      • Mouse Y (Vertical movement): Rotates the camera up and down (clamped between pitchMin and pitchMax).
    • Mouse Scroll Wheel: Scrolling adjusts the camera’s zoom distance:
      • Scroll Up: Zoom in
      • Scroll Down: Zoom out
        (Zoom distance is clamped between zoomMin and zoomMax.)
if(Input.GetMouseButton(1))
{
	float mouseX = Input.GetAxis("Mouse X");
	float mouseY = Input.GetAxis("Mouse Y");
	transform.Rotate(0,mouseX * mouseSensitivity, 0);

	pitch -= mouseY * mouseSensitivity;
	pitch = Mathf.Clamp(pitch, pitchMin, pitchMax);
	camPivot.localEulerAngles = new Vector3(pitch, 0, 0);
}

// Zoom in/out with mouse scroll wheel
float scroll = Input.GetAxis("Mouse ScrollWheel");
if (scroll != 0f)
{
	zoomDistance -= scroll * zoomSpeed;
	zoomDistance = Mathf.Clamp(zoomDistance, zoomMin, zoomMax);
}
  • Character Movement
    • Jump & Double Jump:

      • Press Spacebar to jump (yVelocity = jumpForce).
      • A double jump is available: pressing Spacebar again while in the air applies another jump force once.
    • Speed Limiting:

      • The character’s movement velocity is limited using velocity = Vector3.ClampMagnitude(velocity, 10); This ensures the maximum speed does not exceed 10.
    • Character Movement:

      • The final velocity is applied to the character controller with cc.Move(velocity * Time.deltaTime);.
if (Input.GetKeyDown(KeyCode.Space))
{
	yVelocity = jumpForce; // Apply jump force
                
}
doublejump = false;
.....
if (Input.GetKeyDown(KeyCode.Space) && !(doublejump)){
	doublejump = true;
	yVelocity = jumpForce; // Apply jump force
}

// Limit movement speed
velocity = Vector3.ClampMagnitude(velocity, 10);

// Move the character
cc.Move(velocity * Time.deltaTime);

(2) DialogueManager.cs

  • Origin: The dialogue structure is adapted from the People-1 project.
  • Why local strings? The professor’s ADialogueSystem.unitypackage server is closed, so Google-Sheet–based retrieval is unavailable.
    Instead, all dialogue lines are stored locally in a string array (e.g., line2[]).

How it works

  • Press D to advance the conversation.
public void TalkingNPCLevel2Continue(){

	if(Input.GetKeyDown(KeyCode.D)){
		if(countingTalkNPC2>=maxCountingTalkNPC2){
			TalkingNPCLevel2End();
		}
		else{
			//string line = DialogueManager.scc.getSCCLine("NPC2");
				
			if(countingTalkNPC2 % 2 ==0){
				textObj.SetActive(true);
				left.SetActive(true);
				right.SetActive(false);
				Debug.Log("NPC says: " + line2[countingTalkNPC2]);
				dialogueText.text = line2[countingTalkNPC2];
				countingTalkNPC2++;
			}
			else{
				textObj.SetActive(true);
				left.SetActive(false);
				right.SetActive(true);
				Debug.Log("Character says: "+line2[countingTalkNPC2]);
				dialogueText.text = line2[countingTalkNPC2];
				countingTalkNPC2++;
			}
		}
	}
}

(3) EnemyAI.cs

FSM State Implementation

  • This project includes an FSM (Finite State Machine) system implemented based on what I learned during the Game Programming II course in my undergraduate studies.
  • The FSM framework was applied to manage enemy behaviors such as patrolling, chasing, and attacking, ensuring clear state transitions and maintainable gameplay logic.
    • Example)
      • The enemy’s FSM state changes based on the distance to the player.
      • Patrol State: When the player is not detected (distance is greater than detectRange), the enemy remains in Patrol and moves toward patrolTarget.
      • Chase Transition: If the player comes within detectRange, the state switches from Patrol to Chase.
public enum State { Patrol, Chase, Attack, Dead }
.....
case State.Patrol:
	Patrol();
	if (distanceToPlayer <= detectRange)
	{
		currentState = State.Chase;
	}
break;
.......
void Patrol()
{
	animator.SetBool("Attack", false);
	animator.SetBool("Walk", true);
	MoveTo(patrolTarget);

	if (Vector3.Distance(transform.position, patrolTarget) < 1.5f)
	{
            SetRandomPatrolTarget();
	}
}
......
void SetRandomPatrolTarget()
{
	float randX = Random.Range(xMin, xMax);
	float randZ = Random.Range(zMin, zMax);
	patrolTarget = new Vector3(randX, transform.position.y, randZ);
}
  1. Known Issues & Fixes
  • Animation Transitions: Transitions are not fully smooth. The switch from Idle → Attack can look unnatural. - Character Rotation: The character may rotate too abruptly during movement in some cases.
  • Projectile Behavior: Bullet trajectories can behave oddly when firing.
  • Start Menu Input (Fixed): Pressing a button on the start screen could trigger a different button.
    • Fixed: The input handling on the start screen has been corrected.

License

  • All source code in this repository is licensed under the MIT License.
  • Some codes are adapted from Game Development I course materials (Graduate coursework). Details are listed below.
  • In addition, certain parts of the code were not directly taken from ChatGPT but were instead implemented independently with reference to its output.
  • Third-party assets (models, textures, sounds, fonts, etc.) remain under their original licenses. They may not be licensed for redistribution or in-game use in this repository.
    Please check the original source pages for specific license terms.

Images

Most of the images in the Pictures folder under each Prototype directory were generated by ChatGPT.
However, the player and NPC images in the platformer prototype were generated by ChatGPT based on existing asset images.
Please make sure to review the original asset licenses before using them.

Fonts

  • LegacyRuntime This project uses Unity’s built-in runtime font (LegacyRuntime.ttf). As of Unity 2022.2 and newer, the default built-in Arial.ttf reference was renamed to LegacyRuntime.ttf. Please refer to the Unity license terms for usage.

Asset Reference

Note: Third-party assets remain under their original licenses. They may not be licensed for redistribution or in-game use in this repository. Please review each source page for license terms before redistribution or in-game use.

(1) Breakout game assets

(2) People-1

(3) Platformer-1

About

Repository for game development class in Spring 2025

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published