Objects Spawn & Properties¶
Communication with Beat Detection Server¶
The object spawn takes place in the SpawnManager
class with the logical conditions that have been satisfied in BeatDetection
class. The two classes communicate utilising the event handler:
// SpawnManager.cs ("..." represents other code blocks irrelevant to the current session)
...
void Start() {
// Register the beat callback function
GetComponent<BeatDetection>().CallBackFunction = MyCallbackEventHandler;
}
...
public void MyCallbackEventHandler(BeatDetection.EventInfo eventInfo) {
switch (eventInfo.messageInfo) {
case BeatDetection.EventType.Energy: // low freq, high amp
spawnUpOb();
break;
case BeatDetection.EventType.HitHat: // high freq
spawnDownOb();
break;
case BeatDetection.EventType.Kick:
spawnBubble();
break;
case BeatDetection.EventType.Snare:
spawnLightning();
break;
}
}
// BeatDetection.cs ("..." represents other code blocks irrelevant to the current session)
public enum EventType {
Energy,
Kick,
Snare,
HitHat
}
public class EventInfo {
public EventType messageInfo;
public BeatDetection sender;
}
public delegate void CallbackEventHandler(EventInfo eventInfo);
public CallbackEventHandler CallBackFunction;
...
void SendEvent(EventType theEvent) {
if (CallBackFunction != null) {
EventInfo myEvent = new EventInfo();
myEvent.sender = this;
myEvent.messageInfo = theEvent;
CallBackFunction(myEvent);
}
}
Obstacles¶
There are 3 Obastacle types with two upper and one down:
Down Obstacle | Up Obstacle 1 | Up Obstacle 2 |
Each spawned by the following functions:
// SpawnManager.cs ("..." represents other code blocks irrelevant to the current session)
// spawning up obstacles
void spawnUpOb() {
// instantiate the next spawn
GameObject newSpawnUpOb;
// random 1/2 possibility spawning each of the 2 plausible objects
Random random = new Random();
int randomThreshold = random.Next(1, 3); // generate a integer number between 1, 2
// run this spawn function every certain frames (defined in inspector)
if (Time.frameCount % frameIntervalUpOb == 0) {
if (randomThreshold == 1) {
newSpawnUpOb = Instantiate(upObstacle1, spawnPosUpOb, Quaternion.identity);
addChildToCurrentObject(newSpawnUpOb);
} else if (randomThreshold == 2) {
newSpawnUpOb = Instantiate(upObstacle2, spawnPosUpOb, Quaternion.identity);
addChildToCurrentObject(newSpawnUpOb);
}
}
}
// spawning down obstacles
void spawnDownOb() {
// instantiate the next spawn
GameObject newSpawnDownOb;
// run this spawn function every certain frames (defined in inspector)
if (Time.frameCount % frameIntervalDownOb == 0) {
newSpawnDownOb = Instantiate(downObstacle, spawnPosDownOb, Quaternion.identity);
addChildToCurrentObject(newSpawnDownOb);
}
}
Note
Two up obstacles each has 1/2 chance of being spawned by using the random function
Bubbles Shield System¶
Bubble Spawn¶
Shields will be generated if 5 bubbles have collected as mentioned previously:
The 4 sprites of the shield has been shown below:
// Player.cs (... represents other code blocks irrelevant to the current session)
...
void SpawnShield() {
if (shieldAddable) {
// instantiate the next spawn
GameObject newSpawnShield;
// always update shield position relative to the Player
shieldPos = new Vector3(
transform.position.x - 1.12f,
transform.position.y - 0.07f,
transform.position.z);
// run this spawn function every certain frames (defined in inspector)
newSpawnShield = Instantiate(shield, shieldPos, Quaternion.identity);
// make the current item a child of the SpawnManager
newSpawnShield.transform.parent = transform;
// prevent shield overlapping
shieldAddable = false;
}
}
void DestroyShield() {
if (transform.childCount > 0) {
var shieldInstance = transform.GetChild(0).gameObject;
if (shieldInstance != null) {
Destroy(shieldInstance);
}
}
}
The sprite of the bubbles is:
The bubbles have been spawned by the following functions:
// spawning bubbles
void spawnBubble() {
// instantiate the next spawn
GameObject newSpawnBubble;
// random 1/2 possibility spawning at one of the two plausible heights
Random random = new Random();
int randomThreshold = random.Next(1, 3); // generate a integer number between 1, 2
// run this spawn function every certain frames (defined in inspector)
if (Time.frameCount % frameIntervalDownOb == 0) {
if (randomThreshold == 1) {
newSpawnBubble = Instantiate(bubble, spawnPosBubble, Quaternion.identity);
addChildToCurrentObject(newSpawnBubble);
} else if (randomThreshold == 2) {
newSpawnBubble = Instantiate(
bubble,
new Vector3(
spawnPosBubble.x,
spawnPosBubble.y - 4,
spawnPosBubble.z),
Quaternion.identity);
addChildToCurrentObject(newSpawnBubble);
}
}
}
Note
bubbles are generated in 2 various altitudes each has 1/2 chance
Bubble Collection¶
The bubble collection logic has been implemented in 2 separate classes by:
- defining the counting when colliding with an obstacle of collection and behaviour after collecting 5
- call it in the bubble collision helper class
// PlayerHealth.cs
// collect 5 bubbles to become temporarily invincible
public void CollisionWithBubble() {
if (bubbleCount < 4) {
bubbleCount++;
} else {
EnterInvincibleMode();
bubbleCount = 0;
}
}
void EnterInvincibleMode() {
invincible = true;
Invoke("ExitInvincibleMode", invincibleDuration);
}
void ExitInvincibleMode() {
invincible = false;
}
// BubbleCollisionHelper.cs
private PlayerHealth playerHealth;
private Player player;
void Start() {
playerHealth = FindObjectOfType<PlayerHealth>();
player = FindObjectOfType<Player>();
}
...
private void OnTriggerEnter2D(Collider2D collision) {
if (collision.gameObject.name == "Player") {
playerHealth.CollisionWithBubble();
// Bubble disappear after being collected by the player
Destroy(gameObject);
}
}
Constant Leftward Movement¶
The constant leftward movement of the objects pursue with the following logic:
- when a new object has been spawned, append it to the current spawn manager parent object
- in each iteration of
Update()
function being called, loop through all the current children of the parent spawn manager object in a for-loop - apply a left-ward vector to every single child in the loop
Note
since the child objects of spawn manager could be distroyed due being eaten by the Whale or self-destructed outside the boundary of the screen, the number of items within the spawn manager is varying thus need a agile and flexible approach on a dynamic array instance of collection of all children objects.
// SpawnSeaGullManager.cs (... represents other code blocks irrelevant to the current session)
...
void Update() {
float displacement = Time.deltaTime * speed;
// store all children under Spawn Manager in an array
Transform[] children = transform.Cast<Transform>().ToArray();
// ------- obstacles moving towards left -------
// mind that the moving functionality has to be implemented before destroying redundant objects
// or otherwise the array length will be changed before moving all the objects
for (int i = 0; i < children.Length; i++) {
// beware to add Space.World or otherwise default will be Space.Self
// where rotation angle of the object will be stored as well
children[i].transform.Translate(Vector2.right * displacement, Space.World);
}
...
}
The append of child happend during the creation of each object:
// SpawnSeaGullManager.cs (... represents other code blocks irrelevant to the current session)
// spawning down obstacles
void spawnDownOb() {
// instantiate the next spawn
GameObject newSpawnDownOb;
// run this spawn function every certain frames (defined in inspector)
if (Time.frameCount % frameIntervalDownOb == 0) {
newSpawnDownOb = Instantiate(downObstacle, spawnPosDownOb, Quaternion.identity);
addChildToCurrentObject(newSpawnDownOb);
}
}
void addChildToCurrentObject(GameObject item) {
// make the current item a child of the SpawnManager
item.transform.parent = transform;
}
Destroy Objects¶
If the object spawned hasn’t been eaten, it will continue to move left-wards and stack in the spawn manager parent object, which will consume plenty of computer memory and thus harmful for the program.
Therefore, all object will be destroyed if they are outside the left boundary of the screen to save the computational power.
// DestroyObject.cs (... represents other code blocks irrelevant to the current session)
[SerializeField] private float destroyXPos = -18f;
...
void Update() {
DestroyHierarchy();
}
public void DestroyHierarchy() {
//Debug.Log(gameObject.transform.position.x);
if (gameObject.transform.position.x < destroyXPos) {
Destroy(gameObject);
}
}
Prevent Packed Obstacles¶
Attention
To make the game playable, the minimal inetrval between obstacles are equal to half of character’s jump distance (12/2). Any obstacles generated within that distance will be deleted from the list.
void Update() {
...
// ------- prevent obstacles from spawning too close to each other -----
if (children.Length >= 2) {
var lastChild = children[children.Length - 1].gameObject;
var lastSecondChild = children[children.Length - 2].gameObject;
string lastChildName = lastChild.name;
string lastSecondChildName = lastSecondChild.name;
float lastChildXPos = children[children.Length - 1].transform.position.x;
float lastSecondChildXPos = children[children.Length - 2].transform.position.x;
//Debug.Log(lastSecondChildName);
//Debug.Log(lastChildName);
/*
* if the last obstacle spawned is to close to the last second obstacle spawned,
* destroy the last one to prevent obstacles from spawning too close to each other
* which left impossible situation for the player to mitigate
*/
if (lastChildName == lastSecondChildName && lastChildName == "DownObstacle") {
if (lastChildXPos - lastSecondChildXPos < jumpReactionDistance) {
Destroy(lastChild);
}
} else {
if (lastChildXPos - lastSecondChildXPos < jumpReactionDistance / 2) {
Destroy(lastChild);
}
}
}
}