Skip to content

Ley Lines

Engine: Unity 2018 ◆  Time: 7 weeks (2 week pre-production)

My role: Gameplay Programming and general Game Design

Ley Lines is a first person shooter/platformer in which you use your weapon, not only to vanquish enemies; but also to solve the puzzles and progress in the story.

Using a vast movement-set you can swiftly traverse the world both horizontally and vertically, use the environment as well as your gun “Ra” to open hidden path ways and bounce far out of the reach of enemies.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class InteractableObject : ShootableObject
{
public override void ApplyDamage(float damageTaken, ElementalType type,
Tag damageSource, GameObject attackingObject)
{
if (type == ElementalType.Ice)
{
TakeIceDamage();
}
else if (type == ElementalType.Fire)
{
TakeFireDamage();
}
else if (type == ElementalType.Default)
{
TakeDefaultDamage();
}
}
public virtual void TakeDefaultDamage()
{
}
public virtual void TakeIceDamage()
{
}
public virtual void TakeFireDamage()
{
}
public virtual void SetPowerOn()
{
}
public virtual void SetPowerOff()
{
}
}

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SpikeyFloor : InteractableObject
{
protected AudioSource audioSource;
[SerializeField]
GameObject spikes;
[SerializeField]
GameObject iceBlock;
[SerializeField]
bool frozen = false;
ParticleSystem[] unfreezeParticleSystems;
bool move = true;
[HideInInspector]
public bool canDealDamage = true;
private bool moveUp = true;
protected bool oldMoveState = true;
private bool canTakeFireDamage = true;
Vector3 upPos = new Vector3(0f, -0.75f, 0f);
Vector3 fireShotUpPos = new Vector3(0f, 0f, 0f);
Vector3 downPos = new Vector3(0f, -1.9f, 0f);
Vector3 targetLerpPosition;
float lerpSpeed = 5f;
protected override void Start()
{
audioSource = GetComponent<AudioSource>();
unfreezeParticleSystems = GetComponentsInChildren<ParticleSystem>();
spikes.transform.localPosition = upPos;
if (frozen)
{
SetFrozen();
}
}
private void Update()
{
//Sets if it is in a state to deal damage to the player or not
if (!frozen && spikes.transform.localPosition.y >= (downPos.y * 0.9f))
canDealDamage = true;
else
canDealDamage = false;
//Sets if its going up or down
if (Mathf.Approximately(spikes.transform.localPosition.y, upPos.y) || spikes.transform.localPosition.y > upPos.y)
moveUp = false;
else if (spikes.transform.localPosition == downPos)
moveUp = true;
//The actual movement of the spikes
if (move)
{
if (oldMoveState != moveUp)
{
if (moveUp)
{
SoundManager.soundManager.PlaySpikeUpClip(audioSource);
}
else
{
SoundManager.soundManager.PlaySpikeDownClip(audioSource);
}
oldMoveState = moveUp;
}
spikes.transform.localPosition = Vector3.Lerp(spikes.transform.localPosition, moveUp ? upPos : downPos,
Time.deltaTime * lerpSpeed);
}
}
//Handles the shots you fire at the object
IEnumerator ShotHitIsFire(bool fireHit)
{
move = false;
targetLerpPosition = (fireHit ? fireShotUpPos :
(spikes.transform.localPosition.y > upPos.y ? fireShotUpPos : upPos));
while (spikes.transform.localPosition != targetLerpPosition)
{
spikes.transform.localPosition = Vector3.Lerp(spikes.transform.localPosition,
targetLerpPosition, Time.deltaTime * lerpSpeed * (fireHit ? 2f : 7f));
yield return new WaitForEndOfFrame();
}
if (frozen)
iceBlock.SetActive(true);
else
{
yield return new WaitForSeconds(2f);
if (!frozen)
move = true;
}
}
//handles the lerp of the iceblock
IEnumerator SetFreezeIceBlock(bool freeze)
{
if (!iceBlock.activeSelf)
{
iceBlock.SetActive(true);
}
float lerpAlpha = 0f;
float lerpFrom = (freeze ? 0f : 1f);
float lerpTo = (freeze ? 1f : 0f);
while (lerpAlpha <= 1f)
{
lerpAlpha += Time.deltaTime * (freeze ? 1f : 5f);
iceBlock.GetComponent<MeshRenderer>().material.SetFloat("_IsFrozen", Mathf.Lerp(lerpFrom, lerpTo, lerpAlpha));
yield return new WaitForEndOfFrame();
}
if (!freeze)
iceBlock.SetActive(false);
}
public override void TakeDefaultDamage()
{
if (frozen)
{
SetThawed(ElementalType.Default);
}
}
public override void TakeIceDamage()
{
if (!frozen)
{
SetFrozen();
}
}
public override void TakeFireDamage()
{
if (canTakeFireDamage)
{
canTakeFireDamage = false;
Invoke("SetCanTakeFireDamage", 1);
if (frozen)
SetThawed(ElementalType.Fire);
else
StartCoroutine(ShotHitIsFire(true));
}
}
private void SetCanTakeFireDamage()
{
canTakeFireDamage = true;
}
void SetFrozen()
{
frozen = true;
StartCoroutine(ShotHitIsFire(false));
StartCoroutine(SetFreezeIceBlock(true));
}
void SetThawed(ElementalType elementHit)
{
if (elementHit == ElementalType.Default)
{
iceBlock.SetActive(false);
foreach (ParticleSystem ps in unfreezeParticleSystems)
{
ps.Play();
}
}
else
{
StartCoroutine(SetFreezeIceBlock(false));
}
frozen = false;
move = true;
}
}
view raw SpikeyFloor.cs hosted with ❤ by GitHub

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GasPipe : InteractableObject
{
protected AudioSource audioSource;
[SerializeField]
State currentState = State.Unbroken;
[SerializeField]
GameObject fireCloud;
[SerializeField]
GameObject iceBlock;
MeshRenderer iceBlockRenderer;
//This if for use if the level designer wants the fire to activate in bursts or just be static
[SerializeField]
bool useTimeLimits = true;
[SerializeField]
float activeTimeLimit = 3f;
[SerializeField]
float nonActiveTimeLimit = 2f;
private float currentTimer = 0f;
[HideInInspector]
public bool emitterIsActive = false;
private bool canTakeFireDamage = true;
private enum State
{
Unbroken,
Ice,
Fire
}
protected override void Start()
{
audioSource = GetComponent<AudioSource>();
iceBlockRenderer = iceBlock.GetComponent<MeshRenderer>();
SetState(currentState);
}
private void Update()
{
if (useTimeLimits && currentState == State.Fire)
{
currentTimer += Time.deltaTime;
if (currentTimer >= (emitterIsActive ? activeTimeLimit : nonActiveTimeLimit))
{
SetFireActive(!emitterIsActive);
}
}
}
public override void TakeDefaultDamage()
{
if (currentState == State.Unbroken || currentState == State.Ice)
SetState(State.Fire);
}
public override void TakeIceDamage()
{
if (currentState == State.Fire || currentState == State.Unbroken)
SetState(State.Ice);
}
public override void TakeFireDamage()
{
if (currentState == State.Ice || currentState == State.Unbroken)
SetState(State.Fire);
}
//Custom material made by the tech artist that gradually freezes or unfreezes
IEnumerator SetFreezeIceBlock(bool freeze)
{
if (!freeze && currentState == State.Unbroken)
yield break;
if (!iceBlock.activeSelf)
iceBlock.SetActive(true);
float lerpAlpha = 0f;
float lerpFrom = (freeze ? 0f : 1f);
float lerpTo = (freeze ? 1f : 0f);
while (lerpAlpha <= 1f)
{
lerpAlpha += Time.deltaTime * (freeze ? 2f : 5f);
iceBlock.GetComponent<MeshRenderer>().material.SetFloat("_IsFrozen", Mathf.Lerp(lerpFrom, lerpTo, lerpAlpha));
yield return new WaitForEndOfFrame();
}
if (!freeze)
iceBlock.SetActive(false);
}
private void SetState(State newState)
{
switch (newState)
{
case State.Fire:
{
audioSource.Play();
StartCoroutine(SetFreezeIceBlock(false));
currentState = State.Fire;
SetFireActive(true);
break;
}
case State.Ice:
{
audioSource.Stop();
StartCoroutine(SetFreezeIceBlock(true));
currentState = State.Ice;
SetFireActive(false);
break;
}
case State.Unbroken:
{
SetFireActive(false);
break;
}
}
}
private void SetFireActive(bool newActive)
{
currentTimer = 0f;
emitterIsActive = newActive;
SetEmitterActive(newActive);
}
private void SetEmitterActive(bool active)
{
if (active)
fireCloud.GetComponent<ParticleSystem>().Play();
else if (!active)
fireCloud.GetComponent<ParticleSystem>().Stop();
}
}
view raw GasPipe.cs hosted with ❤ by GitHub

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BreakableWall : InteractableObject
{
AudioSource audioSource;
float directionMultiplier;
Vector3 directionToTarget = new Vector3();
Material frozenMat;
[SerializeField]
float explosionForce = 500f;
bool canTakeFireDamage = true;
Rigidbody[] rigidbodies;
GameObject attackingGameObject;
public override void ApplyDamage(float damageTaken, ElementalType type, Tag damageSource, GameObject attackingObject)
{
attackingGameObject = attackingObject;
base.ApplyDamage(damageTaken, type, damageSource, attackingObject);
}
protected override void Start()
{
rigidbodies = GetComponentsInChildren<Rigidbody>();
audioSource = GetComponent<AudioSource>();
}
public override void TakeFireDamage()
{
if (canTakeFireDamage)
{
canTakeFireDamage = false;
WallExplode();
}
}
//For this to look good and work as intended I needed to apply force to each individual piece of the wall
void WallExplode()
{
GetComponent<Collider>().enabled = false;
//calculates the direction to the player and explodes away from the player
//(No matter what angle the player shoots the wall at it looks nice)
directionToTarget = transform.position - attackingGameObject.transform.position;
float dot = Vector3.Dot(directionToTarget, transform.up);
directionMultiplier = (dot < 0 ? -1f : 1f);
for (int i = 0; i < rigidbodies.Length; i++)
{
rigidbodies[i].isKinematic = false;
rigidbodies[i].AddForce(rigidbodies[i].transform.up * explosionForce * directionMultiplier);
}
SoundManager.soundManager.PlayDestructableWallClip(audioSource);
}
}

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PowerSupply : InteractableObject
{
protected AudioSource audioSource;
private float startDelay = 0.1f;
//Materials for the PS
[SerializeField]
Material activeMaterial;
[SerializeField]
Material frozenMaterial;
MeshRenderer meshRenderer;
[SerializeField]
List<InteractableObject> affectedInteractableObjects = new List<InteractableObject>();
//The state
[SerializeField]
State currentState;
private enum State
{
Active,
Inactive,
}
protected override void Start()
{
audioSource = GetComponent<AudioSource>();
meshRenderer = gameObject.GetComponent<MeshRenderer>();
if (currentState == State.Inactive)
Invoke("SetStateInactive", startDelay);
else
Invoke("SetStateActive", startDelay);
}
//What happens when damage is dealt
public override void TakeDefaultDamage()
{
if (currentState == State.Inactive)
SetStateActive();
}
public override void TakeIceDamage()
{
if (currentState == State.Active)
SetStateInactive();
}
//Set states
private void SetStateActive()
{
SoundManager.soundManager.PlayFuseboxActivationClip(audioSource);
currentState = State.Active;
foreach (InteractableObject interactableObject in affectedInteractableObjects)
{
interactableObject.SetPowerOn();
}
meshRenderer.material = activeMaterial;
}
private void SetStateInactive()
{
currentState = State.Inactive;
foreach (InteractableObject interactableObject in affectedInteractableObjects)
{
interactableObject.SetPowerOff();
}
meshRenderer.material = frozenMaterial;
}
}
view raw PowerSupply.cs hosted with ❤ by GitHub