FolkeLogo

Fragment

Genre: Single Player, Puzzle, Platformer, Narrative driven


Platform & Control: Mouse and keybord, Controller


Engine: Unity Engine


Development Time: 7 Weeks


Team Size: 3 Designers, 3 3D Artist, 2 2D Artis, 3 Programmers


Download: itch.io/Fragment


Describtion of the game:

The game takes place in a mysterious and surreal, almost psychedelic, world. The player needs to progress into the world to restore its lost memories and find out who they are. Whilst progressing further into the unknown, the player finds out the world is getting more dangerous and more complicated. You find a mysterious shining animal that guides you through your journey. 




My responsibility as a programmer:

Character Behaviour

 The game is heavily designed to move around to explore and solve puzzles. For this, I designed climbing and helped set up a state machine for the movement controller.

Jump to Character Behaviour


Puzzle 

I helped design challenges for the player and therefor design a few puzzles. The one I worked the most with was the so called 'lazer puzzle'.

Jump to Puzzles


UI 

The game only used UI for the main menu and pause menu. I have worked with UI in Unity in previous projects, so I felt confident in making UI for this project. 

Jump to UI


Tools

 Since the game has a lot of exploring, I created a tool to divide our game into different scenes to take advantage of level streaming. By doing so, the game runs more smoothly.

Jump to Tools

Character Behaviour

State machine - I made a state machine for the various movement states. By doing so,  I could monitor whenever there was a state that caused a crash or did not work as intended. For the state machine,  I created an interface for the scripts so the transitions between states all had functions such as; "Enter" and "Exit". 


