Divide Them Away!

Introduction: Divide Them Away!

Divide Them Away is a simple 2D shoot em up with a math theme, even if the theme is a bit odd sometimes. The webpage for the game, and where you can play it, is located at Senpairar.github.io (you can play it in your browser!). Please note that you'll only be able to play it on a computer like a laptop or desktop, not a phone or mobile tablet (perhaps a future update may solve this?). In addition, if you wouldn't mind, take a little bit to leave some feedback on the form on the webpage.

The purpose of this instructable is really about showing you the thought process behind the game development choices I made. That means I won't be getting into the bare basics of how the code works. Much of the code evolved over time can be seen on the GitHub repo which can be found here:

https://github.com/SenpaiRar/DivideThemAway

Here is the game homepage which also contains a bunch of other cool stuff:

Senpairar.github.io

So let's get into it! Firstly we'll start off with the various components of the game.

Also, as you read the instructables, I suggest you click on the link in the code snippets, as the GitHub gists mess up the spacing, which can make the code borderline unreadable.

Supplies

The software that I used throughout the game development process were...

  1. Unity3D - Served as the game engine
  2. Visual Studio/ Visual Studio Code - Served as the tools to program the game and create the website
  3. Github Desktop - Used to sync up code and to host the website; will get into later.

Step 1: Divide Them Away Mechanics

What should the game be like?

That was the principle question as I started development. From my experience creating my mountain survival game, I found that many of the game mechanics tried to reach too far. For example, I had a hunger and thirst system, but they were unintuitive and frankly a bit unfun to play with. So, for this game, I decided to keep it simple.

The Player's Job

The player wouldn't be scavenging or finding survival items. Instead, I would go retro and have them in an arena, dodging enemies, and destroying enemies. This left me with a couple mechanics I had to program.

  1. Player Movement
  2. Player Shooting
  3. Player Weapons
  4. Enemy Types
  5. Enemy Spawning

It's important to note that the way I ordered them here was definitely not the way I put them in the game. It was very much a spur of the moment thing to add different things, but they were all confined to this simplistic type of game. So, no item shops, or no complicated menus. So we'll get started with the player's first mechanic, movement.

Step 2: Divide Them Away's Mechanics: Player Movement

Code is down below for your viewing pleasure.

The first thing we need to give the player is sense of agency, which is most easily accomplished through the power of movement! But what kind of movement should a shooter like this have? The player needs to be able to move two different kinds of ways. First is the xy type of movement around the screen; second is rotational movement.

XY Movement

Unlike my previous game, which relied on a character controller which directly manipulated the x-y position of our character, this time we use a rigid body approach to movement. A rigid body is Unity's built-in physics object which can be exposed to different forces, such as explosions, or accelerations! Essentially, what the script does is...

  1. Take input from the player on the horizontal and vertical axis, (a and d / w and s)
  2. creates a force in that direction
  3. multiplying with a constant speed number
  4. applying it to our rigid body

This essentially gives our player a sliding feeling when they move around, since it takes time for the player to accelerate. In addition, we slow the player down as they stop moving, instead of a sudden stop. This also creates that floating on ice effect.

Rotational Movement

Due to the way shooting in the game works, we need the entire player character to rotate. To do this, we simply take the position of the player's cursor, use the built in camera object to convert that into a point in space, and use the LookAt() function to create a direction for the player to look toward.

PlayerMovement.cs

usingSystem.Collections;
usingSystem.Collections.Generic;
usingUnityEngine;
publicclassPlayerMovement : MonoBehaviour
{
publicRigidbodyPlayerBody;
publicfloatSpeed;
publicfloatFrictionalForce;
publicCameraPlayerCam;
privatevoidFixedUpdate()
{
Vector3RotVec=newVector3(PlayerCam.ScreenToWorldPoint(Input.mousePosition).x, 0, PlayerCam.ScreenToWorldPoint(Input.mousePosition).z);
transform.LookAt(RotVec);
Vector3x=newVector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
PlayerBody.AddForce((x*Speed));
PlayerBody.AddForce(-PlayerBody.velocity*PlayerBody.velocity.magnitude*FrictionalForce);
PlayerBody.position=newVector3(transform.position.x, 0, transform.position.z);
}
}

