I’m going to be honest, I really should be doing this more often. Basically, this post will show what has been done up until now. I’ll go over the tiles and their purpose as well as code sources. I’ll go into more detail of each section in later posts.
Source Tile
The source tile is the life-blood of this game: it sends out ‘power’ by pulsing at a set amount (0.5 seconds while debugging; 1 second most likely in the real product). Any tiles that can receive power and are turned on will receive power and pass on to other tiles.
Each source pulse has a unique signature (as unique as a 5 digit number can give) so that each tile can only receive power once from each signature. Basically, if a level becomes really large, I wanted to make sure that activatable/goal tiles don’t become self-powered without use of the source tile.
Source.cs
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class SourceTile : MonoBehaviour {
public float pulseTimer;
public float pulseInterval;
public List neighbors;
private List randomsUsed;
private float sphereRadius = 2.1f;
void Start () {
pulseTimer = 0.0f;
randomsUsed = new List();
FindNeighbors();
}
void Update () {
if (pulseTimer >= pulseInterval) {
pulseTimer = 0.0f;
Debug.Log("Pulse! @ " + Time.time);
GivePower();
} else {
pulseTimer += Time.deltaTime;
}
}
void FindNeighbors () {
neighbors = new List();
Collider[] neighborsHit = Physics.OverlapSphere(transform.position, sphereRadius);
foreach (Collider co in neighborsHit) {
if ((co.CompareTag("Activatable") ||
co.CompareTag("GoalTile") ||
co.CompareTag("SourceTile")) &&
co.transform != transform) {
neighbors.Add(co.gameObject);
}
}
}
void GivePower () {
int signature = GetNewRandom(0, 100000);
foreach (GameObject neighbor in neighbors) {
Powerable p = neighbor.GetComponent();
p.GivePower(signature);
}
}
int GetNewRandom(int min, int max) {
// Create random, check list, if exists, choose new, else return int
int result = Random.Range(min, max);
if (randomsUsed.Count == 0) {
Debug.Log("Random = " + result);
randomsUsed.Add(result);
return result;
}
while (!randomsUsed.Contains(result)) {
Debug.Log("Random = " + result);
randomsUsed.Add(result);
}
return result;
}
// Debug
void OnDrawGizmosSelected() {
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(transform.position, sphereRadius);
}
}
Activatable Tiles
Activatable tiles have been split into two parts: ActivatableTile and Powerable.
ActivatableTile
There are a few things that ActivatableTile handles: Material changes, audio, and collision with the player. There are 3 states for Activatables: off, unpowered, and powered.
Material Changes
All the materials are set using the Inspector. The two scenarios that cause a tile to change material are collision and power state changes. However, technically, both scenarios are routed through the Powerable script to allow a single point of change (“Change it once, change it everywhere” mantra).
Audio
The logic that handles tile audio is similar to the material change. If a material is changed, a sound effect will play. There are four sounds that can happen instead of the three material changes (off -> unpowered; unpowered -> powered; powered -> unpowered; and ANY -> off). I plan on using some kind of audio manager later in development, but I like to make things work first.
Collision with Player
Collision with the player will cause a state change in power, toggling between off and unpowered. There is also a timer associated with the collision to make sure the player doesn’t activate the OnCollisionEnter function more than once within that period.
ActivatableTile.cs
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class ActivatableTile : MonoBehaviour {
public Material unactivatedMaterial;
public Material activatedUnpoweredMaterial;
public Material activatedPoweredMaterial;
public AudioClip offClip;
public AudioClip offToUnpoweredClip;
public AudioClip unpoweredToPoweredClip;
public AudioClip poweredToUnpoweredClip;
public Powerable powerableScript;
public float collisionInterval;
public float collisionTimer;
public bool canCollide;
void Start () {
powerableScript = GetComponent ();
canCollide = true;
collisionTimer = 0.0f;
}
void Update () {
if (!canCollide) {
if (collisionTimer > collisionInterval) {
canCollide = true;
collisionTimer = 0.0f;
} else {
collisionTimer += Time.deltaTime;
}
}
CheckState();
}
void OnCollisionEnter (Collision other) {
if (!other.gameObject.CompareTag ("Player") || !canCollide)
return;
//powerableScript.state = powerableScript.state == PowerState.OFF ? PowerState.UNPOWERED : PowerState.OFF;
if (powerableScript.state == PowerState.OFF) {
powerableScript.ChangeState(PowerState.UNPOWERED);
} else {
powerableScript.ChangeState(PowerState.OFF);
}
canCollide = false;
CheckState();
Debug.Log(transform.name + " collided with " + other.transform.name + " at time " + Time.time);
}
void CheckState () {
if (powerableScript.hasStateChanged) {
powerableScript.hasStateChanged = false;
PowerState currentState = powerableScript.state;
PowerState lastState = powerableScript.lastState;
// Material switching
switch (currentState) {
case PowerState.OFF:
renderer.material = unactivatedMaterial;
//audio.PlayOneShot (offAudioClip);
break;
case PowerState.POWERED:
renderer.material = activatedPoweredMaterial;
//audio.PlayOneShot (poweredAudioClip);
break;
case PowerState.UNPOWERED:
renderer.material = activatedUnpoweredMaterial;
//audio.PlayOneShot (unpoweredAudioClip);
break;
}
// Play audio based on last state and current state
if (lastState == PowerState.OFF && currentState == PowerState.UNPOWERED) {
audio.PlayOneShot(offToUnpoweredClip);
} else if (lastState == PowerState.UNPOWERED && currentState == PowerState.POWERED) {
audio.PlayOneShot(unpoweredToPoweredClip);
} else if (lastState == PowerState.POWERED && currentState == PowerState.UNPOWERED) {
audio.PlayOneShot(poweredToUnpoweredClip);
} else if (currentState == PowerState.OFF) {
audio.PlayOneShot(offClip);
}
}
}
}
Powerable
I split this class out from the functionality of the activatable tiles so that I could use it for other tiles later, and also to keep my sanity because it was getting to be very intertwined and convoluted code.
Powerable keeps a current state and last state for its power state, as well as a flag for if the current state was changed. This helps other tile classes react to state changes better. There’s also timers, last power signature received information, and the ability to detect neighbor tiles with Powerable scripts attached.
Powerable.cs
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public enum PowerState {
OFF,
UNPOWERED,
POWERED
}
public class Powerable : MonoBehaviour {
public PowerState state;
public PowerState lastState;
public bool hasStateChanged;
public float poweredInterval;
public float poweredTimer;
public int lastSignatureReceived;
public List neighbors;
public float sphereRadius = 2.1f;
void Start() {
state = PowerState.OFF;
lastState = PowerState.OFF;
hasStateChanged = false;
poweredTimer = 0.0f;
lastSignatureReceived = 0;
FindNeighbors();
}
void Update() {
switch (state) {
case PowerState.POWERED:
if (poweredTimer >= poweredInterval) {
//state = PowerState.UNPOWERED;
ChangeState(PowerState.UNPOWERED);
poweredTimer = 0.0f;
lastSignatureReceived = 0;
} else {
poweredTimer += Time.deltaTime;
}
break;
default:
poweredTimer = 0.0f;
lastSignatureReceived = 0;
break;
}
}
// Called from source or other powerable tiles
public void GivePower(int signature) {
if (state == PowerState.OFF) return;
if (lastSignatureReceived == signature) return;
else lastSignatureReceived = signature;
//state = PowerState.POWERED;
ChangeState(PowerState.POWERED);
poweredTimer = 0.0f;
foreach (GameObject neighbor in neighbors) {
if (neighbor.CompareTag("GoalTile")) {
GoalTile p = neighbor.GetComponent();
p.GivePower(signature);
} else if (neighbor.CompareTag("Activatable")) {
Powerable p = neighbor.GetComponent();
p.GivePower(signature);
}
}
}
// Used to save the current state and flag as changed
public void ChangeState (PowerState newState) {
lastState = state;
state = newState;
hasStateChanged = true;
}
void FindNeighbors() {
neighbors = new List();
Collider[] neighborsHit = Physics.OverlapSphere(transform.position, sphereRadius);
foreach (Collider co in neighborsHit) {
if ((co.CompareTag("Activatable") || co.CompareTag("GoalTile")) && co.transform != transform) {
neighbors.Add(co.gameObject);
}
}
}
// Debug
void OnDrawGizmosSelected() {
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(transform.position, sphereRadius);
}
}
Goal Tile
The goal tile is relatively simple. It receives power, adds it to a total, drains at a certain rate, and declares victory when its power goal has been met. Right now, the only visual indicator of power is a light source whose intensity is changed according to current power levels.
GoalTile.cs
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class GoalTile : MonoBehaviour {
public float currentPower;
public float powerGoal;
public float gainRate;
public float drainRate;
public GameObject winGUIAnchor;
private ResetGame winGUIAnchorScript;
public GameObject lightObject;
private Light winLight;
public float maxLightIntensity;
private float maxLightRatio;
void Start() {
currentPower = 0.0f;
winLight = lightObject.GetComponent();
winLight.intensity = 0.0f;
maxLightRatio = 100 / maxLightIntensity;
winGUIAnchorScript = winGUIAnchor.GetComponent();
}
void Update() {
if (currentPower >= powerGoal) {
winGUIAnchorScript.hasWon = true;
return;
}
if (currentPower > 0) currentPower -= drainRate * Time.deltaTime;
winLight.intensity = (currentPower / powerGoal * 100) / maxLightRatio;
}
public void GivePower(int signature) {
if (currentPower >= powerGoal) return;
currentPower += gainRate;
}
}
Thanks for sifting through this longer post. Hopefully I can update more often and with more exciting things like pictures and sounds!