Climbing - At the start, the climbing was simple; if a raycast hit a climbing surface, you could climb. Later in the project me and a co-worker refactored the climbing and got a more sustainable solution. We painted a picture how the player went from standing, into finish a climb. We then translated that logic into code. The code need to pass a climb check and calculate the collision.

          		
    [SerializeField] public AirState airState;
    [SerializeField] public CutsceneState cutsceneState;
    [SerializeField] public BoxMoveState boxMoveState;
    [SerializeField] public GroundMoveState groundMoveState;
    [SerializeField] public SlopeSlideState slopeSlideState;
    [SerializeField] public BeamMoveState beamMoveState;
    [SerializeField] public ClimbingState climbingState;


    [SerializeField] public IMovement currentState;

    public InputManager input;
    public CharacterController cc;
    public PlayerAbilities abilities;
    public bool addToMove = false;
    public Vector3 addMoveVector;

    public InteractPrompt interactPrompt;

    private void Awake()
    {
        MyPlayer = this;
        anim = GetComponent<Animator>();
        FindReferences();
    }

    void Start()
    {
        CreatePlayerStates();
        groundCastStart = ( new Vector3(0f, pushBox.radius - pushBox.bounds.extents.y, 0f) + pushBox.center );
        groundCastRadius = pushBox.radius;
        airState.climbCheck = GetComponent<ClimbCheck>();
        currentState = groundMoveState;
        currentState.Start();
    }

    private void FindReferences()
    {
        abilities = GetComponent<PlayerAbilities>();
        cc = GetComponent<CharacterController>();
        pushBox = GetComponent<CapsuleCollider>();
        input = GetComponent<InputManager>();
    }
    private void CreatePlayerStates()
    {
        ///////////////////////////////// Here we create the states. They are not monobehaviour and can therefor not be placed on an object. 
        airState = new AirState(this);
        cutsceneState = new CutsceneState(this);
        boxMoveState = new BoxMoveState(this);
        groundMoveState = new GroundMoveState(this);
        slopeSlideState = new SlopeSlideState(this);
        beamMoveState = new BeamMoveState(this);
        climbingState = new ClimbingState(this);
        /////////////////////////////////
    }
    void Update()
    {
        grounded = GroundCheck();

        Vector3 move = currentState.UpdateState(input.MovementInput(), cc.velocity); //Here we update the current state. Now in Update, it is called every frame

        if(addToMove)
        {
            move += addMoveVector;
            addMoveVector = Vector3.zero;
            addToMove = false;
        }

        cc.Move(move * Time.deltaTime);
    }

    private bool GroundCheck()
    {
        return (Physics.SphereCast(transform.position + groundCastStart, groundCastRadius, Vector3.down, out groundHit, groundCastDistance, blockingLayers));   //Add a layerMask to this at some point.
    }

    public float GroundAngle()
    {
        return Vector3.Angle(Vector3.up, groundHit.normal);
    }
    public bool ReturnGrounded()        //use to check if player is grounded (There's probably a better way to do this??)
    {
        return grounded;
    }
    public RaycastHit ReturnGroundHit()
    {
        return groundHit;
    }
    public IEnumerator SwitchState(IMovement state)
    {
        yield return new WaitForEndOfFrame();
        currentState.ExitState();
        currentState = state;
        currentState.Start();
    }
        
	
      
    [SerializeField] public ClimbSettings settings;

    private PlayerMovement p;
    private CapsuleCollider col;
    private GameObject currentClimbBox;
    private Vector3 flatNormal;
    private Vector3 capsEndPos;
    private float surfaceAngle;
    private float pointsOffset;
    private float radius;
    private bool onCooldown = false;
    private RaycastHit wallhit, topHit;

    public Vector3 rHand, lHand;
    public Quaternion targetRot;
    public Vector3 targetCenter;
    public Vector3 climbUpTarget;

    private void Start()
    {
        p = gameObject.GetComponent<PlayerMovement>();
        col = GetComponent<CapsuleCollider>();
        radius = col.radius;
        pointsOffset = (col.height * 0.5f) - radius;
    }


    private Vector3 CapsulePoint(float offset, Vector3 center)
    {
        return (transform.up * offset + col.center + center);
    }

    public void TryClimb()
    {
        if (onCooldown == false)
        {
            if (TraceForward())
            {
                surfaceAngle = CheckForSurface();

                if (surfaceAngle < settings.maxSurfaceAngle && LookForCorner())
                {
                    if (CheckObstruction() == false)
                    {
                        targetRot = Quaternion.LookRotation(flatNormal * -1f, Vector3.up);
                        StartCoroutine(p.SwitchState(p.climbingState));
                    }
                }
            }
        }
    }

    private bool TraceForward()
    {
        if (Physics.CapsuleCast(
        CapsulePoint(pointsOffset, transform.position), 
        CapsulePoint(-pointsOffset, transform.position),
        radius, transform.forward,
        out wallhit,
        settings.forwardReach,
        settings.climbable))
        {
            Vector3 playerPos = transform.position;
            capsEndPos = wallhit.point - transform.forward * radius * 1.1f;
            playerPos.y = 0f;
            capsEndPos.y = 0f;
            float traceDist = Vector3.Distance(playerPos, capsEndPos);
            
            if (Physics.CapsuleCast(CapsulePoint(pointsOffset, transform.position), 
            CapsulePoint(-pointsOffset, transform.position),
            radius,
            transform.forward,
            traceDist,
            settings.obstructing)
                || Vector3.Angle(wallhit.normal, Vector3.ProjectOnPlane(wallhit.normal, Vector3.up)) > 7.87f)
            {
                return false;
            }
            else
            {
                currentClimbBox = wallhit.transform.gameObject;
                return true;
            }
        }
        return false;
    }

    private float CheckForSurface()
    {
        flatNormal = Vector3.ProjectOnPlane(wallhit.normal, Vector3.up).normalized;
        Vector3 capsCenter = wallhit.point - (flatNormal * (radius + 0.1f));        // 0.1f because we dont wanna be too close to the edge after climbing up.
        capsCenter.y = transform.position.y + settings.upwardReach + col.bounds.extents.y * 0.51f;
        float traceLength = capsCenter.y - transform.position.y;

        if (Physics.CapsuleCast(CapsulePoint(pointsOffset, capsCenter), CapsulePoint(-pointsOffset, capsCenter), radius, Vector3.down, out topHit, traceLength, settings.climbable))
        {
            float angle = Vector3.Angle(topHit.normal, Vector3.up);
            if (angle < settings.maxSurfaceAngle)
            {
                return angle;
            }
        }
        return settings.maxSurfaceAngle + 1f;
    }

    private bool LookForCorner()
    {
        float hitAngle = 99f;
        float yHeightOffset = 0f;
        float yHeight = wallhit.point.y + 0.5f;
        RaycastHit hit = new RaycastHit();

        Vector3 startPos = wallhit.point + flatNormal * 0.5f;

        Vector3 traceDir = Vector3.down - flatNormal;

        while (hitAngle > settings.maxSurfaceAngle && yHeightOffset < settings.upwardReach + 1f)
        {
            startPos.y = yHeight + yHeightOffset;

            if (Physics.Raycast(startPos, traceDir, out hit, 1.5f, settings.climbable))
            {
                hitAngle = Vector3.Angle(hit.normal, Vector3.up);
            }
            Debug.DrawLine(startPos, startPos + traceDir.normalized * 2f, Color.red);
            yHeightOffset += 0.05f;
        }

        if (hitAngle < settings.maxSurfaceAngle && SetTargetPos(hit.point))
        {
            lHand = hit.point + transform.right * settings.handOffset;
            rHand = hit.point - transform.right * settings.handOffset;

            return true;
        }
        else
        {
            return false;
        }
    }
    public IEnumerator ClimbCD(float time)
    {
        onCooldown = true;
        yield return new WaitForSeconds(time);
        onCooldown = false;
    }
    private bool SetTargetPos(Vector3 ledgePos)
    {
        targetCenter = ledgePos + flatNormal * radius * 1.01f + Vector3.down * (col.bounds.extents.y + 0.4f);
        if (Physics.OverlapCapsule(CapsulePoint(pointsOffset, targetCenter), CapsulePoint(-pointsOffset, targetCenter), radius, settings.both).Length > 0)
        {
            targetCenter.y += 0.4f;
            if (Physics.OverlapCapsule(CapsulePoint(pointsOffset, targetCenter), CapsulePoint(-pointsOffset, targetCenter), radius, settings.both).Length > 0)
            {
               for (int i = 0; i < Physics.OverlapCapsule(CapsulePoint(pointsOffset, targetCenter), CapsulePoint(-pointsOffset, targetCenter), radius, settings.both).Length; i++)
                {
                    if (Physics.OverlapCapsule(CapsulePoint(pointsOffset, targetCenter), CapsulePoint(-pointsOffset, targetCenter), radius, settings.both)[i].gameObject != currentClimbBox)
                    {                     
                        return false;
                    }
                }
            }
        }

        return true;
    }

    private bool CheckObstruction()
    {
        Vector3 vec = wallhit.point;
        vec.y = transform.position.y;

        Vector3 target = vec + flatNormal * (radius * 0.5f + 0.001f);
        Vector3 p1 = CapsulePoint(pointsOffset, target);
        Vector3 p2 = CapsulePoint(-pointsOffset, target);
        Vector3 dir = Vector3.up;
        float dist = topHit.point.y + (col.height * 0.5f) + 0.2f - transform.position.y;
        
        if (Physics.CapsuleCast(p1, p2, radius, dir, dist, settings.obstructing) == false)
        {
            p1.y += dist;
            p2.y += dist;
            Vector3 hStart = p1;
            hStart.y = 0f;
            Vector3 hEnd = topHit.point;
            hEnd.y = 0f;
            float dist2 = Vector3.Distance(hEnd, hStart);

            if (Physics.CapsuleCast(p1, p2, radius, -flatNormal, dist2, settings.obstructing) == false)
            {
                climbUpTarget = topHit.point + new Vector3(0f, col.bounds.extents.y + 0.1f, 0f);

                return false;
            }
        }
        return true;
    }

        
	
      
    private bool inPosition = false;
    private bool recieveInput = false;
    private bool climb = false;

    private float releaseCount;
    private bool runFailSafe;
    ClimbCheck climbCheck;
    PlayerMovement p;

    private Quaternion targetRot;

    private Vector3 climbTargetPos;
    private Vector3 targetPos;
    private Vector3 rHand, lHand;

    public ClimbingState(PlayerMovement player)
    {
        p = player;
        climbCheck = p.gameObject.GetComponent<ClimbCheck>();
    }

    private IEnumerator FailSafe(bool climbingUp)
    {
        runFailSafe = false;
        yield return new WaitForSeconds(2f);
        runFailSafe = true;
        if (inPosition == false && climbingUp == false)
        {
            p.StartCoroutine(p.SwitchState(p.airState));
        }
    }
    
    public Vector3 UpdateState(Vector3 input, Vector3 vel)
    {
        if (inPosition == false)
        {
            return(LerpToTarget());
        }
        else 
        {
            if (recieveInput)
            {
                float forwardInput = (Vector3.Project(input, p.gameObject.transform.forward).normalized == p.gameObject.transform.forward) 
                ? Vector3.Project(input, p.gameObject.transform.forward).magnitude : -Vector3.Project(input, p.gameObject.transform.forward).magnitude;
                if (forwardInput > -0.55f)
                {
                    releaseCount += Time.deltaTime;
                    if (releaseCount < 0.2f)
                    {
                        p.StartCoroutine(climbCheck.ClimbCD(0.2f));
                        p.StartCoroutine(p.SwitchState(p.airState));
                    }
                }
                else
                {
                    releaseCount = 0f;
                }
                if (p.input.Buttons() == "Jump")
                {
                    p.anim.SetTrigger("climbUp");
                    FMODUnity.RuntimeManager.PlayOneShotAttached(climbCheck.settings.climbSound, p.gameObject);
                    climb = true;
                    recieveInput = false;
                }
            }
            if (climb)
            {
                if(runFailSafe)
                {
                    p.StartCoroutine(FailSafe(true));
                }
                return ClimbUp();
            }
        }
        return Vector3.zero;
    }

    private Vector3 ClimbUp()
    {
        if ((climbTargetPos - p.gameObject.transform.position).magnitude < 0.1f)
        {
            p.StartCoroutine(p.SwitchState(p.airState));
            return Vector3.zero;
        }
        else
        {
            if (p.gameObject.transform.position.y < climbTargetPos.y)
            {
                return Vector3.up * climbCheck.settings.climbSpeed;
            }
            else
            {
                Vector3 dir = (climbTargetPos - p.gameObject.transform.position).normalized;
                return dir * climbCheck.settings.climbSpeed;
            }
        }
    }

    private Vector3 LerpToTarget()
    {
        if ((p.gameObject.transform.position - targetPos).magnitude < climbCheck.settings.entrySpeed * Time.deltaTime && p.transform.rotation == targetRot)
        {
            p.StartCoroutine(InputDelay());
            inPosition = true;
            return Vector3.zero;
        }

        p.transform.rotation = Quaternion.RotateTowards(p.gameObject.transform.rotation, targetRot, Time.deltaTime * 360f);

        Vector3 dir = (targetPos - p.gameObject.transform.position).normalized;
        return dir * climbCheck.settings.entrySpeed;
    }
}
        
	

