3D Unity Game (Homeward Beta)

This game is supposed to be an RPG with mostly exploration. There will hopefully be some combat, and the mechanics will vary slightly based on the character’s initial choice of Character’s Race. This Race will most heavily affect leveling and EXP Gain to be reliant on a single method: survival time, combat, or exploration, and discovery. It is built using the Unity Game Engine and Blender for graphics.

Engineer

Isaiah D

Area of Interest

Software Engineering/ Music Theory

School

Pathways Through Technology Early College High School

Grade

Incoming Junior

Fifth Milestone: Animation

My fifth milestone was to import a model from mixamo and animate it using the Unity Animator. It uses four basic animation loops: idle, jumping, walking, and running. Each of these states can freely transition between each other based on parameters that are booleans. These booleans are modified in code so that they respond to keypresses and input from the player. For instance, if any motion key is pressed, the boolean isWalking is set to true, which triggers the walking cycle. Such booleans operate all animations. The next step would be to add maybe a crouch and fix the jerkiness of the midair animation.

Fourth Milestone: GUI

My fourth milestone was to develop a fully operational GUI, beginning with the main menu, and culminating with a nearly complete HUD. The main menu features the basic buttons to initiate the game, quit, or check options. The HUD displays character health, mana, and level, along with buttons that trigger some pseudo dropdowns that would view character stats, equipment, quests, etc. As a next step, I would love to develop an operational map system and quest system to display within their respective dropdowns, as well as to make the main menu pop a little more with perhaps some graphic.

Third Milestone: NPCs

My third milestone was to develop the first non-hostile NPC. It wanders within a certain range of its home point, which is represented by any of the huts surrounding it. It always remains within a short distance of the house that is set as its home point. The player is not able to interact or engage with them as of yet, but the hope is to import fully animated NPC characters each with their own dialogues and behaviors, as well as make the player able to talk to or engage with them. As of now, they use a general script that makes them wander.

Second Milestone: Enemies and Hazards

My second milestone was developing a test scene to set up scripts for enemies and hazards within the game. All characters – player or otherwise – are controlled by a Unity Character Controller. They use the move method combined with Time.deltaTime in order to transition. In the sample scene, the cube serves as a hazard that decreases player health by 5 every hit. The cylinder is an enemy that follows after the player. If it gets stuck behind an obstacle it can’t pass after 100 frames, it jumps using a move that takes gravity into account. The only issue is that it can follow the player almost exactly, and tends to jump on top of them.

First Milestone: Setting up the First Scene

My First Milestone for the game is to set up the first scene. The scene would ideally have a player choose between three doors to choose their race. The door they select would change some values for the player and then teleport them to the next location: their home. The doors required access to the player game object and it’s location.

Coming soon!

Basic Initial Scene of the Game

To the left: drawings for some models.

To the right: actual models in blender.

Code Files

Main Menu Control

Code that operates the main menu.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class MainMenuManager : MonoBehaviour
{
public Button playButton;
public Button optionButton;
public Button quitButton;
public Button backButton;
public GameObject MainMenu;
public GameObject options;
// Start is called before the first frame update
void Start()
{
playButton.onClick.AddListener(loadCharacterBuilder);
optionButton.onClick.AddListener(loadOptions);
quitButton.onClick.AddListener(closeGame);
backButton.onClick.AddListener(backToMain);
}
// Update is called once per frame
void Update()
{
}
void loadCharacterBuilder(){
SceneManager.LoadScene("Choice");
}
void loadOptions(){
MainMenu.SetActive(false);
options.SetActive(true);
}
void closeGame(){
Debug.Log("Quitting.");
Application.Quit();
}
void backToMain(){
options.SetActive(false);
MainMenu.SetActive(true);
}
}
Game Control Script