Step 3: Divide Them Away's Mechanics: Player Shooting

Code is down below for your viewing pleasure.

Now that we have movement, we need to give the player the ability to shoot the enemies that will be coming their way. The approach I took this time was a simple approach. Instead of finding the position of the mouse or anything complicated, I would take a simpler logic.

I would just clone a bullet object which would handle everything (movement, damaging enemies) else as it spawns. Here's a quick rundown on what the script does...

  1. Listens for player input, taking into account weapon cooldown
  2. Lets the player shoot and switch weapons
  3. Plays the audio from each weapon

The player shoot script is pretty simple as the shooting mechanic itself is pretty simple.

PlayerShoot.cs

usingSystem.Collections;
usingSystem.Collections.Generic;
usingUnityEngine;
publicclassPlayerShoot : MonoBehaviour
{
publicList<WeaponObject>ListOfWeapons;
publicAudioSourceAudiosrc;
publicAudioClipSwitchSound;
publicAudioClipNoSwitchSound;
publicWeaponObjectCurrentWeaponObject{
get{
returnListOfWeapons[CurrentWeapon];
}
}
publicfloatWeaponSwitchCoolDown;
intCurrentWeapon;
floattimeSinceLastShot;
floattimeSinceLastSwitch;
privatevoidStart(){
CurrentWeapon=0;
timeSinceLastShot=100;
timeSinceLastSwitch=100;
}
privatevoidUpdate()
{
if (Input.GetKeyDown(KeyCode.Mouse0) &&timeSinceLastShot>=ListOfWeapons[CurrentWeapon].CooldownTime)
{
Shoot();
}
SwitchWeapon();
timeSinceLastShot+=Time.deltaTime;
timeSinceLastSwitch+=Time.deltaTime;
}
voidSwitchWeapon(){
if(Input.GetKeyUp(KeyCode.Q)){
if(timeSinceLastSwitch>WeaponSwitchCoolDown){
timeSinceLastSwitch=0;
Audiosrc.PlayOneShot(SwitchSound, AudioConstant.AudioScale);
if(CurrentWeapon==0){
CurrentWeapon=ListOfWeapons.Count-1;
}
else{
CurrentWeapon--;
}
}
else{
Audiosrc.PlayOneShot(NoSwitchSound, AudioConstant.AudioScale);
}
}
if(Input.GetKeyUp(KeyCode.E)){
if(timeSinceLastSwitch>WeaponSwitchCoolDown){
timeSinceLastSwitch=0;
Audiosrc.PlayOneShot(SwitchSound, AudioConstant.AudioScale);
if(CurrentWeapon+1==ListOfWeapons.Count){
CurrentWeapon=0;
}
else{
CurrentWeapon++;
}
}
else{
Audiosrc.PlayOneShot(NoSwitchSound, AudioConstant.AudioScale);
}
}
}
voidShoot()
{
Instantiate(ListOfWeapons[CurrentWeapon].BulletObject, transform.position, transform.rotation);
Audiosrc.PlayOneShot(ListOfWeapons[CurrentWeapon].ShootSound, AudioConstant.AudioScale);
timeSinceLastShot=0;
}
}
view rawPlayerShoot.cs hosted with ❤ by GitHub

Step 4: Divide Them Away's Mechanics: Weapons and the WeaponObject

If you look at the previous script, you can see a list of WeaponObjects. This is a scriptable object which is a useful tool we can use that allows us to create assets with variables. They're essentially containers for different assets like audio or game objects or text.

Here's an example of a scriptable object, our WeaponObject. We can see assets like audio files, game objects, text, etc. These are really useful since they let us create similar objects really quickly, like different weapons (see above).

