Skip to content

Gomystalka/Game-Engines-1-Game

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

90 Commits
 
 
 
 
 
 
 
 

Repository files navigation

Game Engines 1 Assignment

Name: Tomasz Galka

Student Number: C18740411

Class Group: DT508

CONTENT WARNING

This project contains a large amount of flashing lights which may trigger seizures in the vulnerable. Viewer discretion is advised!

Unity Version: Unity 2019.4.1f1

This project is being made for educational purposes only.

Any credits will be made within the game and on this repository

ALL assets apart from NAudio.dll, Audio.xml and MilkyWay Skybox were created by me.

NAudio had to be used as Unity does not support .MP3 file streaming due to licensing issues.

A build of this game can be downloaded from the Releases section on this Repo!

Description of the project

Due to time constraints and many issues some previous features had to be scrapped. This project is a game/music visualizer made in the Unity 2019.4.1f1 game Engine (Education Edition). The main purpose of this game is for music visualization and possible VJing due to one of the features. The project contains a procedurally-generated map using Perlin Noise and a custom terrain. This terrain is generated with correct UVs which allows one of the best features to work; the ability to display any video file on the terrain which gives it VJ potential. Both the video and audio files can be changed by the user which gives the game a lot of customizability. The game's main purpose is once again, for visualization, however there are shooting mechanics implemented in order to make the game slightly more fun. The control scheme of the game was inspired by Star Fox 64 created by Nintendo.

Instructions for use

The game should be fully usable when pulled/downloaded within the editor or when built within the editor. The custom video files must be put into the StreamingAssets/Videos folder to be detected by the game. This is the same for the audio file, except audio files must be placed within the StreamingAssets/Music folder instead. The game cannot pe played when there is no audio file loaded so make sure there is one present within the specified folder!

Here is a quick tutorial I made on how to add new files to the game. Works for both the build and the project.

How it works

Under the hood, the game is powered by a large amount of systems. The brain of the game lies within the AudioManager class. This is the class responsible for analyzing the audio, splitting the audio into frequency bands, and detecting beats through the use of the Frequency Energy or Spectral Flux algorithms (Changeable however Frequency Energy proved to be more reliable.) The second most important class is the AudioBehaviour class. This class is an abstract class which exposes all Spectrum information to any class that inherits it. The CustomVisualizedTerrain class makes heavy use of it for hooking the OnBeat event and changing the terrain color and terrain shader line width based on the combined frequency data of the first, second and third band. This data is then mapped into the HSV color space for a rainbow effect.

There is another important class which takes care of screen bounds collision, ScreenBoundsColliderOld. Old is in the name due to a previous naming mistake which I forgot to fix. This behaviour is achieved by first calculating the frustum corners of the camera and converting them into world space, then in order for all directions and rotations to function correctly, distance calculations and the Dot product is used in order to find out whether the player's distance is positive or negative (Outside the bounds). This function looks like this. It uses a combined enum for precise collision identification.

public CollisionLocation Constrain3DObject(Transform objectTransform, bool collisionInfoOnly = false) {
        yMidPoint = new Vector3(FrustumCorners[3].x, objectTransform.position.y, FrustumCorners[3].z);
        xMidPoint = FrustumCorners[3] - (FrustumCorners[3] - FrustumCorners[1]).normalized * Vector3.Distance(objectTransform.position, yMidPoint);

        float hDist = Vector3.Distance(yMidPoint, objectTransform.position);
        float vDist = Vector3.Distance(xMidPoint, objectTransform.position);
        float hDirDot = Vector3.Dot((yMidPoint - objectTransform.position).normalized, objectTransform.right);
        float vDirDot = Vector3.Dot((xMidPoint - objectTransform.position).normalized, objectTransform.up);
        hDist *= hDirDot < 0f ? -1f : 1f;
        vDist *= vDirDot < 0f ? -1f : 1f;
        CollisionLocation location = CollisionLocation.None;

        if (hDist <= 0f)
        {
            if(!collisionInfoOnly)
                objectTransform.position = new Vector3(yMidPoint.x, objectTransform.position.y, yMidPoint.z);
            location ^= CollisionLocation.Right;
        }

        if (hDist >= HorizontalDistance)
        {
            if(!collisionInfoOnly)
                objectTransform.position = new Vector3(FrustumCorners[0].x, objectTransform.position.y, FrustumCorners[0].z);
            location ^= CollisionLocation.Left;
        }

        if (vDist <= 0f)
        {
            if (!collisionInfoOnly)
                objectTransform.position = new Vector3(objectTransform.position.x, xMidPoint.y, objectTransform.position.z);
            location ^= CollisionLocation.Top;
        }

        if (vDist >= VerticalDistance)
        {
            if (!collisionInfoOnly)
                objectTransform.position = new Vector3(objectTransform.position.x, FrustumCorners[0].y, objectTransform.position.z);
            location ^= CollisionLocation.Bottom;
        }

        return location;
    }

There is also a Player and Camera Controller class which take care of the player movement through data fetched from a custom function in my Utilities class called GetGoodAxis which returns a smoothed value just like Input.GetAxis("") however the smooth rate is customizable and an annoying bug which snaps the axis value if the opposite button is pressed is completely fixed. This function is simple under the hood.