Code that contains basic world rules and manages the active GUI elements.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public enum Race { Chronomian, Sapientan, Hemathian };
public enum Element { Null ,Wood, Fire, Water, Metal, Earth };
public class GameControl : MonoBehaviour
{
public Dropdown element;
// Info Bars
public Slider healthBar;
public Slider manaBar;
public Slider expProgress;
public Text HealthLabel;
public Text ManaLabel;
public Text expLabel;
//Status Displays
public Text statusPointAmount;
public Text playerHealthStat;
public Text playerManaStat;
public Text playerSpeedStat;
public Text playerStrengthStat;
// HUD Panels
public GameObject quests;
public GameObject stats;
public GameObject map;
public GameObject equips;
public GameObject pausePanel;
//HUD Buttons to Toggle Panels
public Button questButton;
public Button statButton;
public Button mapButton;
public Button equipButton;
//Stat Upgrade and Downgrade Buttons
public Button healthPlus;
public Button healthMinus;
public Button manaPlus;
public Button manaMinus;
public Button speedPlus;
public Button speedMinus;
public Button strengthPlus;
public Button strengthMinus;
public Button pause;
public Button resume;
public Button main;
public Element playerElement;
public float doubleTapTime = 0.5f;
public float dashRecharge = 2.0f;
[Header("World Stats + Rules")]
[Tooltip("Force of Gravity on ALL OBJECTS (Default -9.81)")] public float gravitationalForce = -9.81f;
[Tooltip("Stat Increase from stat points for speed")] public float speedGain = 0.05f;
[Tooltip("Stat increase from everything else.")] public int statBoost = 5;
[Header("Player Stats")]
[Tooltip("Player Movement Speed")] public float playerSpeed = 2.0f;
[Tooltip("Vertical Jump Height")] public float playerJump = 2.0f;
[Tooltip("Character Base Health")] public int playerHealth = 50;
[Tooltip("Character Base Mana")] public int playerManaPoint = 50;
public int playerStrength = 10;
[Tooltip("Character Status Points Gained with each Level")] public int statPointGains = 10;
[Tooltip("Current character stat points")] public int statPointCount = 0;
[Tooltip("Character Level")] public int level = 0;
[Tooltip("Character EXP")] public int expCurrent;
[Tooltip("EXP Needed for the next level.")] public int expNeeded;
public GameObject player;
public Race playerRace = Race.Sapientan;
public bool raceChosen = false;
void Start()
{
questButton.onClick.AddListener(questPanelDisplay);
statButton.onClick.AddListener(statsPanelDisplay);
mapButton.onClick.AddListener(mapPanelDisplay);
equipButton.onClick.AddListener(equipPanelDisplay);
healthPlus.onClick.AddListener(HealthUpgrade);
healthMinus.onClick.AddListener(HealthDowngrade);
manaPlus.onClick.AddListener(ManaUpgrade);
manaMinus.onClick.AddListener(ManaDowngrade);
speedPlus.onClick.AddListener(SpeedUpgrade);
speedMinus.onClick.AddListener(SpeedDowngrade);
strengthPlus.onClick.AddListener(StrengthUpgrade);
strengthMinus.onClick.AddListener(StrengthDowngrade);
pause.onClick.AddListener(PauseMenu);
resume.onClick.AddListener(resumeGame);
}
// Update is called once per frame
void Update()
{
if (element.value==0){
playerElement = Element.Null;
}
else if (element.value==1){
playerElement = Element.Wood;
}
else if (element.value == 2)
{
playerElement = Element.Fire;
}
else if (element.value == 3)
{
playerElement = Element.Water;
}
else if (element.value == 4)
{
playerElement = Element.Metal;
}
else if (element.value == 5)
{
playerElement = Element.Earth;
}
else{
playerElement = Element.Null;
}
healthBar.maxValue = playerHealth;
healthBar.value = player.GetComponent<PlayerManaging>().ActualHealth;
HealthLabel.text = ("Health: "+player.GetComponent<PlayerManaging>().ActualHealth+"/"+playerHealth);
manaBar.maxValue = playerManaPoint;
manaBar.value = player.GetComponent<PlayerManaging>().actualMana;
ManaLabel.text = ("Mana: "+player.GetComponent<PlayerManaging>().actualMana+"/"+playerManaPoint);
expProgress.maxValue = expNeeded;
expProgress.value = expCurrent;
expLabel.text = ("EXP: "+expCurrent+"/"+expNeeded);
statusPointAmount.text = statPointCount.ToString();
playerHealthStat.text = playerHealth.ToString();
playerManaStat.text = playerManaPoint.ToString();
float playerSpeedDisplay = 100*playerSpeed;
playerSpeedStat.text = playerSpeedDisplay.ToString();
playerStrengthStat.text = playerStrength.ToString();
if (level != 0 || level>0) {
expNeeded = (int) Mathf.Floor(20*Mathf.Pow(1.05f,(level-1)));
}
else {
expNeeded = 0;
}
if (expCurrent>=expNeeded){
expCurrent -= expNeeded;
level += 1;
statPointCount += statPointGains;
}
if(Input.GetKeyDown(KeyCode.Alpha1)){
questPanelDisplay();
}
if (Input.GetKeyDown(KeyCode.Alpha2)){
statsPanelDisplay();
}
if (Input.GetKeyDown(KeyCode.Alpha3)){
mapPanelDisplay();
}
if(Input.GetKeyDown(KeyCode.Alpha4)){
equipPanelDisplay();
}
}
void questPanelDisplay(){
stats.SetActive(false);
map.SetActive(false);
equips.SetActive(false);
if (quests.activeInHierarchy){
quests.SetActive(false);
}
else{
quests.SetActive(true);
}
}
void statsPanelDisplay(){
quests.SetActive(false);
map.SetActive(false);
equips.SetActive(false);
if (stats.activeInHierarchy){
stats.SetActive(false);
}
else{
stats.SetActive(true);
}
}
void mapPanelDisplay(){
quests.SetActive(false);
stats.SetActive(false);
equips.SetActive(false);
if (map.activeInHierarchy){
map.SetActive(false);
}
else{
map.SetActive(true);
}
}
void equipPanelDisplay(){
quests.SetActive(false);
stats.SetActive(false);
map.SetActive(false);
if (equips.activeInHierarchy){
equips.SetActive(false);
}
else{
equips.SetActive(true);
}
}
void HealthUpgrade(){
if(statPointCount>0){
statPointCount -= 1;
playerHealth += statBoost;
player.GetComponent<PlayerManaging>().ActualHealth +=statBoost;
}
}
void HealthDowngrade(){
if (playerHealth>50){
statPointCount += 1;
playerHealth -= statBoost;
player.GetComponent<PlayerManaging>().ActualHealth -=statBoost;
}
}
void ManaUpgrade(){
if (statPointCount>0){
statPointCount -= 1;
playerManaPoint += statBoost;
player.GetComponent<PlayerManaging>().actualMana +=statBoost;
}
}
void ManaDowngrade(){
if (playerManaPoint>50){
statPointCount += 1;
playerManaPoint -= statBoost;
player.GetComponent<PlayerManaging>().actualMana -=statBoost;
}
}
void SpeedUpgrade(){
if (statPointCount>0){
statPointCount -= 1;
playerSpeed +=speedGain;
}
}
void SpeedDowngrade(){
if (playerSpeed>2.0f){
statPointCount += 1;
playerSpeed -= speedGain;
}
}
void StrengthUpgrade(){
if (statPointCount>0){
statPointCount -= 1;
playerStrength += statBoost;
}
}
void StrengthDowngrade(){
if(playerStrength>10){
statPointCount += 1;
playerStrength -= statBoost;
}
}
void PauseMenu()
{
pausePanel.SetActive(true);
}
void resumeGame(){
pausePanel.SetActive(false);
}
}
Player Control Script

