├── .gitignore ├── Attributes.meta ├── Attributes ├── Health.cs └── Health.cs.meta ├── Audio.meta ├── Audio ├── AudioRandomizer.cs ├── AudioRandomizer.cs.meta ├── Footsteps.cs └── Footsteps.cs.meta ├── Cinematics.meta ├── Cinematics ├── CinematicControlRemover.cs ├── CinematicControlRemover.cs.meta ├── CinematicTrigger.cs └── CinematicTrigger.cs.meta ├── Combat.meta ├── Combat ├── CombatTarget.cs ├── CombatTarget.cs.meta ├── Fighter.cs ├── Fighter.cs.meta ├── HealthPickup.cs ├── HealthPickup.cs.meta ├── PickupBase.cs ├── PickupBase.cs.meta ├── Projectile.cs ├── Projectile.cs.meta ├── Weapon.cs ├── Weapon.cs.meta ├── WeaponConfig.cs ├── WeaponConfig.cs.meta ├── WeaponPickup.cs └── WeaponPickup.cs.meta ├── Control.meta ├── Control ├── AIController.cs ├── AIController.cs.meta ├── PatrolPath.cs ├── PatrolPath.cs.meta ├── PlayerController.cs └── PlayerController.cs.meta ├── Conversing.meta ├── Conversing ├── Dialogue.cs ├── Dialogue.cs.meta ├── DialogueManager.cs └── DialogueManager.cs.meta ├── Core.meta ├── Core ├── ActionScheduler.cs ├── ActionScheduler.cs.meta ├── CharacterBehaviour.cs ├── CharacterBehaviour.cs.meta ├── CursorType.cs ├── CursorType.cs.meta ├── IAction.cs ├── IAction.cs.meta ├── IRaycastable.cs ├── IRaycastable.cs.meta ├── PersistentObjectSpawner.cs └── PersistentObjectSpawner.cs.meta ├── Enhancements.meta ├── Enhancements ├── SerializeReferenceInspectorUI.meta └── SerializeReferenceInspectorUI │ ├── Core.meta │ ├── Core │ ├── SerializeReferenceGenericSelectionMenu.cs │ ├── SerializeReferenceGenericSelectionMenu.cs.meta │ ├── SerializeReferenceTypeNameUtility.cs │ └── SerializeReferenceTypeNameUtility.cs.meta │ ├── Drawing.meta │ ├── Drawing │ ├── SerializeReferenceButton.meta │ ├── SerializeReferenceButton │ │ ├── SerializeReferenceButtonAttribute.cs │ │ ├── SerializeReferenceButtonAttribute.cs.meta │ │ ├── SerializeReferenceButtonAttributeDrawer.cs │ │ └── SerializeReferenceButtonAttributeDrawer.cs.meta │ ├── SerializeReferenceInspectorButton.cs │ ├── SerializeReferenceInspectorButton.cs.meta │ ├── SerializeReferenceInspectorMiddleMouseMenu.cs │ ├── SerializeReferenceInspectorMiddleMouseMenu.cs.meta │ ├── SerializeReferenceMenu.meta │ └── SerializeReferenceMenu │ │ ├── SerializeReferenceMenuAttribute.cs │ │ ├── SerializeReferenceMenuAttribute.cs.meta │ │ ├── SerializeReferenceMenuAttributeDrawer.cs │ │ └── SerializeReferenceMenuAttributeDrawer.cs.meta │ ├── README.md │ ├── README.md.meta │ ├── Restriction.meta │ ├── Restriction │ ├── SerializeReferenceTypeRestrictionFilters.cs │ ├── SerializeReferenceTypeRestrictionFilters.cs.meta │ ├── SerializeReferenceUIRestrictionExcludeTypes.cs │ ├── SerializeReferenceUIRestrictionExcludeTypes.cs.meta │ ├── SerializeReferenceUIRestrictionIncludeTypes.cs │ ├── SerializeReferenceUIRestrictionIncludeTypes.cs.meta │ ├── SerializedReferenceUIBuiltInTypeRestrictions.cs │ └── SerializedReferenceUIBuiltInTypeRestrictions.cs.meta │ ├── SerializeReferenceUI.asmdef │ └── SerializeReferenceUI.asmdef.meta ├── Events.meta ├── Events ├── Editor.meta ├── Editor │ ├── EventEditor.cs │ └── EventEditor.cs.meta ├── GameEvent.cs ├── GameEvent.cs.meta ├── GameEventListener.cs └── GameEventListener.cs.meta ├── LICENSE ├── LICENSE.meta ├── Movement.meta ├── Movement ├── Mover.cs └── Mover.cs.meta ├── NPC.meta ├── NPC ├── DialogueInitiator.cs ├── DialogueInitiator.cs.meta ├── NPCBase.cs └── NPCBase.cs.meta ├── Questing.meta ├── Questing ├── Goal.meta ├── Goal │ ├── ConversationGoal.cs │ ├── ConversationGoal.cs.meta │ ├── Goals.cs │ ├── Goals.cs.meta │ ├── IGoal.cs │ ├── IGoal.cs.meta │ ├── KillGoalsS.cs │ ├── KillGoalsS.cs.meta │ ├── PickupGoal.cs │ └── PickupGoal.cs.meta ├── Quest.cs ├── Quest.cs.meta ├── QuestGiver.cs ├── QuestGiver.cs.meta ├── QuestManager.cs ├── QuestManager.cs.meta ├── QuestSaver.cs ├── QuestSaver.cs.meta ├── Stage.meta └── Stage │ ├── Stage.cs │ └── Stage.cs.meta ├── README.md ├── README.md.meta ├── Saving.meta ├── Saving ├── ISaveable.cs ├── ISaveable.cs.meta ├── SaveableEntity.cs ├── SaveableEntity.cs.meta ├── SavingSystem.cs ├── SavingSystem.cs.meta ├── SerializableVector3.cs └── SerializableVector3.cs.meta ├── SceneManagement.meta ├── SceneManagement ├── Fader.cs ├── Fader.cs.meta ├── Portal.cs ├── Portal.cs.meta ├── SavingWrapper.cs └── SavingWrapper.cs.meta ├── Stats.meta ├── Stats ├── BaseStats.cs ├── BaseStats.cs.meta ├── CharacterClass.cs ├── CharacterClass.cs.meta ├── Experience.cs ├── Experience.cs.meta ├── IModifierProvider.cs ├── IModifierProvider.cs.meta ├── Progression.cs ├── Progression.cs.meta ├── Stats.cs └── Stats.cs.meta ├── UI.meta ├── UI ├── CameraFacing.cs ├── CameraFacing.cs.meta ├── Damage Text.meta ├── Damage Text │ ├── DamageText.cs │ ├── DamageText.cs.meta │ ├── DamageTextSpawner.cs │ └── DamageTextSpawner.cs.meta ├── ExperienceDisplay.cs ├── ExperienceDisplay.cs.meta ├── Health.meta ├── Health │ ├── EnemyHealthDisplay.cs │ ├── EnemyHealthDisplay.cs.meta │ ├── HealthBar.cs │ ├── HealthBar.cs.meta │ ├── HealthDisplay.cs │ └── HealthDisplay.cs.meta ├── LevelToDisplay.cs ├── LevelToDisplay.cs.meta ├── QuestToDisplay.cs └── QuestToDisplay.cs.meta ├── Util.meta └── Util ├── LazyValue.cs ├── LazyValue.cs.meta ├── ObjectPooler.cs ├── ObjectPooler.cs.meta ├── Utility.cs └── Utility.cs.meta /.gitignore: -------------------------------------------------------------------------------- 1 | [Ll]ibrary/ 2 | [Tt]emp/ 3 | [Oo]bj/ 4 | [Bb]uild/ 5 | [Bb]uilds/ 6 | Assets/AssetStoreTools* 7 | 8 | # Visual Studio cache directory 9 | .vs/ 10 | 11 | # Autogenerated VS/MD/Consulo solution and project files 12 | ExportedObj/ 13 | .consulo/ 14 | *.csproj 15 | *.unityproj 16 | *.sln 17 | *.suo 18 | *.tmp 19 | *.user 20 | *.userprefs 21 | *.pidb 22 | *.booproj 23 | *.svd 24 | *.pdb 25 | *.opendb 26 | 27 | # Unity3D generated meta files 28 | *.pidb.meta 29 | *.pdb.meta 30 | 31 | # Unity3D Generated File On Crash Reports 32 | sysinfo.txt 33 | 34 | # Builds 35 | *.apk 36 | *.unitypackage 37 | 38 | # Never ignore Asset meta data 39 | !/[Aa]ssets/**/*.meta 40 | -------------------------------------------------------------------------------- /Attributes.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 064261372beb5ec499a64afb7ab3e3d4 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Attributes/Health.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using RPG.Core; 3 | using RPG.Saving; 4 | using RPG.Stats; 5 | using RPG.Util; 6 | using UnityEngine; 7 | using UnityEngine.Events; 8 | 9 | namespace RPG.Attributes 10 | { 11 | public class Health : MonoBehaviour, ISaveable 12 | { 13 | [SerializeField] private float regenerationPercentage = 70f; 14 | [SerializeField] private TakeDamageEvent takeDamage; 15 | [SerializeField] private bool startDead; 16 | [SerializeField] private OnDieEvent onDie; 17 | 18 | private LazyValue m_HealthPoints; 19 | private Animator m_Animator; 20 | private ActionScheduler m_ActionScheduler; 21 | private BaseStats m_BaseStats; 22 | private static readonly int s_Die = Animator.StringToHash("die"); 23 | 24 | public event Action OnHealthUpdate; 25 | 26 | [Serializable] 27 | public class TakeDamageEvent : UnityEvent { } 28 | 29 | [Serializable] 30 | public class OnDieEvent : UnityEvent { } 31 | 32 | public bool IsDead { get; private set; } 33 | public float Percentage => Fraction * 100; 34 | public float HealthPoints => m_HealthPoints.Value; 35 | public float MaxHealthPoints => m_BaseStats.GetStat(Stat.Health); 36 | public float Fraction => m_HealthPoints.Value / m_BaseStats.GetStat(Stat.Health); 37 | 38 | private void Awake() 39 | { 40 | m_Animator = GetComponent(); 41 | m_ActionScheduler = GetComponent(); 42 | m_BaseStats = GetComponent(); 43 | m_HealthPoints = new LazyValue(GetInitialHealth); 44 | } 45 | 46 | private void Start() 47 | { 48 | m_HealthPoints.ForceInit(); 49 | if (startDead) 50 | { 51 | Die(); 52 | } 53 | } 54 | 55 | private void OnEnable() 56 | { 57 | m_BaseStats.OnLevelUp += RegenerateHealth; 58 | } 59 | 60 | private void OnDisable() 61 | { 62 | m_BaseStats.OnLevelUp -= RegenerateHealth; 63 | } 64 | 65 | private float GetInitialHealth() 66 | { 67 | return m_BaseStats.GetStat(Stat.Health); 68 | } 69 | 70 | public void TakeDamage(GameObject instigator, float damage) 71 | { 72 | if (IsDead) 73 | { 74 | return; 75 | } 76 | 77 | m_HealthPoints.Value = Mathf.Max(m_HealthPoints.Value - damage, 0); 78 | takeDamage?.Invoke(damage); 79 | OnHealthUpdate?.Invoke(); 80 | if (m_HealthPoints.Value >= Mathf.Epsilon) return; 81 | onDie?.Invoke(gameObject.name); 82 | Die(); 83 | AwardExperience(instigator); 84 | } 85 | 86 | public void Heal(float healthPercentToRestore) 87 | { 88 | m_HealthPoints.Value = Mathf.Min(m_HealthPoints.Value + (MaxHealthPoints * healthPercentToRestore / 100), 89 | MaxHealthPoints); 90 | OnHealthUpdate?.Invoke(); 91 | } 92 | 93 | public object CaptureState() 94 | { 95 | return m_HealthPoints.Value; 96 | } 97 | 98 | public void RestoreState(object state) 99 | { 100 | m_HealthPoints.Value = (float) state; 101 | if (m_HealthPoints.Value <= 0) 102 | { 103 | Die(); 104 | } 105 | } 106 | 107 | private void AwardExperience(GameObject instigator) 108 | { 109 | var experience = instigator.GetComponent(); 110 | if (experience == null) 111 | { 112 | return; 113 | } 114 | 115 | experience.GainExperience(m_BaseStats.GetStat(Stat.ExperienceReward)); 116 | } 117 | 118 | private bool RegenerateHealth() 119 | { 120 | if (IsDead) 121 | { 122 | return false; 123 | } 124 | 125 | float regenHealthPoints = m_BaseStats.GetStat(Stat.Health) * (regenerationPercentage / 100); 126 | m_HealthPoints.Value = Mathf.Max(m_HealthPoints.Value, regenHealthPoints); 127 | OnHealthUpdate?.Invoke(); 128 | return true; 129 | } 130 | 131 | private void Die() 132 | { 133 | if (IsDead) 134 | { 135 | return; 136 | } 137 | 138 | m_Animator.SetTrigger(s_Die); 139 | m_ActionScheduler.CancelCurrentAction(); 140 | IsDead = true; 141 | } 142 | } 143 | } -------------------------------------------------------------------------------- /Attributes/Health.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8be62ff894ab8bd41a0f96509090ded6 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Audio.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1bbb8f693b23bb644b67c459a61fc6b2 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Audio/AudioRandomizer.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace RPG.Audio 4 | { 5 | /// 6 | /// Allows playing random audio FX from a collection. 7 | /// 8 | public class AudioRandomizer : MonoBehaviour 9 | { 10 | [SerializeField] private AudioClip[] audioClips; 11 | 12 | private AudioSource m_AudioSource; 13 | 14 | private void Awake() 15 | { 16 | m_AudioSource = GetComponent(); 17 | m_AudioSource.clip = audioClips[0]; 18 | } 19 | 20 | public void PlayRandomClip() 21 | { 22 | if (audioClips.Length > 1) 23 | { 24 | AudioClip prevClip = m_AudioSource.clip; 25 | 26 | do 27 | { 28 | m_AudioSource.clip = audioClips[Random.Range(0, audioClips.Length)]; 29 | } while (m_AudioSource.clip == prevClip); 30 | } 31 | 32 | m_AudioSource.pitch = Random.Range(0.9f, 1.1f); 33 | m_AudioSource.Play(); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /Audio/AudioRandomizer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5cd0ebe7be8c84140983b8fba372684d 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Audio/Footsteps.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace RPG.Audio 4 | { 5 | // TODO 6 | /// 7 | /// The implementation for footsteps sounds is still TODO 8 | /// 9 | public class Footsteps : MonoBehaviour 10 | { 11 | [SerializeField] private AudioClip walkClip; 12 | [SerializeField] private AudioClip runClip; 13 | [SerializeField] private AudioSource audioSource; 14 | private const string ACTION_WALK = "walk"; 15 | private const string ACTION_RUN = "run"; 16 | 17 | private void Step(string action) 18 | { 19 | if (!gameObject.CompareTag("Player")) return; 20 | Debug.Log(action); 21 | switch (action) 22 | { 23 | case ACTION_RUN: 24 | { 25 | if (audioSource.clip != runClip) 26 | { 27 | audioSource.Stop(); 28 | audioSource.clip = runClip; 29 | } 30 | 31 | if (!audioSource.isPlaying) 32 | { 33 | audioSource.PlayOneShot(audioSource.clip); 34 | } 35 | else 36 | { 37 | audioSource.Stop(); 38 | } 39 | 40 | break; 41 | } 42 | case ACTION_WALK: 43 | { 44 | if (audioSource.clip != walkClip) 45 | { 46 | audioSource.Stop(); 47 | audioSource.clip = walkClip; 48 | } 49 | 50 | if (!audioSource.isPlaying) 51 | { 52 | audioSource.PlayOneShot(audioSource.clip); 53 | } 54 | else 55 | { 56 | audioSource.Stop(); 57 | } 58 | 59 | break; 60 | } 61 | default: 62 | audioSource.Stop(); 63 | break; 64 | } 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /Audio/Footsteps.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: aa6faf9b6798d7a48ada230dc2deb293 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Cinematics.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 811dd41714b84da4b9372d399670b54f 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Cinematics/CinematicControlRemover.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using RPG.Control; 3 | using RPG.Core; 4 | using UnityEngine; 5 | using UnityEngine.Playables; 6 | 7 | namespace RPG.Cinematics 8 | { 9 | public class CinematicControlRemover : MonoBehaviour 10 | { 11 | public event Action OnCinematicStart; 12 | public event Action OnCinematicEnd; 13 | private PlayableDirector m_PlayableDirector; 14 | private ActionScheduler m_ActionScheduler; 15 | private GameObject m_Player; 16 | private PlayerController m_PlayerController; 17 | 18 | private void Awake() 19 | { 20 | m_PlayableDirector = GetComponent(); 21 | m_Player = GameObject.FindWithTag("Player"); 22 | m_PlayerController = m_Player.GetComponent(); 23 | m_ActionScheduler = m_Player.GetComponent(); 24 | } 25 | 26 | private void OnEnable() 27 | { 28 | m_PlayableDirector.played += DisableControl; 29 | m_PlayableDirector.stopped += EnableControl; 30 | } 31 | 32 | private void OnDisable() 33 | { 34 | m_PlayableDirector.played -= DisableControl; 35 | m_PlayableDirector.stopped -= EnableControl; 36 | } 37 | 38 | private void EnableControl(PlayableDirector pd) 39 | { 40 | if (m_PlayableDirector != null) 41 | { 42 | m_PlayerController.enabled = true; 43 | } 44 | 45 | OnCinematicEnd(false); 46 | } 47 | 48 | private void DisableControl(PlayableDirector pd) 49 | { 50 | m_ActionScheduler.CancelCurrentAction(); 51 | m_PlayerController.enabled = false; 52 | OnCinematicStart(true); 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /Cinematics/CinematicControlRemover.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 959f3d751c214ee40ae7731be0a5d03e 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Cinematics/CinematicTrigger.cs: -------------------------------------------------------------------------------- 1 | using RPG.Saving; 2 | using UnityEngine; 3 | using UnityEngine.Playables; 4 | 5 | namespace RPG.Cinematics 6 | { 7 | public class CinematicTrigger : MonoBehaviour, ISaveable 8 | { 9 | private PlayableDirector m_PlayableDirector; 10 | private bool m_IsAlreadyTriggered; 11 | 12 | private void Awake() 13 | { 14 | m_PlayableDirector = GetComponent(); 15 | } 16 | 17 | private void OnTriggerEnter(Collider other) 18 | { 19 | if (m_IsAlreadyTriggered || !other.gameObject.CompareTag("Player")) return; 20 | m_PlayableDirector.Play(); 21 | m_IsAlreadyTriggered = true; 22 | } 23 | 24 | public object CaptureState() 25 | { 26 | return m_IsAlreadyTriggered; 27 | } 28 | 29 | public void RestoreState(object state) 30 | { 31 | m_IsAlreadyTriggered = (bool) state; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /Cinematics/CinematicTrigger.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0cb436e1090a5fb4386f260a0f89ba55 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Combat.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f0869797ff692f747a99a80d583c3272 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Combat/CombatTarget.cs: -------------------------------------------------------------------------------- 1 | using RPG.Attributes; 2 | using RPG.Core; 3 | using UnityEngine; 4 | 5 | namespace RPG.Combat 6 | { 7 | /// 8 | /// GameObjects with this component will become attackable. 9 | /// 10 | [RequireComponent(typeof(Health))] 11 | public class CombatTarget : MonoBehaviour, IRaycastable 12 | { 13 | public CursorType Cursor => CursorType.Combat; 14 | 15 | public bool HandleRaycast(GameObject callingObject) 16 | { 17 | var fighter = callingObject.GetComponent(); 18 | if (!fighter.CanAttack(gameObject)) 19 | { 20 | return false; 21 | } 22 | 23 | if (Input.GetMouseButton(0)) 24 | { 25 | fighter.Attack(gameObject); 26 | } 27 | 28 | return true; 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /Combat/CombatTarget.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 420d3ae772b8d484d817664184fccaed 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Combat/Fighter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using RPG.Attributes; 4 | using RPG.Cinematics; 5 | using RPG.Core; 6 | using RPG.Movement; 7 | using RPG.Saving; 8 | using RPG.Stats; 9 | using RPG.Util; 10 | using UnityEngine; 11 | using static RPG.Util.Utility; 12 | 13 | namespace RPG.Combat 14 | { 15 | [RequireComponent(typeof(CharacterBehaviour))] 16 | public class Fighter : MonoBehaviour, IAction, ISaveable, IModifierProvider 17 | { 18 | [SerializeField] private Transform leftHandTransform; 19 | [SerializeField] private Transform rightHandTransform; 20 | [SerializeField] private WeaponConfig defaultWeapon; 21 | [SerializeField] private float timeBetweenAttacks = 1f; 22 | 23 | private Health m_Target; 24 | private Health m_PrevTarget; 25 | private Mover m_Mover; 26 | private ActionScheduler m_ActionScheduler; 27 | private Animator m_Animator; 28 | private BaseStats m_BaseStats; 29 | private CharacterBehaviour m_CharacterBehaviour; 30 | private bool m_Stopped; 31 | private float m_TimeSinceLastAttack = Mathf.Infinity; 32 | private WeaponConfig m_CurrentWeaponConfig; 33 | private LazyValue m_CurrentWeapon; 34 | private CinematicControlRemover[] m_CinematicControlRemovers; 35 | 36 | public bool attackDisabled; 37 | private static readonly int s_StopAttack = Animator.StringToHash("stopAttack"); 38 | private static readonly int s_Attack = Animator.StringToHash("attack"); 39 | public event Action UpdateTargetUi; 40 | public Health Target => m_Target; 41 | 42 | private void Awake() 43 | { 44 | m_Mover = GetComponent(); 45 | m_Animator = GetComponent(); 46 | m_ActionScheduler = GetComponent(); 47 | m_CharacterBehaviour = GetComponent(); 48 | m_Animator = GetComponent(); 49 | m_BaseStats = GetComponent(); 50 | m_CurrentWeaponConfig = defaultWeapon; 51 | m_CurrentWeapon = new LazyValue(SetupDefaultWeapon); 52 | m_CinematicControlRemovers = FindObjectsOfType(); 53 | } 54 | 55 | private void Start() 56 | { 57 | m_CurrentWeapon.ForceInit(); 58 | } 59 | 60 | private void OnEnable() 61 | { 62 | for (int i = 0; i < m_CinematicControlRemovers.Length; i++) 63 | { 64 | m_CinematicControlRemovers[i].OnCinematicStart += ToggleAttack; 65 | m_CinematicControlRemovers[i].OnCinematicEnd += ToggleAttack; 66 | } 67 | } 68 | 69 | private void OnDisable() 70 | { 71 | for (int i = 0; i < m_CinematicControlRemovers.Length; i++) 72 | { 73 | m_CinematicControlRemovers[i].OnCinematicStart -= ToggleAttack; 74 | m_CinematicControlRemovers[i].OnCinematicEnd -= ToggleAttack; 75 | } 76 | } 77 | 78 | private void Update() 79 | { 80 | m_TimeSinceLastAttack += Time.deltaTime; 81 | if (m_Target == null || m_Target.IsDead || attackDisabled) 82 | { 83 | return; 84 | } 85 | 86 | if (CombatTargetChanged(m_Target)) 87 | { 88 | StopAttack(); 89 | } 90 | 91 | if (!GetIsInRange(m_Target.transform)) 92 | { 93 | m_Mover.MoveTo(m_Target.transform.position, 1f); 94 | m_Stopped = false; 95 | } 96 | else 97 | { 98 | if (!m_Stopped) 99 | { 100 | m_Mover.Cancel(); 101 | m_Stopped = true; 102 | } 103 | 104 | AttackBehaviour(); 105 | } 106 | } 107 | 108 | private bool CombatTargetChanged(Health newTarget) 109 | { 110 | if (m_PrevTarget == null) 111 | { 112 | m_PrevTarget = newTarget; 113 | return false; 114 | } 115 | 116 | if (newTarget == m_PrevTarget) return false; 117 | m_PrevTarget = newTarget; 118 | return true; 119 | } 120 | 121 | private Weapon SetupDefaultWeapon() 122 | { 123 | return defaultWeapon.Spawn(rightHandTransform, leftHandTransform, m_Animator); 124 | } 125 | 126 | private void AttackBehaviour() 127 | { 128 | MakeEyeContact(); 129 | if (!(m_TimeSinceLastAttack > timeBetweenAttacks)) return; 130 | // This will trigger the animation Hit() event 131 | TriggerAttack(); 132 | m_TimeSinceLastAttack = 0; 133 | } 134 | 135 | private void MakeEyeContact() 136 | { 137 | m_CharacterBehaviour.LookAtTarget(m_Target.transform); 138 | m_Target.GetComponent().LookAtTarget(gameObject.transform); 139 | } 140 | 141 | private void TriggerAttack() 142 | { 143 | m_Animator.ResetTrigger(s_StopAttack); 144 | m_Animator.SetTrigger(s_Attack); 145 | } 146 | 147 | private bool GetIsInRange(Transform targetTransform) 148 | { 149 | return IsTargetInRange(transform, targetTransform, m_CurrentWeaponConfig.Range); 150 | } 151 | 152 | // Animation Trigger 153 | private void Hit() 154 | { 155 | if (m_Target != null) 156 | { 157 | if (m_CurrentWeapon.Value != null) 158 | { 159 | m_CurrentWeapon.Value.OnHit(m_Target); 160 | } 161 | 162 | float damage = m_BaseStats.GetStat(Stat.Damage); 163 | if (m_CurrentWeaponConfig.HasProjectile()) 164 | { 165 | m_CurrentWeaponConfig.LaunchProjectile(rightHandTransform, leftHandTransform, m_Target, gameObject, 166 | damage, UpdateTargetUi); 167 | } 168 | else 169 | { 170 | m_Target.TakeDamage(gameObject, damage); 171 | } 172 | } 173 | 174 | UpdateTargetUi?.Invoke(); 175 | } 176 | 177 | // Animation Trigger 178 | private void Shoot() 179 | { 180 | Hit(); 181 | } 182 | 183 | private void StopAttack() 184 | { 185 | m_Animator.ResetTrigger(s_Attack); 186 | m_Animator.SetTrigger(s_StopAttack); 187 | } 188 | 189 | public void EquipWeapon(WeaponConfig weapon) 190 | { 191 | m_CurrentWeaponConfig = weapon; 192 | m_CurrentWeapon.Value = weapon.Spawn(rightHandTransform, leftHandTransform, m_Animator); 193 | } 194 | 195 | public bool CanAttack(GameObject target) 196 | { 197 | if (target == null || (!m_Mover.CanMoveTo(target.transform.position) && !GetIsInRange(target.transform))) 198 | { 199 | return false; 200 | } 201 | 202 | Health targetToAttack = target.GetComponent(); 203 | return targetToAttack != null && !targetToAttack.IsDead; 204 | } 205 | 206 | public void Attack(GameObject combatTarget) 207 | { 208 | m_ActionScheduler.StartAction(this); 209 | m_Target = combatTarget.GetComponent(); 210 | } 211 | 212 | public void Cancel() 213 | { 214 | StopAttack(); 215 | m_Target = null; 216 | m_Mover.Cancel(); 217 | UpdateTargetUi?.Invoke(); 218 | } 219 | 220 | public object CaptureState() 221 | { 222 | return m_CurrentWeaponConfig.name; 223 | } 224 | 225 | public void RestoreState(object state) 226 | { 227 | var weaponName = (string) state; 228 | var weapon = UnityEngine.Resources.Load(weaponName); 229 | EquipWeapon(weapon); 230 | } 231 | 232 | public IEnumerable GetAdditiveModifiers(Stat stat) 233 | { 234 | if (stat == Stat.Damage) 235 | { 236 | yield return m_CurrentWeaponConfig.Damage; 237 | } 238 | } 239 | 240 | public IEnumerable GetPercentageModifiers(Stat stat) 241 | { 242 | if (stat == Stat.Damage) 243 | { 244 | yield return m_CurrentWeaponConfig.PercentageBonus; 245 | } 246 | } 247 | 248 | private void ToggleAttack(bool disabled) 249 | { 250 | attackDisabled = disabled; 251 | } 252 | } 253 | } -------------------------------------------------------------------------------- /Combat/Fighter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3b6068c8f47462646a3f1dff6e80be7c 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Combat/HealthPickup.cs: -------------------------------------------------------------------------------- 1 | using RPG.Attributes; 2 | using UnityEngine; 3 | 4 | namespace RPG.Combat 5 | { 6 | public class HealthPickup : PickupBase 7 | { 8 | [Range(0, 100)] [SerializeField] private float healthPercentToRestore = 25; 9 | [SerializeField] private GameObject healFx; 10 | [SerializeField] private bool respawnable = true; 11 | [SerializeField] private float respawnTime = 5f; 12 | 13 | private void OnTriggerEnter(Collider other) 14 | { 15 | if (other.gameObject.CompareTag("Player")) 16 | { 17 | Pickup(other.GetComponent(), healFx, healthPercentToRestore, respawnable, respawnTime); 18 | } 19 | } 20 | 21 | private void Pickup(Health health, GameObject healFX, float percentToRestore, bool respawn, 22 | float time) 23 | { 24 | Instantiate(healFX, health.transform); 25 | health.Heal(percentToRestore); 26 | base.Pickup(respawn, time); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /Combat/HealthPickup.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 21862aff3cf5de84db0d0c7e8bc7ac5c 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Combat/PickupBase.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using RPG.Core; 3 | using RPG.Movement; 4 | using UnityEngine; 5 | using UnityEngine.Events; 6 | 7 | namespace RPG.Combat 8 | { 9 | public class PickupBase : MonoBehaviour, IRaycastable 10 | { 11 | [SerializeField] private OnPickupEvent onPickUp; 12 | 13 | [System.Serializable] 14 | public class OnPickupEvent : UnityEvent { } 15 | 16 | public CursorType Cursor => CursorType.Pickup; 17 | 18 | private Collider m_ObjectCollider; 19 | 20 | private void Awake() 21 | { 22 | m_ObjectCollider = GetComponent(); 23 | } 24 | 25 | private IEnumerator HideForSeconds(float time) 26 | { 27 | ShowPickup(false); 28 | yield return new WaitForSeconds(time); 29 | ShowPickup(true); 30 | } 31 | 32 | private void ShowPickup(bool shouldShow) 33 | { 34 | m_ObjectCollider.enabled = shouldShow; 35 | for (int i = 0; i < transform.childCount; i++) 36 | { 37 | transform.GetChild(i).gameObject.SetActive(shouldShow); 38 | } 39 | } 40 | 41 | protected void Pickup(bool respawnable, float respawnTime) 42 | { 43 | if (respawnable) 44 | { 45 | StartCoroutine(HideForSeconds(respawnTime)); 46 | } 47 | else 48 | { 49 | Destroy(gameObject); 50 | } 51 | 52 | onPickUp?.Invoke(gameObject.name); 53 | } 54 | 55 | public bool HandleRaycast(GameObject callingObject) 56 | { 57 | if (Input.GetMouseButtonDown(0)) 58 | { 59 | callingObject.GetComponent().StartMovement(transform.position, 1f); 60 | } 61 | 62 | return true; 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /Combat/PickupBase.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: da5f19f5ff320764a89b611133e696d2 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Combat/Projectile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using RPG.Attributes; 4 | using RPG.Util; 5 | using UnityEngine; 6 | using UnityEngine.Events; 7 | 8 | namespace RPG.Combat 9 | { 10 | public class Projectile : MonoBehaviour 11 | { 12 | [SerializeField] private float speed = 1f; 13 | [SerializeField] private bool isHoming; 14 | [SerializeField] private float projectileTTL = 10f; 15 | [SerializeField] private float afterHitTTL = 0.2f; 16 | [SerializeField] private GameObject hitEffect; 17 | [SerializeField] private GameObject[] destroyAfterHit; 18 | [SerializeField] private UnityEvent onHit; 19 | [SerializeField] private UnityEvent onLaunch; 20 | private const string TAG_CINEMATIC = "Cinematic"; 21 | private const string TAG_PICKUP = "Pickup"; 22 | private float m_Damage; 23 | private Health m_Target; 24 | private GameObject m_Instigator; 25 | private ObjectPooler m_Pooler; 26 | private Transform m_Transform; 27 | private string m_PoolTag; 28 | 29 | /// 30 | /// Can update UI when damaging an enemy (e.g. update enemy health on HUD) 31 | /// 32 | private Action m_UpdateUi; 33 | 34 | private float m_OriginalSpeed; 35 | 36 | private void Awake() 37 | { 38 | m_OriginalSpeed = speed; 39 | m_Transform = transform; 40 | } 41 | 42 | private void Start() 43 | { 44 | m_Pooler = ObjectPooler.Instace; 45 | } 46 | 47 | private void Update() 48 | { 49 | if (m_Target == null) 50 | { 51 | return; 52 | } 53 | 54 | if (isHoming && !m_Target.IsDead) 55 | { 56 | m_Transform.LookAt(GetAimLocation()); 57 | } 58 | 59 | m_Transform.Translate(Vector3.forward * (speed * Time.deltaTime)); 60 | StartCoroutine(ReturnToPoolWithDelay(projectileTTL)); 61 | } 62 | 63 | private void OnTriggerEnter(Collider other) 64 | { 65 | // make projectiles go trough cinematic triggers and items on the ground 66 | if (other.gameObject.CompareTag(TAG_CINEMATIC) || other.gameObject.CompareTag(TAG_PICKUP)) 67 | { 68 | return; 69 | } 70 | 71 | foreach (GameObject toDestroy in destroyAfterHit) 72 | { 73 | toDestroy.SetActive(true); 74 | } 75 | 76 | var targetHealth = other.GetComponent(); 77 | 78 | if (targetHealth == null) 79 | { 80 | // to disallow friendly fire - || targetHealth != target 81 | ReturnToPool(); 82 | return; 83 | } 84 | 85 | if (targetHealth.IsDead) 86 | { 87 | StartCoroutine(ReturnToPoolWithDelay(projectileTTL)); 88 | return; 89 | } 90 | 91 | speed = 0; 92 | 93 | if (hitEffect != null) 94 | { 95 | Vector3 position = m_Transform.position; 96 | Vector3 closestPoint = !targetHealth.IsDead 97 | ? other.ClosestPoint(position) 98 | : m_Target.GetComponent().ClosestPoint(position); 99 | GameObject impactObj = Instantiate(hitEffect); 100 | impactObj.transform.position = closestPoint; 101 | impactObj.transform.rotation = m_Transform.rotation; 102 | } 103 | 104 | if (targetHealth == m_Target) 105 | { 106 | m_Target.TakeDamage(m_Instigator, m_Damage); 107 | } 108 | else 109 | { 110 | targetHealth.TakeDamage(m_Instigator, m_Damage); 111 | } 112 | 113 | foreach (GameObject toDestroy in destroyAfterHit) 114 | { 115 | toDestroy.SetActive(false); 116 | } 117 | 118 | onHit?.Invoke(); 119 | m_UpdateUi?.Invoke(); 120 | 121 | StartCoroutine(ReturnToPoolWithDelay(afterHitTTL)); 122 | } 123 | 124 | private void PointTowardsTarget() 125 | { 126 | if (m_Target == null) return; 127 | m_Transform.LookAt(GetAimLocation()); 128 | onLaunch?.Invoke(); 129 | } 130 | 131 | private Vector3 GetAimLocation() 132 | { 133 | var targetCollider = m_Target.GetComponent(); 134 | if (targetCollider == null) 135 | { 136 | return m_Target.transform.position; 137 | } 138 | 139 | return m_Target.transform.position + Vector3.up * targetCollider.height / 2; 140 | } 141 | 142 | private void ReturnToPool() 143 | { 144 | m_Pooler.AddToPool(m_PoolTag, gameObject); 145 | } 146 | 147 | private IEnumerator ReturnToPoolWithDelay(float delay) 148 | { 149 | yield return new WaitForSeconds(delay); 150 | ReturnToPool(); 151 | } 152 | 153 | public void SetTarget(Health target, GameObject instigator, float damage, Action updateUI, string poolTag) 154 | { 155 | m_Target = target; 156 | m_Damage = damage; 157 | m_Instigator = instigator; 158 | m_UpdateUi = updateUI; 159 | speed = m_OriginalSpeed; 160 | if (string.IsNullOrEmpty(m_PoolTag)) 161 | { 162 | m_PoolTag = poolTag; 163 | } 164 | 165 | PointTowardsTarget(); 166 | } 167 | } 168 | } -------------------------------------------------------------------------------- /Combat/Projectile.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c54b9a32719c4114bbe2fa5081e4cad1 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Combat/Weapon.cs: -------------------------------------------------------------------------------- 1 | using RPG.Attributes; 2 | using UnityEngine; 3 | using UnityEngine.Events; 4 | 5 | namespace RPG.Combat 6 | { 7 | public class Weapon : MonoBehaviour 8 | { 9 | [SerializeField] private UnityEvent onHit; 10 | [SerializeField] private GameObject hitEffect; 11 | 12 | public void OnHit(Health target) 13 | { 14 | onHit?.Invoke(); 15 | 16 | if (hitEffect == null) return; 17 | GameObject impactObj = Instantiate(hitEffect); 18 | impactObj.transform.position = target.GetComponent().ClosestPoint(transform.position); 19 | impactObj.transform.rotation = transform.rotation; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /Combat/Weapon.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: fb5dae9aadc672f4884b018879e35411 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Combat/WeaponConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using RPG.Attributes; 3 | using RPG.Util; 4 | using UnityEngine; 5 | 6 | namespace RPG.Combat 7 | { 8 | [CreateAssetMenu(fileName = "Weapon", menuName = "Weapons/Create New Weapon", order = 0)] 9 | public class WeaponConfig : ScriptableObject 10 | { 11 | [SerializeField] private AnimatorOverrideController animatorOverride; 12 | [SerializeField] private Weapon equippedPrefab; 13 | [SerializeField] private string projectileTag = "arrow"; 14 | [SerializeField] private Projectile projectile; 15 | [SerializeField] private float weaponRange = 3f; 16 | [SerializeField] private float weaponDamage = 25f; 17 | [SerializeField] private float percentageBonus; 18 | [SerializeField] private Hand hand = Hand.Right; 19 | private const string WEAPON_NAME = "Weapon"; 20 | private const string DESTORYING = "Destroying"; 21 | 22 | private enum Hand 23 | { 24 | Left, 25 | Right 26 | } 27 | 28 | private ObjectPooler m_Pooler; 29 | public float Range => weaponRange; 30 | public float Damage => weaponDamage; 31 | public float PercentageBonus => percentageBonus; 32 | 33 | private void DestroyOldWeapon(Transform rightHand, Transform leftHand) 34 | { 35 | Transform currentWeapon = rightHand.Find(WEAPON_NAME); 36 | if (currentWeapon == null) 37 | { 38 | currentWeapon = leftHand.Find(WEAPON_NAME); 39 | } 40 | 41 | if (currentWeapon == null) 42 | { 43 | return; 44 | } 45 | 46 | currentWeapon.name = DESTORYING; 47 | Destroy(currentWeapon.gameObject); 48 | } 49 | 50 | private Transform GetTransform(Transform rightHand, Transform leftHand) 51 | { 52 | return hand == Hand.Right ? rightHand : leftHand; 53 | } 54 | 55 | public Weapon Spawn(Transform rightHand, Transform leftHand, Animator animator) 56 | { 57 | DestroyOldWeapon(rightHand, leftHand); 58 | Weapon weapon = null; 59 | if (equippedPrefab != null) 60 | { 61 | weapon = Instantiate(equippedPrefab, GetTransform(rightHand, leftHand)); 62 | weapon.gameObject.name = WEAPON_NAME; 63 | } 64 | 65 | AnimatorOverrideController overrideController = 66 | animator.runtimeAnimatorController as AnimatorOverrideController; 67 | if (animatorOverride != null) 68 | { 69 | animator.runtimeAnimatorController = animatorOverride; 70 | } 71 | else if (overrideController != null) 72 | { 73 | animator.runtimeAnimatorController = overrideController.runtimeAnimatorController; 74 | } 75 | 76 | return weapon; 77 | } 78 | 79 | public bool HasProjectile() 80 | { 81 | return projectile != null; 82 | } 83 | 84 | public void LaunchProjectile(Transform rightHand, Transform leftHand, Health target, GameObject instigator, 85 | float calculatedDamage, Action updateUI) 86 | { 87 | if (m_Pooler == null) 88 | { 89 | m_Pooler = ObjectPooler.Instace; 90 | } 91 | 92 | GameObject instance = m_Pooler.SpawnFromPool(projectileTag); 93 | if (!instance) 94 | { 95 | return; 96 | } 97 | 98 | var projectileInstance = instance.GetComponent(); 99 | projectileInstance.transform.position = GetTransform(rightHand, leftHand).position; 100 | projectile.transform.rotation = Quaternion.identity; 101 | projectileInstance.SetTarget(target, instigator, calculatedDamage, updateUI, projectileTag); 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /Combat/WeaponConfig.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9b07020dc47af464684973e354a3efe2 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Combat/WeaponPickup.cs: -------------------------------------------------------------------------------- 1 | using RPG.Combat; 2 | using UnityEngine; 3 | 4 | public class WeaponPickup : PickupBase 5 | { 6 | [SerializeField] private WeaponConfig weapon = default; 7 | [SerializeField] private float respawnTime = 5f; 8 | [SerializeField] private bool respawnable = true; 9 | 10 | private void OnTriggerEnter(Collider other) 11 | { 12 | if (other.gameObject.CompareTag("Player")) 13 | { 14 | Pickup(other.GetComponent(), weapon, respawnable, respawnTime); 15 | } 16 | } 17 | 18 | private void Pickup(Fighter fighter, WeaponConfig w, bool respawn, float time) 19 | { 20 | fighter.EquipWeapon(w); 21 | base.Pickup(respawn, time); 22 | } 23 | } -------------------------------------------------------------------------------- /Combat/WeaponPickup.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 72668e38c51bed7498b3720266d5ccfa 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Control.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f1eb114801ff8ca49baf51fb5ab17df5 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Control/AIController.cs: -------------------------------------------------------------------------------- 1 | using RPG.Attributes; 2 | using RPG.Combat; 3 | using RPG.Core; 4 | using RPG.Movement; 5 | using RPG.Util; 6 | using UnityEngine; 7 | using static RPG.Util.Utility; 8 | 9 | namespace RPG.Control 10 | { 11 | public class AIController : MonoBehaviour 12 | { 13 | [SerializeField] private float chaseDistance = 5f; 14 | [SerializeField] private float suspicionTime = 5f; 15 | [SerializeField] private float aggroCooldown = 5f; 16 | [SerializeField] private float shoutDistance = 5f; 17 | [SerializeField] private float dwellTime = 3f; 18 | [SerializeField] private float wayPointTolerance = 1f; 19 | [Range(0, 1)] [SerializeField] private float patrolSpeedFraction = 0.2f; 20 | [SerializeField] private PatrolPath patrolPath; 21 | 22 | private int m_CurrentWaypointIndex; 23 | private float m_TimeSinceLastSawPlayer = Mathf.Infinity; 24 | private float m_TimeSinceLastWaypoint = Mathf.Infinity; 25 | private float m_TimeSinceAggrevated = Mathf.Infinity; 26 | private bool m_HasNotAggrevated = true; 27 | private GameObject m_Player; 28 | private Health m_PlayerHealth; 29 | private Health m_AiHealth; 30 | private Fighter m_Fighter; 31 | private Mover m_Mover; 32 | private ActionScheduler m_ActionScheduler; 33 | private LazyValue m_GuardPosition; 34 | 35 | public PatrolPath PatrolPath => patrolPath; 36 | 37 | private void Awake() 38 | { 39 | m_Player = GameObject.FindWithTag("Player"); 40 | m_PlayerHealth = m_Player.GetComponent(); 41 | m_AiHealth = GetComponent(); 42 | m_Fighter = GetComponent(); 43 | m_Mover = GetComponent(); 44 | m_ActionScheduler = GetComponent(); 45 | m_GuardPosition = new LazyValue(GetGuardPosition); 46 | } 47 | 48 | private void Start() 49 | { 50 | m_GuardPosition.ForceInit(); 51 | } 52 | 53 | private void Update() 54 | { 55 | if (m_AiHealth.IsDead) 56 | { 57 | return; 58 | } 59 | 60 | if (IsAggrevated()) 61 | { 62 | m_TimeSinceLastSawPlayer = 0; 63 | AttackBehaviour(); 64 | } 65 | else if (m_TimeSinceLastSawPlayer < suspicionTime) 66 | { 67 | SuspicionBehaviour(); 68 | } 69 | else 70 | { 71 | m_HasNotAggrevated = true; 72 | PatrolBehaviour(); 73 | } 74 | 75 | UpdateTimers(); 76 | } 77 | 78 | private void OnDrawGizmosSelected() 79 | { 80 | Gizmos.color = Color.blue; 81 | Gizmos.DrawWireSphere(transform.position, chaseDistance); 82 | } 83 | 84 | private void UpdateTimers() 85 | { 86 | m_TimeSinceLastSawPlayer += Time.deltaTime; 87 | m_TimeSinceLastWaypoint += Time.deltaTime; 88 | m_TimeSinceAggrevated += Time.deltaTime; 89 | } 90 | 91 | private Vector3 GetGuardPosition() 92 | { 93 | return transform.position; 94 | } 95 | 96 | private void AttackBehaviour() 97 | { 98 | m_Fighter.Attack(m_Player); 99 | AggrevateNearbyEnemies(); 100 | if (!m_HasNotAggrevated) return; 101 | m_HasNotAggrevated = false; 102 | Aggrevate(); 103 | } 104 | 105 | private void AggrevateNearbyEnemies() 106 | { 107 | RaycastHit[] hits = Physics.SphereCastAll(transform.position, shoutDistance, Vector3.up, 0); 108 | foreach (RaycastHit hit in hits) 109 | { 110 | var ai = hit.collider.GetComponent(); 111 | 112 | if (ai == null || ai == this) 113 | { 114 | continue; 115 | } 116 | 117 | ai.Aggrevate(); 118 | } 119 | } 120 | 121 | private bool IsAggrevated() 122 | { 123 | return !m_PlayerHealth.IsDead && IsTargetInRange(transform, m_Player.transform, chaseDistance) || 124 | m_TimeSinceAggrevated < aggroCooldown; 125 | } 126 | 127 | private void SuspicionBehaviour() 128 | { 129 | m_ActionScheduler.CancelCurrentAction(); 130 | } 131 | 132 | private void PatrolBehaviour() 133 | { 134 | Vector3 nextPosition = m_GuardPosition.Value; 135 | 136 | if (patrolPath != null) 137 | { 138 | if (IsAtWayPoint()) 139 | { 140 | if (dwellTime < m_TimeSinceLastWaypoint) 141 | { 142 | m_TimeSinceLastWaypoint = 0; 143 | CycleWaypoint(); 144 | } 145 | } 146 | 147 | nextPosition = GetCurrentWaypoint().position; 148 | } 149 | 150 | m_Mover.StartMovement(nextPosition, patrolSpeedFraction); 151 | } 152 | 153 | private bool IsAtWayPoint() 154 | { 155 | return IsTargetInRange(transform, GetCurrentWaypoint(), wayPointTolerance); 156 | } 157 | 158 | private void CycleWaypoint() 159 | { 160 | m_CurrentWaypointIndex = patrolPath.GetNextIndex(m_CurrentWaypointIndex); 161 | } 162 | 163 | private Transform GetCurrentWaypoint() 164 | { 165 | return patrolPath.GetWayPoint(m_CurrentWaypointIndex); 166 | } 167 | 168 | private void Aggrevate() 169 | { 170 | m_TimeSinceAggrevated = 0; 171 | } 172 | } 173 | } -------------------------------------------------------------------------------- /Control/AIController.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 54aa845c86f604c4eb29f026ceddc07f 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Control/PatrolPath.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace RPG.Control 4 | { 5 | /// 6 | /// Used for calculating patrol path of NPCs and visualizing it with gizmos in the editor. 7 | /// 8 | public class PatrolPath : MonoBehaviour 9 | { 10 | private const float waypointRadius = 0.3f; 11 | 12 | #if UNITY_EDITOR 13 | private void OnDrawGizmos() 14 | { 15 | Gizmos.color = Color.white; 16 | GameObject active = UnityEditor.Selection.activeGameObject; 17 | if (active != null) 18 | { 19 | var AI = active.GetComponent(); 20 | if (AI != null && AI.PatrolPath == this) 21 | { 22 | Gizmos.color = Color.yellow; 23 | } 24 | 25 | if (active == this.gameObject) 26 | { 27 | Gizmos.color = new Color(0.4f, 1, 0.5f); 28 | } 29 | } 30 | 31 | for (int i = 0; i < transform.childCount; i++) 32 | { 33 | Gizmos.DrawSphere(GetWayPoint(i).position, waypointRadius); 34 | Gizmos.DrawLine(GetWayPoint(i).position, GetWayPoint(i + 1).position); 35 | } 36 | } 37 | #endif 38 | 39 | public int GetNextIndex(int i) 40 | { 41 | return (i + 1) % transform.childCount; 42 | } 43 | 44 | public Transform GetWayPoint(int i) 45 | { 46 | if (i >= transform.childCount) 47 | { 48 | i = 0; 49 | } 50 | 51 | return transform.GetChild(i); 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /Control/PatrolPath.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 977bcaaf197a9e74cb193f3b1c494a6f 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Control/PlayerController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using RPG.Attributes; 3 | using RPG.Core; 4 | using RPG.Movement; 5 | using UnityEngine; 6 | using UnityEngine.AI; 7 | using UnityEngine.EventSystems; 8 | 9 | namespace RPG.Control 10 | { 11 | public class PlayerController : MonoBehaviour 12 | { 13 | [System.Serializable] 14 | public struct CursorMapping 15 | { 16 | public CursorType type; 17 | public Texture2D texture; 18 | public Vector2 hotspot; 19 | } 20 | 21 | [SerializeField] private CursorMapping[] mappings; 22 | [SerializeField] private float navMeshMaxProjectionDistance = 1f; 23 | [SerializeField] private float raycastRadius = 1f; 24 | 25 | private Mover m_Mover; 26 | private Health m_PlayerHealth; 27 | private Camera m_MainCamera; 28 | 29 | private void Awake() 30 | { 31 | m_Mover = GetComponent(); 32 | m_PlayerHealth = GetComponent(); 33 | m_MainCamera = Camera.main; 34 | } 35 | 36 | private void Update() 37 | { 38 | if (Input.GetKey("escape")) 39 | { 40 | Application.Quit(); 41 | } 42 | 43 | if (InteractWithUI()) 44 | { 45 | return; 46 | } 47 | 48 | if (m_PlayerHealth.IsDead) 49 | { 50 | SetCursor(CursorType.None); 51 | return; 52 | } 53 | 54 | if (InteractWithComponent()) 55 | { 56 | return; 57 | } 58 | 59 | if (InteractWithMovement()) 60 | { 61 | return; 62 | } 63 | 64 | SetCursor(CursorType.None); 65 | } 66 | 67 | private bool InteractWithUI() 68 | { 69 | if (!EventSystem.current.IsPointerOverGameObject()) return false; 70 | SetCursor(CursorType.UI); 71 | return true; 72 | } 73 | 74 | private bool InteractWithComponent() 75 | { 76 | RaycastHit[] hits = RaycastAllSorted(); 77 | foreach (RaycastHit hit in hits) 78 | { 79 | IRaycastable[] raycastables = hit.transform.GetComponents(); 80 | foreach (IRaycastable raycastable in raycastables) 81 | { 82 | if (!raycastable.HandleRaycast(gameObject)) continue; 83 | SetCursor(raycastable.Cursor); 84 | return true; 85 | } 86 | } 87 | 88 | return false; 89 | } 90 | 91 | private RaycastHit[] RaycastAllSorted() 92 | { 93 | RaycastHit[] hits = Physics.SphereCastAll(GetMouseRay(), raycastRadius); 94 | var distances = new float[hits.Length]; 95 | for (int i = 0; i < distances.Length; i++) 96 | { 97 | distances[i] = hits[i].distance; 98 | } 99 | 100 | Array.Sort(distances, hits); 101 | return hits; 102 | } 103 | 104 | private bool InteractWithMovement() 105 | { 106 | bool hasHit = RaycastNavMesh(out Vector3 target); 107 | 108 | if (!hasHit) return false; 109 | if (!m_Mover.CanMoveTo(target)) return false; 110 | 111 | if (Input.GetMouseButton(0)) 112 | { 113 | m_Mover.StartMovement(target, 1f); 114 | } 115 | 116 | SetCursor(CursorType.Movement); 117 | return true; 118 | } 119 | 120 | private bool RaycastNavMesh(out Vector3 target) 121 | { 122 | target = new Vector3(); 123 | bool hasHit = Physics.Raycast(GetMouseRay(), out RaycastHit hit); 124 | if (!hasHit) 125 | { 126 | return false; 127 | } 128 | 129 | bool hasCastToNavMesh = NavMesh.SamplePosition(hit.point, out NavMeshHit navMeshHit, 130 | navMeshMaxProjectionDistance, 131 | NavMesh.AllAreas); 132 | if (!hasCastToNavMesh) 133 | { 134 | return false; 135 | } 136 | 137 | target = navMeshHit.position; 138 | 139 | return true; 140 | } 141 | 142 | private CursorMapping GetCursorMapping(CursorType type) 143 | { 144 | for (int i = 0; i < mappings.Length; i++) 145 | { 146 | if (mappings[i].type == type) 147 | { 148 | return mappings[i]; 149 | } 150 | } 151 | 152 | return mappings[0]; 153 | } 154 | 155 | private Ray GetMouseRay() 156 | { 157 | return m_MainCamera.ScreenPointToRay(Input.mousePosition); 158 | } 159 | 160 | public void SetCursor(CursorType type) 161 | { 162 | CursorMapping mapping = GetCursorMapping(type); 163 | Cursor.SetCursor(mapping.texture, mapping.hotspot, CursorMode.Auto); 164 | } 165 | } 166 | } -------------------------------------------------------------------------------- /Control/PlayerController.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 61fb32b17daf9e14cad9cc8f724afc54 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Conversing.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 17ed78efb2a175f41bdc6d90e36b5a10 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Conversing/Dialogue.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace RPG.Conversing 4 | { 5 | [System.Serializable] 6 | public struct Dialogue 7 | { 8 | [SerializeField] private string name; 9 | [TextArea(3, 10)] [SerializeField] private string[] dialogueLines; 10 | 11 | public Dialogue(string n, string[] d) 12 | { 13 | name = n; 14 | dialogueLines = d; 15 | } 16 | 17 | public string[] DialogueLines => dialogueLines; 18 | public string Name => name; 19 | } 20 | } -------------------------------------------------------------------------------- /Conversing/Dialogue.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d5b7f0356c373b3468d7271deb119cd2 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Conversing/DialogueManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using TMPro; 5 | using UnityEngine; 6 | 7 | namespace RPG.Conversing 8 | { 9 | public class DialogueManager : MonoBehaviour 10 | { 11 | [SerializeField] private TextMeshProUGUI dialogueText; 12 | [SerializeField] private TextMeshProUGUI nameText; 13 | [SerializeField] private Animator animator; 14 | private readonly Queue m_DialogLines = new Queue(); 15 | private Coroutine m_CurrentDialogue; 16 | private const string IS_OPEN_TRIGGER = "IsOpen"; 17 | 18 | public event Action onDialogueClose; 19 | 20 | private IEnumerator TypeSentance(IReadOnlyList dialogLine) 21 | { 22 | dialogueText.text = ""; 23 | for (int i = 0; i < dialogLine.Count; i++) 24 | { 25 | dialogueText.text += dialogLine[i]; 26 | yield return new WaitForSecondsRealtime(0.01f); 27 | } 28 | } 29 | 30 | public void StartDialogue(Dialogue dialogue) 31 | { 32 | Debug.Log($"Starting a conversation with {dialogue.Name}."); 33 | 34 | m_DialogLines.Clear(); 35 | 36 | for (int i = 0; i < dialogue.DialogueLines.Length; i++) 37 | { 38 | m_DialogLines.Enqueue(dialogue.DialogueLines[i]); 39 | } 40 | 41 | nameText.text = dialogue.Name; 42 | animator.SetBool(IS_OPEN_TRIGGER, true); 43 | ContinueDialogue(); 44 | } 45 | 46 | public void ContinueDialogue() 47 | { 48 | if (m_DialogLines.Count == 0) 49 | { 50 | CloseDialogue(); 51 | return; 52 | } 53 | 54 | if (m_CurrentDialogue != null) 55 | { 56 | StopCoroutine(m_CurrentDialogue); 57 | } 58 | 59 | m_CurrentDialogue = StartCoroutine(TypeSentance(m_DialogLines.Dequeue().ToCharArray())); 60 | } 61 | 62 | // TODO Implement close dialogue on moving away from NPC 63 | private void CloseDialogue() 64 | { 65 | Debug.Log("End of conversation with."); 66 | animator.SetBool(IS_OPEN_TRIGGER, false); 67 | m_DialogLines.Clear(); 68 | onDialogueClose?.Invoke(); 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /Conversing/DialogueManager.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b3f4f046a93336941add203a66d8a87f 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Core.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 307f1e605802fc749997b8d1a2705c8b 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Core/ActionScheduler.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace RPG.Core 4 | { 5 | public class ActionScheduler : MonoBehaviour 6 | { 7 | private IAction m_CurrentAction; 8 | 9 | public void StartAction(IAction action) 10 | { 11 | if (m_CurrentAction == action) return; 12 | 13 | m_CurrentAction?.Cancel(); 14 | m_CurrentAction = action; 15 | } 16 | 17 | public void CancelCurrentAction() 18 | { 19 | StartAction(null); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /Core/ActionScheduler.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9ba54ae50bf7277458fcdd98c7ec5da6 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Core/CharacterBehaviour.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace RPG.Core 4 | { 5 | public class CharacterBehaviour : MonoBehaviour 6 | { 7 | [SerializeField] private float rotationSpeed = 5f; 8 | 9 | public void LookAtTarget(Transform targetTransform) 10 | { 11 | Quaternion targetRotation = Quaternion.LookRotation(targetTransform.position - transform.position); 12 | transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, rotationSpeed * Time.deltaTime); 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /Core/CharacterBehaviour.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: bdebba2e049c0314ea5194381a84584c 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Core/CursorType.cs: -------------------------------------------------------------------------------- 1 | namespace RPG.Core 2 | { 3 | public enum CursorType 4 | { 5 | None, 6 | Movement, 7 | Combat, 8 | UI, 9 | Pickup, 10 | Quest, 11 | Converse, 12 | NPC 13 | } 14 | } -------------------------------------------------------------------------------- /Core/CursorType.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: cffd7d15ffc05a643a3eefba8eb9a9db 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Core/IAction.cs: -------------------------------------------------------------------------------- 1 | namespace RPG.Core 2 | { 3 | public interface IAction 4 | { 5 | void Cancel(); 6 | } 7 | } -------------------------------------------------------------------------------- /Core/IAction.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 18cbc199c427c3a45a9168b0c93e6d83 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Core/IRaycastable.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace RPG.Core 4 | { 5 | public interface IRaycastable 6 | { 7 | bool HandleRaycast(GameObject callingObject); 8 | 9 | CursorType Cursor { get; } 10 | } 11 | } -------------------------------------------------------------------------------- /Core/IRaycastable.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a23d07bf19a6cde4a9699d802d4586b1 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Core/PersistentObjectSpawner.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace RPG.Core 4 | { 5 | public class PersistentObjectSpawner : MonoBehaviour 6 | { 7 | [SerializeField] private GameObject persistentObjectsPrefab = default; 8 | 9 | private static bool _hasSpawned; 10 | 11 | private void Awake() 12 | { 13 | if (_hasSpawned) 14 | { 15 | return; 16 | } 17 | 18 | SpawnPersistentObjects(); 19 | 20 | _hasSpawned = true; 21 | } 22 | 23 | private void SpawnPersistentObjects() 24 | { 25 | GameObject persistentObjects = Instantiate(persistentObjectsPrefab); 26 | DontDestroyOnLoad(persistentObjects); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /Core/PersistentObjectSpawner.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1d0b53048aebca94e97d37cc5ef6f7f4 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Enhancements.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 93f6639adf8455949b59148899a7b586 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Enhancements/SerializeReferenceInspectorUI.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f549199d6eac96943ae073e003f12aa3 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Enhancements/SerializeReferenceInspectorUI/Core.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1388bc43afa1f4e41ba1032dcee62b04 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Enhancements/SerializeReferenceInspectorUI/Core/SerializeReferenceGenericSelectionMenu.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using UnityEditor; 7 | using UnityEngine; 8 | 9 | // possibly try to Migrate from local functions to normal private functions 10 | public static class SerializeReferenceGenericSelectionMenu 11 | { 12 | /// Purpose. 13 | /// This is generic selection menu. 14 | /// Filtering. 15 | /// You can add substring filter here to filter by search string. 16 | /// As well ass type or interface restrictions. 17 | /// As well as any custom restriction that is based on input type. 18 | /// And it will be performed on each Appropriate type found by TypeCache. 19 | public static void ShowContextMenuForManagedReference(this SerializedProperty property, 20 | IEnumerable> filters = null) 21 | { 22 | var context = new GenericMenu(); 23 | FillContextMenu(); 24 | context.ShowAsContext(); 25 | 26 | void FillContextMenu() 27 | { 28 | context.AddItem(new GUIContent("Null"), false, MakeNull); 29 | Type realPropertyType = 30 | SerializeReferenceTypeNameUtility.GetRealTypeFromTypename(property.managedReferenceFieldTypename); 31 | if (realPropertyType == null) 32 | { 33 | Debug.LogError("Can not get type from"); 34 | return; 35 | } 36 | 37 | TypeCache.TypeCollection types = TypeCache.GetTypesDerivedFrom(realPropertyType); 38 | foreach (Type type in types) 39 | { 40 | // Skips unity engine Objects (because they are not serialized by SerializeReference) 41 | if (type.IsSubclassOf(typeof(UnityEngine.Object))) 42 | continue; 43 | // Skip abstract classes because they should not be instantiated 44 | if (type.IsAbstract) 45 | continue; 46 | if (FilterTypeByFilters(filters, type) == false) 47 | continue; 48 | 49 | AddContextMenu(type, context); 50 | } 51 | 52 | void MakeNull() 53 | { 54 | property.serializedObject.Update(); 55 | property.managedReferenceValue = null; 56 | property.serializedObject.ApplyModifiedPropertiesWithoutUndo(); // undo is bugged for now 57 | } 58 | 59 | void AddContextMenu(Type type, GenericMenu genericMenuContext) 60 | { 61 | string assemblyName = type.Assembly.ToString().Split('(', ',')[0]; 62 | string entryName = type + " ( " + assemblyName + " )"; 63 | genericMenuContext.AddItem(new GUIContent(entryName), false, AssignNewInstanceOfType, type); 64 | } 65 | 66 | void AssignNewInstanceOfType(object typeAsObject) 67 | { 68 | var type = (Type) typeAsObject; 69 | object instance = Activator.CreateInstance(type); 70 | property.serializedObject.Update(); 71 | property.managedReferenceValue = instance; 72 | property.serializedObject.ApplyModifiedPropertiesWithoutUndo(); // undo is bugged for now 73 | } 74 | 75 | bool FilterTypeByFilters(IEnumerable> filters_, Type type) => 76 | filters_.All(f => f.Invoke(type)); 77 | } 78 | } 79 | } 80 | 81 | #endif -------------------------------------------------------------------------------- /Enhancements/SerializeReferenceInspectorUI/Core/SerializeReferenceGenericSelectionMenu.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d7796948b498be241ba10c88bcf3d5fe 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Enhancements/SerializeReferenceInspectorUI/Core/SerializeReferenceTypeNameUtility.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | /// This utility exists, because serialize reference managed reference typename returns combined string 4 | /// and not data class that contains separate strings for assembly name and for class name (and possibly namespace name) 5 | public static class SerializeReferenceTypeNameUtility 6 | { 7 | public static Type GetRealTypeFromTypename(string stringType) 8 | { 9 | (string assemblyName, string className) = GetSplitNamesFromTypename(stringType); 10 | var realType = Type.GetType($"{className}, {assemblyName}"); 11 | return realType; 12 | } 13 | 14 | public static (string AssemblyName, string ClassName) GetSplitNamesFromTypename(string typename) 15 | { 16 | if (string.IsNullOrEmpty(typename)) 17 | return ("", ""); 18 | 19 | string[] typeSplitString = typename.Split(char.Parse(" ")); 20 | string typeClassName = typeSplitString[1]; 21 | string typeAssemblyName = typeSplitString[0]; 22 | return (typeAssemblyName, typeClassName); 23 | } 24 | } -------------------------------------------------------------------------------- /Enhancements/SerializeReferenceInspectorUI/Core/SerializeReferenceTypeNameUtility.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 07d7d3b6c96d5a04093953c060518522 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Enhancements/SerializeReferenceInspectorUI/Drawing.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4a5d0da55302d3d418d5afe408a9a916 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Enhancements/SerializeReferenceInspectorUI/Drawing/SerializeReferenceButton.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 827f3a2847b39a94eb6fcbdb0d73d6ca 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Enhancements/SerializeReferenceInspectorUI/Drawing/SerializeReferenceButton/SerializeReferenceButtonAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | [AttributeUsage(AttributeTargets.Field)] 5 | public class SerializeReferenceButtonAttribute : PropertyAttribute { } -------------------------------------------------------------------------------- /Enhancements/SerializeReferenceInspectorUI/Drawing/SerializeReferenceButton/SerializeReferenceButtonAttribute.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c64e0f68b41b48939c9b98cc29a19b20 3 | timeCreated: 1579584059 -------------------------------------------------------------------------------- /Enhancements/SerializeReferenceInspectorUI/Drawing/SerializeReferenceButton/SerializeReferenceButtonAttributeDrawer.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using UnityEditor; 6 | using UnityEngine; 7 | 8 | [CustomPropertyDrawer(typeof(SerializeReferenceButtonAttribute))] 9 | public class SerializeReferenceButtonAttributeDrawer : PropertyDrawer 10 | { 11 | public override float GetPropertyHeight(SerializedProperty property, GUIContent label) 12 | { 13 | return EditorGUI.GetPropertyHeight(property, true); 14 | } 15 | 16 | public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) 17 | { 18 | EditorGUI.BeginProperty(position, label, property); 19 | 20 | var labelPosition = new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight); 21 | EditorGUI.LabelField(labelPosition, label); 22 | 23 | IEnumerable> typeRestrictions = 24 | SerializedReferenceUIBuiltInTypeRestrictions.GetAllBuiltInTypeRestrictions(fieldInfo); 25 | property.DrawSelectionButtonForManagedReference(position, typeRestrictions); 26 | 27 | EditorGUI.PropertyField(position, property, GUIContent.none, true); 28 | 29 | EditorGUI.EndProperty(); 30 | } 31 | } 32 | #endif -------------------------------------------------------------------------------- /Enhancements/SerializeReferenceInspectorUI/Drawing/SerializeReferenceButton/SerializeReferenceButtonAttributeDrawer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5a07638044724309b6088c28a11a15af 3 | timeCreated: 1579584059 -------------------------------------------------------------------------------- /Enhancements/SerializeReferenceInspectorUI/Drawing/SerializeReferenceInspectorButton.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using UnityEditor; 4 | using UnityEngine; 5 | 6 | public static class SerializeReferenceInspectorButton 7 | { 8 | /// Must be drawn before DefaultProperty in order to receive input 9 | public static void DrawSelectionButtonForManagedReference(this SerializedProperty property, 10 | Rect position, IEnumerable> filters = null) 11 | { 12 | //var backgroundColor = new Color(0f, 0.7f, 0.7f, 1f); 13 | var backgroundColor = new Color(0.1f, 0.45f, 0.8f, 1f); 14 | //var backgroundColor = GUI.backgroundColor; 15 | 16 | Rect buttonPosition = position; 17 | buttonPosition.x += EditorGUIUtility.labelWidth + 1 * EditorGUIUtility.standardVerticalSpacing; 18 | buttonPosition.width = 19 | position.width - EditorGUIUtility.labelWidth - 1 * EditorGUIUtility.standardVerticalSpacing; 20 | buttonPosition.height = EditorGUIUtility.singleLineHeight; 21 | 22 | int storedIndent = EditorGUI.indentLevel; 23 | EditorGUI.indentLevel = 0; 24 | Color storedColor = GUI.backgroundColor; 25 | GUI.backgroundColor = backgroundColor; 26 | 27 | 28 | (string AssemblyName, string ClassName) names = 29 | SerializeReferenceTypeNameUtility.GetSplitNamesFromTypename(property.managedReferenceFullTypename); 30 | string className = string.IsNullOrEmpty(names.ClassName) ? "Null (Assign)" : names.ClassName; 31 | string assemblyName = names.AssemblyName; 32 | if (GUI.Button(buttonPosition, new GUIContent(className, className + " ( " + assemblyName + " )"))) 33 | property.ShowContextMenuForManagedReference(filters); 34 | 35 | GUI.backgroundColor = storedColor; 36 | EditorGUI.indentLevel = storedIndent; 37 | } 38 | } -------------------------------------------------------------------------------- /Enhancements/SerializeReferenceInspectorUI/Drawing/SerializeReferenceInspectorButton.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 50b013bc3b4409b438f8781dee4dbbf1 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Enhancements/SerializeReferenceInspectorUI/Drawing/SerializeReferenceInspectorMiddleMouseMenu.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using UnityEditor; 6 | using UnityEngine; 7 | 8 | public static class SerializeReferenceInspectorMiddleMouseMenu 9 | { 10 | public static void ShowContextMenuForManagedReferenceOnMouseMiddleButton(this SerializedProperty property, 11 | Rect position, IEnumerable> filters = null) 12 | { 13 | Event e = Event.current; 14 | if (e.type != EventType.MouseDown || !position.Contains(e.mousePosition) || e.button != 2) 15 | return; 16 | 17 | property.ShowContextMenuForManagedReference(filters); 18 | } 19 | } 20 | 21 | #endif -------------------------------------------------------------------------------- /Enhancements/SerializeReferenceInspectorUI/Drawing/SerializeReferenceInspectorMiddleMouseMenu.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ba51bcbc90077924abfcf504152b4a51 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Enhancements/SerializeReferenceInspectorUI/Drawing/SerializeReferenceMenu.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ff8136ca43f822642adfa6388b137133 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Enhancements/SerializeReferenceInspectorUI/Drawing/SerializeReferenceMenu/SerializeReferenceMenuAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | [AttributeUsage(AttributeTargets.Field)] 5 | public class SerializeReferenceMenuAttribute : PropertyAttribute { } -------------------------------------------------------------------------------- /Enhancements/SerializeReferenceInspectorUI/Drawing/SerializeReferenceMenu/SerializeReferenceMenuAttribute.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4f1c372fa681cf146973df30a0d969fd 3 | timeCreated: 1579584059 -------------------------------------------------------------------------------- /Enhancements/SerializeReferenceInspectorUI/Drawing/SerializeReferenceMenu/SerializeReferenceMenuAttributeDrawer.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using UnityEditor; 6 | using UnityEngine; 7 | 8 | [CustomPropertyDrawer(typeof(SerializeReferenceMenuAttribute))] 9 | public class SerializeReferenceMenuAttributeDrawer : PropertyDrawer 10 | { 11 | public override float GetPropertyHeight(SerializedProperty property, GUIContent label) 12 | { 13 | return EditorGUI.GetPropertyHeight(property, true); 14 | } 15 | 16 | public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) 17 | { 18 | EditorGUI.BeginProperty(position, label, property); 19 | 20 | IEnumerable> typeRestrictions = 21 | SerializedReferenceUIBuiltInTypeRestrictions.GetAllBuiltInTypeRestrictions(fieldInfo); 22 | property.ShowContextMenuForManagedReferenceOnMouseMiddleButton(position, typeRestrictions); 23 | 24 | EditorGUI.PropertyField(position, property, true); 25 | EditorGUI.EndProperty(); 26 | } 27 | } 28 | #endif -------------------------------------------------------------------------------- /Enhancements/SerializeReferenceInspectorUI/Drawing/SerializeReferenceMenu/SerializeReferenceMenuAttributeDrawer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: eaa31b1c39ee9424abe12fa0034d9d1b 3 | timeCreated: 1579584059 -------------------------------------------------------------------------------- /Enhancements/SerializeReferenceInspectorUI/README.md: -------------------------------------------------------------------------------- 1 | Created by Unity forums user TextusGames 2 | https://forum.unity.com/threads/serializereference-genericserializedreferenceinspectorui.813366/#post-5597977 -------------------------------------------------------------------------------- /Enhancements/SerializeReferenceInspectorUI/README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6d097ddca178ba14eaebba48ec73be33 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Enhancements/SerializeReferenceInspectorUI/Restriction.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c962b02723999754ba9b137d407b5ec0 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Enhancements/SerializeReferenceInspectorUI/Restriction/SerializeReferenceTypeRestrictionFilters.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | public static class SerializeReferenceTypeRestrictionFilters 5 | { 6 | public static Func TypeIsNotSubclassOrEqualOrHasInterface(Type[] types) => type => 7 | TypeIsSubclassOrEqualOrHasInterface(types).Invoke(type) == false; 8 | 9 | // public static Func TypeIsSubclassOrEqualOrHasInterface(Type[] types) => type => type.IsInterface 10 | // ? types.Any(e => TypeHasInterface(type, e)) 11 | // : types.Any(e => TypeIsSubclassOrEqual(type, e)); 12 | 13 | public static Func TypeIsSubclassOrEqualOrHasInterface(Type[] types) => type => 14 | types.Any(e => e.IsInterface ? TypeHasInterface(type, e) : TypeIsSubclassOrEqual(type, e)); 15 | 16 | public static bool TypeIsSubclassOrEqual(Type type, Type comparator) => 17 | type.IsSubclassOf(comparator) || type == comparator; 18 | 19 | public static bool TypeHasInterface(this Type type, Type comparator) => 20 | type.GetInterface(comparator.ToString()) != null; 21 | } -------------------------------------------------------------------------------- /Enhancements/SerializeReferenceInspectorUI/Restriction/SerializeReferenceTypeRestrictionFilters.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b71802ffed0a18d43901672fcbf668c9 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Enhancements/SerializeReferenceInspectorUI/Restriction/SerializeReferenceUIRestrictionExcludeTypes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | /// None of this types are valid. 5 | [AttributeUsage(AttributeTargets.Field)] 6 | public class SerializeReferenceUIRestrictionExcludeTypes : PropertyAttribute 7 | { 8 | public readonly Type[] Types; 9 | public SerializeReferenceUIRestrictionExcludeTypes(params Type[] types) => Types = types; 10 | } -------------------------------------------------------------------------------- /Enhancements/SerializeReferenceInspectorUI/Restriction/SerializeReferenceUIRestrictionExcludeTypes.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ff5f6060287437048acba59e670cd000 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Enhancements/SerializeReferenceInspectorUI/Restriction/SerializeReferenceUIRestrictionIncludeTypes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | /// Any of this types are valid. And only this types can be presented. 5 | [AttributeUsage(AttributeTargets.Field)] 6 | public class SerializeReferenceUIRestrictionIncludeTypes : PropertyAttribute 7 | { 8 | public readonly Type[] Types; 9 | public SerializeReferenceUIRestrictionIncludeTypes(params Type[] types) => Types = types; 10 | } -------------------------------------------------------------------------------- /Enhancements/SerializeReferenceInspectorUI/Restriction/SerializeReferenceUIRestrictionIncludeTypes.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6d10fb8315984ef458e4129191b17931 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Enhancements/SerializeReferenceInspectorUI/Restriction/SerializedReferenceUIBuiltInTypeRestrictions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | 5 | public static class SerializedReferenceUIBuiltInTypeRestrictions 6 | { 7 | public static IEnumerable> GetAllBuiltInTypeRestrictions(FieldInfo fieldInfo) 8 | { 9 | var result = new List>(); 10 | 11 | object[] attributeObjects = fieldInfo.GetCustomAttributes(false); 12 | foreach (object attributeObject in attributeObjects) 13 | { 14 | switch (attributeObject) 15 | { 16 | case SerializeReferenceUIRestrictionIncludeTypes includeTypes: 17 | result.Add( 18 | SerializeReferenceTypeRestrictionFilters 19 | .TypeIsSubclassOrEqualOrHasInterface(includeTypes.Types)); 20 | continue; 21 | case SerializeReferenceUIRestrictionExcludeTypes excludeTypes: 22 | result.Add( 23 | SerializeReferenceTypeRestrictionFilters.TypeIsNotSubclassOrEqualOrHasInterface(excludeTypes 24 | .Types)); 25 | continue; 26 | } 27 | } 28 | 29 | return result; 30 | } 31 | } -------------------------------------------------------------------------------- /Enhancements/SerializeReferenceInspectorUI/Restriction/SerializedReferenceUIBuiltInTypeRestrictions.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 95f44dc0cd315cf4988029eb8618aa02 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Enhancements/SerializeReferenceInspectorUI/SerializeReferenceUI.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SerializeReferenceUI" 3 | } 4 | -------------------------------------------------------------------------------- /Enhancements/SerializeReferenceInspectorUI/SerializeReferenceUI.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 73106583b323919458c1e05166706ce3 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Events.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: dc08324e14253424380f8851c9faa323 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Events/Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 307f6a101b276c3498e34755015220d9 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Events/Editor/EventEditor.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | using UnityEngine; 3 | 4 | namespace RPG.Events { 5 | [CustomEditor(typeof(GameEvent))] 6 | public class EventEditor : Editor { 7 | public override void OnInspectorGUI() { 8 | base.OnInspectorGUI(); 9 | 10 | GUI.enabled = Application.isPlaying; 11 | 12 | GameEvent e = target as GameEvent; 13 | if (GUILayout.Button("Raise")) { 14 | e.Raise("Raise"); 15 | } 16 | 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Events/Editor/EventEditor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d795aa8074393a74ca31b1b05828c15b 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Events/GameEvent.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | 4 | namespace RPG.Events 5 | { 6 | [CreateAssetMenu(fileName = "GameEvent", menuName = "Events/New Game Event", order = 0)] 7 | public class GameEvent : ScriptableObject 8 | { 9 | /// 10 | /// The list of listeners that this event will notify if it is raised. 11 | /// 12 | private readonly List m_EventListeners = new List(); 13 | 14 | public void Raise(string context) 15 | { 16 | for (int i = m_EventListeners.Count - 1; i >= 0; i--) 17 | { 18 | m_EventListeners[i].OnEventRaised(context); 19 | } 20 | } 21 | 22 | public void RegisterListener(GameEventListener listener) 23 | { 24 | if (!m_EventListeners.Contains(listener)) 25 | { 26 | m_EventListeners.Add(listener); 27 | } 28 | } 29 | 30 | public void UnregisterListener(GameEventListener listener) 31 | { 32 | if (m_EventListeners.Contains(listener)) 33 | { 34 | m_EventListeners.Remove(listener); 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /Events/GameEvent.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2b4f88121523fe04cb0452400f0d9cfc 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Events/GameEventListener.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEngine.Events; 3 | 4 | namespace RPG.Events 5 | { 6 | public class GameEventListener : MonoBehaviour 7 | { 8 | [Tooltip("Event to register with.")] public GameEvent Event; 9 | 10 | [Tooltip("Response to invoke when Event is raised.")] 11 | public StringEvent Response; 12 | 13 | [System.Serializable] 14 | public class StringEvent : UnityEvent { } 15 | 16 | private void OnEnable() 17 | { 18 | Event.RegisterListener(this); 19 | } 20 | 21 | private void OnDisable() 22 | { 23 | Event.UnregisterListener(this); 24 | } 25 | 26 | public void OnEventRaised(string context) 27 | { 28 | Response.Invoke(context); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /Events/GameEventListener.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6cda09a19d683a741b5bdc4a290035c0 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b625b9c246563d2499979db82de3a92b 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Movement.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ebad6d82e7e117149b86bd55fd1c0549 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Movement/Mover.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using RPG.Attributes; 3 | using RPG.Core; 4 | using RPG.Saving; 5 | using UnityEngine; 6 | using UnityEngine.AI; 7 | 8 | namespace RPG.Movement 9 | { 10 | public class Mover : MonoBehaviour, IAction, ISaveable 11 | { 12 | [SerializeField] private float maxSpeed = 6f; 13 | [SerializeField] private float maxNavPathLength = 40f; 14 | [SerializeField] private float interactStoppingDistance = 3f; 15 | private float m_OriginalStoppingDistance; 16 | private Action m_CallbackOnReachingDestination; 17 | private NavMeshAgent m_NavMeshAgent; 18 | private Animator m_Animator; 19 | private ActionScheduler m_ActionScheduler; 20 | private Health m_Health; 21 | private static readonly int s_ForwardSpeed = Animator.StringToHash("forwardSpeed"); 22 | 23 | private void Awake() 24 | { 25 | m_NavMeshAgent = GetComponent(); 26 | m_Animator = GetComponent(); 27 | m_ActionScheduler = GetComponent(); 28 | m_Health = GetComponent(); 29 | 30 | m_OriginalStoppingDistance = m_NavMeshAgent.stoppingDistance; 31 | } 32 | 33 | private void Update() 34 | { 35 | m_NavMeshAgent.enabled = !m_Health.IsDead; 36 | UpdateAnimator(); 37 | 38 | if (m_CallbackOnReachingDestination == null || m_NavMeshAgent.pathPending) return; 39 | m_NavMeshAgent.stoppingDistance = interactStoppingDistance; 40 | if (!(m_NavMeshAgent.remainingDistance <= m_NavMeshAgent.stoppingDistance)) return; 41 | if (m_NavMeshAgent.hasPath && !Mathf.Approximately(m_NavMeshAgent.velocity.sqrMagnitude, 0)) return; 42 | Cancel(); 43 | m_CallbackOnReachingDestination(); 44 | m_CallbackOnReachingDestination = null; 45 | m_NavMeshAgent.stoppingDistance = m_OriginalStoppingDistance; 46 | } 47 | 48 | private float GetPathLength(NavMeshPath path) 49 | { 50 | float total = 0; 51 | 52 | if (path.corners.Length < 2) 53 | { 54 | return 0; 55 | } 56 | 57 | for (int i = 0; i < path.corners.Length - 1; i++) 58 | { 59 | total += Vector3.Distance(path.corners[i], path.corners[i + 1]); 60 | } 61 | 62 | return total; 63 | } 64 | 65 | private void UpdateAnimator() 66 | { 67 | Vector3 velocity = m_NavMeshAgent.velocity; 68 | Vector3 localVelocity = transform.InverseTransformDirection(velocity); 69 | float speed = Math.Abs(localVelocity.z); 70 | m_Animator.SetFloat(s_ForwardSpeed, speed); 71 | } 72 | 73 | public bool CanMoveTo(Vector3 destination) 74 | { 75 | var path = new NavMeshPath(); 76 | bool hasPath = NavMesh.CalculatePath(transform.position, destination, NavMesh.AllAreas, path); 77 | if (!hasPath || path.status != NavMeshPathStatus.PathComplete || GetPathLength(path) > maxNavPathLength) 78 | { 79 | return false; 80 | } 81 | 82 | ; 83 | return true; 84 | } 85 | 86 | public void MoveTo(Vector3 destination, float speedFraction) 87 | { 88 | m_NavMeshAgent.destination = destination; 89 | m_NavMeshAgent.speed = maxSpeed * Mathf.Clamp01(speedFraction); 90 | m_NavMeshAgent.isStopped = false; 91 | } 92 | 93 | public void InteractWithTarget(Action callback) 94 | { 95 | m_CallbackOnReachingDestination = callback; 96 | } 97 | 98 | public Mover StartMovement(Vector3 destination, float speedFraction) 99 | { 100 | m_ActionScheduler.StartAction(this); 101 | MoveTo(destination, speedFraction); 102 | 103 | return this; 104 | } 105 | 106 | public void Cancel() 107 | { 108 | m_NavMeshAgent.isStopped = true; 109 | } 110 | 111 | public object CaptureState() 112 | { 113 | return new SerializableVector3(transform.position); 114 | } 115 | 116 | public void RestoreState(object state) 117 | { 118 | var position = (SerializableVector3) state; 119 | m_NavMeshAgent.enabled = false; 120 | transform.position = position.ToVector(); 121 | m_NavMeshAgent.enabled = true; 122 | m_ActionScheduler.CancelCurrentAction(); 123 | } 124 | } 125 | } -------------------------------------------------------------------------------- /Movement/Mover.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f5bf7be6d2b00d942b0b01cb6e3ba0aa 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /NPC.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4b9ba8902f6ab26478edd200280b167b 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /NPC/DialogueInitiator.cs: -------------------------------------------------------------------------------- 1 | using RPG.Conversing; 2 | using RPG.Core; 3 | using UnityEngine; 4 | using UnityEngine.Events; 5 | 6 | namespace RPG.NPC 7 | { 8 | public class DialogueInitiator : NPCBase 9 | { 10 | [SerializeField] private Dialogue dialogue = default; 11 | [SerializeField] private DialogueManager dialogueManager = default; 12 | private bool m_IsInteracting; 13 | public DialogueInitiatedEvent onDialogueInitiated; 14 | 15 | [System.Serializable] 16 | public class DialogueInitiatedEvent : UnityEvent { } 17 | 18 | public override CursorType Cursor => CursorType.Converse; 19 | protected DialogueManager DialogueManager => dialogueManager; 20 | 21 | private void OnEnable() 22 | { 23 | dialogueManager.onDialogueClose += EndInteraction; 24 | } 25 | 26 | private void OnDisable() 27 | { 28 | dialogueManager.onDialogueClose -= EndInteraction; 29 | } 30 | 31 | private void EndInteraction() 32 | { 33 | m_IsInteracting = false; 34 | } 35 | 36 | protected override void Interact() 37 | { 38 | if (m_IsInteracting) return; 39 | onDialogueInitiated?.Invoke(gameObject.name); 40 | StartDialogue(dialogue); 41 | } 42 | 43 | protected void StartDialogue(Dialogue dialogue) 44 | { 45 | dialogueManager.StartDialogue(dialogue); 46 | m_IsInteracting = true; 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /NPC/DialogueInitiator.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 31a3b73cf4f00ae48b1a5914b50b15c8 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /NPC/NPCBase.cs: -------------------------------------------------------------------------------- 1 | using RPG.Core; 2 | using RPG.Movement; 3 | using UnityEngine; 4 | using static RPG.Util.Utility; 5 | 6 | namespace RPG.NPC 7 | { 8 | [DisallowMultipleComponent] 9 | public class NPCBase : MonoBehaviour, IRaycastable 10 | { 11 | public virtual CursorType Cursor => CursorType.NPC; 12 | protected virtual void Interact() { } 13 | 14 | public bool HandleRaycast(GameObject callingObject) 15 | { 16 | if (!Input.GetMouseButtonDown(0)) return true; 17 | if (IsTargetInRange(callingObject.transform, transform, 2.5f)) 18 | { 19 | // TODO make this work with CharacterBehaviour's smooth LookAt. Maybe use a Coroutine? 20 | transform.LookAt(callingObject.transform); 21 | callingObject.transform.LookAt(transform); 22 | Interact(); 23 | } 24 | else 25 | { 26 | callingObject.GetComponent().StartMovement(transform.position, 1f) 27 | .InteractWithTarget(Interact); 28 | } 29 | 30 | return true; 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /NPC/NPCBase.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ec3cf96e5798f06408a9b8ce951590a0 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Questing.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 81217cf59622c46439f7e001b7ff8620 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Questing/Goal.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ce458273816bd8a4eb5ce2af1f68b646 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Questing/Goal/ConversationGoal.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace RPG.Questing 4 | { 5 | [System.Serializable] 6 | public class ConversationGoal : Goal, IGoal 7 | { 8 | [Tooltip("string or substring to match against")] [SerializeField] 9 | private string target = default; 10 | 11 | public string Target => target; 12 | 13 | public bool Evaluate(string npc) 14 | { 15 | if (!npc.Contains(Target)) return false; 16 | Complete(); 17 | return true; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /Questing/Goal/ConversationGoal.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 371fa5f4bf942e24b88213336e12ab58 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Questing/Goal/Goals.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | namespace RPG.Questing 5 | { 6 | [Serializable] 7 | public abstract class Goal 8 | { 9 | [SerializeField] private string description; 10 | [SerializeField] private bool completed; 11 | [SerializeField] private int currentAmount; 12 | [SerializeField] private int requiredAmount = 1; 13 | 14 | public int CurrentAmount 15 | { 16 | get => currentAmount; 17 | set => currentAmount = value; 18 | } 19 | 20 | public bool Completed 21 | { 22 | get => completed; 23 | set => completed = value; 24 | } 25 | 26 | public int RequiredAmount => requiredAmount; 27 | public string Description => description; 28 | 29 | public event Action OnGoalCompleted; 30 | 31 | public bool Evaluate() 32 | { 33 | if (CurrentAmount >= RequiredAmount) 34 | { 35 | Complete(); 36 | } 37 | 38 | return Completed; 39 | } 40 | 41 | protected void Complete() 42 | { 43 | Completed = true; 44 | Debug.Log("Goal " + Description + " has been completed!"); 45 | if (OnGoalCompleted == null) return; 46 | OnGoalCompleted(this); 47 | Delegate[] delegates = OnGoalCompleted.GetInvocationList(); 48 | for (int i = 0; i < delegates.Length; i++) 49 | { 50 | OnGoalCompleted -= delegates[i] as Action; 51 | } 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /Questing/Goal/Goals.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3888fe8d3afd5f14c9536e01062a5e94 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Questing/Goal/IGoal.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RPG.Questing 4 | { 5 | public interface IGoal 6 | { 7 | string Description { get; } 8 | bool Completed { get; set; } 9 | int CurrentAmount { get; set; } 10 | event Action OnGoalCompleted; 11 | bool Evaluate(string context); 12 | } 13 | } -------------------------------------------------------------------------------- /Questing/Goal/IGoal.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1b84b5dbb02329b4687fe8f5b6627550 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Questing/Goal/KillGoalsS.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace RPG.Questing 4 | { 5 | [System.Serializable] 6 | public class KillGoalsS : Goal, IGoal 7 | { 8 | [Tooltip("string or substring to match against")] [SerializeField] 9 | private string enemy; 10 | 11 | public string Enemy => enemy; 12 | 13 | public bool Evaluate(string enemy) 14 | { 15 | if (!enemy.Contains(Enemy)) return false; 16 | CurrentAmount++; 17 | return base.Evaluate(); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /Questing/Goal/KillGoalsS.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: df40ee2dfe3bf82428d9f69d774b67c9 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Questing/Goal/PickupGoal.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace RPG.Questing 4 | { 5 | [System.Serializable] 6 | public class PickupGoalS : Goal, IGoal 7 | { 8 | [Tooltip("string or substring to match against")] [SerializeField] 9 | private string pickup; 10 | 11 | public string Pickup => pickup; 12 | 13 | public bool Evaluate(string item) 14 | { 15 | if (!item.Contains(Pickup)) return false; 16 | CurrentAmount++; 17 | return base.Evaluate(); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /Questing/Goal/PickupGoal.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d05b258976e4f034894d2649b9876a70 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Questing/Quest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using RPG.Combat; 4 | using RPG.Stats; 5 | using RPG.Util; 6 | using UnityEngine; 7 | 8 | namespace RPG.Questing 9 | { 10 | [CreateAssetMenu(fileName = "QuestS", menuName = "Quest/New Quest", order = 0)] 11 | public class Quest : ScriptableObject 12 | { 13 | [SerializeField] private string id; 14 | [SerializeField] private string questName; 15 | [SerializeField] private string description; 16 | [SerializeField] private int experienceReward; 17 | [SerializeField] private bool completed; 18 | [SerializeField] private bool assigned; 19 | [SerializeField] private bool active; 20 | [SerializeField] private WeaponConfig itemReward; 21 | [SerializeField] private List stages; 22 | 23 | private GameObject m_Player; 24 | private Experience m_Experience; 25 | 26 | public string ID => id; 27 | public string QuestName => questName; 28 | public string Description => description; 29 | public bool Completed => completed; 30 | public int ExperienceReward => experienceReward; 31 | public bool Assigned => assigned; 32 | public bool Active => active; 33 | public WeaponConfig ItemReward => itemReward; 34 | public List Stages => stages; 35 | 36 | public event Action OnQuestCompleted; 37 | 38 | private void GiveItemReward() 39 | { 40 | if (itemReward != null) 41 | { 42 | // TODO Item rewards after inventory system is implemented 43 | } 44 | } 45 | 46 | private void GiveExperienceReward() 47 | { 48 | if (m_Experience == null || experienceReward == 0) 49 | { 50 | return; 51 | } 52 | 53 | m_Experience.GainExperience(experienceReward); 54 | } 55 | 56 | private void CompleteQuest() 57 | { 58 | completed = true; 59 | GiveItemReward(); 60 | GiveExperienceReward(); 61 | if (OnQuestCompleted == null) return; 62 | OnQuestCompleted(); 63 | Delegate[] delegates = OnQuestCompleted.GetInvocationList(); 64 | for (int i = 0; i < delegates.Length; i++) 65 | { 66 | OnQuestCompleted -= delegates[i] as Action; 67 | } 68 | } 69 | 70 | public void Init() 71 | { 72 | m_Player = GameObject.FindWithTag("Player"); 73 | m_Experience = m_Player.GetComponent(); 74 | assigned = true; 75 | active = true; 76 | for (int i = 0; i < stages.Count; i++) 77 | { 78 | stages[i].Init(); 79 | stages[i].OnStageCompleted += Evaluate; 80 | } 81 | 82 | stages[0].Activate(); 83 | } 84 | 85 | private void Evaluate(StageS currentStage) 86 | { 87 | //check for slicing by current stage position 88 | var stagesCompleted = true; 89 | for (int stage = 0; stage < stages.Count; stage++) 90 | { 91 | if (stages[stage].Completed) continue; 92 | stagesCompleted = false; 93 | break; 94 | } 95 | 96 | if (stagesCompleted) 97 | { 98 | CompleteQuest(); 99 | return; 100 | } 101 | 102 | int currentStageIndex = stages.IndexOf(currentStage); 103 | if (currentStageIndex < stages.Count - 1) 104 | { 105 | stages[currentStageIndex + 1].Activate(); 106 | } 107 | } 108 | 109 | public void SetActiveStage(int stage, bool[] goalsCompleted, int[] goalsCurrentAmount) 110 | { 111 | for (int i = 0; i < Stages.Count; i++) 112 | { 113 | if (i == stage) 114 | { 115 | Stages[i].Activate(); 116 | } 117 | else if (i < stage) 118 | { 119 | Stages[i].Complete(); 120 | } 121 | } 122 | 123 | if (goalsCompleted.Length == 0 || goalsCurrentAmount.Length == 0) return; 124 | 125 | for (int i = 0; i < Stages[stage].Goals.Count; i++) 126 | { 127 | Stages[stage].Goals[i].Completed = goalsCompleted[i]; 128 | Stages[stage].Goals[i].CurrentAmount = goalsCurrentAmount[i]; 129 | } 130 | } 131 | } 132 | } -------------------------------------------------------------------------------- /Questing/Quest.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a241f516fa7557a409d718434ee5dcd0 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Questing/QuestGiver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using RPG.Conversing; 3 | using RPG.Core; 4 | using RPG.NPC; 5 | using RPG.Saving; 6 | using UnityEngine; 7 | 8 | namespace RPG.Questing 9 | { 10 | public class QuestGiver : DialogueInitiator, IRaycastable, ISaveable 11 | { 12 | [SerializeField] private Quest quest; 13 | [SerializeField] private Dialogue questPendingDialogue; 14 | [SerializeField] private Dialogue questCompletedDialogue; 15 | [SerializeField] private Dialogue afterQuestDialogue; 16 | 17 | private bool m_AssignedQuest; 18 | private bool m_HasBeenHelped; 19 | private QuestManager m_QuestManager; 20 | 21 | public override CursorType Cursor => CursorType.Quest; 22 | 23 | private void Awake() 24 | { 25 | m_QuestManager = GameObject.FindWithTag("QuestManager").GetComponent(); 26 | } 27 | 28 | private void AssignQuest() 29 | { 30 | if (m_AssignedQuest) return; 31 | m_AssignedQuest = true; 32 | m_QuestManager.AddQuest(this, quest); 33 | DialogueManager.onDialogueClose -= AssignQuest; 34 | } 35 | 36 | private void CheckQuest() 37 | { 38 | if (quest == null) return; 39 | onDialogueInitiated?.Invoke(gameObject.name); 40 | StartDialogue(m_HasBeenHelped ? questCompletedDialogue : questPendingDialogue); 41 | } 42 | 43 | protected override void Interact() 44 | { 45 | if (!m_AssignedQuest && !m_HasBeenHelped) 46 | { 47 | base.Interact(); 48 | DialogueManager.onDialogueClose += AssignQuest; 49 | } 50 | else if (m_AssignedQuest && !m_HasBeenHelped) 51 | { 52 | CheckQuest(); 53 | } 54 | else 55 | { 56 | onDialogueInitiated?.Invoke(gameObject.name); 57 | StartDialogue(afterQuestDialogue); 58 | } 59 | } 60 | 61 | public void MarkQuestCompleted() 62 | { 63 | m_HasBeenHelped = true; 64 | } 65 | 66 | public object CaptureState() 67 | { 68 | return new Tuple(m_AssignedQuest, m_HasBeenHelped); 69 | } 70 | 71 | public void RestoreState(object state) 72 | { 73 | (bool assignedQuest, bool hasBeenHelped) = (Tuple) state; 74 | m_AssignedQuest = assignedQuest; 75 | m_HasBeenHelped = hasBeenHelped; 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /Questing/QuestGiver.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9869e99628c772243a283c7918708179 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Questing/QuestManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using RPG.Saving; 3 | using RPG.UI; 4 | using UnityEngine; 5 | 6 | namespace RPG.Questing 7 | { 8 | public class QuestManager : MonoBehaviour, ISaveable 9 | { 10 | [SerializeField] private QuestToDisplay questHUD; 11 | 12 | private Quest m_LatestQuest; 13 | private StageS m_LatestStage; 14 | private QuestGiver m_QuestGiver; 15 | private string m_QuestGiverName; 16 | 17 | private void OnDisable() 18 | { 19 | if (m_LatestQuest == null) return; 20 | m_LatestQuest.OnQuestCompleted -= QuestHasBeenCompleted; 21 | for (int i = 0; i < m_LatestQuest.Stages.Count; i++) 22 | { 23 | m_LatestQuest.Stages[i].OnStageActivated -= StageHasBeenActivated; 24 | for (int j = 0; j < m_LatestQuest.Stages[i].Goals.Count; j++) 25 | { 26 | m_LatestQuest.Stages[i].Goals[j].OnGoalCompleted -= questHUD.UpdateQuestDisplay; 27 | } 28 | } 29 | } 30 | 31 | private void OnEnable() 32 | { 33 | AttachEvents(); 34 | } 35 | 36 | private void AttachEvents() 37 | { 38 | if (m_LatestQuest == null) return; 39 | m_LatestQuest.OnQuestCompleted += QuestHasBeenCompleted; 40 | for (int i = 0; i < m_LatestQuest.Stages.Count; i++) 41 | { 42 | m_LatestQuest.Stages[i].OnStageActivated += StageHasBeenActivated; 43 | if (m_LatestQuest.Stages[i].Active) 44 | { 45 | m_LatestStage = m_LatestQuest.Stages[i]; 46 | } 47 | 48 | for (int j = 0; j < m_LatestQuest.Stages[i].Goals.Count; j++) 49 | { 50 | m_LatestQuest.Stages[i].Goals[j].OnGoalCompleted += questHUD.UpdateQuestDisplay; 51 | } 52 | } 53 | } 54 | 55 | public void OnPlayeAction(string context) 56 | { 57 | if (m_LatestStage == null || m_LatestQuest.Completed) return; 58 | for (int i = 0; i < m_LatestStage.Goals.Count; i++) 59 | { 60 | if (!m_LatestStage.Goals[i].Completed) 61 | { 62 | m_LatestStage.Goals[i].Evaluate(context); 63 | } 64 | } 65 | } 66 | 67 | private void QuestHasBeenCompleted() 68 | { 69 | m_QuestGiver.MarkQuestCompleted(); 70 | questHUD.DisplayDefaultText(); 71 | } 72 | 73 | private void StageHasBeenActivated(StageS stage) 74 | { 75 | m_LatestStage = stage; 76 | questHUD.UpdateQuestDisplay(stage); 77 | } 78 | 79 | public void AddQuest(QuestGiver qg, Quest quest) 80 | { 81 | if (qg == null || quest == null) 82 | { 83 | return; 84 | } 85 | 86 | m_QuestGiverName = qg.name; 87 | m_QuestGiver = qg; 88 | m_LatestQuest = Instantiate(quest); 89 | m_LatestQuest.Init(); 90 | AttachEvents(); 91 | questHUD.UpdateQuestDisplay(m_LatestStage); 92 | } 93 | 94 | public object CaptureState() 95 | { 96 | if (m_LatestQuest == null) return new Tuple(null, null, 0); 97 | // fix scene prefab overrides! 98 | QuestSaver.Save(m_LatestQuest); 99 | return new Tuple(m_LatestQuest.ID, m_QuestGiverName, 100 | m_LatestQuest.Stages.IndexOf(m_LatestStage)); 101 | } 102 | 103 | public void RestoreState(object state) 104 | { 105 | if (m_LatestQuest != null) return; 106 | (string questName, string questGiverName, int latestStage) = (Tuple) state; 107 | if (string.IsNullOrEmpty(questName)) return; 108 | m_LatestQuest = QuestSaver.Load(questName); 109 | m_QuestGiverName = questGiverName; 110 | if (!string.IsNullOrEmpty(m_QuestGiverName)) 111 | { 112 | GameObject qgGObj = GameObject.Find(m_QuestGiverName); 113 | if (qgGObj != null) 114 | { 115 | m_QuestGiver = qgGObj.GetComponent(); 116 | } 117 | } 118 | 119 | m_LatestQuest.Init(); 120 | m_LatestStage = m_LatestQuest.Stages[latestStage]; 121 | AttachEvents(); 122 | questHUD.UpdateQuestDisplay(m_LatestStage); 123 | } 124 | } 125 | } -------------------------------------------------------------------------------- /Questing/QuestManager.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f8e1b25ac3b252a4ea831f7c528e40f1 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Questing/QuestSaver.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using UnityEngine; 3 | 4 | namespace RPG.Questing 5 | { 6 | public static class QuestSaver 7 | { 8 | public static void Save(Quest quest) 9 | { 10 | string json = JsonUtility.ToJson(quest); 11 | File.WriteAllText(Application.persistentDataPath + Path.DirectorySeparatorChar + quest.ID + ".json", json); 12 | } 13 | 14 | public static Quest Load(string questID) 15 | { 16 | Quest quest; 17 | if (File.Exists(Application.persistentDataPath + Path.DirectorySeparatorChar + questID + ".json")) 18 | { 19 | quest = ScriptableObject.CreateInstance(); 20 | string json = File.ReadAllText(Application.persistentDataPath + Path.DirectorySeparatorChar + questID + 21 | ".json"); 22 | JsonUtility.FromJsonOverwrite(json, quest); 23 | return quest; 24 | } 25 | 26 | quest = Resources.Load(questID); 27 | 28 | return GameObject.Instantiate(quest); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /Questing/QuestSaver.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e271fb5ff14260447a007909dc3583b1 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Questing/Stage.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 660587b6e09922843a75e0c1eb42cb41 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Questing/Stage/Stage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | namespace RPG.Questing 6 | { 7 | [Serializable] 8 | public class StageS 9 | { 10 | [SerializeField] private bool active; 11 | [SerializeField] private bool completed; 12 | [SerializeField] private string description; 13 | 14 | [SerializeReference] [SerializeReferenceButton] 15 | private List goals = default; 16 | 17 | public bool Active => active; 18 | public string Description => description; 19 | public bool Completed => completed; 20 | public List Goals => goals; 21 | 22 | public event Action OnStageActivated; 23 | public event Action OnStageCompleted; 24 | 25 | public void Init() 26 | { 27 | for (int i = 0; i < Goals.Count; i++) 28 | { 29 | Goals[i].OnGoalCompleted += Evalute; 30 | } 31 | } 32 | 33 | public void Activate() 34 | { 35 | active = true; 36 | OnStageActivated?.Invoke(this); // maybe find a way to not use ? 37 | } 38 | 39 | public void Complete() 40 | { 41 | active = false; 42 | completed = true; 43 | for (int i = 0; i < Goals.Count; i++) 44 | { 45 | Goals[i].Completed = true; 46 | } 47 | } 48 | 49 | public void Evalute(Goal lastCompletedGoal) 50 | { 51 | var goalsCompleted = true; 52 | 53 | for (int goal = 0; goal < Goals.Count; goal++) 54 | { 55 | if (Goals[goal].Completed) continue; 56 | goalsCompleted = false; 57 | break; 58 | } 59 | 60 | completed = goalsCompleted; 61 | 62 | if (!completed) return; 63 | active = false; 64 | if (OnStageCompleted == null) return; 65 | OnStageCompleted(this); 66 | Delegate[] completedDelegates = OnStageCompleted.GetInvocationList(); 67 | for (int i = 0; i < completedDelegates.Length; i++) 68 | { 69 | OnStageCompleted -= completedDelegates[i] as Action; 70 | } 71 | 72 | if (OnStageActivated == null) return; 73 | Delegate[] activatedDelegates = OnStageActivated.GetInvocationList(); 74 | for (int i = 0; i < activatedDelegates.Length; i++) 75 | { 76 | OnStageActivated -= activatedDelegates[i] as Action; 77 | } 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /Questing/Stage/Stage.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b142ed43ab83f7249bfb7229f1975c6d 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unity-RPG-Scripts 2 | Scripts used for a Unity RPG originally in version 2019.2. Since from what I understand, we are not allowed to share any assets from the Asset store without explicit permission, I decided to share the scripts for my game at least. 3 | New additions are added under version 2019.3 so be sure to check the tag of the previous version if you need it. 4 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 625abe9e1af7db04e9c2219adc16147f 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Saving.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9c07687ef09bf45488e79722589cfa57 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Saving/ISaveable.cs: -------------------------------------------------------------------------------- 1 | namespace RPG.Saving 2 | { 3 | /// 4 | /// Implemented by every component that desires to save its' state. 5 | /// 6 | public interface ISaveable 7 | { 8 | object CaptureState(); 9 | void RestoreState(object state); 10 | } 11 | } -------------------------------------------------------------------------------- /Saving/ISaveable.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: cb68c8a880e55f8408675308f3781caa 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Saving/SaveableEntity.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEditor; 3 | using UnityEngine; 4 | 5 | namespace RPG.Saving 6 | { 7 | /// 8 | /// GameObjects with this component will allow saving the state of its' components. 9 | /// 10 | [ExecuteAlways] 11 | public class SaveableEntity : MonoBehaviour 12 | { 13 | [SerializeField] private string uniqueIdentifier = ""; 14 | 15 | private static readonly Dictionary s_GlobalLookUp = 16 | new Dictionary(); 17 | 18 | private ISaveable[] saveables; 19 | 20 | public string UUID => uniqueIdentifier; 21 | 22 | private void Awake() 23 | { 24 | saveables = GetComponents(); 25 | } 26 | 27 | #if UNITY_EDITOR 28 | private void Update() 29 | { 30 | if (Application.IsPlaying(gameObject) || string.IsNullOrEmpty(gameObject.scene.path)) 31 | { 32 | return; 33 | } 34 | 35 | var serializedObject = new SerializedObject(this); 36 | SerializedProperty property = serializedObject.FindProperty("uniqueIdentifier"); 37 | 38 | if (property.stringValue == "" || !IsUnique(property.stringValue)) 39 | { 40 | property.stringValue = System.Guid.NewGuid().ToString(); 41 | serializedObject.ApplyModifiedProperties(); 42 | } 43 | 44 | s_GlobalLookUp[property.stringValue] = this; 45 | } 46 | #endif 47 | private bool IsUnique(string candidate) 48 | { 49 | if (!s_GlobalLookUp.ContainsKey(candidate) || s_GlobalLookUp[candidate] == this) 50 | { 51 | return true; 52 | } 53 | 54 | if (s_GlobalLookUp[candidate] == null || s_GlobalLookUp[candidate].UUID != candidate) 55 | { 56 | s_GlobalLookUp.Remove(candidate); 57 | return true; 58 | } 59 | 60 | return false; 61 | } 62 | 63 | public object CaptureState() 64 | { 65 | var state = new Dictionary(); 66 | foreach (ISaveable saveable in saveables) 67 | { 68 | state[saveable.GetType().ToString()] = saveable.CaptureState(); 69 | } 70 | 71 | return state; 72 | } 73 | 74 | public void RestoreState(object savedState) 75 | { 76 | var state = (Dictionary) savedState; 77 | foreach (ISaveable saveable in saveables) 78 | { 79 | var type = saveable.GetType().ToString(); 80 | if (state.ContainsKey(type)) 81 | { 82 | saveable.RestoreState(state[type]); 83 | } 84 | } 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /Saving/SaveableEntity.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0030fd1a8c480974da5c7b35b987c2fd 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Saving/SavingSystem.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Runtime.Serialization.Formatters.Binary; 5 | using UnityEngine; 6 | using UnityEngine.SceneManagement; 7 | 8 | namespace RPG.Saving 9 | { 10 | /// 11 | /// Core saving system functionality based on the BinaryFormatter. 12 | /// 13 | public class SavingSystem : MonoBehaviour 14 | { 15 | private const string LAST_SCENE_BUILD_INDEX = "lastSceneBuildIndex"; 16 | 17 | private void SaveFile(string fileName, object state) 18 | { 19 | string path = GetPathFromSaveFile(fileName); 20 | print("Saving to " + path); 21 | using (FileStream stream = File.Open(path, FileMode.Create)) 22 | { 23 | var formatter = new BinaryFormatter(); 24 | formatter.Serialize(stream, state); 25 | } 26 | } 27 | 28 | private Dictionary LoadFile(string fileName) 29 | { 30 | string path = GetPathFromSaveFile(fileName); 31 | print("Loading from " + GetPathFromSaveFile(fileName)); 32 | if (!File.Exists(path)) 33 | { 34 | return new Dictionary(); 35 | } 36 | 37 | using (FileStream stream = File.Open(path, FileMode.Open)) 38 | { 39 | var formatter = new BinaryFormatter(); 40 | return (Dictionary) formatter.Deserialize(stream); 41 | } 42 | } 43 | 44 | private static void CaptureState(IDictionary state) 45 | { 46 | foreach (SaveableEntity saveable in FindObjectsOfType()) 47 | { 48 | state[saveable.UUID] = saveable.CaptureState(); 49 | } 50 | 51 | state[LAST_SCENE_BUILD_INDEX] = SceneManager.GetActiveScene().buildIndex; 52 | } 53 | 54 | private static void RestoreState(IReadOnlyDictionary state) 55 | { 56 | foreach (SaveableEntity saveable in FindObjectsOfType()) 57 | { 58 | if (state.ContainsKey(saveable.UUID)) 59 | { 60 | saveable.RestoreState(state[saveable.UUID]); 61 | } 62 | } 63 | } 64 | 65 | private static string GetPathFromSaveFile(string fileName) 66 | { 67 | return Path.Combine(Application.persistentDataPath, fileName + ".sav"); 68 | } 69 | 70 | public IEnumerator LoadLastScene(string fileName) 71 | { 72 | Dictionary state = LoadFile(fileName); 73 | int buildIndex = SceneManager.GetActiveScene().buildIndex; 74 | if (state.ContainsKey(LAST_SCENE_BUILD_INDEX)) 75 | { 76 | buildIndex = (int) state[LAST_SCENE_BUILD_INDEX]; 77 | } 78 | 79 | yield return SceneManager.LoadSceneAsync(buildIndex); 80 | RestoreState(state); 81 | } 82 | 83 | public void Save(string fileName) 84 | { 85 | Dictionary state = LoadFile(fileName); 86 | CaptureState(state); 87 | SaveFile(fileName, state); 88 | } 89 | 90 | public void Load(string fileName) 91 | { 92 | RestoreState(LoadFile(fileName)); 93 | } 94 | 95 | public static void Delete(string fileName) 96 | { 97 | File.Delete(GetPathFromSaveFile(fileName)); 98 | } 99 | } 100 | } -------------------------------------------------------------------------------- /Saving/SavingSystem.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3353a345e5d2a8441b30d5225a73ecf6 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Saving/SerializableVector3.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace RPG.Saving 4 | { 5 | /// 6 | /// Serializable variant of Unity's Vector3 for use with the BinaryFormatter. 7 | /// 8 | [System.Serializable] 9 | public class SerializableVector3 10 | { 11 | private float m_X, m_Y, m_Z; 12 | 13 | public SerializableVector3(Vector3 vector) 14 | { 15 | m_X = vector.x; 16 | m_Y = vector.y; 17 | m_Z = vector.z; 18 | } 19 | 20 | public Vector3 ToVector() 21 | { 22 | return new Vector3(m_X, m_Y, m_Z); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /Saving/SerializableVector3.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8d38047b5e835464f8311be95aecae1a 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /SceneManagement.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a21743bddca866c4e8eb1f470ddeff38 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /SceneManagement/Fader.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using UnityEngine; 3 | 4 | namespace RPG.SceneManagement 5 | { 6 | public class Fader : MonoBehaviour 7 | { 8 | private CanvasGroup m_CanvasGroup; 9 | private Coroutine m_CurrentActiveFade; 10 | 11 | private void Awake() 12 | { 13 | m_CanvasGroup = GetComponent(); 14 | } 15 | 16 | private IEnumerator FadeRoutine(float target, float time) 17 | { 18 | while (!Mathf.Approximately(m_CanvasGroup.alpha, target)) 19 | { 20 | m_CanvasGroup.alpha = Mathf.MoveTowards(m_CanvasGroup.alpha, target, Time.deltaTime / time); 21 | yield return null; 22 | } 23 | } 24 | 25 | public void FadeOutImmediate() 26 | { 27 | m_CanvasGroup.alpha = 1; 28 | } 29 | 30 | public Coroutine FadeOut(float time) 31 | { 32 | return Fade(1, time); 33 | } 34 | 35 | public Coroutine FadeIn(float time) 36 | { 37 | return Fade(0, time); 38 | } 39 | 40 | private Coroutine Fade(float target, float time) 41 | { 42 | if (m_CurrentActiveFade != null) 43 | { 44 | StopCoroutine(m_CurrentActiveFade); 45 | } 46 | 47 | m_CurrentActiveFade = StartCoroutine(FadeRoutine(target, time)); 48 | return m_CurrentActiveFade; 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /SceneManagement/Fader.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f5221e7270b1ae24ea90d1a6e1433afa 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /SceneManagement/Portal.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using RPG.Control; 3 | using RPG.Core; 4 | using RPG.Questing; 5 | using UnityEngine; 6 | using UnityEngine.AI; 7 | using UnityEngine.SceneManagement; 8 | 9 | namespace RPG.SceneManagement 10 | { 11 | public class Portal : MonoBehaviour 12 | { 13 | [SerializeField] private int sceneToLoad = -1; 14 | [SerializeField] private float fadeOutTime = 1f; 15 | [SerializeField] private float fadeInTime = .5f; 16 | [SerializeField] private float fadeWaitTime = .5f; 17 | [SerializeField] private Transform spawnPoint; 18 | [SerializeField] private DestinationIdentifier destination = DestinationIdentifier.A; 19 | 20 | private enum DestinationIdentifier 21 | { 22 | A, 23 | B, 24 | C, 25 | D, 26 | E 27 | } 28 | 29 | public Transform SpawnPoint => spawnPoint; 30 | 31 | private void OnTriggerEnter(Collider other) 32 | { 33 | if (other.CompareTag("Player")) 34 | { 35 | StartCoroutine(Transition(other.GetComponent())); 36 | } 37 | } 38 | 39 | private IEnumerator Transition(PlayerController playerController) 40 | { 41 | if (sceneToLoad < 0) 42 | { 43 | Debug.LogError("Scene to load is not set!"); 44 | yield break; 45 | } 46 | 47 | playerController.enabled = false; 48 | playerController.SetCursor(CursorType.None); 49 | DontDestroyOnLoad(gameObject); 50 | 51 | var fader = FindObjectOfType(); 52 | var savingWrapper = FindObjectOfType(); 53 | yield return fader.FadeOut(fadeOutTime); 54 | 55 | savingWrapper.Save(); 56 | 57 | yield return SceneManager.LoadSceneAsync(sceneToLoad); 58 | var newPlayerController = GameObject.FindWithTag("Player").GetComponent(); 59 | newPlayerController.enabled = false; 60 | newPlayerController.SetCursor(CursorType.None); 61 | 62 | savingWrapper.Load(); 63 | 64 | Portal destinationPortal = GetDestinationPortal(); 65 | UpdatePlayer(destinationPortal); 66 | 67 | savingWrapper.Save(); 68 | 69 | yield return new WaitForSeconds(fadeWaitTime); 70 | fader.FadeIn(fadeInTime); 71 | newPlayerController.enabled = true; 72 | newPlayerController.SetCursor(CursorType.Movement); 73 | Destroy(gameObject); 74 | } 75 | 76 | private Portal GetDestinationPortal() 77 | { 78 | foreach (Portal portal in FindObjectsOfType()) 79 | { 80 | if (portal == this || portal.destination != destination) 81 | { 82 | continue; 83 | } 84 | 85 | return portal; 86 | } 87 | 88 | return null; 89 | } 90 | 91 | private static void UpdatePlayer(Portal destinationPortal) 92 | { 93 | if (!destinationPortal) return; 94 | GameObject player = GameObject.FindWithTag("Player"); 95 | var navMeshAgent = player.GetComponent(); 96 | navMeshAgent.enabled = false; 97 | player.transform.position = destinationPortal.SpawnPoint.position; 98 | player.transform.rotation = destinationPortal.SpawnPoint.rotation; 99 | navMeshAgent.enabled = true; 100 | } 101 | } 102 | } -------------------------------------------------------------------------------- /SceneManagement/Portal.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1e03377020698ea4e88c5b048daa73f1 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /SceneManagement/SavingWrapper.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using RPG.Saving; 3 | using UnityEngine; 4 | 5 | namespace RPG.SceneManagement { 6 | /// 7 | /// Leverages the core saving functionality to match the game's designed way of saving. 8 | /// 9 | public class SavingWrapper : MonoBehaviour { 10 | [SerializeField] private float fadeOutTime = 0.5f; 11 | private const string defaultSaveFile = "save"; 12 | private SavingSystem savingSystem; 13 | private Fader fader; 14 | 15 | private void Awake() { 16 | StartCoroutine(LoadLastScene()); 17 | } 18 | 19 | private void Update() { 20 | if (Input.GetKeyDown(KeyCode.S)) { 21 | Save(); 22 | } 23 | if (Input.GetKeyDown(KeyCode.L)) { 24 | Load(); 25 | } 26 | if (Input.GetKeyDown(KeyCode.Delete)) { 27 | Delete(); 28 | } 29 | } 30 | 31 | private IEnumerator LoadLastScene() { 32 | savingSystem = GetComponent(); 33 | yield return savingSystem.LoadLastScene(defaultSaveFile); 34 | fader = FindObjectOfType(); 35 | fader.FadeOutImmediate(); 36 | yield return fader.FadeIn(fadeOutTime); 37 | } 38 | 39 | public void Save() { 40 | savingSystem.Save(defaultSaveFile); 41 | } 42 | 43 | public void Load() { 44 | savingSystem.Load(defaultSaveFile); 45 | } 46 | 47 | public void Delete() { 48 | SavingSystem.Delete(defaultSaveFile); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /SceneManagement/SavingWrapper.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4073a99bdaaa21c4dacab9acc4055f82 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Stats.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: af751c310b8c08446baa47bbde0e0740 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Stats/BaseStats.cs: -------------------------------------------------------------------------------- 1 | using RPG.Util; 2 | using UnityEngine; 3 | 4 | namespace RPG.Stats 5 | { 6 | public class BaseStats : MonoBehaviour 7 | { 8 | [Range(1, 99)] [SerializeField] private int startingLevel = 1; 9 | [SerializeField] private bool shouldUseModifiers; 10 | [SerializeField] private CharacterClass characterClass = CharacterClass.Player; 11 | [SerializeField] private Progression progression; 12 | [SerializeField] private GameObject levelUpParticleEffect; 13 | private LazyValue m_CurrentLevel; 14 | private Experience m_Experience; 15 | 16 | public delegate bool LevelUpHandler(); 17 | 18 | public event LevelUpHandler OnLevelUp; 19 | public CharacterClass CharacterClass => characterClass; 20 | public int Level => m_CurrentLevel.Value; 21 | 22 | private void Awake() 23 | { 24 | m_Experience = GetComponent(); 25 | m_CurrentLevel = new LazyValue(CalculateLevel); 26 | } 27 | 28 | private void Start() 29 | { 30 | m_CurrentLevel.ForceInit(); 31 | } 32 | 33 | private void OnEnable() 34 | { 35 | if (m_Experience != null) 36 | { 37 | m_Experience.OnExperienceGained += UpdateLevel; 38 | } 39 | } 40 | 41 | private void OnDisable() 42 | { 43 | if (m_Experience != null) 44 | { 45 | m_Experience.OnExperienceGained -= UpdateLevel; 46 | } 47 | } 48 | 49 | private void UpdateLevel() 50 | { 51 | int newLevel = CalculateLevel(); 52 | if (newLevel <= m_CurrentLevel.Value) return; 53 | m_CurrentLevel.Value = newLevel; 54 | if (OnLevelUp?.Invoke() == true) 55 | { 56 | LevelUpEffect(); 57 | } 58 | } 59 | 60 | private float GetAdditiveModifiers(Stat stat) 61 | { 62 | if (!shouldUseModifiers) 63 | { 64 | return 0; 65 | } 66 | 67 | float total = 0; 68 | foreach (IModifierProvider provider in GetComponents()) 69 | { 70 | foreach (float modifier in provider.GetAdditiveModifiers(stat)) 71 | { 72 | total += modifier; 73 | } 74 | } 75 | 76 | return total; 77 | } 78 | 79 | private float GetPercentageModifier(Stat stat) 80 | { 81 | if (!shouldUseModifiers) 82 | { 83 | return 0; 84 | } 85 | 86 | float total = 0; 87 | foreach (IModifierProvider provider in GetComponents()) 88 | { 89 | foreach (float modifier in provider.GetPercentageModifiers(stat)) 90 | { 91 | total += modifier; 92 | } 93 | } 94 | 95 | return total; 96 | } 97 | 98 | private void LevelUpEffect() 99 | { 100 | Instantiate(levelUpParticleEffect, transform); 101 | } 102 | 103 | private int CalculateLevel() 104 | { 105 | if (m_Experience == null) 106 | { 107 | return startingLevel; 108 | } 109 | 110 | float currentXP = m_Experience.ExperiencePoints; 111 | int penultimateLevel = progression.GetLevels(Stat.ExperienceToLevelUp, characterClass); 112 | for (int level = 1; level <= penultimateLevel; level++) 113 | { 114 | float XPToLevelUp = progression.GetStat(Stat.ExperienceToLevelUp, characterClass, level); 115 | if (XPToLevelUp > currentXP) 116 | { 117 | return level; 118 | } 119 | } 120 | 121 | return penultimateLevel + 1; 122 | } 123 | 124 | public float GetStat(Stat stat) 125 | { 126 | return (progression.GetStat(stat, characterClass, m_CurrentLevel.Value) + GetAdditiveModifiers(stat)) * 127 | (1 + GetPercentageModifier(stat) / 100); 128 | } 129 | } 130 | } -------------------------------------------------------------------------------- /Stats/BaseStats.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 72c2fc1f9505f8d49a2a1bb0f825d200 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Stats/CharacterClass.cs: -------------------------------------------------------------------------------- 1 | namespace RPG.Stats 2 | { 3 | public enum CharacterClass 4 | { 5 | Player, 6 | Grunt, 7 | HeavySoldier, 8 | Minion, 9 | Thug, 10 | Archer, 11 | Knight, 12 | Mage, 13 | Beast 14 | } 15 | } -------------------------------------------------------------------------------- /Stats/CharacterClass.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2388f87d051432345a31e59e992adba4 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Stats/Experience.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using RPG.Saving; 3 | using UnityEngine; 4 | 5 | namespace RPG.Stats 6 | { 7 | public class Experience : MonoBehaviour, ISaveable 8 | { 9 | [SerializeField] private float experiencePoints; 10 | 11 | public float ExperiencePoints => experiencePoints; 12 | public event Action OnExperienceGained; 13 | 14 | public void GainExperience(float xp) 15 | { 16 | experiencePoints += xp; 17 | OnExperienceGained?.Invoke(); 18 | } 19 | 20 | public object CaptureState() 21 | { 22 | return experiencePoints; 23 | } 24 | 25 | public void RestoreState(object state) 26 | { 27 | experiencePoints = (float) state; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /Stats/Experience.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d95c53d71f9c9f943ac7f6df3ec04df1 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Stats/IModifierProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace RPG.Stats 4 | { 5 | /// 6 | /// Classes that provide modifiers to base stats implement this interface 7 | /// 8 | public interface IModifierProvider 9 | { 10 | IEnumerable GetAdditiveModifiers(Stat stat); 11 | IEnumerable GetPercentageModifiers(Stat stat); 12 | } 13 | } -------------------------------------------------------------------------------- /Stats/IModifierProvider.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 64caa57af6a590943904a8dcbcc7323c 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Stats/Progression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | namespace RPG.Stats 6 | { 7 | [CreateAssetMenu(fileName = "Progression", menuName = "Stats/New Progression", order = 0)] 8 | public class Progression : ScriptableObject 9 | { 10 | private static int _instances; 11 | private static int _statInstances; 12 | 13 | [SerializeField] private CharacterProgression[] characterClasses = 14 | new CharacterProgression[Enum.GetNames(typeof(CharacterClass)).Length]; 15 | 16 | private Dictionary> lookupTable; 17 | 18 | [Serializable] 19 | private class CharacterProgression 20 | { 21 | [HideInInspector] [SerializeField] 22 | private string Name = Enum.GetName(typeof(CharacterClass), GetActiveInstances()); 23 | 24 | [SerializeField] private ProgressionStat[] stats = new ProgressionStat[Enum.GetNames(typeof(Stat)).Length]; 25 | private CharacterClass m_CharacteClass = (CharacterClass) GetActiveInstances(); 26 | 27 | public CharacterClass CharacterClass => m_CharacteClass; 28 | public ProgressionStat[] Stats => stats; 29 | 30 | public CharacterProgression() 31 | { 32 | _instances++; 33 | } 34 | 35 | ~CharacterProgression() 36 | { 37 | _instances--; 38 | } 39 | 40 | public static int GetActiveInstances() 41 | { 42 | if (_instances >= Enum.GetNames(typeof(CharacterClass)).Length) 43 | { 44 | _instances = 0; 45 | } 46 | 47 | ; 48 | return _instances; 49 | } 50 | } 51 | 52 | [Serializable] 53 | private class ProgressionStat 54 | { 55 | [HideInInspector] [SerializeField] 56 | private string statName = Enum.GetName(typeof(Stat), GetActiveInstances()); 57 | 58 | [SerializeField] private Stat stat = (Stat) GetActiveInstances(); 59 | [SerializeField] private float[] levels = new float[5]; 60 | 61 | public Stat Stat => stat; 62 | public float[] Levels => levels; 63 | 64 | public ProgressionStat() 65 | { 66 | if (_statInstances < Enum.GetNames(typeof(Stat)).Length) 67 | { 68 | _statInstances++; 69 | } 70 | } 71 | 72 | ~ProgressionStat() 73 | { 74 | _statInstances--; 75 | } 76 | 77 | public float GetStat(int level) 78 | { 79 | if (level > levels.Length) 80 | { 81 | return 0; 82 | } 83 | 84 | return levels[level - 1]; 85 | } 86 | 87 | public static int GetActiveInstances() 88 | { 89 | if (_statInstances >= Enum.GetNames(typeof(Stat)).Length) 90 | { 91 | _statInstances = 0; 92 | } 93 | 94 | ; 95 | return _statInstances; 96 | } 97 | } 98 | 99 | private void BuildLookup() 100 | { 101 | if (lookupTable != null) 102 | { 103 | return; 104 | } 105 | 106 | lookupTable = new Dictionary>(); 107 | 108 | for (int progressionClass = 0; progressionClass < characterClasses.Length; progressionClass++) 109 | { 110 | var statLookupTable = new Dictionary(); 111 | for (int progressionStat = 0; 112 | progressionStat < characterClasses[progressionClass].Stats.Length; 113 | progressionStat++) 114 | { 115 | statLookupTable[characterClasses[progressionClass].Stats[progressionStat].Stat] = 116 | characterClasses[progressionClass].Stats[progressionStat].Levels; 117 | } 118 | 119 | lookupTable[characterClasses[progressionClass].CharacterClass] = statLookupTable; 120 | } 121 | } 122 | 123 | public float GetStat(Stat stat, CharacterClass characterClass, int level) 124 | { 125 | BuildLookup(); 126 | float[] levels = lookupTable[characterClass][stat]; 127 | if (levels.Length < level) 128 | { 129 | return 0; 130 | } 131 | 132 | return levels[level - 1]; 133 | } 134 | 135 | public int GetLevels(Stat stat, CharacterClass characterClass) 136 | { 137 | BuildLookup(); 138 | float[] levels = lookupTable[characterClass][stat]; 139 | 140 | return levels.Length; 141 | } 142 | } 143 | } -------------------------------------------------------------------------------- /Stats/Progression.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3c44bb2cd928d4d4096b12bd972df34e 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Stats/Stats.cs: -------------------------------------------------------------------------------- 1 | namespace RPG.Stats 2 | { 3 | public enum Stat 4 | { 5 | Health, 6 | ExperienceReward, 7 | ExperienceToLevelUp, 8 | Damage 9 | } 10 | } -------------------------------------------------------------------------------- /Stats/Stats.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b41a5d79f263057469837e0886114ea7 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /UI.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c6b459c2ddd5e6c4ca7fc65f87aaeef2 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /UI/CameraFacing.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace RPG.UI 4 | { 5 | public class CameraFacing : MonoBehaviour 6 | { 7 | private Camera m_MainCamera; 8 | 9 | private void Start() 10 | { 11 | m_MainCamera = Camera.main; 12 | } 13 | 14 | private void LateUpdate() 15 | { 16 | transform.forward = m_MainCamera.transform.forward; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /UI/CameraFacing.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 582e959fc7a8e134ca2a3d7b0e38eddf 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /UI/Damage Text.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a7233a8916b5e8642a3d8d6eeca02578 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /UI/Damage Text/DamageText.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using RPG.Util; 3 | using TMPro; 4 | using UnityEngine; 5 | 6 | namespace RPG.UI 7 | { 8 | public class DamageText : MonoBehaviour 9 | { 10 | [SerializeField] private TextMeshProUGUI damageText; 11 | private ObjectPooler m_Pooler; 12 | private string m_PoolTag; 13 | 14 | private void Start() 15 | { 16 | m_Pooler = ObjectPooler.Instace; 17 | } 18 | 19 | public void DestroyText() 20 | { 21 | m_Pooler.AddToPool(m_PoolTag, gameObject); 22 | } 23 | 24 | public void SetPoolTag(string tag) 25 | { 26 | if (string.IsNullOrEmpty(m_PoolTag)) 27 | { 28 | m_PoolTag = tag; 29 | } 30 | } 31 | 32 | public void SetValue(float amount) 33 | { 34 | damageText.text = $"{amount:0}"; 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /UI/Damage Text/DamageText.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d2933d9c59c3c0f42bcfb4163cd67529 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /UI/Damage Text/DamageTextSpawner.cs: -------------------------------------------------------------------------------- 1 | using RPG.Util; 2 | using UnityEngine; 3 | 4 | namespace RPG.UI 5 | { 6 | public class DamageTextSpawner : MonoBehaviour 7 | { 8 | [SerializeField] private string poolTag = "damageText"; 9 | private ObjectPooler m_Pooler; 10 | 11 | private void Start() 12 | { 13 | m_Pooler = ObjectPooler.Instace; 14 | } 15 | 16 | public void Spawn(float damage) 17 | { 18 | GameObject instance = m_Pooler.SpawnFromPool(poolTag); 19 | if (instance == null) 20 | { 21 | return; 22 | } 23 | 24 | instance.transform.position = transform.position; 25 | var damageText = instance.GetComponent(); 26 | damageText.SetValue(damage); 27 | damageText.SetPoolTag(poolTag); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /UI/Damage Text/DamageTextSpawner.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: bae6f105237741a47a5995d35a542732 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /UI/ExperienceDisplay.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using RPG.Stats; 3 | using TMPro; 4 | using UnityEngine; 5 | 6 | namespace RPG.UI 7 | { 8 | public class ExperienceDisplay : MonoBehaviour 9 | { 10 | private Experience m_Experience; 11 | private TextMeshProUGUI m_Text; 12 | 13 | private void Awake() 14 | { 15 | m_Experience = GameObject.FindWithTag("Player").GetComponent(); 16 | m_Text = GetComponent(); 17 | } 18 | 19 | private void Start() 20 | { 21 | UpdateExperience(); 22 | } 23 | 24 | private void OnEnable() 25 | { 26 | m_Experience.OnExperienceGained += UpdateExperience; 27 | } 28 | 29 | private void OnDisable() 30 | { 31 | m_Experience.OnExperienceGained -= UpdateExperience; 32 | } 33 | 34 | private void UpdateExperience() 35 | { 36 | m_Text.text = $"{m_Experience.ExperiencePoints:0}"; 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /UI/ExperienceDisplay.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c480c4b7570775f42bb4d77be7f90e7d 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /UI/Health.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: eb957cf64fb24594fa96efdcbbe30c25 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /UI/Health/EnemyHealthDisplay.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using RPG.Combat; 3 | using TMPro; 4 | using UnityEngine; 5 | 6 | namespace RPG.UI 7 | { 8 | public class EnemyHealthDisplay : MonoBehaviour 9 | { 10 | private Fighter m_Fighter; 11 | private TextMeshProUGUI m_Text; 12 | private string m_DefaultText; 13 | 14 | private void Awake() 15 | { 16 | m_Fighter = GameObject.FindWithTag("Player").GetComponent(); 17 | m_Text = GetComponent(); 18 | m_DefaultText = m_Text.text; 19 | } 20 | 21 | private void Start() 22 | { 23 | UpdateTargetHealth(); 24 | } 25 | 26 | private void OnEnable() 27 | { 28 | m_Fighter.UpdateTargetUi += UpdateTargetHealth; 29 | } 30 | 31 | private void OnDisable() 32 | { 33 | m_Fighter.UpdateTargetUi -= UpdateTargetHealth; 34 | } 35 | 36 | private void UpdateTargetHealth() 37 | { 38 | if (m_Fighter.Target == null) 39 | { 40 | m_Text.text = m_DefaultText; 41 | } 42 | else 43 | { 44 | m_Text.text = $"{m_Fighter.Target.HealthPoints:0}/{m_Fighter.Target.MaxHealthPoints:0}"; 45 | } 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /UI/Health/EnemyHealthDisplay.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c3d24e9e082d57c4a8d0bb80d64ff6d9 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /UI/Health/HealthBar.cs: -------------------------------------------------------------------------------- 1 | using RPG.Attributes; 2 | using UnityEngine; 3 | 4 | namespace RPG.UI 5 | { 6 | public class HealthBar : MonoBehaviour 7 | { 8 | [SerializeField] private Health health; 9 | [SerializeField] private RectTransform foreground; 10 | [SerializeField] private Canvas rootCanvas; 11 | 12 | private void Update() 13 | { 14 | if (Mathf.Approximately(health.Fraction, 0) || Mathf.Approximately(health.Fraction, 1)) 15 | { 16 | rootCanvas.enabled = false; 17 | } 18 | else 19 | { 20 | rootCanvas.enabled = true; 21 | foreground.localScale = new Vector3(health.Fraction, 1, 1); 22 | } 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /UI/Health/HealthBar.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 06a75b14e1caa1648a2989cba632a552 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /UI/Health/HealthDisplay.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using RPG.Attributes; 3 | using TMPro; 4 | using UnityEngine; 5 | 6 | namespace RPG.UI 7 | { 8 | public class HealthDisplay : MonoBehaviour 9 | { 10 | private Health m_Health; 11 | private TextMeshProUGUI m_Text; 12 | 13 | private void Awake() 14 | { 15 | m_Health = GameObject.FindWithTag("Player").GetComponent(); 16 | m_Text = GetComponent(); 17 | } 18 | 19 | private void Start() 20 | { 21 | UpdateHealth(); 22 | } 23 | 24 | private void OnEnable() 25 | { 26 | m_Health.OnHealthUpdate += UpdateHealth; 27 | } 28 | 29 | private void OnDisable() 30 | { 31 | m_Health.OnHealthUpdate -= UpdateHealth; 32 | } 33 | 34 | private void UpdateHealth() 35 | { 36 | m_Text.text = $"{m_Health.HealthPoints:0}/{m_Health.MaxHealthPoints:0}"; 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /UI/Health/HealthDisplay.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 80e3ac65ea88e0442a4507533f55dcd7 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /UI/LevelToDisplay.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using RPG.Stats; 3 | using TMPro; 4 | using UnityEngine; 5 | 6 | namespace RPG.UI 7 | { 8 | public class LevelToDisplay : MonoBehaviour 9 | { 10 | private BaseStats m_BaseStats; 11 | private TextMeshProUGUI m_Text; 12 | 13 | private void Awake() 14 | { 15 | m_BaseStats = GameObject.FindWithTag("Player").GetComponent(); 16 | m_Text = GetComponent(); 17 | } 18 | 19 | private void Start() 20 | { 21 | UpdateLevel(); 22 | } 23 | 24 | private void OnEnable() 25 | { 26 | m_BaseStats.OnLevelUp += UpdateLevel; 27 | } 28 | 29 | private void OnDisable() 30 | { 31 | m_BaseStats.OnLevelUp -= UpdateLevel; 32 | } 33 | 34 | private bool UpdateLevel() 35 | { 36 | m_Text.text = $"{m_BaseStats.Level:0}"; 37 | return true; 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /UI/LevelToDisplay.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e611141b9590b3244a1fc72d0f11025b 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /UI/QuestToDisplay.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using RPG.Questing; 3 | using TMPro; 4 | using UnityEngine; 5 | 6 | namespace RPG.UI 7 | { 8 | public class QuestToDisplay : MonoBehaviour 9 | { 10 | private TextMeshProUGUI m_Text; 11 | private string m_DefaultText; 12 | private string m_Tmp = ""; 13 | 14 | private void Awake() 15 | { 16 | m_Text = GetComponent(); 17 | m_DefaultText = m_Text.text; 18 | } 19 | 20 | public void UpdateQuestDisplay(StageS stage) 21 | { 22 | if (stage == null) return; 23 | if (stage.Goals[0].Completed) 24 | { 25 | m_Tmp += "" + stage.Goals[0].Description + ""; 26 | } 27 | else 28 | { 29 | m_Tmp += stage.Goals[0].Description; 30 | } 31 | 32 | for (int j = 1; j < stage.Goals.Count; j++) 33 | { 34 | if (stage.Goals[j].Completed) 35 | { 36 | m_Tmp += "" + String.Concat("\n", stage.Goals[j].Description) + ""; 37 | } 38 | else 39 | { 40 | m_Tmp += String.Concat("\n", stage.Goals[j].Description); 41 | } 42 | } 43 | 44 | m_Text.text = m_Tmp; 45 | m_Tmp = ""; 46 | } 47 | 48 | public void UpdateQuestDisplay(Goal goal) 49 | { 50 | if (goal != null) 51 | { 52 | m_Text.text = m_Text.text.Replace(goal.Description, "" + goal.Description + ""); 53 | } 54 | } 55 | 56 | public void DisplayDefaultText() 57 | { 58 | m_Text.text = m_DefaultText; 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /UI/QuestToDisplay.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: dde79927f9c6534458af5b03bdd8502c 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Util.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6a71399f3aec5164381475d9c6b8a52c 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Util/LazyValue.cs: -------------------------------------------------------------------------------- 1 | namespace RPG.Util 2 | { 3 | /// 4 | /// Container class that wraps a value and ensures initialisation is 5 | /// called just before first use. 6 | /// 7 | public class LazyValue 8 | { 9 | private T m_Value; 10 | private bool m_Initialized; 11 | private readonly InitializerDelegate _initializer; 12 | 13 | public delegate T InitializerDelegate(); 14 | 15 | /// 16 | /// Setup the container but don't initialise the value yet. 17 | /// 18 | /// 19 | /// The initialiser delegate to call when first used. 20 | /// 21 | public LazyValue(InitializerDelegate initializer) 22 | { 23 | _initializer = initializer; 24 | } 25 | 26 | /// 27 | /// Get or set the contents of this container. 28 | /// 29 | /// 30 | /// Note that setting the value before initialisation will initialise 31 | /// the class. 32 | /// 33 | public T Value 34 | { 35 | get 36 | { 37 | // Ensure we init before returning a value. 38 | ForceInit(); 39 | return m_Value; 40 | } 41 | set 42 | { 43 | // Don't use default init anymore. 44 | m_Initialized = true; 45 | m_Value = value; 46 | } 47 | } 48 | 49 | /// 50 | /// Force the initialisation of the value via the delegate. 51 | /// 52 | public void ForceInit() 53 | { 54 | if (m_Initialized) return; 55 | m_Value = _initializer(); 56 | m_Initialized = true; 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /Util/LazyValue.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8775b6a15af80c84b97e8d7858b4a70b 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Util/ObjectPooler.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | 4 | namespace RPG.Util { 5 | public class ObjectPooler : MonoBehaviour { 6 | 7 | [System.Serializable] 8 | public struct Pool { 9 | public string tag; 10 | public GameObject prefab; 11 | public int size; 12 | } 13 | 14 | [SerializeField] private List pools = default; 15 | [SerializeField] private Dictionary> poolDict = default; 16 | 17 | public static ObjectPooler Instace; 18 | 19 | private void Awake() { 20 | Instace = this; 21 | poolDict = new Dictionary>(); 22 | 23 | FillPools(); 24 | } 25 | 26 | private void FillPool(string tag) { 27 | if (!poolDict.ContainsKey(tag)) { 28 | return; 29 | } 30 | Pool pool = pools.Find(p => p.tag == tag); 31 | for (int j = 0; j < pool.size; j++) { 32 | GameObject obj = Instantiate(pool.prefab, transform, true); 33 | obj.SetActive(false); 34 | poolDict[tag].Enqueue(obj); 35 | } 36 | } 37 | 38 | private void FillPools() { 39 | for (int i = 0; i < pools.Count; i++) { 40 | var objectPool = new Queue(); 41 | for (int j = 0; j < pools[i].size; j++) { 42 | GameObject obj = Instantiate(pools[i].prefab, transform, true); 43 | obj.SetActive(false); 44 | objectPool.Enqueue(obj); 45 | } 46 | poolDict.Add(pools[i].tag, objectPool); 47 | } 48 | } 49 | 50 | public void AddToPool(string tag, GameObject instance) { 51 | if (!poolDict.ContainsKey(tag)) { 52 | return; 53 | } 54 | instance.transform.SetParent(transform); 55 | instance.SetActive(false); 56 | poolDict[tag].Enqueue(instance); 57 | } 58 | 59 | public GameObject SpawnFromPool(string tag) { 60 | if (!poolDict.ContainsKey(tag)) { 61 | return null; 62 | } 63 | 64 | if (poolDict[tag].Count == 0) { 65 | FillPool(tag); 66 | } 67 | 68 | GameObject instance = poolDict[tag].Dequeue(); 69 | instance.SetActive(true); 70 | 71 | return instance; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Util/ObjectPooler.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ee90eb3c3ee26244dad3154a2d3beaf1 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Util/Utility.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace RPG.Util 4 | { 5 | public static class Utility 6 | { 7 | /// 8 | /// More performant way of calculating distance than using Vector3.Distance(). 9 | /// 10 | /// First object of interest. 11 | /// Second object of interest. 12 | /// Distance between objects to check. 13 | /// Whether the target is in range or not. 14 | public static bool IsTargetInRange(Transform me, Transform target, float distance) 15 | { 16 | return (me.position - target.position).sqrMagnitude < distance * distance; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /Util/Utility.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ec0199c80e50b024487c76679377882e 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | --------------------------------------------------------------------------------