private static Vector2 _goodAxis;
    private static Vector2 _goodAxisRaw;
    public static float inputInterpolationSpeed = 1f;

    public static float GetGoodAxis(string axis)
    {
        float value = 0f;
        if (axis == "Horizontal")
        {
            value = _goodAxis.x;
            if (Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.LeftArrow))
                value = Mathf.MoveTowards(_goodAxis.x, -1f, inputInterpolationSpeed * Time.deltaTime);
            else if (Input.GetKey(KeyCode.D) || Input.GetKey(KeyCode.RightArrow))
                value = Mathf.MoveTowards(_goodAxis.x, 1f, inputInterpolationSpeed * Time.deltaTime);
            else
                value = Mathf.MoveTowards(_goodAxis.x, 0f, inputInterpolationSpeed * Time.deltaTime);
            _goodAxis = new Vector2(value, _goodAxis.y);
            return value;
        }
        else if (axis == "Vertical")
        {
            value = _goodAxis.y;
            if (Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.UpArrow))
                value = Mathf.MoveTowards(_goodAxis.y, 1f, inputInterpolationSpeed * Time.deltaTime);
            else if (Input.GetKey(KeyCode.S) || Input.GetKey(KeyCode.DownArrow))
                value = Mathf.MoveTowards(_goodAxis.y, -1f, inputInterpolationSpeed * Time.deltaTime);
            else
                value = Mathf.MoveTowards(_goodAxis.y, 0f, inputInterpolationSpeed * Time.deltaTime);

            _goodAxis = new Vector2(_goodAxis.x, value);

            return value;
        }
        return value;
    }

The KeyCode values however are currently hardcoded but it would be difficult to specify custom values.

The visualization on the terrain is done through the use of custom mesh generation within the CustomTerrain class. The terrain first generates a plane with a set amount of vertices determined by the Width and Height parameters. The terrain is then divided up into Chunks which store the indices and X and Y positions of each vertex within said chunk. There is also a function within the Chunk object which remaps a X and Y value into the specified chunk's X and Y value, then clamps it. Through the use of this, a seamless terrain generation could be achieved by teleporting the terrain's start point to the player's forward once the player reaches a certain point on the terrain. When this point is reached, the last three chunks are copied and pasted into the first three chunks and the chunks after the first three are regenerated based on Perlin Noise data. The visualization of the video on the terrain was achieved through the use of the Video Player Unity component and a custom shader (ScalableStandard) which scaled the texture as for some reason Unity renders the video on the terrain at a very big scale by default. The terrain wireframe was also achieved through a modified built-in Unity shader (WireframeShaderEx).

The enemy spawns are determined by the beat of the audio file specified. A beat detection algorithm is used in order to detect beats within a song, and when one is detected, an enemy is spawned. In order to avoid too many enemies spawning, a time between spawns value was added. The shape of the enemy is determined by the data from the Spectrum at the time of the beat that spawns said enemy. In order to not cull backfaces for when the enemy faces become too distorted, the Standard Unity shader (NoCullingStandardShader) was modified to not Cull anything.

The lighting effects were added thorugh Unity's Post Processing package. Effects involved were mainly Bloom and Color Grading.

Many aspects of this game were visualized through the use of Unity's powerful Gizmo system including the Custom Terrain, Camera Bounds and Player information.

Finally, the loading of the files was achieved through the use of the UnityWebRequest class which loads a file from a URL which supports both HTTP and File protocols. The loading of the video file is natively supported by the Video Player component. There is also a Refresh Button on the Menu so that you don't need to restart the game to load new files!

References

  • Star Fox 64 by Nintento
  • Minim for Processing by ddf/Compartmental
  • Unity Docs
  • Stackoverflow

What I am most proud of in the assignment

I am very proud of how the custom terrain generation and visualization turned out. Despite my limited knowledge of ShaderLab, HLSL and mesh manipulation, I managed to create a fairly optimized and fully working terrain generation system which can be altered in many ways and supports many materials when the appropriate shader is used.

Project Feature Overview

  • Procedural Generation
  • Audio Visualization
  • Custom Editor Inspector for the AudioManager class
  • Interchangable Video and Audio files
  • Support for MP3 files thanks to NAudio!

Gameplay Demo

Controls

  • WSAD: Move
  • Left Mouse Button: Fire Lasers
  • Right Mouse Button: Fire Rockets when you have a target

Credits

  • Adam Bielecki (theadambielecki@gmail.com) - Amazing Skybox
  • Mark Heath - NAudio - I love you.

This is NOT a commercial project. It is completely free and always will be.

This assignment was created on an Educational Unity License

Previous Proposal

This'll be a bullet-hell 3D game for which the enemies and certain parts of the map will be either randomly generated, generated based off spectrum data from an audio file of choice or preset but will react to music. The surrounding landscape will also change based on the audio file provided. The music will be freely interchangable by the player/user and most of the map will be generated as the music plays. There will be a length restriction placed on the imported audio file as to avoid both too long and too short clips.This assignment will be developed through the use of the Unity 2019.4.1f1 Game Engine.

The overall difficulty of the map will be determined by the audio file provided. UI will consist of a generic menu with an option to select an audio file. Most if not all of the landscape/world will be procedurally-generated/generated relative to the spectrum data as the game plays. There will be no pre-processing of any kind therefore the game will not support pre-made maps.