We can see below we just define some variables, but don't assign them. These are the variables we plug in assets and that we feed to the playershoot script to use; it actually reaches into the weapon object and clones the Bullet Object. Now that we have this in mind, let's go onto the actual weapons themselves.

WeaponObject.cs

usingSystem.Collections;
usingSystem.Collections.Generic;
usingUnityEngine;
[CreateAssetMenu(fileName="Weapon", menuName="Weapon")]
publicclassWeaponObject : ScriptableObject
{
publicGameObjectBulletObject;
publicstringName;
publicfloatCooldownTime;
publicAudioClipShootSound;
}
view rawWeaponObject.cs hosted with ❤ by GitHub

Step 5: Divide Them Away's Weapons: Straightliner

The simplest and easiest to understand weapon, it harkens back to retro game in being a pea shooter. This is meant to be the most basic and simplest weapon, which is why it's the player's starting weapon.

All it does is move forward using Transform.translate at a constant speed. It checks if it hits another object using OnTriggerEnter(Collider Col) and checks if its an enemy. If it is, it'll destroy that object and then destroy itself. Lifespan() is a function which destroys the object after 10 seconds, making sure that bullet is eventually destroyed; this is done so that computer doesn't have to deal with too many objects.

Pea.cs

usingSystem.Collections;
usingSystem.Collections.Generic;
usingUnityEngine;
publicclassPea :Bullet
{
publicintDamageValue;
publicfloatSpeed;
publicfloatTimeToDestroy;
floattimer;
privatevoidStart(){
StartCoroutine(Lifespan());
}
privatevoidFixedUpdate()
{
transform.Translate(Vector3.forward*Speed*Time.deltaTime, Space.Self);
}
voidOnTriggerEnter(ColliderCol){
if(Col.tag=="Enemy"||Col.tag=="Enemy_Bullet"){
Entityx=Col.gameObject.GetComponent<Entity>();
x.TakeDamage(DamageValue);
Destroy(gameObject);
}
Debug.Log("Encounted Collision");
}
publicoverrideintGetDamage()
{
return (DamageValue);
}
IEnumeratorLifespan(){
yieldreturnnewWaitForSecondsRealtime(10.0f);
Destroy(gameObject);
}
}
view rawPea.cs hosted with ❤ by GitHub

Step 6: Divide Them Away's Mechanics: Circle

The second weapon is the circle, which is in many ways a lot like the straightliner, except it doesn't disappear when hitting an enemy, and it's a lot bigger.

Like the straightliner, it moves forward using Transform.translate at a constant speed. It also checks for if it hits an enemy and destroys them. However, it doesn't destroy itself, making it different from the straightliner in that regard.

Circle.cs