Puzzle

For the game I made a lazer puzzle. The idea was to first lead the beam throughout various boxes to blast open the doors. But it was changed into have a start point that can be rotated, to shoot into boxes, to redirect into a final receiver. 

Each part have a Linerenderer that starts to render when activated. I use a boxcast to try and hit next reciever and once it hit, it either activate certain event or re-direct to the next output. 

My original idea was to have multiply exit points in each box, and depending on the angle you hit the first point, it will redirect towards certain exit point. 

This was changed into having only 2 points on each box which resulted in if you hit 1 of the point, the lazer will continue through the other. 

          		
[RequireComponent(typeof(LineRenderer))]
public class LazerBeamStatue : MonoBehaviour
{
    [SerializeField] private LayerMask lazerMask;
    private LineRenderer myLineRender;
    private RaycastHit myRayhit;
    private Lazer currentTarget = null;
    
    public bool canShoot = true; //if it should shoot or not

    public void Start()
    {
        myLineRender = GetComponent<LineRenderer>();
        myLineRender.positionCount = 2;
    }
    public void Update()
    {
        if (canShoot)
        {
            myLineRender.SetPosition(0, transform.position);
            myLineRender.SetPosition(1, GetLazerEnd(this.transform));
        }
    }
    Vector3 GetLazerEnd(Transform lazerStart)
    {
        if (Physics.BoxCast(
            lazerStart.transform.position,
            lazerStart.transform.localScale * 0.5f,
            lazerStart.transform.forward,
            out myRayhit,
            Quaternion.LookRotation(lazerStart.transform.forward, Vector3.up),
            300,
            lazerMask)
            )
        {
            if (
                myRayhit.transform.GetComponent<Lazer>() &&
                currentTarget != myRayhit.transform.GetComponent<Lazer>() && 
                !myRayhit.transform.GetComponent<Lazer>().lazerRecieve
                )
            {
                if(currentTarget)
                    currentTarget.LazerDisconnected();

                currentTarget = myRayhit.transform.GetComponent<Lazer>();
                currentTarget.lazerFrom = transform.position;
                currentTarget.LazerConnected();
            }

            else if (!myRayhit.transform.GetComponent<Lazer>())
            {
                if (currentTarget)
                {
                    currentTarget.LazerDisconnected();
                    currentTarget = null;
                }
            }
            return myRayhit.point;
        }
        else
        {
            if (currentTarget)
            {
                currentTarget.LazerDisconnected();
                currentTarget = null;
            }
            return lazerStart.transform.forward * 300 + lazerStart.transform.position;
        }
    }
}
        
	
      
    private List<Lazer> Directions = new List<Lazer>();
    private Lazer currentTarget = null;
    private LineRenderer MyLineRender;
    private RaycastHit myRayhit;
    private int lazerFrom, lazerTo;
    [SerializeField] private float activeAmount = 0f;
    private List<MeshRenderer> mesh = new List<MeshRenderer>();

    public bool isRecieving = false;
    public bool canRecieve = true;
    public LayerMask lazerMask;
    public Material onMat, offMat;

    [Header("EndOfPuzzle")]
    public bool theEnd;
    public List<Material> mat = new List<Material>();
    [SerializeField]private Shader shader;
    bool ReachedMyGoal = false;

    void Start()
    {
        foreach (MeshRenderer item in GetComponentsInChildren<MeshRenderer>())
        {
            if (item.material.shader == shader)
            {
                mesh.Add(item);
            }
        }

        canRecieve = true;
        isRecieving = false;
        MyLineRender = GetComponent<LineRenderer>();
        ReachedMyGoal = false;
        MyLineRender.positionCount = 2;

        foreach (Lazer lazer in transform.GetComponentsInChildren<Lazer>())
        {
            Directions.Add(lazer);
            lazer.lazerRecieve = false;
            lazer.lazerSend = false;
        }
    }

    void Update()
    {
        if (isRecieving)
        {
            foreach (MeshRenderer item in mesh)
            {
                item.material = onMat;
            }
            if (theEnd)
            {
                ReachedMyGoal = true;
                return;
            }
            MyLineRender.SetPosition(0, Directions[lazerTo].transform.position);
            MyLineRender.SetPosition(1, GetLazerEnd(Directions[lazerTo]));
        }
        if (canRecieve)
        {
            foreach (MeshRenderer item in mesh)
            {
                item.material = offMat;
            }
            if (theEnd)
            {

            }
            else
            {
                shouldPlay = true;
            }
            if (currentTarget)
            {
                currentTarget.LazerDisconnected();
                Directions[lazerTo].lazerSend = false;
                currentTarget = null;
            }
            for (int i = 0; i < Directions.Count; i++)
            {
                if (Directions[i].lazerRecieve)
                {
                    isRecieving = true;
                    lazerFrom = i;
                    canRecieve = false;
                    if (!theEnd)
                    {
                        lazerTo = GetLazerTo(lazerFrom);
                        Directions[lazerTo].lazerSend = true;
                    }
                }
                ReachedMyGoal = false;
            }
        }
    }
    int GetLazerTo(int indexFrom)
    {
        float Angle = 0;
        int Index = 0;
        for (int i = 0; i < Directions.Count; i++)
        {
            if (i != indexFrom && !Directions[i].lazerRecieve)
            {
                float A = Vector3.Angle(Directions[indexFrom].LazerFrom, Directions[indexFrom].transform.forward);
                float B = Vector3.Angle(Directions[indexFrom].LazerFrom, Directions[i].transform.position);
                Angle = B - A;
                Index = i;
            }
        }
        for (int i = 0; i < Directions.Count; i++)
        {

            if (Directions[indexFrom] != Directions[i] && !Directions[i].lazerRecieve) //No need to check if it already have been checked or if it is the same
            {
                float A = Vector3.Angle(Directions[indexFrom].LazerFrom, Directions[indexFrom].transform.forward);
                float B = Vector3.Angle(Directions[indexFrom].LazerFrom, Directions[i].transform.position);
                float C = B - A;
                if (C > Angle)
                {
                    Angle = C;
                    Index = i;
                }
            }
        }
        return Index;
    }
    Vector3 GetLazerEnd(Lazer lazerStart)
    {
        if (Physics.BoxCast(
            lazerStart.transform.position,
            lazerStart.transform.localScale,
            lazerStart.transform.forward,
            out myRayhit,
            Quaternion.identity,
            300,
            lazerMask)
            )
        {
            if (myRayhit.transform.GetComponent<Lazer>())
            {
                if (!myRayhit.transform.GetComponent<Lazer>().lazerSend || !myRayhit.transform.GetComponent<Lazer>().lazerRecieve)
                {
                    for (int i = 0; i < myRayhit.transform.GetComponent<Lazer>().transform.parent.GetComponent<LazerPuzzle>().Directions.Count; i++)
                    {
                        if (myRayhit.transform.GetComponent<Lazer>().transform.parent.GetComponent<LazerPuzzle>().Directions[i].lazerRecieve)
                            return myRayhit.point;
                    }
                    if (currentTarget)
                    {

                        currentTarget.LazerDisconnected();
                        ReachedMyGoal = false;
                        Directions[lazerTo].lazerSend = false;

                        currentTarget = myRayhit.transform.GetComponent<Lazer>();
                        currentTarget.LazerFrom = transform.position;
                        currentTarget.LazerConnected();
                    }
                    else if (!currentTarget && myRayhit.transform.GetComponent<Lazer>())
                    {
                        currentTarget = myRayhit.transform.GetComponent<Lazer>();
                        currentTarget.LazerFrom = transform.position;
                        currentTarget.LazerConnected();
                    }
                }
            }
            else if (!myRayhit.transform.GetComponent<Lazer>())
            {
                if (currentTarget)
                {
                    currentTarget.LazerDisconnected();
                    ReachedMyGoal = false;
                    currentTarget = null;
                }
            }
            else
            {
                return lazerStart.transform.forward * 300 + lazerStart.transform.position;
            }
            return myRayhit.point;
        }
        else
        {
            if (currentTarget)
            {
                currentTarget.LazerDisconnected();
                ReachedMyGoal = false;
                currentTarget = null;
            }
            return lazerStart.transform.forward * 300 + lazerStart.transform.position;
        }
    }

    public bool ReachedGoal()
    {
        return ReachedMyGoal;
    }
}

        
	

