Jump to content
  • Advertisement
  • entries
    7
  • comments
    4
  • views
    937

Dev Log #6

Treader

435 views

Intro

So this has been a long time coming so some details might be missing but praise be to source control. Since the last blog I have updated the engine, and done some major code refactoring in the way references are handled. The major game-play update has been ability to move the character and double jump along with new visuals including boost particle animation and end screen in animation. Note I am also starting to add code snippets now with class names at the start mentioned in a comment.

 

World Config

As an infinite runner game the word moves towards the player, the player does not move through the world so this config will maintain configurable data that affects the environment as a whole. Currently that is the speed that the world moves at.

using UnityEngine;

public class WorldConfig : ScriptableObject
{
    public float worldBaseSpeed = 3;
}
//PlayerIdle.cs 

     [SerializeField] Transform endMarker;
     [SerializeField] PlayerData playerData;
+    [SerializeField] WorldConfig worldConfig;
 
-    float worldSpeed = 3; //TODO: this needs to come from config
     Vector3 idleMoveVector;
     bool isLeftOfObstacleColliding = false;
     bool isAtEnd = false;
//PlayerIdle.cs

	void DoIdle()
     {
         idleMoveVector = transform.position;
-        idleMoveVector.x = idleMoveVector.x - (worldSpeed * Time.fixedDeltaTime);
+        idleMoveVector.x = idleMoveVector.x - (worldConfig.worldBaseSpeed * Time.fixedDeltaTime);
         transform.position = idleMoveVector;
     }
//Obstacle.cs


     public Action<Obstacle> OnReachedEnd;
 
     [SerializeField] ObstacleConfig config;
+    [SerializeField] WorldConfig worldConfig;
     [SerializeField] GameObject endPoint;
 
     Vector3 moveVector;
//Obstacle.cs

     void Move()
     {
-        moveVector.x = moveVector.x - (config.horizontalSpeed * Time.fixedDeltaTime);
+        moveVector.x = moveVector.x - (worldConfig.worldBaseSpeed * Time.fixedDeltaTime);
         transform.position = moveVector;
     }

 

 

Service Locator

This is one of my biggest refactoring update for this time the service locator. What it does is simply searches for a MonoBehavior using Find by type and returns its instance in case the instance is not found it has the option to create a new instance. This eliminates the need for singleton or a lot of drag and drop references in the inspector.

using System.Collections.Generic;
using UnityEngine;
using System;

namespace Treader
{
    public class ServiceLocator : MonoBehaviour
    {
        public static ServiceLocator Instance;
        Dictionary<object, MonoBehaviour> services;
        bool canCreateIfNotFound = false;

        public T GetService<T>(bool createIfNotFound) where T : MonoBehaviour
        {
            canCreateIfNotFound = createIfNotFound;

            if (services == null)
            {
                services = new Dictionary<object, MonoBehaviour>();
            }
            try
            {
                if (services.ContainsKey(typeof(T)))
                {
                    T service = (T)services[typeof(T)];
                    if (service != null)
                    {
                        return service;
                    }
                    else
                    {
                        services.Remove(typeof(T));
                        return FindService<T>();
                    }
                }
                else
                {
                    return FindService<T>();
                }
            }
            catch
            {
                throw new Exception();
            }
        }

        T FindService<T>() where T : MonoBehaviour
        {
            T service = GameObject.FindObjectOfType<T>();
            if (service != null)
            {
                services.Add(typeof(T), service);
            }
            else if (canCreateIfNotFound)
            {
                service = gameObject.AddComponent<T>();
                services.Add(typeof(T), service);
            }

            return (T)services[typeof(T)];
        }

        private void Awake()
        {
            if (Instance == null)
            {
                Instance = this;
            }
        }
    }
}

 

 

Horizontal Movement

This just involves the ability for the player to move horizontally giving the player a lot more versatility to avoid the obstacles.

playermove.gif.3b219ff9c46bf038ddb8bf223c7795a7.gif

using UnityEngine;

public class PlayerHorizontalMovement : MonoBehaviour
{
    [SerializeField] PlayerData playerData;

    Vector3 moveVector;
    // Start is called before the first frame update
    void Start()
    {
        moveVector = Vector3.zero;
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetAxis("Horizontal") != 0)
        {
            DoMovement();
        }
    }

    void DoMovement()
    {
        moveVector.x = (Input.GetAxis("Horizontal") * playerData.pPlayerConfig.speed * Time.fixedDeltaTime) + transform.localPosition.x;
        moveVector.y = transform.localPosition.y;

        transform.localPosition = moveVector;
    }
}
 public class PlayerConfig : ScriptableObject
 {
     public float jumpForce = 20;
+    public float speed = 20;
     public float gravityModifier = 0.5f;
 }

 

Falling

