PROJECT DESCRIPTION

Stranded Alone is another title that was created for a college assignment. We were tasked to research and create assets for a suitable audience that have an interest in Sci Fi and the British nationality. 

 

This game was my most exaggerated game yet, more than ROLLCORE and therfore will plan to keep updating this title. At the moment is currently playable and has minimal bugs and issues which will be fixed in due time. 

 

The aim of the game is to power up a base that you find yourself in by going round and gathering resources to help craft materials, resources, tools and more while also managing health, hunger and thirst.

 

Stranded Alone is intended to be played on 1920x1080 or any 16:9 Aspect Ratio

 

Windows/Linux Builds are available on Itch.io

! PLAY HERE !

Power the base, reach beyond...

GAME DESIGN DOCUMENT

HIGHLIGHT IDEAS GENERATION

 

Resource Gathering

 

This flowchart covers the most parts of the Resource Gathering mechanic, it shows my idea on how to go about performing this mechanic in terms of looking at a resource and it is showing UI for which I will use Look At(player) and animation controllers to help create a seamless world space UI that details what the player is looking at.

 

Crafting

 

These two flowcharts above show how I would be able to implement the feature that allows the camera to focus on the crafting station when interacted with and show its UI, locking movement and rotation from the player to ensure that the view of the station is not obscured in any way due to movement and rotation. I have implemented features like this before in my work with projects, especially in Drowsy Yokai, allowing the player to click on an interactable and proceed to focus closer to that element and inspect it. I felt like reusing this as it would increase the immersive element the player using the station. It makes the user and player feel like they are looking onto the station. To help me create this, I would have to use cinemachine to increase the control I have over the camera’s movement with the help of transition blends and camera priority. The focus and unfocus methos controls when the UI on the camera station activates and when it locks/ unlocks the movement and rotation of the player. The script can be seen below.

 

For the recipes, I decided to make another set of scriptable objects, by referencing an array of Items (the other scriptable object that holds that data of all resources and tools), as well as the item rewarded after the craft as an Item object, as everything the player crafts will be added to their inventory. When a certain recipe is clicked, it checks through the whole inventory to see if it has the items that are in the recipe and stores the index position in a temporary array, if there are more items in the recipe, it continues along with the inventory to attempt to find that item in the inventory and stores them to the array If they have been found. If all items have been found, it goes through all the elements within the temporary array and removes all those items from the inventory, after that is complete, it goes ahead with adding the crafted item to the inventory

 

The images above show the visor UI element that I stuck to the mask, helping to increase the immersion that your vision is somewhat restricted due to you wearing a mask, on the image to the right above here shows the layout of the inventory/menu that will help guide the player. It shows the inventory grid to the left, and will be able to show you details on the right once you hover over a resource that you have gathered, it also contains tabs for a Map and your settings and quit button which will allow the player to switch  the settings while in gameplay as well as quit more efficiently. I really liked how it turned out, and it was quite challenging to implement, and its not 100% complete as I still need to implement the resource information as well to drop materials and finally to check if the inventory is full or not which will all be implemented in the future.

HIGHLIGHT CODE SNIPPETS


          public class InventoryManager : MonoBehaviour
{
    private Animator _animator;
    public bool _activeInventory;
    public bool LockInventory;
    private FirstPersonController _characterController;
    private RawImage[] _resourcesIcons;
    private bool _hovering;
    public Inventory[] items;
    private int _hoverindex;
    private Objectives _mission;
    private PlayerStats _playerStats;

    [SerializeField] private Transform _recepieGrid;
    [SerializeField] public Texture _emptySlot;

    [SerializeField] private TMP_Text _itemName;
    [SerializeField] private TMP_Text _itemDescription;

    public CanvasGroup GameplayCanvasGroup;

    private void Start()
    {
        _animator = _recepieGrid.transform.parent.GetComponentInParent();
        _characterController = GetComponent();
        _mission = GetComponent();
        _playerStats = GetComponent();
        _resourcesIcons = new RawImage[items.Length];

        for (int i = 0; i < _recepieGrid.childCount; i++)
        {
            _resourcesIcons[i] = _recepieGrid.GetChild(i).GetComponent();
            _resourcesIcons[i].texture = _emptySlot;
        }
    }

    public void ConsumeItem()
    {
        if (!_hovering)
        { return; }

        if (items[_hoverindex].Consumable)
        {
            _playerStats.ConsumeFoodDrink(items[_hoverindex]);
            items[_hoverindex] = null;
            _resourcesIcons[_hoverindex].texture = _emptySlot;
            _hovering = false;
        }
    }

    public void AddItem(Inventory newItem)
    {
        if (!_mission.Missions[2].Achieved && newItem.Name == "Common Fuse")
        {
            _mission.Missions[2].Achieved = true;
            _mission.ChangeMission(_mission.Missions[2]);
        }
        
        for (int i = 0; i < items.Length; i++)
        {
            if (items[i] == null)
            {
                items[i] = newItem;
                _resourcesIcons[i].texture = newItem.Texture;
                return;
            }
        }
    }

