Max Nikander Portfolio

Logo

Personal Portfolio

View My GitHub Profile

DodoFarm

Student project. A working proof of concept demo was created with proper models and sounds.

Image

Features

Image

Example of the multiplayer splitscreen effect.

Some Code snippets

Full source is in a private repository. Here are some code snippets from various things I worked on.

Control handler for switching between keyboard and gamepad controls
 

public class ControlHandler : MonoBehaviour
{


    public delegate void OnControlChanged();
    public OnControlChanged OnControlsChangedCallback;

    public KeyboardControl _keyboardControl;
    public GamepadControl _gamepadControl;

    [SerializeField] private bool _keyboardOn;
    [SerializeField] private bool _gamepadOn;
    private bool _touchHeld = false;
    public bool TouchHeld { get => _touchHeld; set => _touchHeld = value; }

    public Camera _camera;



    public bool CurrentControlTouchHeld()
    {
        if (_keyboardOn) return _keyboardControl.HoldingTouch;
        if (_gamepadOn) return _gamepadControl.HoldingTouch;
        return false;
    }

    public bool CurrentControlState()
    {
        if (_keyboardOn) return _keyboardControl.On;
        if (_gamepadOn) return _gamepadControl.On;
        return false;
    }

    public bool KeyboardOn
    {
        get => _keyboardOn;
        set
        {
            _keyboardOn = value;
            if (OnControlsChangedCallback != null) OnControlsChangedCallback.Invoke();
        }
    }

    public bool GamepadOn
    {
        get => _gamepadOn;
        set
        {
            _gamepadOn = value;
            if (OnControlsChangedCallback != null) OnControlsChangedCallback.Invoke();
        }
    }


    public void updateControls()
    {
        if (KeyboardOn && !_keyboardControl.enabled) _keyboardControl.enabled = true;
        else if (!KeyboardOn && _keyboardControl.enabled) _keyboardControl.enabled = false;

        if (GamepadOn && !_gamepadControl.enabled) _gamepadControl.enabled = true;
        else if (!GamepadOn && _gamepadControl.enabled) _gamepadControl.enabled = false;
    }


    public void EnableCurrentControls(InputUser user)
    {
        if (_keyboardOn) _keyboardControl.EnableControlsUser(user);
        if (_gamepadOn) _gamepadControl.EnableControlsUser(user);
    }


    public void DisableCurrentControls(InputUser user)
    {
        if (_keyboardOn) _keyboardControl.DisableControlsUser(user);
        if (_gamepadOn) _gamepadControl.DisableControlsUser(user);
    }

    void OnEnable()
    {
        OnControlsChangedCallback += updateControls;
        if (OnControlsChangedCallback != null) OnControlsChangedCallback.Invoke();
    }

    void OnDisable()
    {
        OnControlsChangedCallback -= updateControls;
    }

    public void toggleKeyboard()
    {
        if (KeyboardOn) KeyboardOn = false;
        else KeyboardOn = true;
    }

    public void toggleGamepad()
    {
        if (GamepadOn) GamepadOn = false;
        else GamepadOn = true;
    }

    public void CreateControls(InputDevice device, InputUser user, bool tkp)
    {
        switch (device.description.deviceClass)
        {
            case (""):
                KeyboardOn = false;
                GamepadOn = true;
                _gamepadControl.InitializeControlsForPlayer(user);
                break;
            case ("Keyboard"):
                GamepadOn = false;
                KeyboardOn = true;
                if (tkp)
                {
                    _keyboardControl.InitializeControlsForPlayer2(user);
                    break;
                }
                _keyboardControl.InitializeControlsForPlayer(user);
                break;
        }
    }
Camera controller
 
public class CameraMovement : MonoBehaviour
{

    public Transform _target = null;
    private Vector3 _targetPos;
    [SerializeField] private float smoothing = 2f;
    private Vector3 nullVelocity = Vector3.zero;
    private Vector3 _offset;
    [SerializeField] Camera attachedCamera;