UI

We realized way to late that we had no one assigned in making UI for the game; such as main menu or pause menu. I had just finished up my current tasks and decided to make it.

Now, Unity is very nice to 2D elements, such as menus, and therefor did not require much coding.

I made a scene with assets from our first level and created a canvas for that scene. In the canvas there was the buttons:

  • New game - Calls scenemanager to load next level
  • Settings - Opens alternative for sound and quality
  • Credits - Shows a picture of the participants
  • Quit - Exit the application

         

For the in-game pause menu, lot of the functionality was copied from main menu. It set the timescale to 0 to pause the game until either pressing the "Back" option or "Esc" key.

         

Tools

The game consist in levels and between levels we used level streaming. Everyone that was working on the project could use their own scenes to later add it into the main levels. 


The functionallity of the code is straight forward; it load and unload scenes when called upon. We made an invisible hitbox that called the loading. 


The designer could call the event however they wanted. Either they could activate it through collision, or activate it by calling an event. 

I made a script called "SceneManager" that hold the functionality of the level streaming. You had to manually add the name of the scene that was going to be loaded and the scene must be added into the final build. 


I also made a script that gather information from the editor to get the name and index of each added scene that is added to the build. This made it easier for the designers to later add the scenes into the sceneloader. 

          		
[Serializable]
public class SceneInformation
{
    public string name;
    public int index;
}
public class MainMenuButtons : MonoBehaviour
{
    public int SceneIndex = 0;
    public List<SceneInformation> MySceneList = new List<SceneInformation>();