    public void RemoveItem(Inventory itemToRemove)
    {
        for (int i = 0; i < items.Length; i++)
        {
            if (items[i] == itemToRemove)
            {
                items[i] = null;
                _resourcesIcons[i].texture = _emptySlot;
                return;
            }
        }
    }
    public void Death()
    {
        for (int i = 0; i < items.Length; i++)
        {
            if (items[i] == null) continue;

            Instantiate(items[i].Prefab, transform.position, transform.rotation);
            RemoveItem(items[i]);
        }
    }

    public void DropItem()
    {
        if (!_hovering)
        { return; }

        Instantiate(items[_hoverindex].Prefab, transform.position, transform.rotation);

        items[_hoverindex] = null;
        _resourcesIcons[_hoverindex].texture = _emptySlot;
        _hovering = false;
    }

    public void ToggleInventory()
    {
        if(LockInventory)
        { return; }

        if (!_activeInventory)
        {
            _animator.SetTrigger("Activate");
            _activeInventory = true;
            GameplayCanvasGroup.alpha = 0;
            _characterController.enabled = false;
            Cursor.lockState = CursorLockMode.None; 
        }
        else
        {
            _animator.SetTrigger("Deactivate");
            _activeInventory = false;
            GameplayCanvasGroup.alpha = 1;
            _characterController.enabled = true;
            Cursor.lockState = CursorLockMode.Locked;
        }
    }
    public void SetIndex(GameObject child)
    {
        for (int i = 0; i < _recepieGrid.childCount; i++)
        {
            if( _recepieGrid.GetChild(i).gameObject == child )
            {
                
                _hoverindex = i;
            }
        }
    }

    public void Hover(RawImage texture)
    {
        if (texture.texture == _emptySlot)
        { return;  }

        _hovering = true;

        for (int i = 0; i < items.Length; i++)
        {
            if(items[i] == null)
            { continue; }

            if (texture.texture == items[i].Texture)
            {
                _itemName.text = items[i].Name;
                _itemDescription.text = items[i].Description;
                return;
            }
        }
    }

    public void UnHover()
    {
        _itemName.text = "";
        _itemDescription.text = "";

        _hovering = false;
    }

    public bool FullInventoryCheck()
    {
        foreach(Inventory item in items)
        {
            if (item == null)
            { return false; }
        }

        return true;
    }
}
        

      public class Crafting : MonoBehaviour
{
    [SerializeField] private TMP_Text _recepieText;

    private InventoryManager _inventoryManager;

    private bool _inFocus = false;

    private CinemachineCamera _focusCamera;
    private CinemachineCamera _playerCamera;

    private Animator _animator;
    private FirstPersonController _firstPersonController;
    private PlayerTools _playerTools;

    void Start()
    {
        _focusCamera = GetComponentInChildren();
        _playerCamera = GameObject.FindGameObjectWithTag("Cinemachine").GetComponent();
        _firstPersonController = GameObject.FindGameObjectWithTag("Player").GetComponent();
        _inventoryManager = GameObject.FindGameObjectWithTag("Player").GetComponent();
        _playerTools = GameObject.FindGameObjectWithTag("Player").GetComponent();
        _animator = GetComponentInChildren();
    }

    public void Craft(RecepieObject recepie)
    {
        int NumberOfResourcesNeeded = recepie.Recepie.Length;
        int CurrentResourceCount = 0;

        int[] index = new int[NumberOfResourcesNeeded];

        for (int i = 0; i < _inventoryManager.items.Length; i++)
        {
            if (Array.Exists(recepie.Recepie, element => element == _inventoryManager.items[i]))
            {
                index[CurrentResourceCount] = i;
                CurrentResourceCount++;

                if (CurrentResourceCount == NumberOfResourcesNeeded)
                {
                    foreach (int x in index)
                    {  
                        _inventoryManager.RemoveItem(_inventoryManager.items[x]);
                    }
                    _inventoryManager.AddItem(recepie.Item);
                    CurrentResourceCount = 0;
                    break;
                }
            }
        }
    }

    public void Focus()
    {
        if (_inFocus)
        { 
            UnFocus();
            return;
        }
        _inventoryManager.GameplayCanvasGroup.alpha = 0.15f;
        _inventoryManager.LockInventory = true;
        _playerCamera.Priority = 0;
        _focusCamera.Priority = 1;

        _firstPersonController.enabled = false;
        _animator.SetTrigger("Activate");
        Cursor.lockState = CursorLockMode.None;

        _inFocus = true;
        _playerTools.Lock = true;
    }

    public void UnFocus()
    {
        _inventoryManager.GameplayCanvasGroup.alpha = 1f;
        _inventoryManager.LockInventory = false;
        _playerCamera.Priority = 1;
        _focusCamera.Priority = 0;

        _firstPersonController.enabled = true;
        _animator.SetTrigger("Deactivate");
        Cursor.lockState = CursorLockMode.Locked;

        _inFocus = false;
        _playerTools.Lock = false;
    }

