Hello all, I hope that this blog post finds you all well. Given the COVID-19 circumstances, I (and everyone I know) have been pushed into online classrooms. It’s been fine, but luckily it has also given me some personal time to try and post on this site, and work on more personal projects… That being said this post is a class project by Nicole Havin, and myself. Nicole worked on the mostly on the aesthetic side of things, implementing sounds, Timer UI and Main menu. I’m writing this post to talk more about Scriptable Object Dependent Architecture. I’m posting this game here to provide my readers with an opportunity to experience the architecture of which I will be writing about.
Disclaimers: I do not own anything in this game
This is a remake, for a class project, of Konami’s 1981 classic “Frogger”
https://simmer.io/@aidantakami/~bcd44750-53b0-3b08-07fd-bcb7b3778673
This game is buggy and was made under a time crunch for class. I am only posting this game here to give examples of the architecture I am speaking of, and showing how it is used in this specific build. I am also experimenting with UnityWebGL and trying to get those games hosted here on my site. For those who don’t know, exporting to UnityWebGL carries with it a plethora of issues in how the game in converted to be playable via a browser. There are certainly lots of bugs which I have not run into on the WebGL version. So please consider this as you play this game! Please feel free to contact me if you find any bugs! If this goes well I will continue to try and import playable demos of the architecture of which I’m writing about. Give it a few plays through and I’ll catch you on the other side.
This short experience is build to be modular, easily debuggable, and dependency free through the use of SODA Game Events & Listeners, and SODA Variables.
I’d like to first draw your attention to how when the player is in the first half of the map they must avoid collisions. Collisions here are from incoming cars, which are deadly to frogger. However, the second half of the map, this is flipped. The second half of the map the player must seek collisions. Collisions in the latter half of the map are from the stable log platforms or the finish line colliders.
So how might we “normally” do this? I would say it would involve an edge trigger collider that would tell the player script that it needs to collide or else die. But alas, the eternal game dev issue of detecting if something isn’t there, in this case that something would be the log. I don’t want to get into how we might complete “this” way of solving the issue, but it’s just messy and involves heavy dependencies.
What is the SODA way of doing this? I appreciate you asking. Imagine if we could just have a Scriptable Object which holds a bool value, and we call it BoolVariable “isOverWater”. We set up a collider at the edge of land and water and have that trigger toggle between the 2 states of isOverWater, true and false. Now how do we get logs in there? Simple, the same thing, just have a single BoolVariable, playerIsOnMe, which all log prefabs reference. Does that make sense? This single variable is being operated on by all instances of the log prefab. These logs simply set the variable to true OnTriggerEnter() and to false OnTriggerExit(). Here’s that code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class LogScript : MonoBehaviour
{
//BoolVariable to represent if player is on this log
public BoolVariable playerIsOnMe;
//Used for spawning purposes
private bool startsOnRight;
public SpriteRenderer sr;
//Used to move the player when on log
[SerializeField]public IntVariable logSpeedToPlayer;
[SerializeField] public int logSpeed;
//Flips sprite depending on start side
private void Start()
{
if(transform.position.x > 0)
{
startsOnRight = true;
sr = gameObject.GetComponent<SpriteRenderer>();
sr.flipX = true;
}
}
// Update is called once per frame
void Update()
{
//Moves the log in accordance to where it was spawned
if (!startsOnRight)
{
transform.position += transform.right * logSpeed * Time.deltaTime;
}
else
{
transform.position -= transform.right * logSpeed * Time.deltaTime;
}
}
public void OnTriggerEnter2D(Collider2D col)
{
//Player enters log
if (col.gameObject.CompareTag("Player"))
{
//Set BoolVariable value
Debug.Log("Player is on");
playerIsOnMe.SetValue(true);
//Gives this logs speed to the IntVariable which gives it to the Player
if (!startsOnRight)
{
logSpeedToPlayer.SetValue(logSpeed);
}
else
{
logSpeedToPlayer.SetValue(-logSpeed);
}
}
}
public void OnTriggerExit2D(Collider2D col)
{
//Player Leaves Log
if (col.gameObject.CompareTag("Player"))
{
Debug.Log("Player is off");
playerIsOnMe.SetValue(false);
logSpeedToPlayer.SetValue(0);
}
}
}
If this makes any sense to you, then odds are you can understand what is going on in the rest of this script (beyond the OnTriggers), but more on that later. The main note here is that when the player enters the log, beneath the Debug statement, the BoolVariable playerIsOnMe is set to true. This lets the player movement script know to continue displaying the idle sprite, this bool lets the GameManager know the player is still alive. These BoolVariables (playerIsOnMe and isOverWater) are both accessible by all other scripts, and never providing conflicting values because all scripts are refrencing the same ScriptableObject.
How are these two BoolVariables being compared? Once again, well thought out question. So since both these Bools are being determined elsewhere in the script, our PlayerMovement script, which also serves as the animator controller, simply has to reference these 2 variables values to know the state of the player. I did this in Frogger by having an event at the end of every “Jump” animation which would check the state of the 2 BoolVariables, as at the end of the Jump animation the player would either be securely on a log or dead.
About the IntVariables: If you understand the above architecture then you’ll enjoy knowing what’s going on with the IntVariable logSpeedToPlayer in the above code. Basically, that IntVariable, when set to 0, will not affect the player’s movement at all. When set to x it will apply a constant drag to the player to the right to simulate the effect of the player being on the log. So… Do you see why this is so rad? I can just SET THIS INT in my LOG SCRIPT and then my player will INSTANTLY BE EFFECTED by the change to this variable! COME ON!!! How Cool?! So notice that logspeed is just a regular int, but it is used to set the value of logSpeedToPlayer when needed. For now, that’s it! I will try to be more present on here, but I’m busy! But I hope you enjoy! and stay safe!