    private void Start()
    {
        MySceneList.Clear();
        for (int i = 0; i < SceneManager.sceneCountInBuildSettings; ++i)
        {
            string name = System.IO.Path.GetFileNameWithoutExtension(SceneUtility.GetScenePathByBuildIndex(i));
            int index = SceneUtility.GetBuildIndexByScenePath(name);
            SceneInformation tempScene = new SceneInformation();
            tempScene.name = name;
            tempScene.index = index;
            MySceneList.Add(tempScene);
        }
    }
        
	
          
              
    public static ManagerForScenes Instance { set; get; }
    public List<string> StartLevels = new List<string>();
    public string PlayerLevel;
    public static PlayerMovement playerMovement;
    void Awake()
    {
        Instance = this;
        playerMovement = FindObjectOfType<PlayerMovement>();
        for (int i = 0; i < StartLevels.Count; i++)
        {
            if(i == 0)
            {
                SceneManager.LoadScene(StartLevels[i], LoadSceneMode.Additive);
                continue;
            }
            LoadFirstScene(StartLevels[i]);
        }
        LoadFirstScene(PlayerLevel);
        GameObject[] p_lay = SceneManager.GetSceneByName(PlayerLevel).GetRootGameObjects();
        for (int i = 0; i < p_lay.Length; i++)
        {
            if (p_lay[i].GetComponent<PlayerMovement>())
            {
                playerMovement = p_lay[i].GetComponent<PlayerMovement>();
            }
        }
    }
    public void Load(string sceneName)
    {
        if(!SceneManager.GetSceneByName(sceneName).isLoaded)
        {
           SceneManager.LoadScene(sceneName, LoadSceneMode.Additive);
         
        }
    }
    public void Unload(string sceneName)
    {
        if (SceneManager.GetSceneByName(sceneName).isLoaded)
        {
            SceneManager.UnloadSceneAsync(sceneName);
        }
    }
    private void LoadFirstScene(string sceneName)
    {
        if(sceneName != "")
        {
            SceneManager.LoadScene(sceneName, LoadSceneMode.Additive);
        }
    }
        
	
FolkeLogo