    public void Hover(RecepieObject recepie)
    {
        var builder = new StringBuilder();
        
        for(int i = 0; i < recepie.Recepie.Length; i++)
        {
            builder.Append(recepie.Recepie[i].name + ", ");
        }

        string result = builder.ToString();

        _recepieText.text = result;
    }
}
  

          using JetBrains.Annotations;
using StarterAssets;
using UnityEngine;
using TMPro;
using Unity.Cinemachine;

public class PlayerStats : MonoBehaviour
{
    private Vector3 _spawnPos;

    private FirstPersonController _firstPersonController;
    private InventoryManager _inventoryManager;
    private CharacterController _charctercontroller;
    [SerializeField] private CinemachineCamera _cinemachineCamera;
    private bool _dead;
    private bool _starving;

    public int _health;
    public int _maxHealth;
    public int _hunger;
    public int _thirst;
    public GameObject _deathUI;

    public TMP_Text HealthValue, ThirstValue, HungerValue;

    void Start()
    {
        _health = 100;
        _maxHealth = 100;
        _thirst = 50;
        _hunger = 75;
        _deathUI.SetActive(false);

        InvokeRepeating("HungerThirstDecrease", 100f, 10f);
        _spawnPos = transform.position;
        _firstPersonController = GetComponent();
        _inventoryManager = GetComponent();
        _charctercontroller = GetComponent();

        HealthValue.text = _health.ToString();
        HungerValue.text = _hunger.ToString();
        ThirstValue.text = _thirst.ToString();
    }

    private void HungerThirstDecrease()
    {
        if (_dead) return;

        if(_hunger > 0)
        { _hunger -= 1; HungerValue.text = _hunger.ToString(); }
        else if(!_starving)
        {
            _hunger = 0;
            HungerValue.text = _hunger.ToString();
            InvokeRepeating("PassiveDamage", 5f, 10f);
        }

        if (_thirst > 0)
        { _thirst -= 1; ThirstValue.text = _thirst.ToString(); }
        else if(!_starving)
        {
            _thirst = 0;
            ThirstValue.text = _thirst.ToString();
            InvokeRepeating("PassiveDamage", 5f, 10f);
        }
    }

    public void StopPassiveHealing()
    {
        CancelInvoke(nameof(PassiveHealing));
    }

    public void PassiveHealing()
    {
        if(_health >= 100)
        {
            _health = 100;
            return;
        }
        _health = _health + 5;
        HealthValue.text = _health.ToString();
    }
    public void PassiveDamage()
    {
        _starving = true;
        _health -= 5;

        HealthValue.text = _health.ToString();

        if (_health <= 0)
        {
            _dead = true;
            death();
        }

        if (_hunger > 0 && _thirst > 0)
        {
            CancelInvoke(nameof(PassiveDamage));
            _starving = false;
        }
    }

    public void ConsumeFoodDrink(Inventory Consumable)
    {
        _hunger = _hunger + Consumable.hungerAdded;
        _thirst = _thirst + Consumable.thirstAdded;

        if (_hunger > 100) _hunger = 100;
        if (_thirst > 100) _thirst = 100;

        HungerValue.text = _hunger.ToString();
        ThirstValue.text = _thirst.ToString();

        if (_hunger > 90 && _thirst > 75)
        {
            InvokeRepeating("PassiveHealing", 0f, 20f);
        }

        
    }

    public void death()
    {
        CancelInvoke(nameof(PassiveDamage));
        _deathUI.SetActive(true);
        _firstPersonController.enabled = false;
        _charctercontroller.enabled = false;
        _inventoryManager.Death();
        Cursor.lockState = CursorLockMode.None;
        _dead = true;
        _inventoryManager.LockInventory = true;
    }

    public void Respawn()
    {
        transform.position = _spawnPos;
        _cinemachineCamera.Priority = 4;
        _deathUI.SetActive(false);
        _firstPersonController.enabled = true;
        _charctercontroller.enabled = true;
        Cursor.lockState = CursorLockMode.Locked;

        _health = 100;
        _maxHealth = 100;
        _thirst = 50;
        _hunger = 75;
        _dead = false;
        _inventoryManager.LockInventory = false;

        HealthValue.text = _health.ToString();
        HungerValue.text = _hunger.ToString();
        ThirstValue.text = _thirst.ToString();
    }
}

          

EXTERNAL ASSETS/ PACKAGES USED

I used the Standard Assets First Person Controller to help get a headstart on player movement and rotation, the movement scripting was not completed by me, although I did tweak some elements within the scripts such as the "head bob" effect that you will see when sprinting. 

I used DOTweens unity package to help script tweens and animations, mainly used for the Fuse material changing colour when inserting a Fuse into the fuse box.