To begin this week, I’ve had a thought about what else I’m going to need to program for the base part of my game. There’s been some debate in recent weeks about the use of AI in our work, but we’ve been told that it’s ok to use text-based AI services like ChatGPT to consult about problems we might be having with programming in Unity. With this knowledge, I’ve tried out the system and have begun compiling some potential solutions to each of the mechanics I wish to implement into my game, and will begin trying those out over the next few weeks.
For example, using this service, I have been able to create music that persists when moving between rooms (or scenes). With the Forest environment, you can now traverse from room to room whilst continuing to listen to the beautiful soundtrack. With this set up, I intend to try and get the interactable objects of my game set up so I can start creating a more interactable environment, as per the goals I set out for myself.
AudioManager Script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AudioManager : MonoBehaviour
{
private static AudioManager instance = null;
private void Awake()
{
if (instance == null)
{
instance = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
}
}
Since introducing ChatGPT to my list of resources, I’ve made lots of progress with the coding of Guardians. I’ve now set up a system where the player has a resource which they can spend to activate various objects across the game environment. For example, in Windfell Forest, there are craters which release updrafts of wind, allowing the player to reach greater heights. However, I’m not sure of these objects needing a cost to activate, and so I may opt to make it so that these objects remain inactive until the boss has been defeated, at which point they will activate and more paths will be open to the player.
InteractableObject Script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class InteractableObject : MonoBehaviour
{
[SerializeField] public float interactionCost = 5f;
[SerializeField] public float liftForce = 10f;
[SerializeField] private GameObject player;
// Start is called before the first frame update
void Start()
{
player = GameObject.FindWithTag("Player");
}
private void OnMouseDown()
{
ActivateObject();
}
void ActivateObject()
{
Debug.Log("Object activated!");
PlayerMeter PlayerMeter = player.GetComponent<PlayerMeter>();
if (PlayerMeter.points >= interactionCost)
{
PlayerMeter.points -= interactionCost;
// Activate object
Rigidbody2D rb = player.GetComponent<Rigidbody2D>();
rb.AddForce(Vector2.up * liftForce, ForceMode2D.Impulse);
}
else
{
// Play sound or show message indicating the player doesn't have enough points
}
}
}
I’ve also now been able to give the player Health points, and created a UI system showing that. Alongside this, I have developed a simple Enemy script which gives them Health points, and allows them to deal damage when coming into contact with the player, dealing 1 point of damage. The player is also able to attack the enemy (though there’s no animation showing this, so it’s hard to tell), and earn points to activate various objects in the environment, too.
PlayerHealth Script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerHealth : MonoBehaviour
{
public int startingLives = 5; // The number of lives the player starts with
public int currentLives; // The number of lives the player starts with
public GameObject[] lifeIcons; // An array of GameObjects representing the player's lives
// Set the player's initial health
void Start()
{
currentLives = startingLives;
UpdateLifeIcons();
}
// Update the player's health display
void UpdateLifeIcons()
{
// Disable all life icons
foreach (GameObject lifeIcon in lifeIcons)
{
lifeIcon.SetActive(false);
}
// Enable the correct number of life icons based on the player's current health
for (int i = 0; i < currentLives; i++)
{
lifeIcons[i].SetActive(true);
}
}
// Take damage and update the health display
public void TakeDamage(int amount)
{
currentLives -= amount;
UpdateLifeIcons();
// Check if the player has run out of lives
if (currentLives <= 0)
{
Die();
}
}
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.CompareTag("Enemy"))
{
TakeDamage(1);
}
}
// Method to handle the player's death
void Die()
{
// Find the nearest spawn point to the player's current position
GameObject[] spawnPoints = GameObject.FindGameObjectsWithTag("SpawnPoint");
GameObject nearestSpawnPoint = null;
float minDistance = float.MaxValue;
foreach (GameObject spawnPoint in spawnPoints)
{
float distance = Vector3.Distance(transform.position, spawnPoint.transform.position);
if (distance < minDistance)
{
nearestSpawnPoint = spawnPoint;
minDistance = distance;
}
}
// Move the player to the nearest spawn point
transform.position = nearestSpawnPoint.transform.position;
// Reset the player's health
currentLives = startingLives;
UpdateLifeIcons();
}
}
PlayerAttack Script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerAttack : MonoBehaviour
{
public float attackRange = 2f; // The range of the player's attack
public int attackDamage = 10; // The amount of damage the player's attack does
public GameObject enemy;
private void Start()
{
//enemy = GameObject.FindWithTag("Enemy");
}
// Update is called once per frame
void Update()
{
// Check for the attack input (e.g. pressing a button)
if (Input.GetKeyDown(KeyCode.P))
{
// Perform the attack
Attack();
}
}
// Perform the attack
void Attack()
{
// Find all enemies within the attack range
Collider2D[] hitColliders = Physics2D.OverlapCircleAll(transform.position, attackRange);
foreach (Collider2D hitCollider in hitColliders)
{
// Check if the object has an EnemyHealth component
Enemy enemy = hitCollider.GetComponent<Enemy>();
if (enemy != null)
{
// Deal damage to the enemy
enemy.TakeDamage(attackDamage);
}
}
}
// Draw a gizmo to show the attack range in the scene view
void OnDrawGizmosSelected()
{
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(transform.position, attackRange);
}
}
Enemy Script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Enemy : MonoBehaviour
{
public int maxHealth = 3;
public int currentHealth;
[SerializeField] private GameObject player;
[SerializeField] private PlayerMeter playerMeter; // reference to the PlayerMeter script
public GameObject enemy;
private void Start()
{
// get the PlayerMeter component on the Player object
playerMeter = GameObject.FindGameObjectWithTag("Player").GetComponent<PlayerMeter>();
currentHealth = maxHealth;
}
public void TakeDamage(int damage)
{
currentHealth -= damage;
if (currentHealth <= 0)
{
playerMeter.points++; // increment the score by 1
Destroy(gameObject);
}
}
private void OnTriggerEnter2D(Collider2D collision)
{
// check if the collider is the player's attack
if (collision.gameObject.CompareTag("PlayerAttack"))
{
// get the damage from the PlayerAttack script and pass it to TakeDamage
int damage = collision.gameObject.GetComponent<PlayerAttack>().attackDamage;
TakeDamage(damage);
}
}
}
However, when entering another room, and coming back to the same room, the player’s health returns to 5 points, even if they had been hit previously, and the enemy respawns, too. I’m currently looking into this issue, and it seems to be resolvable by using game saves, to save the states of various objects in the game, but it’s something that’s going to take further researching and testing to see if I can implement it. For now, though, I am happy with the progress that’s been made this week.