Code that manages the player. It draws data from the GameSystemManaging.cs file.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class PlayerManaging : MonoBehaviour
{
public GameObject GameManager;
private CharacterController controller;
private Vector3 playerVelocity;
private bool groundedPlayer;
public bool isLiving = true;
[Header("Player Management")]
[Tooltip("Player Movement Speed")]
public float playerSpeed;
[Tooltip("Players Jumping Height")]
public float jumpHeight;
[Tooltip("Force of Gravity (-9.81 default)")]
public float gravityValue;
[Tooltip("Character Initial Health")]
public int baseHealth;
public int ActualHealth;
[Tooltip("Character Initial Magic Power/Points")]
public int baseMana;
public int actualMana;
[Tooltip("Character Race (Determines EXP Gain Style as Time, Knowledge, or Blood)")]
public Race characterRace;
public bool raceChosen = false;
private void Start()
{
playerSpeed = GameManager.GetComponent<GameControl>().playerSpeed;
jumpHeight = GameManager.GetComponent<GameControl>().playerJump;
gravityValue = GameManager.GetComponent<GameControl>().gravitationalForce;
baseHealth = GameManager.GetComponent<GameControl>().playerHealth;
baseMana = GameManager.GetComponent<GameControl>().playerManaPoint;
ActualHealth = baseHealth;
actualMana = baseMana;
controller = gameObject.GetComponent<CharacterController>();
if (!controller) {
controller = gameObject.AddComponent<CharacterController>();
}
}
void Update()
{
groundedPlayer = controller.isGrounded;
if (groundedPlayer && playerVelocity.y < 0)
{
playerVelocity.y = 0f;
}
// EXP-Level Conversion
Vector3 move = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
controller.Move(move * Time.deltaTime * playerSpeed);
if (move != Vector3.zero)
{
gameObject.transform.forward = move;
}
// Changes the height position of the player..
if (Input.GetButtonDown("Jump") && groundedPlayer)
{
playerVelocity.y += Mathf.Sqrt(jumpHeight * -3.0f * gravityValue);
}
playerVelocity.y += gravityValue * Time.deltaTime;
controller.Move(playerVelocity * Time.deltaTime);
}
}
NPC Management Scripts