Once a player gets on top of an obstacle they need to be able to fall off it. To my pleasant surprise the same code that handles the jump for the character also handles their fall after all what is falling if not jumping with 0 force.

 public class PlayerCollisionEvents : MonoBehaviour
 {
-    public Action OnTopOfSomething;
+    public Action<GameObject> OnTopOfSomething;
     public Action OnCollideObstacleLeft;
     public Action OnObstacleCollisionExit;
//PlayerCollisionEvents .cs

	void OnTriggerEnter2D(Collider2D collision)
     {
         eventsCollider = collision;
 
         if (IsOnTop())
         {
             if (OnTopOfSomething != null)
             {
-                OnTopOfSomething();
+                OnTopOfSomething(collision.gameObject);
             }
         }
+using UnityEngine;
 
 public class PlayerJump : MonoBehaviour
 {
     [SerializeField] PlayerData playerData;
 
     bool canJump = true;
+    bool onTopOfObstacle = false;
 
     Vector3 jumpVector;
     Vector3 fallVector;
//PlayerJump.cs     

     private void Start()
     {
         playerData.playerCollisionEvents.OnTopOfSomething += HandleOnTopOfSomething;
+        playerData.playerCollisionEvents.OnObstacleCollisionExit += HandleObstacleCollisionExit;
     }
//PlayerJump.cs
 
+    void InitFall()
+    {
+        jumpVector = transform.position;
+        currentForce = 0;
+        canJump = false;
+    }
//PlayerJump.cs

-    void HandleOnTopOfSomething()
+    void HandleOnTopOfSomething(GameObject topOfObject)
     {
-        Debug.Log("HandleOnTopOfSomething");
         canJump = true;
         playerData.mainAnimator.SetBool(PlayerAnimatorKeys.GROUNDED, true);
+        onTopOfObstacle = topOfObject.GetComponent<Obstacle>() != null;
+    }
+
+    void HandleObstacleCollisionExit()
+    {
+        if (onTopOfObstacle)
+        {
+            InitFall();
+        }
     }

 

 

Double Jump and Jump Boost Effect

Double jump simply involves checking if a double jump is done or not and allowing the player to initiate a jump again. Now the booster was a little more tricky because it needs to stay in scene at the position the double jump triggered run the course of its animation and then go away. Now the simple solution would be to destroy the booster each time it has run its course but that seemed a little wasteful so I put the booster in a pool each time its used and reuse the same booster again and again. If a double jump is initiated before the booster has run its course then a new booster in initiated and the pool keeps track of all these boosters.

doublejump.gif.47c93005062f0a6d136d275a7d64cd8b.gif

 

Double Jump

//PlayerJump.cs

     [SerializeField] PlayerData playerData;
 
     bool canJump = true;
+    bool doubleJump = true;
     bool onTopOfObstacle = false;
 
     Vector3 jumpVector;
//PlayerJump.cs

     void InitJump()
     {
         jumpVector = transform.position;
         currentForce = playerData.playerConfig.jumpForce;
-        canJump = false;
+
+        if (!doubleJump)
+        {
+            canJump = false;
+        }
+        else
+        {
+            doubleJump = false;
+        }
     }

     void InitFall()
     {
         jumpVector = transform.position;
         currentForce = 0;
-        canJump = false;
+        //canJump = false;
+        doubleJump = false;
     }
//PlayerJump.cs

     void FixedUpdate()
     {
-        if (!canJump)
+        if (!canJump || !doubleJump)
         {
             DoJump();
         }
//PlayerJump.cs

     void HandleOnTopOfSomething(GameObject topOfObject)
     {
         canJump = true;
+        doubleJump = true;
         playerData.mainAnimator.SetBool(PlayerAnimatorKeys.GROUNDED, true);
         onTopOfObstacle = topOfObject.GetComponent<Obstacle>() != null;
     }
 
     void HandleObstacleCollisionExit()
     {
-        if (onTopOfObstacle)
+        if (onTopOfObstacle && canJump && doubleJump)
         {
             InitFall();
         }

 

Booster Effect

using System.Collections.Generic;
using UnityEngine;

public class BoosterPool : MonoBehaviour
{
    [SerializeField] Booster boosterTemplate;
    [SerializeField] List<Booster> boosterPool;

    Vector3 boosterPosition;

    // Start is called before the first frame update
    void Start()
    {
        boosterPool = new List<Booster>();
        boosterPosition = Vector3.zero;
    }

    public void TriggerBoost(Vector3 position)
    {
        Booster booster = null;
        boosterPosition = position;

        booster = FetchFromPool();
        if (booster == null)
        {
            InstantiateBooster();
        }
        else
        {
            InitBooster(booster);
        }
    }

    void InstantiateBooster()
    {
        Booster booster = GameObject.Instantiate<Booster>(boosterTemplate, transform, true);
        InitBooster(booster);
        boosterPool.Add(booster);
    }

    Booster FetchFromPool()
    {
        Booster booster = null;
        for (int i = 0; i < boosterPool.Count; i++)
        {
            booster = boosterPool[i];

            if (!booster.IsEnabled())
            {
                return booster;
            }
        }

        return null;
    }

    void InitBooster(Booster booster)
    {
        booster.Play();
        booster.transform.position = boosterPosition;
    }
}
using UnityEngine;

public class Booster : MonoBehaviour
{
    [SerializeField] SpriteRenderer spriteRenderer;
    [SerializeField] Animator animator;

    public void Play()
    {
        animator.SetTrigger("enable");
    }

    public bool IsEnabled()
    {
        return spriteRenderer.enabled;
    }
}
 using UnityEngine;
+using Treader;
 
 public class PlayerJump : MonoBehaviour
 {
//PlayerJump.cs

+    BoosterPool boosterPool;
+
     private void Start()
     {
         playerData.playerCollisionEvents.OnTopOfSomething += HandleOnTopOfSomething;
         playerData.playerCollisionEvents.OnObstacleCollisionExit += HandleObstacleCollisionExit;
+
+        boosterPool = ServiceLocator.Instance.GetService<BoosterPool>(false);
     }
//PlayerJump.cs

     void InitJump()
     {
         jumpVector = transform.position;
         currentForce = playerData.playerConfig.jumpForce;
 
         if (!doubleJump)
         {
             canJump = false;
+            boosterPool.TriggerBoost(transform.position);
         }

 

 

Upcoming

  • End screen animation
  • Config reference refactoring
  • Hotkeys for ease of screen change
  • Prevent the player from passing through obstacles


0 Comments


Recommended Comments

There are no comments to display.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!