Skip to content

Instantly share code, notes, and snippets.

@benanil
Last active June 28, 2022 17:23
Show Gist options
  • Save benanil/9ce2d7a68e56177026acac4c21e55b57 to your computer and use it in GitHub Desktop.
Save benanil/9ce2d7a68e56177026acac4c21e55b57 to your computer and use it in GitHub Desktop.
#undef UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class Character : MonoBehaviour
{
public static Character instance;
public Transform firstStair;
public Transform secondStair;
public Transform TrapPrefab;
public Transform skullPrefab;
public Transform RecordLineTransform;
public TMPro.TextMeshProUGUI comboTextPrefab;
public Transform[] chunkPrefabs = new Transform[Biom.BiomCount];
public PrayerGroup[] prayerPrefabs = new PrayerGroup[3];
public Image CoinPrefab;
[SerializeField]
private uint state = State.playing;
[Header("stair")]
public float stepInterval = 0.6f;
public float moveSpeed = 1;
public float blockSpeed = 1;
public Vector3 characterOffset;
private float stepTimer;
public int money;
public AudioClip stairStepSound;
private AudioSource audioSource;
public ParticleSystem checkpointParticle;
[Header("tiredness")]
public float tiredness;
public float tirednessSpeed = 0.5f;
public float maxTiredness = 30;
public float regeneration = 0.1f;
public Material characterMaterial;
private readonly TirednessStage[] tirednessStages = new TirednessStage[5];
private int tirednessStage;
private int lastTirednessStage;
public float tirednessSinceLastStartup = 30;
public ParticleSystem sweatingParticle;
private Vector3 characterStartScale;
private FootTracer[] footTracers;
[Header("UI")]
[SerializeField] private GameObject checkpointUI;
[SerializeField] private GameObject pauseMenu;
internal void PlayFootstepSound()
{
audioSource.PlayOneShot(stairStepSound);
}
[SerializeField] private GameObject playerUI;
[SerializeField] private GameObject failMenu;
[SerializeField] private GameObject HighScoreGO;
[SerializeField] private Transform coinImage;
[SerializeField] private Image stepsToNextLevelSlider;
[SerializeField] private TMPro.TextMeshProUGUI moneyText;
[SerializeField] private TMPro.TextMeshProUGUI MettersPassedText;
[SerializeField] private TMPro.TextMeshProUGUI MettersRecordText;
[Header("Upgrade")]
public int staminaLevel;
public int moneyEarningLevel;
public int speedLevel;
private int staminaPrice = 250, incomePrice = 250, speedPrice = 250;
[SerializeField] TMPro.TextMeshProUGUI staminaPriceText, incomePriceText, speedPriceText;
[SerializeField] TMPro.TextMeshProUGUI staminaLevelText, incomeLevelText, speedLevelText;
[SerializeField] TMPro.TextMeshProUGUI LevelText;
public int moneyPerStair = 50;
public float speedPerUpgrade = 0.05f;
public float staminaPerLevel = 5;
public Button staminaButton, incomeButton, speedButton;
public ParticleSystem upgradeParticle;
public ParticleSystem windParticle;
private Vector3 stairDiff;
// Systems
private WalkSystem walkSystem;
private CoinSystem coinSystem;
private ComboTextSystem comboTextSystem;
private InfiniteSystem infiniteSystem;
private Animator animator;
private bool startedMoving;
private Vector3 coinStartScale;
private Vector3 coinSmallScale;
private float targetFov;
private float startFov;
private float characterStartXPos;
private static float highestMeter = 100;
private bool recordPassed = true;
private AnimListenner listenner;
private float targetWalkAnimValue;
private float animSpeedAdition;
// Combo
private int ComboIndex = 0;
private Dictionary<int, Combo> combos = new Dictionary<int, Combo>(3); // unordered_map , map
private Combo currentCombo;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static float EaseIn(float x) { return x * x * x; }
private void SetCharacterRedness()
{
float tirednessMap = tirednessSinceLastStartup / maxTiredness;
float t = tiredness / (maxTiredness * tirednessMap);
characterMaterial.SetFloat("_Value", EaseIn(t) * 10.0f);
listenner.SetColor(t);
}
////////
private void Awake() { instance = this; }
////////
private void Start()
{
tirednessSinceLastStartup = maxTiredness;
listenner = FindObjectOfType<AnimListenner>();
targetWalkAnimValue = 1.0f;
tirednessStage = lastTirednessStage = 0;
characterStartScale = transform.localScale;
targetFov = Camera.main.fieldOfView;
startFov = targetFov;
coinStartScale = coinImage.localScale;
coinSmallScale = coinImage.localScale / 1.4f;
characterStartXPos = transform.position.x;
stairDiff = firstStair.position - secondStair.position;
secondStair.position = transform.position - characterOffset;
characterMaterial.SetFloat("_Value", 0);
transform.GetChild(4).TryGetComponent(out animator);
TryGetComponent(out audioSource);
animator.SetFloat("Speed", 0);
Load();
float targetChunkMeterRecord = Mathf.Approximately(lastChunkMeterRecord, 0) ? 400 : lastChunkMeterRecord;
RecordLineTransform.position = new Vector3(Mathf.Min(-targetChunkMeterRecord, -400) , -3.79999995f, -0.0799999982f);
RecordLineTransform.GetChild(0).GetComponent<TMPro.TextMeshPro>().text = Mathf.Max(targetChunkMeterRecord + 300, 700).ToString() + 'm';
recordPassed = false;
footTracers = FindObjectsOfType<FootTracer>();
// initialize systems
//
walkSystem = new WalkSystem(transform);
coinSystem = new CoinSystem();
infiniteSystem = new InfiniteSystem();
comboTextSystem = new ComboTextSystem();
//
infiniteSystem.SetCurrentBiom((uint)PlayerPrefs.GetInt("BiomIndex" + Character.PV));
tirednessStages[0] = new TirednessStage(
onActivate : () =>
{
sweatingParticle.Stop();
targetWalkAnimValue = 1;
characterMaterial.color = Color.white;
characterMaterial.SetFloat("_Value", 0);
},
onDeactivate: () => { },
onEvaluete : (float dt) => { SetCharacterRedness(); }
);
tirednessStages[1] = new TirednessStage(
onActivate : () => { targetWalkAnimValue = 2; sweatingParticle.Play(); },
onDeactivate: () => { sweatingParticle.Stop(); },
onEvaluete : (float dt) => { SetCharacterRedness(); }
);
tirednessStages[2] = new TirednessStage(
onActivate : () => { targetWalkAnimValue = 3; sweatingParticle.Play(); },
onDeactivate: () => {
sweatingParticle.Stop();
transform.localScale = characterStartScale;
},
onEvaluete : (float dt) => {
SetCharacterRedness();
transform.localScale = characterStartScale + (0.025f * Mathf.Sin(Time.timeSinceLevelLoad * 8) * Vector3.one);
}
);
tirednessStages[3] = tirednessStages[2];
tirednessStages[4] = new TirednessStage(
onActivate : () => {targetWalkAnimValue = 3; sweatingParticle.Play(); },
onDeactivate: () => {
sweatingParticle.Stop();
transform.localScale = characterStartScale;
},
onEvaluete : (float dt) => {
SetCharacterRedness();
transform.localScale = characterStartScale + (0.045f * Mathf.Sin(Time.timeSinceLevelLoad * 14) * Vector3.one);
}
);
combos.Add(16, new Combo(
onActivate : () => {
animSpeedAdition += 0.4f;
moveSpeed += 2.5f;
targetFov = startFov + 2.5f;
windParticle.gameObject.SetActive(true);
windParticle.Play();
stepInterval -= 0.02f;
moneyPerStair += 5;
},
onDeactivate: () => {
animSpeedAdition -= 0.4f;
moveSpeed -= 2.5f;
targetFov = startFov;
windParticle.gameObject.SetActive(false);
stepInterval += 0.02f;
moneyPerStair -= 5;
},
onEvaluete : (float dt) => {}
));
combos.Add(32, new Combo(
onActivate : () => {
animSpeedAdition += 0.4f;
moveSpeed += 2.5f;
targetFov = startFov + 5f;
stepInterval -= 0.02f;
moneyPerStair += 5;
},
onDeactivate: () => {
animSpeedAdition -= 0.4f;
moveSpeed -= 2.5f;
targetFov = startFov;
windParticle.gameObject.SetActive(false);
stepInterval += 0.02f;
moneyPerStair -= 5;
},
onEvaluete : (float dt) => { }
));
combos.Add(64, new Combo(
onActivate : () => {
animSpeedAdition += 0.4f;
moveSpeed += 2.5f;
targetFov = startFov + 7.5f;
stepInterval -= 0.02f;
moneyPerStair += 10;
},
onDeactivate: () => {
animSpeedAdition -= 0.4f;
moveSpeed -= 2.5f;
targetFov = startFov;
windParticle.gameObject.SetActive(false);
stepInterval += 0.02f;
moneyPerStair -= 10;
}, // also revert first and second combo upgrades
onEvaluete : (float dt) => { }
));
Canvas canvas = FindObjectOfType<Canvas>();
for (int i = 0; i < CoinSystem.MaximumCoins; i++)
coinSystem.PoolCoin(Instantiate(CoinPrefab, parent: canvas.transform));
for (int i = 0; i < ComboTextSystem.MaximumCoins; i++)
comboTextSystem.PoolCoin(Instantiate(comboTextPrefab, parent: canvas.transform));
for (uint biomIndex = 0; biomIndex < Biom.BiomCount; ++biomIndex)
for (int i = 0; i < InfiniteSystem.MaximumChunks; i++)
infiniteSystem.AddChunk(biomIndex, Instantiate(chunkPrefabs[biomIndex], new Vector3(0, -8.2f, 0), Quaternion.identity));
for (int i = 0; i < 3; i++)
infiniteSystem.AddPrayer(Instantiate(prayerPrefabs[i]));
infiniteSystem.PlaceChunks();
firstStair.gameObject.SetActive(false);
secondStair.gameObject.SetActive(false);
UpdateUpgradeMenu();
}
public void AddStair()
{
coinSystem.MoveCoin(transform.position - characterOffset, moneyPerStair);
money += moneyPerStair;
moneyText.text = FormatNumber(money);
startedMoving = true;
ComboIndex++;
coinImage.localScale = coinStartScale;
if (combos.TryGetValue(ComboIndex, out Combo combo))
{
comboTextSystem.MoveCoin(transform.position + new Vector3(0,0,2), ComboIndex);
combo.Activate();
currentCombo = combo;
}
}
private void EliminateCombos()
{
if (currentCombo != null && ComboIndex != 0)
{
foreach (var combo in combos)
if (combo.Key <= ComboIndex)
combo.Value.Deactivate();
}
ComboIndex = 0;
currentCombo = null;
}
private float lastChunkMeterRecord;
private float stopTimer;
public void CheckPoint()
{
// open upgrade ui, play dance animation, blow up confetti,
playerUI.SetActive(false);
checkpointParticle.gameObject.SetActive(true);
checkpointParticle.Play();
ToggleCheckpointUI(true);
float mettersPassed = Mathf.Abs(characterStartXPos - transform.position.x);
lastChunkMeterRecord = mettersPassed + 100;
TirednessStage currentTirednessStage = tirednessStages[tirednessStage];
currentTirednessStage.Deactivate();
tirednessStages[0].Activate();
coinSystem.DisposeCurrentMovingCoins();
EliminateCombos();
ContinuePressed = false;
int.TryParse(LevelText.text[5..], out int level);
LevelText.text = "Level " + (level+1).ToString();
animator.SetFloat("Speed", 0.0f);
ComboIndex = tirednessStage = 0;
tiredness = 0.0f;
state = State.paused;
}
private void CloseHighScore() { HighScoreGO.SetActive(false); }
private void Update()
{
// handle coins
coinSystem.Update(Time.deltaTime);
float mettersPassed = Mathf.Abs(characterStartXPos - transform.position.x);
MettersPassedText.text = (mettersPassed + lastChunkMeterRecord).ToString(".00");
if ((state & State.playing) == 0 || state == State.dead || !ContinuePressed) return;
stepsToNextLevelSlider.fillAmount =
infiniteSystem.MettersPassedSinceLastBiom(transform.position.x)
/ infiniteSystem.TotalMetersToNextBiom;
float deltaTime = Time.deltaTime;
if (transform.position.x < RecordLineTransform.position.x)
{
highestMeter = mettersPassed;
if (!recordPassed)
{
PlayerPrefs.SetFloat("lastChunkMeterRecord" + PV, lastChunkMeterRecord + 400);
HighScoreGO.SetActive(true);
Invoke(nameof(CloseHighScore), 2);
recordPassed = true;
}
}
if (MettersRecordText) MettersRecordText.text = highestMeter.ToString(".00");
float tirednessMap = tirednessSinceLastStartup / maxTiredness;
TirednessStage currentTirednessStage;
{
float oneStagePercent = tirednessStages.Length / (maxTiredness / tirednessMap); // 4 / 40 = 0.1 = s;
tirednessStage = Mathf.Max(Mathf.FloorToInt((oneStagePercent * tiredness) - 1), 0);
currentTirednessStage = tirednessStages[tirednessStage];
currentTirednessStage.Evaluete(deltaTime);
}
currentCombo?.Evaluate(deltaTime);
if (Input.GetMouseButton(0))
{
animator.speed = 1 + animSpeedAdition + (speedLevel * 0.12f);
stepTimer += deltaTime; // stEP
tiredness += deltaTime * tirednessSpeed;
stopTimer += deltaTime; // stOP
if (stopTimer > 0.21f)
{
animator.SetFloat("Speed", Mathf.Lerp(animator.GetFloat("Speed"), targetWalkAnimValue, Time.deltaTime * 8));
walkSystem.Walk(moveSpeed);
foreach (FootTracer tracer in footTracers) tracer.UpdateThisThing();
}
if (tirednessStage != lastTirednessStage)
{
currentTirednessStage.Deactivate();
lastTirednessStage = tirednessStage;
TirednessStage newTirednessStage = tirednessStages[tirednessStage];
newTirednessStage.Activate();
}
const float bias = 3.0f;
if (tiredness - bias >= tirednessSinceLastStartup * tirednessMap)
{
Fail();
return;
}
}
else
{
stopTimer = 0;
animator.SetFloat("Speed", Mathf.Lerp(animator.GetFloat("Speed"), 0.0f, Time.deltaTime * 12));
tiredness = Mathf.Max(tiredness - (deltaTime * regeneration), 0);
if (!Mathf.Approximately(tiredness, 0))
{
// reduce maximum stamina
tirednessSinceLastStartup = MathF.Max(tirednessSinceLastStartup - (deltaTime * 0.6f), 1);
}
}
if (stepTimer >= stepInterval)
{
AddStair();
stepTimer = 0;
}
if (Input.GetMouseButtonUp(0))
{
stepTimer = 0;
EliminateCombos();
}
Camera.main.fieldOfView = Mathf.LerpUnclamped(Camera.main.fieldOfView, targetFov, Time.deltaTime * 3);
coinImage.localScale = Vector3.LerpUnclamped(coinImage.localScale, coinSmallScale, deltaTime * 3);
{
walkSystem.Update(moveSpeed);
infiniteSystem.Update(transform.position.x);
comboTextSystem.Update(Time.deltaTime);
}
}
public void Fail()
{
if (!startedMoving || state != State.playing) return;
animator.SetTrigger("Die");
currentCombo?.Deactivate();
tirednessStages[^1].Deactivate();
Invoke(nameof(Fail2), 3.5f);
playerUI.SetActive(false);
animator.SetFloat("Speed", 0);
Save();
state = State.dead;
if (transform.position.x < RecordLineTransform.position.x)
{
HighScoreGO.SetActive(true);
Invoke(nameof(CloseHighScore), 2);
recordPassed = true;
}
}
public GameObject LevelGOLeftUpper;
public GameObject CoinGORightUpper;
private void Fail2()
{
LevelGOLeftUpper.SetActive(false);
CoinGORightUpper.SetActive(false);
failMenu.SetActive(true);
}
public void Quit() { Application.Quit(); }
// player pref version
public const char PV = '5';
// save load
private void Save(int biomeAddition = 0)
{
PlayerPrefs.SetInt("Money" + PV, (int)money);
PlayerPrefs.SetInt("Stamina" + PV, staminaLevel);
PlayerPrefs.SetInt("Speed" + PV, speedLevel);
PlayerPrefs.SetInt("Income" + PV, moneyEarningLevel);
PlayerPrefs.SetFloat("MeterRecore" + PV, highestMeter);
int.TryParse(LevelText.text[5..], out int level);
PlayerPrefs.SetInt("Level" + PV, level);
}
// returns false if there is no save
private bool Load()
{
// if (!PlayerPrefs.HasKey("Money")) return false;
money = Mathf.Max(PlayerPrefs.GetInt("Money" + PV), 0);
int level = PlayerPrefs.GetInt("Level" + PV);
LevelText.text = "Level " + (level == 0 ? 1 : level);
highestMeter = PlayerPrefs.GetFloat("MeterRecore" + PV);
lastChunkMeterRecord = PlayerPrefs.GetFloat("lastChunkMeterRecord" + PV) ;
LoadSkills(PlayerPrefs.GetInt("Stamina" + PV), PlayerPrefs.GetInt("Income" + PV), PlayerPrefs.GetInt("Speed" + PV));
return true;
}
public void Restart() { SceneManager.LoadScene(0); }
public void ResetGame()// button event
{
PlayerPrefs.SetInt("Stamina" + PV, 0);
PlayerPrefs.SetInt("Speed" + PV, 0);
PlayerPrefs.SetInt("Income" + PV, 0);
PlayerPrefs.SetInt("CheckpointIndex" + PV, 0);
PlayerPrefs.SetInt("Money" + PV, 0);
SceneManager.LoadScene(0);
}
// Upgrade
[ContextMenu("UpgradeStamina")]
public void UpgradeStamina() { // button event
if (money < staminaPrice || staminaLevel >= 8) return;
money -= staminaPrice;
maxTiredness += staminaPerLevel;
staminaPrice += staminaPrice / 2;
tirednessSinceLastStartup = maxTiredness;
staminaLevel++;
staminaLevelText.text = staminaLevel.ToString() + "lvl";
UpdateUpgradeMenu();
}
[ContextMenu("UpgradeIncome")]
public void UpgradeIncome() { // button event
if (money < incomePrice || moneyEarningLevel >= 8) return;
money -= incomePrice;
moneyPerStair += moneyPerStair / 2;
incomePrice += incomePrice / 2;
moneyEarningLevel++;
incomeLevelText.text = moneyEarningLevel.ToString() + "lvl";
UpdateUpgradeMenu();
}
[ContextMenu("UpgradeSpeed")]
public void UpgradeSpeed() { // button event
if (money < speedPrice || speedLevel >= 8) return;
money -= speedPrice;
stepInterval = Mathf.Max(stepInterval - speedPerUpgrade, 0.1f) ;
moveSpeed += 2.0f;
animSpeedAdition += 0.4f;
speedPrice += speedPrice / 2;
speedLevel++;
speedLevelText.text = speedLevel.ToString() + "lvl";
UpdateUpgradeMenu();
}
public void LoadSkills(int stamina, int income, int speed) {
staminaLevel = stamina; moneyEarningLevel = income; speedLevel = speed;
speedLevelText.text = speedLevel.ToString() + "lvl";
incomeLevelText.text = moneyEarningLevel.ToString() + "lvl";
staminaLevelText.text = staminaLevel.ToString() + "lvl";
while (income -- > 0) {
moneyPerStair += moneyPerStair / 2;
incomePrice += incomePrice / 2;
}
while (stamina-- > 0) {
maxTiredness += staminaPerLevel;
staminaPrice += staminaPrice / 2;
}
while (speed-- > 0) {
animSpeedAdition += 0.4f;
stepInterval = Mathf.Max(stepInterval - speedPerUpgrade, 0.1f);
moveSpeed += 2.0f;
speedPrice += speedPrice / 2;
}
tirednessSinceLastStartup = maxTiredness;
UpdateUpgradeMenu();
}
private bool ContinuePressed = false;
// UI
public void ToggleCheckpointUI(bool open) { checkpointUI.SetActive(open); UpdateUpgradeMenu(); }
public void TogglePlayerUI() { playerUI.SetActive(!playerUI.activeSelf); }
public void TogglePauseMenu() { pauseMenu.SetActive(!pauseMenu.activeSelf); }
public void Continue()
{
ContinuePressed = true;
state = State.playing;
Save(1);
AddStair();
playerUI.SetActive(true);
checkpointParticle.Stop();
checkpointParticle.gameObject.SetActive(false);
}
public void TogleTimeScale()
{
Time.timeScale = Mathf.Approximately(Time.timeScale, 1.0f) ? 0.0f : 1.0f;
state = state == State.playing ? State.paused : State.playing;
}
public void UpdateUpgradeMenu()
{
staminaButton.enabled = money >= staminaPrice && staminaLevel < 8;
incomeButton .enabled = money >= incomePrice && moneyEarningLevel < 8;
speedButton .enabled = money >= speedPrice && speedLevel < 8;
staminaPriceText.text = FormatNumber(staminaPrice);
incomePriceText.text = FormatNumber(incomePrice);
speedPriceText.text = FormatNumber(speedPrice);
moneyText.text = FormatNumber(money);
}
public static string FormatNumber(int num)
{
if (num >= 100000000)
return (num / 1000000).ToString("#,0M");
if (num >= 10000000)
return (num / 1000000).ToString("0.#") + "M";
if (num >= 100000)
return (num / 1000).ToString("#,0K");
if (num >= 10000)
return (num / 1000).ToString("0.#") + "K";
return num.ToString("#,0");
}
} // character class end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment