Атрибуты RPG и прокачка персонажа

Система прокачки персонажа, состоит из развития и управления различными атрибутами, таких как: ловкость, удача, навык взлома и прочие. Кроме того, в систему должно быть встроено управление опытом персонажа, так как прокачка какого-либо атрибута напрямую связано со статусом опыта. То есть, когда персонаж повышает уровень, ему выдается определенное количество очков прокачки, которые и тратятся на повышение атрибута. Так же у нас должна быть возможность изменения значения атрибута или его настроек, в ручном режиме, так сказать читерить. Такая возможность будет полезна, если, например, персонаж получает перк, влияющий на некоторые параметры.


Первое что нам потребуется, это инструменты настройки и создания атрибутов.

Добавим на сцену скрипт:

using UnityEngine;
using System.Collections;

public class PlayerSetup : MonoBehaviour {

	[Header("Редактирование атрибутов:")]
	[SerializeField] private PlayerRPG.Attributes[] attributes;
	[Header("Настройки опыта:")]
	[SerializeField] private int experienceRequired = 1000; // стартовое значение, если еще нет сохранений, по достижению которого, происходит повышение уровня
	[SerializeField] private float experienceMultiplier = 1.7f; // умножение требуемого значения, после повышения уровня
	[SerializeField] private int experiencePoints = 3; // сколько очков прокачки будет начисляться с повышением уровня

	void Awake()
	{
		Initialize();
	}

	void Initialize()
	{
		PlayerRPG.Initialize(attributes, experienceMultiplier, experienceRequired, experiencePoints);
	}

	#if UNITY_EDITOR
	public void CreateInEditor()
	{
		string filePath = "Assets/NULLcode Studio/RPGAttributes/Scripts"; // путь где в проекте лежит скрипт PlayerAttribute.cs
		string[] list = new string[attributes.Length];
		for(int i = 0; i < attributes.Length; i++)
		{
			list[i] = attributes[i].name;
		}
		EnumGenerator.Go(filePath, list);
		Debug.Log(this + " обновление класса 'PlayerAttribute'");
	}
	#endif
}

Атрибуты RPG и прокачка персонажа

Здесь мы создаем массив всех нужным нам атрибутов, а так же настраиваем опыт. После чего жмем кнопку "Генерировать Enum". Сгенерированный Enum на основе массива атрибутов, будет использоваться как фильтр для их запроса. Все указанные значения, будут считаться как настройки по умолчанию, если сохранений нет или это первый запуск игры, то будут приняты текущие значения. Данный класс, должен присутствовать во всех игровых сценах.

В папку со скриптами закидываем:

#if UNITY_EDITOR
using UnityEngine;
using System.Collections;
using UnityEditor;
using System.IO;

[CustomEditor(typeof(PlayerSetup))]

public class PlayerSetupEditor : Editor {

	public override void OnInspectorGUI()
	{
		DrawDefaultInspector();
		PlayerSetup e = (PlayerSetup)target;
		GUILayout.Label("Генерировать Enum:", EditorStyles.boldLabel);
		if(GUILayout.Button("Create / Update"))
		{
			e.CreateInEditor();
		}
	}
}

public static class EnumGenerator
{
	public static void Go(string filePath, string[] list)
	{
		using(StreamWriter streamWriter = new StreamWriter(filePath + "/PlayerAttribute.cs"))
		{
			streamWriter.WriteLine("// Enum Generator: " + list.Length + " elements");
			streamWriter.WriteLine("// Внимание! Не редактировать вручную!");
			streamWriter.WriteLine("");
			streamWriter.WriteLine("public enum PlayerAttribute");
			streamWriter.WriteLine("{");
			for(int i = 0; i < list.Length; i++)
			{
				streamWriter.WriteLine("\t" + list[i] + ",");
			}
			streamWriter.WriteLine("}");
		}
		AssetDatabase.Refresh();
	}
}
#endif

Скрипт работает в связке с предыдущим. Создадим еще скрипт PlayerAttribute.cs, в который генерируется список атрибутов.

Теперь основной класс, через который мы управляем опытом и атрибутами.

Создаем следующий скрипт:

using UnityEngine;
using System.Collections;

public delegate void EventAdjust(string attribute);
public delegate void EventAttribute(string attribute);
public delegate void EventExperience();