Code that moderates NPC behavior, including enemies.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class NPCScript : MonoBehaviour
{
public float moveSpeed = 2.0f;
public bool colliding;
// Start is called before the first frame update
public float gravityValue = -9.81f;
public int counter = 90;
public int jumpCounter = 0;
public bool isHostile;
public bool playerSpotted;
public bool obstacle = false;
public float jumpHeight = 2.0f;
public int x;
public int z;
public GameObject House;
public GameObject player;
public Vector2 center;
public Vector2 randomWaypoint;
public Vector3 waypoint;
public Vector2 playerLoc = new Vector2(0,0);
public Vector2 selfLoc;
public Vector3 direction;
public Vector3 move;
public Vector3 npcVelocity;
CharacterController controller;
void Start()
{
player = GameObject.Find("Player");
controller = gameObject.GetComponent<CharacterController>();
if (!controller) {
controller = gameObject.AddComponent<CharacterController>();
}
}
// Update is called once per frame
void Update()
{
if (controller.isGrounded && npcVelocity.y < 0)
{
npcVelocity.y = 0f;
npcVelocity.x = 0f;
npcVelocity.z = 0f;
}
playerLoc = new Vector2(player.GetComponent<Transform>().position.x, player.GetComponent<Transform>().position.z);
selfLoc = new Vector2(this.GetComponent<Transform>().position.x, this.GetComponent<Transform>().position.z);
counter += 1;
if (isHostile){
//Code that searches for player, will then change variableplayerSpotted to true
}
center = new Vector2(House.GetComponent<Transform>().position.x,House.GetComponent<Transform>().position.z);
if (counter==100){
randomWaypoint = (Random.insideUnitCircle);
waypoint = new Vector3(randomWaypoint.x, 0, randomWaypoint.y);
counter = 0;
}
if (isHostile && playerSpotted) {
direction = playerLoc - selfLoc;
}
else {
direction = randomWaypoint - selfLoc;
}
move = waypoint * Time.deltaTime;
controller.Move(move * moveSpeed);
if (move != Vector3.zero)
{
gameObject.transform.forward = move;
}
if (obstacle){
jumpCounter += 1;
}
if (jumpCounter >= 100 && obstacle && controller.isGrounded)
{
npcVelocity.y += Mathf.Sqrt(jumpHeight * -3.0f * gravityValue);
jumpCounter = 0;
}
npcVelocity.y += gravityValue * Time.deltaTime;
controller.Move(npcVelocity * Time.deltaTime);
}
void OnControllerColliderHit(ControllerColliderHit hit) {
obstacle = true;
}
}
view raw NPCManaging.cs hosted with ❤ by GitHub
Hazard Script

Code that sets up hazards to actually deal damage.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class HazardScript : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
void OnTriggerEnter(Collider other){
if (other.GetComponent<PlayerManaging>().isLiving) {
other.GetComponent<PlayerManaging>().ActualHealth -= 5;
}
}
}
view raw HazardScript.cs hosted with ❤ by GitHub
Door Script

Code for the doors in the game, which are heavily related to choice.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class ChronomianDoorInfo : MonoBehaviour
{
// Start is called before the first frame update
public Race doorRace = Race.Chronomian;
public bool doorAccessible;
public bool doorModding;
public bool doorEntered;
public GameObject player;
public GameObject gameManager;
void Start()
{
player = GameObject.Find("Player");
gameManager = GameObject.Find("Game Manager");
}
// Update is called once per frame
void Update()
{
if (doorAccessible) {
if (doorModding) {
if (Input.GetKey(KeyCode.Y)){
doorEntered = true;
}
}
}
}
void FixedUpdate() {
if (doorEntered){
gameManager.GetComponent<GameControl>().raceChosen = true;
gameManager.GetComponent<GameControl>().playerRace = Race.Chronomian;
}
}
void OnTriggerStay(Collider other) {
if (other.name == "Player") {
Debug.Log("You see a door.");
if (other.GetComponent<PlayerManaging>().raceChosen && other.GetComponent<PlayerManaging>().characterRace.Equals(doorRace)==false) {
Debug.Log("You are not the race that can open this door.");
doorAccessible = false;
}
else if (other.GetComponent<PlayerManaging>().raceChosen && other.GetComponent<PlayerManaging>().characterRace.Equals(doorRace)) {
Debug.Log("Press Y to enter.");
doorAccessible = true;
doorModding = false;
}
else
{
Debug.Log("This is a Door of Time. Enter (press y) to become:");
Debug.Log(doorRace);
if (Input.GetKey(KeyCode.Y))
{
Debug.Log("Presto change-o!");
gameManager.GetComponent<GameControl>().raceChosen = true;
gameManager.GetComponent<GameControl>().playerRace = Race.Chronomian;
SceneManager.LoadScene("GameWorld");
}
}
}
}
void OnTriggerExit(Collider other) {
doorAccessible = false;
doorModding = false;
}
}
view raw DoorExample.cs hosted with ❤ by GitHub

Start typing and press Enter to search

Bluestamp Engineering