usingSystem.Collections;
usingSystem.Collections.Generic;
usingUnityEngine;
publicclassCircleWeapon : Bullet
{
publicintDamageValue;
publicintSpeed;
publicfloatLifetime;
privatevoidStart(){
StartCoroutine(Lifespan());
}
voidFixedUpdate(){
transform.Translate(Vector3.forward*Speed*Time.deltaTime, Space.Self);
}
voidOnTriggerEnter(Collidercol){
if(col.tag=="Enemy"||col.tag=="Enemy_Bullet"){
col.GetComponent<Entity>().TakeDamage(DamageValue);
}
}
publicoverrideintGetDamage(){
returnDamageValue;
}
IEnumeratorLifespan(){
yieldreturnnewWaitForSecondsRealtime(Lifetime);
Destroy(gameObject);
}
view rawCircle.cs hosted with ❤ by GitHub

Step 7: Divide Them Away's Weapons: Parabolic

The parabolic is the most complicated weapon in the game for both player and developer. It's meant to represent a parabola, and the bullet travels in a parabolic curve, well mostly. In compensation for this unusual travel path, it. doesn't get destroyed on impact, and it shoots pretty fast.

Unlike both previous examples, this bullet doesn't use Transform.translate. Instead, it uses Bezier Curves! Bezier curves are a really cool (and easy) way to implement curves into games. Given three points, one being the destination, one the start, and the third being the "curve" point, you can get a curve. It's hard to explain, but the wikipedia page sums it up pretty well.

https://en.wikipedia.org/wiki/Bézier_curve

We use this equation to get the point the bullet should be. The Ps represent the points I mentioned previously. So, what the script below does it plug those three points into this equation. T in this case means would be time. As time passes, we get a point further along the curve. So, we set bullet's position to that point. Repeat over time and we get this. However, if the bullet doesn't get destroyed along the way, we check if t is past a certain value, and if it is, destroy the game object.

With that, that's all our weapons! What we do then is assign these scripts to game objects and then make them into prefabs. This prefab is then dragged into our weapon object scriptable object to be cloned by the playershoot script. Now let's get onto to the other stars of our game, the enemies!

ParabolicBullet.cs

usingSystem.Collections;
usingSystem.Collections.Generic;
usingUnityEngine;
publicclassParabolicBullet : Bullet
{
Vector2StartPoint;
Vector2EndPoint;
Vector2CurvePoint;
Vector2Derv;
publicGameObjectFirstPoint;
publicGameObjectSecondPoint;
publicGameObjectThirdPoint;
publicfloatSpeed;
publicintDamage;
floatp;
floattOff;
privatevoidStart(){
p=0;
StartPoint=newVector2(FirstPoint.transform.position.x, FirstPoint.transform.position.z);
EndPoint=newVector2(SecondPoint.transform.position.x, SecondPoint.transform.position.z);
CurvePoint=newVector2(ThirdPoint.transform.position.x, ThirdPoint.transform.position.z);
tOff=Time.time;
}
privatevoidUpdate(){
StartPoint=newVector2(FirstPoint.transform.position.x, FirstPoint.transform.position.z);
EndPoint=newVector2(SecondPoint.transform.position.x, SecondPoint.transform.position.z);
CurvePoint=newVector2(ThirdPoint.transform.position.x, ThirdPoint.transform.position.z);
transform.position=newVector3(CalculatePosition(p).x, 0, CalculatePosition(p).y);
p=Mathf.PingPong((Time.time-tOff)*Speed, 1);
if(p>0.9f){
Destroy(gameObject);
}
}
voidOnTriggerEnter(Collidercol){
if(col.tag=="Enemy"||col.tag=="Enemy_Bullet"){
col.GetComponent<Entity>().TakeDamage(Damage);
}
}
publicoverrideintGetDamage(){
returnDamage;
}
Vector2CalculatePosition(floatt){
Vector2B;
B= (Mathf.Pow(1-t,2)*StartPoint) + ((2*(1-t))*t*CurvePoint) + (Mathf.Pow(t,2)*EndPoint);
return(B);
}
}

Step 8: Divide Them Away's Mechanics: the Enemies

There are four enemies in Divide Them Away. They are the...

1. Straightliner (Yes, they're the same name)

2. Missile

3. Sinosodal

4. Shooting Platform

It's a bit of a smorgasbord of enemies but they all serve different roles in the game.

The Straight liner serves to keep the player on their feet; it's not hard to dodge, but it keeps them moving.

The missile chases the player so it keeps them moving around the screen and shooting at things.

The Sinosodal effectively blocks off a portion of the screen, so it makes things more difficult.

The Shooting Platform serves as kind of a main enemy, where the player has to focus on them or they run the risk of dying.

I'll go over each enemy's programming in this section.

Step 9: Divide Them Away's Enemies: Straightliner

The straightliner is the simplest enemy in that it basically's the straightliner weapon, but pointed toward the player.

At spawn, it finds the player position and finds the direction to move toward that point in space. After that, it just Transform.translate() in that direction forever, until its either destroyed by the player or destroyed after a given set of time.

StraightLiner.cs

usingSystem.Collections;
usingSystem.Collections.Generic;
usingUnityEngine;
publicclassStraightLiner : Enemy
{
GameObjectTarget;
Vector3Direction;
publicfloatSpeed;
publicfloatTimeTillDeath; //How long until self-destruct
publicAudioClipDeathSound;
publicintDamage;
publicintScore;
privatevoidStart(){
StartCoroutine(Lifespan());
Target=GameObject.FindWithTag("Player");
Direction=Target.transform.position-transform.position;
}
privatevoidUpdate(){
transform.Translate(Direction.normalized*Speed*Time.deltaTime);
}
privatevoidOnTriggerEnter(Collidercol){
if(col.gameObject.tag=="Player"){
col.GetComponent<Entity>().TakeDamage(Damage);
Destroy(gameObject);
}
}
publicoverridevoidTakeDamage(intT){
GameObject.FindGameObjectWithTag("GameController").GetComponent<Score_Manager>().AddScore(Score);
Target.GetComponent<AudioSource>().PlayOneShot(DeathSound, AudioConstant.AudioScale);
Destroy(gameObject);
}
publicoverridevoidSpawnRoutine(Vector3T){
Instantiate(this.gameObject, newVector3(T.x,0,T.z), Quaternion.identity);
}
IEnumeratorLifespan(){
yieldreturnnewWaitForSecondsRealtime(TimeTillDeath);
Destroy(gameObject);
}
}
view rawStraightLiner.cs hosted with ❤ by GitHub

Step 10: Divide Them Away's Enemies: Missile

The second simplest enemy in the game, the missile follows the player around.

Instead of finding the point the player was at when it was created, the missile stores a reference to the player's game object. So, we use this position, which is updated in real time, to track the player. It finds the direction it needs to travel every frame and rotates toward that direction. It then uses transform.translate to move forward, which is constantly changing since the object is rotating.

Missile_Enemy.cs

usingSystem.Collections;
usingSystem.Collections.Generic;
usingUnityEngine;
publicclassMissile_Enemy : Enemy
{
publicEntityTarget;
publicfloatSpeed;
publicAudioClipDeathSound;
publicintScore;
privatevoidStart()
{
Target=GameObject.FindGameObjectWithTag("Player").GetComponent<Entity>();
}
privatevoidUpdate()
{
transform.rotation=Quaternion.LookRotation(Target.transform.position-transform.position);
transform.Translate(Vector3.forward*Time.deltaTime*Speed);
}
privatevoidOnTriggerEnter(Collidercollision)
{
if (collision.gameObject.tag=="Player")
{
Target.TakeDamage(1);
Destroy(gameObject);
//StartCoroutine(HitFreeze());
}
}
publicoverridevoidTakeDamage(intT)
{
GameObject.FindGameObjectWithTag("GameController").GetComponent<Score_Manager>().AddScore(Score);
Target.GetComponent<AudioSource>().PlayOneShot(DeathSound);
Destroy(gameObject);
}
publicoverridevoidSpawnRoutine(Vector3T)
{
Instantiate(this.gameObject, newVector3(T.x, 0, T.z), Quaternion.identity);
}
}
view rawMissile_Enemy.cs hosted with ❤ by GitHub

Step 11: Divide Them Away's Enemies: Sinosodal

The sinosodal is the first enemy that's a step above the transform.translate() monopoly on the enemies. It's also a bit more complicated than the original. It's made up of two parts.

1. A spawner object is spawned and rotates in the direction of the player. It's job is to move up and down relative to itself in a sine wave fashion

2. It spawns object that use transform.translate() to move forward, which produces a sine wave as it moves across the screen.

SinoSodalEnemy.cs

usingSystem.Collections;
usingSystem.Collections.Generic;
usingUnityEngine;
publicclassSinoSodalEnemy : Enemy
{
publicVector3Target;
publicVector3Direction;
publicGameObjectSinosodalBulletObject;
privatevoidStart(){
Target=GameObject.FindGameObjectWithTag("Player").transform.position;
Direction=Target-transform.position;
StartCoroutine(SpawnSines());
}
publicoverridevoidSpawnRoutine(Vector3T){
Instantiate(this.gameObject, newVector3(T.x, 0, T.z), Quaternion.identity);
}
IEnumeratorSpawnSines(){
floatx=10f;
StartCoroutine(DoIt());
while(x>0){
x-=Time.smoothDeltaTime;
yieldreturnnull;
}
StopCoroutine(DoIt());
Destroy(gameObject);
}
IEnumeratorDoIt(){
for(;;){
Instantiate(SinosodalBulletObject, transform.position, Quaternion.LookRotation(Direction));
yieldreturnnewWaitForSecondsRealtime(0.1f);
}
}
publicoverridevoidTakeDamage(intT){
//do nothing
}
}

SionSodalBullet.cs

usingSystem.Collections;
usingSystem.Collections.Generic;
usingUnityEngine;
publicclassSinoSodalBullet : MonoBehaviour
{
publicfloatSpeed;
privatevoidStart(){
StartCoroutine(LifeSpawn());
}
privatevoidUpdate(){
transform.Translate(Vector3.forward*Time.deltaTime*Speed, Space.Self);
}
IEnumeratorLifeSpawn(){
yieldreturnnewWaitForSecondsRealtime(10.0f);
Destroy(gameObject);
}
}

Step 12: Divide Them Away's Enemies: Shooting Platform

The final enemy of the game is the Shooting Platform, which moves around the screen, shooting at the player with a straightliner. It accomplishes this by...

  1. Generate a random point
  2. Wait
  3. Move to that point
  4. Wait
  5. Repeat

However, I quickly ran into the issue where the player might be in between the shooting platform and the random point, and it felt bad since the player would lose health to something they wouldn't be able to react to. So I stored two random points ahead of time, so that the shooting platform would rotate toward that new random point.

It also flashes the color of the enemy when it gets hit to show that the player did some damage to the enemy.

ShootingPlatform.cs

singSystem.Collections;
usingSystem.Collections.Generic;
usingUnityEngine;
usingUnityEngine.UI;
publicclassShootingPlatform : Enemy
{
GameObjectPlayer;
publicintStartingHeatlth;
publicGameObjectBullet;
publicfloatWaitTime;
publicTextBodyText;
publicColorHitColor;
publicAudioClipDamageSound;
publicAudioClipDeathSound;
publicintScore;
AudioSourceAudiosrc;
Vector3[] NextTargets=newVector3[2];
Vector3Direction;
voidStart()
{
Player=GameObject.FindGameObjectWithTag("Player");
NextTargets[0] =newVector3(Random.Range(-26.7f, 26.7f), 0, Random.Range(-15f, 15f));
NextTargets[1] =newVector3(Random.Range(-26.7f, 26.7f), 0, Random.Range(-15f, 15f));
Direction=NextTargets[1] -transform.position;
Audiosrc=GetComponent<AudioSource>();
StartCoroutine(Move());
}
privatevoidOnTriggerEnter(Colliderother)
{
if (other.tag=="Player")
{
other.GetComponent<Entity>().TakeDamage(1);
}
}
publicoverridevoidSpawnRoutine(Vector3T)
{
Instantiate(this.gameObject, T, Quaternion.identity);
}
publicoverridevoidTakeDamage(intT)
{
Audiosrc.PlayOneShot(DamageSound, AudioConstant.AudioScale);
StartCoroutine(HitSignal());
StartingHeatlth-=T;
if(StartingHeatlth<=0)
{
GameObject.FindGameObjectWithTag("GameController").GetComponent<Score_Manager>().AddScore(Score);
Player.GetComponent<AudioSource>().PlayOneShot(DeathSound, AudioConstant.AudioScale);
Destroy(gameObject);
}
}
IEnumeratorMove()
{
for (; ; )
{
if (Vector3.Distance(transform.position, NextTargets[0]) <0.5)
{
NextTargets[0] =NextTargets[1];
NextTargets[1] =newVector3(Random.Range(-26.7f, 26.7f), 0, Random.Range(-15f, 15f));
ShootAtPlayer();
yieldreturnnewWaitForSecondsRealtime(WaitTime);
}
Direction=NextTargets[1] -transform.position;
transform.position=Vector3.Lerp(transform.position, NextTargets[0], 0.1f*Time.timeScale);
transform.rotation=Quaternion.LookRotation(Direction);
yieldreturnnewWaitForEndOfFrame();
}
}
publicvoidShootAtPlayer()
{
Vector3x=Player.transform.position-transform.position;
Instantiate(Bullet, transform.position, Quaternion.identity);
}
IEnumeratorHitSignal(){
BodyText.color=HitColor;
yieldreturnnewWaitForSecondsRealtime(0.1f);
BodyText.color=Color.black;
yieldreturnnull;
}
}

Step 13: Divide Them Away's Enemies: Enemy Spawner and Enemy Objects

Just like the weapon system, the enemies in this game also have a scriptable object. However, instead of providing audio data or names, it gives the enemy spawner information on the timing of the spawn. We use this information in the enemy spawner.

First we define some difficulty levels as enumeration, which allows us to refer to a number value by a name.

Then we define an internal enemy object. While scriptable objects are useful, the only issue is that we can't manipulate the data inside. So we create an object with the data we need and just transfer it over from the scriptable object.

The enemy spawner works off of enemy spawn timers. Every enemy in the list has an internal clock that updates with time. The enemy spawner checks if this internal clock is past the time it should spawn, and if it is, it checks whether or not the enemy should be spawned at the current difficulty; if it is, it will spawn the enemy.

When it spawns the enemy, it uses a preloaded set of areas where it will pick a random area and then generate a random point inside that area to spawn the enemy at.

It also increases the difficulty over time. RampUpDifficulty() checks the difficulty after a set waiting period. Once it does, it changes the current difficulty level and waits another period of time. It repeats this until you hit the final level of insanity.

EnemyObject.cs

singSystem.Collections;
usingSystem.Collections.Generic;
usingUnityEngine;usingSystem.Collections;
usingSystem;
usingUnityEngine;
[CreateAssetMenu(fileName="Enemy", menuName="Enemy")]
publicclassEnemyObject : ScriptableObject{
publicGameObjectEnemy;
publicDifficultyLevelToSpawnAt;
publicintnumberMultiplier;
publicfloatSpawnInterval;
}
usingUnityEngine.UI;
publicclassShootingPlatform : Enemy
{
GameObjectPlayer;
publicintStartingHeatlth;
publicGameObjectBullet;
publicfloatWaitTime;
publicTextBodyText;
publicColorHitColor;
publicAudioClipDamageSound;
publicAudioClipDeathSound;
publicintScore;
AudioSourceAudiosrc;
Vector3[] NextTargets=newVector3[2];
Vector3Direction;
voidStart()
{
Player=GameObject.FindGameObjectWithTag("Player");
NextTargets[0] =newVector3(Random.Range(-26.7f, 26.7f), 0, Random.Range(-15f, 15f));
NextTargets[1] =newVector3(Random.Range(-26.7f, 26.7f), 0, Random.Range(-15f, 15f));
Direction=NextTargets[1] -transform.position;
Audiosrc=GetComponent<AudioSource>();
StartCoroutine(Move());
}
privatevoidOnTriggerEnter(Colliderother)
{
if (other.tag=="Player")
{
other.GetComponent<Entity>().TakeDamage(1);
}
}
publicoverridevoidSpawnRoutine(Vector3T)
{
Instantiate(this.gameObject, T, Quaternion.identity);
}
publicoverridevoidTakeDamage(intT)
{
Audiosrc.PlayOneShot(DamageSound, AudioConstant.AudioScale);
StartCoroutine(HitSignal());
StartingHeatlth-=T;
if(StartingHeatlth<=0)
{
GameObject.FindGameObjectWithTag("GameController").GetComponent<Score_Manager>().AddScore(Score);
Player.GetComponent<AudioSource>().PlayOneShot(DeathSound, AudioConstant.AudioScale);
Destroy(gameObject);
}
}
IEnumeratorMove()
{
for (; ; )
{
if (Vector3.Distance(transform.position, NextTargets[0]) <0.5)
{
NextTargets[0] =NextTargets[1];
NextTargets[1] =newVector3(Random.Range(-26.7f, 26.7f), 0, Random.Range(-15f, 15f));
ShootAtPlayer();
yieldreturnnewWaitForSecondsRealtime(WaitTime);
}
Direction=NextTargets[1] -transform.position;
transform.position=Vector3.Lerp(transform.position, NextTargets[0], 0.1f*Time.timeScale);
transform.rotation=Quaternion.LookRotation(Direction);
yieldreturnnewWaitForEndOfFrame();
}
}
publicvoidShootAtPlayer()
{
Vector3x=Player.transform.position-transform.position;
Instantiate(Bullet, transform.position, Quaternion.identity);
}
IEnumeratorHitSignal(){
BodyText.color=HitColor;
yieldreturnnewWaitForSecondsRealtime(0.1f);
BodyText.color=Color.black;
yieldreturnnull;
}
}
view rawEnemyObject.cs hosted with ❤ by GitHub

Step 14: Divide Them Away's Animations: Animations!? (Also Hitfreeze)

That's right! This time we actually have animations! We use Unity's built in animator system to animate various objects in the game at certain events. And the best part is that its easy to use once you get the hang of it. Also I'm including hit freeze, which I'll explain first because it's easier to explain.

If you have played fighting games, you will have experienced hit freeze; in Smash Bros, it's like when your character for a split second stays on screen before being launched off the map. Whenever you get hit, or you hit another character, the game characters will freeze for a tiny moment of time, before continuing to move. I decided to implement that in the game.

(The code is too small to justify a Github Gist, see attached image above)

This sets the flow of time within the game to 0, waits a set time, and then returns everything back to normal. This is called whenever the player takes damage.

Now Animations.

Animations are contained in animation clips that manipulate the different values of a game object. For example, in the game, an animation manipulates the position of text on the screen. We can see here in this damage animation that the values are moving without us actually manipulating them.

We string these different animation clips together in the animator component where they transition from one animation to another when we tell them. The second clip is the animator in action. We use the animation window to create clips, animating the game objects and their properties, and string them together in the animation. We then use them in code, manipulating the properties we create in the animator window. For example, to start the animation for the game over, we use

Animator.SetBool("GameOver", True);

This allows us control over our animation.

Step 15: Divide Them Away's Homepage!

The last phase of the project was concentrated into the webpage of the game. Unity offers a wonderful option to build the game into the WebGL framework, which allows us to embed our game into a website. And luckily for us, Github, the code management website I use, offers a free hosting service where you get one page. However, I ran into an issue where the game would crash after you shot your parabolic weapon.

I tried a multitude of solutions (see image above) and got pretty desperate until I figured out that the game raising an exception, an error basically, would crash WebGL but not crash the Unity editor.

After I got that sorted I wrote some basic HTML code to lay out the website and gave it a functional design. You can check out the code on

https://github.com/SenpaiRar/SenpaiRar.github.io

In addition, I also added a link to the Github page for the game, a tutorial, a page to show my development log, as well as a page with a google form embedded for feedback.

Step 16: Divide Them Away Conclusion

That was a quick rundown of the core mechanics and the programming behind them. This project was a blast to develop and program! It was a return to basics but by doing so I improved on some fundamentals and learned some new concepts, like bezier curves. I also learned a lot about web development which will be really handy in the future. There's more information, including development log, on the homepage. So if you want more insight on how to the project evolved, go checkout the website (also if you want to play the game too, which I suggest you do, though, I may be a bit bias).

Senpairar.github.io

Be the First to Share

    Recommendations

    • Make it Fly Challenge

      Make it Fly Challenge
    • First Time Author Contest

      First Time Author Contest
    • DIY Summer Camp Contest

      DIY Summer Camp Contest

    Comments

    0
    Me uh
    Me uh

    1 year ago on Step 16

    Very cool! I especially like your commit comments. :)