public static class PlayerRPG {

	public static event EventAdjust OnValueChange;
	public static event EventAttribute OnAttributeChange;
	public static event EventExperience OnExperience;
	private static Attributes[] _attributes;
	private static int expRequired, experience, expPointsCur, expPoints;
	private static float expMultiplier;

	public static int ExperiencePoints // очки прокачки
	{
		get{ return expPointsCur; }
	}

	public static int Experience // текущий опыт
	{
		get{ return experience; }
	}

	public static int ExperienceRequired // требуемый опыт, для повышения уровня
	{
		get{ return expRequired; }
	}

	// прокачка атрибута, при которой происходит трата очков прокачки
	// повышение происходит на один уровень, списывается один балл
	public static void AddLevelAttribute(PlayerAttribute attribute)
	{
		if(expPointsCur == 0 || _attributes[(int)attribute].value == _attributes[(int)attribute].valueMax) return;
		expPointsCur--;
		AdjustCurrentValue(1, attribute);
	}

	// изменение текущего опыта
	// добавление происходит без потерь
	public static void AdjustExperience(int value)
	{
		experience += value;
		if(experience < 0) experience = 0;
		if(experience >= expRequired)
		{
			int j = experience - expRequired;
			experience = (j > 0) ? j : 0;
			float ex = expRequired * expMultiplier;
			expRequired = (int)ex;
			expPointsCur += expPoints;
			if(OnExperience != null) OnExperience();
		}
	}

	// изменение настроек атрибута, например
	// если получен некий перк, который допустим, расширяет максимальное значение
	public static void ChangeAttributeSettings(int min, int max, PlayerAttribute attribute)
	{
		if(!IsLoaded() || min > max) return;
		int i = (int)attribute;
		_attributes[i].valueMin = min;
		_attributes[i].valueMax = max;
		if(OnAttributeChange != null) OnAttributeChange(attribute.ToString());
	}

	// изменение текущего значения атрибута (без учета текущих очков прокачки)
	public static void AdjustCurrentValue(int current, PlayerAttribute attribute)
	{
		if(!IsLoaded()) return;
		int i = (int)attribute;
		int cur = _attributes[i].value;
		int min = _attributes[i].valueMin;
		int max = _attributes[i].valueMax;
		if(cur == max && cur + current > max || cur == min && cur + current < min) return;
		cur += current;
		if(cur > max) cur = max;
		if(cur < min) cur = min;
		_attributes[i].value = cur;
		if(OnValueChange != null) OnValueChange(attribute.ToString());
	}

	public static int GetCurrent(PlayerAttribute attribute) // запрос текущего значения атрибута
	{
		return (IsLoaded()) ? _attributes[(int)attribute].value : 0;
	}

	public static int GetMax(PlayerAttribute attribute) // запрос максимального значения атрибута
	{
		return (IsLoaded()) ? _attributes[(int)attribute].valueMax : 0;
	}

	public static int GetMin(PlayerAttribute attribute) // запрос минимального значения атрибута
	{
		return (IsLoaded()) ? _attributes[(int)attribute].valueMin : 0;
	}

	static bool IsLoaded()
	{
		if(_attributes != null) return true;
		return false;
	}

	public static void Initialize(Attributes[] attributes, float eMultiplier, int eRequired, int ePoints)
	{
		expPoints = ePoints;
		expMultiplier = eMultiplier;
		if(!IsLoaded()) Load(attributes.Length);
		if(!IsLoaded())
		{
			experience = 0;
			expPointsCur = 0;
			expRequired = eRequired;
			_attributes = attributes;
		}
	}

	public static void Save()
	{
		string vCur = string.Empty, vMax = string.Empty, vMin = string.Empty;

		for(int i = 0; i < _attributes.Length; i++)
		{
			if(i > 0)
			{
				vCur += "|";
				vMax += "|";
				vMin += "|";
			}

			vCur += _attributes[i].value.ToString();
			vMax += _attributes[i].valueMax.ToString();
			vMin += _attributes[i].valueMin.ToString();
		}

		PlayerPrefs.SetString("AVCur", vCur);
		PlayerPrefs.SetString("AVMax", vMax);
		PlayerPrefs.SetString("AVMin", vMin);
		PlayerPrefs.SetInt("Exp", experience);
		PlayerPrefs.SetInt("ExpR", expRequired);
		PlayerPrefs.SetInt("ExpP", expPointsCur);
		PlayerPrefs.Save();
		Debug.Log("PlayerRPG : сохранение прогресса.");
	}

	static void Load(int count)
	{
		if(!PlayerPrefs.HasKey("Exp")) return;

		string[] vCur = PlayerPrefs.GetString("AVCur").Split(new char[]{'|'});
		string[] vMax = PlayerPrefs.GetString("AVMax").Split(new char[]{'|'});
		string[] vMin = PlayerPrefs.GetString("AVMin").Split(new char[]{'|'});

		experience = PlayerPrefs.GetInt("Exp");
		expRequired = PlayerPrefs.GetInt("ExpR");
		expPointsCur = PlayerPrefs.GetInt("ExpP");

		_attributes = new Attributes[count];

		for(int i = 0; i < _attributes.Length; i++)
		{
			_attributes[i].value = int.Parse(vCur[i]);
			_attributes[i].valueMax = int.Parse(vMax[i]);
			_attributes[i].valueMin = int.Parse(vMin[i]);
		}
	}

	[System.Serializable] public struct Attributes
	{
		public string name;
		public int value;
		public int valueMax;
		public int valueMin;
	}

}

Рассмотрим несколько примеров использования:

Изменение опыта:

PlayerRPG.AdjustExperience(150);

Добавляем 150 опыта. Сама функция работает так, чтобы не было потерь. Это значит, что если для повышение уровня, нужно сто опыта, а персонаж получил двести, то когда произойдет переход на новый уровень, лишние сто опыта будут зачислены как остаток.

Прокачка атрибута с учетом текущих очков прокачки:

PlayerRPG.AddLevelAttribute(PlayerAttribute.Luck);

Повышаем атрибут "Luck" и отнимаем один балл прокачки.

Использование событий:

Предусмотрено три типа событий:
1. меняются настройки атрибута; 2. меняется значение атрибута; 3. повышается уровень персонажа.

Например, если мы хотим отслеживать повышение уровня.

void Awake()
{
	PlayerRPG.OnExperience += EventExperience;
}

void OnDestroy()
{
	PlayerRPG.OnExperience -= EventExperience;
}

void EventExperience()
{
	Debug.Log("Cобытие: повышение уровня персонажа.");
}

С начало мы подписываемся на событие и если объект уничтожается, отписываемся.
Внимание! Отписываться необходимо обязательно! Чтобы избежать утечек памяти.

Больше примеров, можно посмотреть в проекте:

Внимание! Посетители, находящиеся в группе Гости, не могут скачивать файлы.
Тестировалось на: Unity 5.4.1

Комментариев 6

Офлайн
Light 9 октября 2017
KaKTyS_76Rus, надо все залить и ошибок не будет.
Офлайн
KaKTyS_76Rus 8 октября 2017
При заливке первого скрипта пишет что "The type or namespace name `PlayerRPG' could not be found". Что делать?
Офлайн
Light 20 сентября 2016
Цитата: Vitremer
Класс который есть в юньке по умолчанию?

Да.

Цитата: Vitremer
не очень врубился в следующую строчку кода:
[CustomEditor(typeof(PlayerSetup))]

https://docs.unity3d.com/ru/current/Manual/editor-CustomEditors.html

Цитата: Vitremer
Я бы еще добавил такой нюанс

Можно просто создать соответствующий атрибут (не доступный для игрока), который будет плюсоваться к определенным навыкам. Так как яды могут быть разные например, действовать одновременно на разные навыки или здоровье/ману и т.п.

Цитата: Vitremer
админы почему длина коментов короткая?

Поправим.
Офлайн
Vitremer2 20 сентября 2016
Дак вот)) дальше:


Офлайн
Vitremer 20 сентября 2016
Почти все понял. Можно несколько вопросов:
1.Не нашел PlayerPrefs - что это и откуда? Класс который есть в юньке по умолчанию?
2.не очень врубился в следующую строчку кода:
[CustomEditor(typeof(PlayerSetup))]




не влазит весь комент! админы почему длина коментов короткая?
Офлайн
Slava XD 11 сентября 2016
Классно спасибо большое
Информация
Посетители, находящиеся в группе Гости, не могут оставлять комментарии к данной публикации.
  • Яндекс.Метрика