    [Range(1, 10)]
    public float _cameraZ;
    [Range(1, 10)]
    public float _cameraY;
    [Range(-10, 10)]
    public float _cameraX;
    [Range(0, 90)]
    public float _cameraRot;

    public float _spcXOffset = 0; // Splitscreen offset
    public float _spcYOffset = 0; // Splitscreen offset
    public float _spcZOffset = 0; // Splitscreen offset

    public Vector3 _relativePos;

    public bool _Ready = false;

    private void OnValidate()
    {
        if (_target)
        {
            SetPos();
            calcOffset();
        }
    }

    public void calcOffset()
    {
        _offset = _targetPos - _target.position;
    }

    public void SetPos()
    {
        transform.position = new Vector3(_target.position.x + _cameraX,
                                 _target.position.y + _cameraY,
                                 _target.position.z + _cameraZ);
        transform.rotation = Quaternion.identity;
        transform.Rotate(_cameraRot, 180, 0);
    }

    void Update()
    {
        if (_target)
        {
            _relativePos = _target.position + _offset;
            Vector3 targetPosition = _target.position + _offset;
            if (_spcXOffset != 0) targetPosition += new Vector3(_spcXOffset, _spcYOffset, 0);

            transform.rotation = Quaternion.Lerp(transform.rotation, Quaternion.Euler(_cameraRot + _spcZOffset, 180, 0), 0.1f);
            transform.position = Vector3.SmoothDamp(transform.position, targetPosition, ref nullVelocity, smoothing);
        }
    }

    public void TargetObject(Transform target, bool rotate)
    {
        //_Ready = false;
        _target = target;
        _targetPos = new Vector3(target.position.x + _cameraX,
                                         target.position.y + _cameraY,
                                         target.position.z + _cameraZ);
        if (rotate)
        {
            transform.rotation = Quaternion.identity;
            transform.Rotate(_cameraRot, 180, 0);
        }
        calcOffset();
        //if (gameObject.activeSelf) StartCoroutine(MoveToTarget(_target.position + _offset));
        //else
        //{
        //    SetPos();
        //    _Ready = true;sa
        //}

    }

    IEnumerator MoveToTarget(Vector3 targetPosition)
    {
        while (!_Ready)
        {
            transform.position = Vector3.SmoothDamp(transform.position, targetPosition, ref nullVelocity, smoothing);
            if (transform.position == targetPosition) _Ready = true;
            yield return null;
        }
    }
}
Controller class for the dynamic splitscreen effect. First time ever attempting this so implementation may need some work
 
public class SplitscreenControl : MonoBehaviour
{
    [SerializeField] Camera _SingleCamera;
    Camera cam1;
    Camera cam2;

    [SerializeField] GameObject _PlayerOne;
    [SerializeField] GameObject _PlayerTwo;

    [SerializeField] GameObject _PlayerOneCone;
    [SerializeField] GameObject _PlayerTwoCone;

    [SerializeField] float _spcCutoff;
    [SerializeField] float _spcXOffset;
    [SerializeField] float _spcZOffset;
    [SerializeField] float _spcYOffset;


    [SerializeField] CameraMovement _PlayerOneCameraScript;
    [SerializeField] CameraMovement _PlayerTwoCameraScript;


    [SerializeField] Camera _MaskCamera;

    [SerializeField] RenderTexture _MaskCameraRenderTexture;

    [SerializeField] RenderTexture _PlayerTwoCameraRenderTexture;

    [SerializeField] PostProcessVolume _volume;

    [SerializeField] LayerMask _mask;

    public GameObject _midPoint;
    bool _setup = false;

    SplitScreen _spcSettings;
    [SerializeField] private bool _split = false;

    public bool Setup { get => _setup; set => _setup = value; }

    public void SetupPlayers(List<GameObject> playerList)
    {
        _PlayerOne = playerList[0];
        _PlayerTwo = playerList[1];

        _PlayerOneCone = Instantiate(_PlayerOneCone, _PlayerOne.transform.position, Quaternion.identity);
        _PlayerTwoCone = Instantiate(_PlayerTwoCone, _PlayerTwo.transform.position, Quaternion.identity);

        Setup = true;
    }

