Demo
Screenshots and videos
Use case
Shiny Stats main configuration
Attributes:
- Stamina
- Strength
- Intellect
- Agility
Classes:
- Warrior
- Paladin
- Rogue
- Wizard
- Cleric
Stats:
- Health Points
- Mana Points
- Attack Power
- Spell Power
- Attack Speed
- Walking Speed
Extra Stats:
- Experience - Amount of experience points to gather in order to level up
- Experience Drop - Amount of experience points to drop on entity death
Level and Experience:
- Level 1 to 20
- Use the built-in Experience System of Shiny Stats
Meta
Stat | Affected by (Attribute) |
---|---|
Health Points | Stamina |
Mana Points | Intellect |
Attack Power | Strength |
Spell Power | Intellect |
Attack Speed | Agility |
Walking Speed | Agility |
Stats-Attributes interactions
Class | Health Points (Stamina) | Mana Points (Intellect) | Attack Power (Strength) | Spell Power (Intellect) | Attack Speed (Agility) | Walking Speed (Agility) | Sum |
---|---|---|---|---|---|---|---|
Warrior | ++ | + | +++ | + | ++ | + | 10 |
Paladin | +++ | ++ | ++ | ++ | ++ | + | 11 |
Rogue | + | + | ++ | ++ | +++ | +++ | 12 |
Wizard | + | +++ | + | +++ | + | + | 9 |
Cleric | ++ | +++ | + | ++ | + | + | 10 |
Class balance idea
Gameplay
There are two type of attack:
regular hit
- Gives physical damage (usingattack power
stat). Trigger with mouse LEFT clickspecial skill
- Depends on the class, and cost mana. Trigger with mouse RIGHT click
Class | Skill | Description |
---|---|---|
All | Attack | Regular attack using Attack Power |
Warrior | Fatal Strike | 150% regular attack damage |
Paladin | Holy Strike | 100% regular attack damage, the damage dealt are returned as Health Points |
Rogue | Backstab | 75% chance to deal 100% regular attack damage, 25% chance to deal 500% regular attack damage |
Wizard | Flame Strike | Use Spell Power instead of Attack Power to deal damage |
Cleric | Self Heal | Use Spell Power to heal himself, no damage dealt to enemies |
Difficulty to defeat | Enemy lvl 1-4 | Enemy lvl 5-9 | Enemy lvl 10-14 | Enemy lvl 15-19 | Boss (lvl 20) |
---|---|---|---|---|---|
Player level 1-4 | medium | hard | - | - | - |
Player level 5-9 | easy | medium | hard | - | - |
Player level 10-14 | - | easy | medium | hard | - |
Player level 15-19 | - | - | easy | medium | hard |
Player level 20 | - | - | - | easy | medium |
Class configuration
Reference
Class | Stamina | Strength | Intellect | Agility | Sum |
---|---|---|---|---|---|
Warrior | 10 | 15 | 5 | 7 | 37 |
Paladin | 15 | 6 | 9 | 7 | 37 |
Rogue | 5 | 8 | 9 | 15 | 37 |
Wizard | 7 | 5 | 18 | 7 | 37 |
Cleric | 12 | 5 | 13 | 7 | 37 |
in Shiny Stats
Classes configured in the Shiny Stats Unity assetStats configuration
Experiences stats
The Experience
and Experience Drop
stats will be the easiest stats to configure.
We will use arbitrary values for each level so it requires a single kill to level up from 1 to 2 and increase the
difficulty gradually so there is ~10 enemies to grind at level 19 to reach 20.
Reference
Level(s) | Experience Drop (on enemy’s death) | Experience (required to level up the player) | Expected kills to level up |
---|---|---|---|
1-4 | 80 + Level * 10 | 100 | 1 - 2 enemies |
5-9 | 80 + Level * 10 | 250 | 2 - 4 enemies |
10-14 | 80 + Level * 10 | 500 | 3 - 6 enemies |
15-18 | 80 + Level * 10 | 1000 | 5 - 6 enemies |
19 | 80 + Level * 10 | 2000 | 10 - 11 enemies |
20 | 1500 (the boss) | - | - |
in Shiny Stats
Experience stat configured in the Shiny Stats Unity assetWalking Speed stat
Walking speed will marginally increase with the level, but is extremely influenced by the initial Agility
attribute of the character. The Rogue will walk way faster than other
classes.
The funny mechanism that I certainly want to introduce is that the player won’t be able to outrun a rogue enemy
(unless the player picked Rogue too).
Walking Speed = 20 + Agility + Level / 10
Rogue class
Shiny Stats preview for the Rogue classOther classes
Shiny Stats preview for the Wizard classin Shiny Stats
Shiny Stats stat configurationHealth and Mana stats
We will also use a linear function for Health and Mana to increase constantly over the levels. Unlike the walking speed, the Health and Mana will be mainly influenced by the level.
Health Points = 20 + Stamina + Stamina * (Level - 1) / 4
Mana Points = Intellect + Intellect * (Level - 1) / 2
Attack & Spell stats
The remaining stats will use logarithms-like functions so the level affects them less and less.
The mechanics I want to introduce is that the Health points
will increase
more than the Attack Power
, Spell Power
and Attack Speed
in the top
levels, to extend the combats duration in the end game.
Attack Power = Strength / 5 + Strength * log(Level, 10)
Spell Power = Intellect / 5 + Intellect * log(Level, 10)
The attack speed case
The Attack Speed is way more connected into the game and might mess around the animations and the experience overall. To be fair, I didn’t plan very well for attack speed variation in the gameplay. So we will leave this as a constant in Shiny Stats.
Attack Speed = 0.2
This will be the minimum, constant, period of time in seconds between two consecutive attacks regardless the class and level.
C# scripts
Player Movement
We will use the CharacterController
component from Unity to handle the
player’s movements.
The initial code, without the extra game management logic and Shiny Stats, looks like the code below.
using UnityEngine;
public class PlayerMovement : MonoBehaviour
{
private CharacterController CharacterController;
private void Awake()
{
CharacterController = GetComponent<CharacterController>();
}
void FixedUpdate()
{
var moveHorizontal = Input.GetAxisRaw("Horizontal");
var moveVertical = Input.GetAxisRaw("Vertical");
var movement = new Vector3(moveHorizontal, 0F, moveVertical).normalized;
CharacterController.Move(movement * Time.fixedDeltaTime * 0.2F);
if (movement != Vector3.zero)
transform.rotation = Quaternion.Lerp(transform.rotation, Quaternion.LookRotation(movement), 0.5F);
}
}
Health System
The health system will cover the characters life and death.
We reload the scene to restart the game when the player dies.
When this script is attached to an enemy, it will give experience points to the player on death.
using ShinyStatsn; // Enable ShinyStats.
using UnityEngine;
using UnityEngine.SceneManagement;
[RequireComponent(typeof(ShinyStatsEntity))]
public class HealthSystem : MonoBehaviour
{
public double MaxValue;
public double CurrentValue;
private ShinyStatsEntity ShinyStatsEntity;
private void Awake()
{
ShinyStatsEntity = GetComponent<ShinyStatsEntity>();
// Subscribe to the OnLevelChanged event.
ShinyStatsEntity.OnLevelChanged += ShinyStatsEntity_OnLevelChanged;
}
/// <summary>
/// Callback from ShinyStatsEntity when the Level change.
/// </summary>
private void ShinyStatsEntity_OnLevelChanged(object sender, EventArgsInt e)
{
MaxValue = ShinyStatsEntity.EvaluateStat("Health Points");
CurrentValue = MaxValue; // Reset to 100% HP on level up.
}
/// <summary>
/// Handle the damage dealt by another entity.
/// Negative values are perceived as heal.
/// </summary>
/// <param name="value"></param>
public void ReceiveDamage(double value)
{
CurrentValue = CurrentValue - value;
if (CurrentValue < 0)
{
var tag = gameObject.tag;
Destroy(gameObject);
if (tag == "Player")
SceneManager.LoadScene(SceneManager.GetActiveScene().name); // Reset the game on player death.
else
{
// Drop experience to the player on enemy death.
var player = FindObjectOfType<ShinyTinyRPGManager>().GetPlayerGo();
var expDrop = (int)ShinyStatsEntity.EvaluateStat("Experience Drop");
player.GetComponent<ShinyStatsEntity>().AddExp(expDrop);
}
}
}
}
We can also add a passive health regen that we always have in RPG games.
...
private void FixedUpdate()
{
// Regen of the Health Points over the time.
if (CurrentValue < MaxValue)
CurrentValue += Time.deltaTime;
}
...
CharacterSheet UI script
A last script good to share is the character sheet panel code.
We adopted a stateless design that solves the ShinyStatsEntity
reference on
its own when possible.
Indeed, the player can change its character at any moment, breaking references due to GameOject.Destroy()
and GameObject.Instanciate()
actions I am using.
There are many ways to get over this, but this design is modular and maintainable.
using ShinyStatsn;
using UnityEngine;
using UnityEngine.UI;
public class CharacterSheetUI : MonoBehaviour
{
public Text Subtitle; // Set in the Inspector.
public Text[] AttributesValue; // Set in the Inspector.
public Text[] StatsValue; // Set in the Inspector.
public Text SpecialSkill; // Set in the Inspector.
private ShinyStatsEntity ShinyStatsEntity;
private void FixedUpdate()
{
if (ShinyStatsEntity == null)
{
var go = GameObject.FindWithTag("Player");
if (go != null)
ShinyStatsEntity = go.GetComponent<ShinyStatsEntity>();
}
if (ShinyStatsEntity == null)
return;
subtitle.text = ShinyStatsEntity.ClassSelected.label + " Lv. " + ShinyStatsEntity.CurrentLevel;
AttributesValue[0].text = ShinyStatsEntity.EvaluateAttribute("Stamina").ToString("0.#");
AttributesValue[1].text = ShinyStatsEntity.EvaluateAttribute("Strength").ToString("0.#");
AttributesValue[2].text = ShinyStatsEntity.EvaluateAttribute("Intellect").ToString("0.#");
AttributesValue[3].text = ShinyStatsEntity.EvaluateAttribute("Agility").ToString("0.#");
StatsValue[0].text = ShinyStatsEntity.EvaluateStat("Health Points").ToString("0.#");
StatsValue[1].text = ShinyStatsEntity.EvaluateStat("Mana Points").ToString("0.#");
StatsValue[2].text = ShinyStatsEntity.EvaluateStat("Attack Power").ToString("0.#");
StatsValue[3].text = ShinyStatsEntity.EvaluateStat("Spell Power").ToString("0.#");
StatsValue[4].text = ShinyStatsEntity.EvaluateStat("Attack Speed").ToString("0.#");
StatsValue[5].text = ShinyStatsEntity.EvaluateStat("Walking Speed").ToString("0.#");
}
}
An performance improvement is possible: register to the OnLevelChanged
event
to catch updates instead of looping in the FixedUpdate
loop.