    public void SetupMainCamera(List<Camera> camList)
    {

        var layer = camList[1].gameObject.GetComponent<PostProcessLayer>();
        layer.volumeLayer.value = _mask;

        _volume.profile.TryGetSettings(out _spcSettings); // Get post processing settings

        _spcSettings.enabled.value = true; // Set splitscreen post processing to enabled

        _spcSettings._MaskTex.value = _MaskCameraRenderTexture; // Set Splitscreen effects mask to target texture

        camList[1].targetTexture = new RenderTexture(Screen.width, Screen.height, 24); // New render texture

        _spcSettings._Tex.value = camList[1].targetTexture; // Set second players camera texture to shaders Second camera texture

        _PlayerOneCameraScript = camList[0].gameObject.GetComponent<CameraMovement>();
        _PlayerTwoCameraScript = camList[1].gameObject.GetComponent<CameraMovement>();

        cam1 = camList[0];
        cam2 = camList[1];
    }

    public void ResetSplitscreenControl()
    {
        _volume.profile.TryGetSettings(out _spcSettings); // Get post processing settings

        _spcSettings.enabled.value = false; // Set splitscreen post processing to enabled

        _spcSettings._MaskTex.value = null; // Set Splitscreen effects mask to target texture

        _spcSettings._Tex.value = null; // Set second players camera texture to shaders Second camera texture

        _PlayerOneCameraScript = null;
        _PlayerTwoCameraScript = null;

        cam1 = null;
        cam2 = null;

        _setup = false;
    }

    void Update()
    {
        if (Setup)
        {
            _PlayerOneCone.transform.position = _PlayerOne.transform.position;
            _PlayerTwoCone.transform.position = _PlayerTwo.transform.position;


            Vector3 midPointXZ =
                    new Vector3((_PlayerOne.transform.position.x +
                                (_PlayerTwo.transform.position.x - _PlayerOne.transform.position.x) / 2),
                                _MaskCamera.gameObject.transform.position.y,
                                _PlayerOne.transform.position.z +
                                (_PlayerTwo.transform.position.z - _PlayerOne.transform.position.z) / 2);

            Vector3 midPoint =
                                        new Vector3((_PlayerOne.transform.position.x +
                                (_PlayerTwo.transform.position.x - _PlayerOne.transform.position.x) / 2),
                                (_PlayerOne.transform.position.y +
                                (_PlayerTwo.transform.position.y - _PlayerOne.transform.position.y) / 2),
                                _PlayerOne.transform.position.z +
                                (_PlayerTwo.transform.position.z - _PlayerOne.transform.position.z) / 2);


            if (Vector3.Distance(_PlayerOne.transform.position, _midPoint.transform.position) < _spcCutoff && _split)
            {
                // cam1.enabled = false;
                // cam2.enabled = false;
                //_SingleCamera.enabled = true;
                //_spcSettings.enabled.value = false;
                _PlayerOneCameraScript.TargetObject(_midPoint.transform, false);
                _PlayerTwoCameraScript.TargetObject(_midPoint.transform, false);
                _split = false;
            }
            else if (Vector3.Distance(_PlayerOne.transform.position, _midPoint.transform.position) > _spcCutoff && !_split)
            {
                cam1.enabled = true;
                cam2.enabled = true;
                //_SingleCamera.enabled = false;
                _spcSettings.enabled.value = true;
                _PlayerOneCameraScript.TargetObject(_PlayerOne.transform, false);
                _PlayerTwoCameraScript.TargetObject(_PlayerTwo.transform, false);
                _split = true;
            }

            _MaskCamera.gameObject.transform.position = midPointXZ;


            _midPoint.transform.position = midPoint;


            var z1 = _PlayerOne.transform.position.z - midPoint.z;
            var z2 = _PlayerTwo.transform.position.z - midPoint.z;

            if (z1 < 0) z1 = -z1;
            if (z2 < 0) z2 = -z2;

            if (_split)
            {
                // X Camera offset
                // When a player is on the right camera moves slighly left
                // Vice versa if player is on the left.
                if (_PlayerOne.transform.position.x > _PlayerTwo.transform.position.x)   // Player 1 Left
                {                                                                        // Player 2 Right
                    _PlayerOneCameraScript._spcXOffset =
                    Mathf.Lerp(0,
                               -_spcXOffset,
                               (Mathf.Clamp(_PlayerOne.transform.position.x - _PlayerTwo.transform.position.x, 0, 10) / 10));

                    _PlayerTwoCameraScript._spcXOffset =
                    Mathf.Lerp(0,
                               _spcXOffset,
                               (Mathf.Clamp(_PlayerOne.transform.position.x - _PlayerTwo.transform.position.x, 0, 10) / 10));
                }
                else                                                                    //  Player 1 Right         
                {                                                                       //  Player 2 Left 
                    _PlayerOneCameraScript._spcXOffset =
                    Mathf.Lerp(0,
                               _spcXOffset,
                               (Mathf.Clamp(_PlayerTwo.transform.position.x - _PlayerOne.transform.position.x, 0, 10) / 10));

                    _PlayerTwoCameraScript._spcXOffset =
                    Mathf.Lerp(0,
                               -_spcXOffset,
                               (Mathf.Clamp(_PlayerTwo.transform.position.x - _PlayerOne.transform.position.x, 0, 10) / 10));
                }

                //-------------------------------------------------------------------------//
                //-------------------------------------------------------------------------// 
                //-------------------------------------------------------------------------//

                if (_PlayerOne.transform.position.z > _PlayerTwo.transform.position.z)  //Player 1 is above Player 2
                {
                    _PlayerOneCameraScript._spcZOffset =
                    Mathf.Lerp(0,
                               -_spcZOffset,
                               (Mathf.Clamp(_PlayerOne.transform.position.z - _PlayerTwo.transform.position.z, 0, 10) / 10));


                    _PlayerTwoCameraScript._spcZOffset =
                    Mathf.Lerp(0,
                               _spcZOffset,
                               (Mathf.Clamp(_PlayerOne.transform.position.z - _PlayerTwo.transform.position.z, 0, 10) / 5));
                }
                else
                {
                    _PlayerOneCameraScript._spcZOffset =
                    Mathf.Lerp(0,
                               _spcZOffset,
                               (Mathf.Clamp(_PlayerTwo.transform.position.z - _PlayerOne.transform.position.z, 0, 10) / 10));

                    _PlayerTwoCameraScript._spcZOffset =
                    Mathf.Lerp(0,
                               -_spcZOffset,
                               (Mathf.Clamp(_PlayerTwo.transform.position.z - _PlayerOne.transform.position.z, 0, 10) / 10));
                }
            }
            else
            {
                _PlayerTwoCameraScript._spcXOffset = 0;
                _PlayerOneCameraScript._spcXOffset = 0;
                _PlayerTwoCameraScript._spcZOffset = 0;
                _PlayerOneCameraScript._spcZOffset = 0;
            }

            if (cam1.transform.position.x == midPoint.x)
            {
                _spcSettings.enabled.value = false;
                //_split = false;
            }
        }
    }
}
Audio manager class using FMOD studio
 
public class AudioManager : MonoBehaviour
{
    public static AudioManager manager;
    public AudioData _data;

    EventInstance _atmoInstance;
    EventInstance _musicInstance;

    [Range(0, 1)]
    [SerializeField]
    private float _masterVolume = 1f;
    Bus masterBus;

    private void Awake()
    {
        if (manager == null)
        {
            manager = this;
            DontDestroyOnLoad(manager);
        }
        else if (manager != this)
        {
            Destroy(this);
        }

        StartMusic();
    }

    void Start()
    {
        masterBus = RuntimeManager.GetBus("Bus:/");
        _masterVolume = Settings.MasterVolume;
        masterBus.setVolume(_masterVolume);
    }

    private void OnValidate()
    {
        masterBus.setVolume(_masterVolume);
    }

    public void OnValueChanged(Slider slider)
    {
        _masterVolume = slider.value;
        masterBus.setVolume(_masterVolume);
    }

    /*private void OnEnable()
    {
        StartAtmo();
        StartMusic();
    }*/

    public void OnStartGame()
    {
        StartAtmo();
    }

    public void OnStopGame()
    {
        StopAtmo();
    }

    private void StopAllSounds()
    {
        masterBus.stopAllEvents(FMOD.Studio.STOP_MODE.IMMEDIATE);
    }

    private void StopAtmo()
    {
        _atmoInstance.stop(FMOD.Studio.STOP_MODE.IMMEDIATE);
        _atmoInstance.release();
    }

    public void PauseAtmoSmooth()
    {
        _atmoInstance.stop(FMOD.Studio.STOP_MODE.ALLOWFADEOUT);
    }

    public void ResumeAtmo()
    {
        _atmoInstance.start();
    }

    public void StartAtmo()
    {
        _atmoInstance = RuntimeManager.CreateInstance(_data.Atmosphere.Atmo);
        _atmoInstance.start();
    }

    public void StartMusic()
    {
        _musicInstance = RuntimeManager.CreateInstance(_data.Music.Music);
        _musicInstance.start();
    }

    /// <summary>
    /// changes music.
    /// </summary>
    /// <param name="val">0 = normal music, 2 = raptor rush music</param>
    public void ChangeMusic(float val)
    {
        _musicInstance.setParameterByName("MUSIC_TRANS", val);
    }

    public void StopMusic()
    {
        _musicInstance.stop(FMOD.Studio.STOP_MODE.IMMEDIATE);
        _musicInstance.release();
    }

    public void PlayOneShot(string audio, Vector3 position)
    {
        RuntimeManager.PlayOneShot(audio, position);
    }

    /// <summary>
    /// Creates new instance and starts it immediately
    /// </summary>
    /// <param name="audio"></param>
    /// <param name="position"></param>
    /// <returns></returns>
    public EventInstance NewLoopSound(string audio, Vector3 position)
    {
        EventInstance audioInstance = RuntimeManager.CreateInstance(audio);
        audioInstance.start();
        return audioInstance;
    }

    public void StartSound(EventInstance instance)
    {
        instance.start();
    }

    public void PauseSound(EventInstance instance)
    {
        instance.stop(FMOD.Studio.STOP_MODE.IMMEDIATE);
    }

    public void StopSound(EventInstance instance)
    {
        instance.stop(FMOD.Studio.STOP_MODE.IMMEDIATE);
        instance.release();
    }
}
Generic class to pool any monobehavior classes
 
public abstract class GenericPool<T> : MonoBehaviour where T : Component
{

    [SerializeField] private T prefab;
    [SerializeField] private List<T> _objectPool = new List<T>();

    public static GenericPool<T> Instance;
    private void Awake()
    {
        Instance = this;
    }

    public T Get()
    {
        foreach (var item in _objectPool)
        {
            if (!item.gameObject.activeSelf)
            {
                return item;
            }
        }
        return AddAndReturn();
    }

    private T AddAndReturn()
    {
        T newObject = Instantiate(prefab);
        newObject.gameObject.SetActive(false);
        _objectPool.Add(newObject);
        newObject.transform.parent = gameObject.transform;
        return newObject;
    }

    public void Put(T objectToReturn)
    {
        objectToReturn.gameObject.SetActive(false);
    }

    private void Add(int x)
    {
        for (int i = 0; i < x; i++)
        {
            T newObject = Instantiate(prefab);
            newObject.gameObject.SetActive(false);
            _objectPool.Add(newObject);
            newObject.transform.parent = gameObject.transform;
        }
    }


}