├── README.md
└── Scripts
├── Contents
├── CharacterCustom.cs
├── Effect.cs
├── EffectData.cs
├── EffectParticle.cs
├── ItemPickUp.cs
├── MonsterStat.cs
├── Portal.cs
└── SpawningPool.cs
├── Controllers
├── BaseController.cs
├── Monster
│ ├── DarkKnightController.cs
│ ├── MonsterAttackCollistion.cs
│ └── MonsterController.cs
├── Npc
│ ├── NpcController.cs
│ ├── QuestNpcController.cs
│ ├── ShopNpcController.cs
│ └── UpgradeNpcController.cs
└── Player
│ ├── CameraController.cs
│ ├── CursorController.cs
│ ├── MapCameraController.cs
│ ├── PlayerAnimEvent.cs
│ ├── PlayerAttackCollistion.cs
│ └── PlayerController.cs
├── Data
├── ArmorItemData.cs
├── EquipmentData.cs
├── ItemData.cs
├── LevelData.cs
├── QuestData.cs
├── SkillData.cs
├── SkinnedData.cs
├── StartData.cs
├── TalkData.cs
├── UseItemData.cs
└── WeaponItemData.cs
├── Manager
├── Contents
│ └── GameManager.cs
├── Core
│ ├── DataManager.cs
│ ├── InputManager.cs
│ ├── PoolManager.cs
│ ├── Poolable.cs
│ ├── ResourceManager.cs
│ ├── SceneManagerEx.cs
│ └── UIManager.cs
└── Managers.cs
├── Scenes
├── BaseScene.cs
├── BossScene.cs
├── CustomScene.cs
├── DungeonScene.cs
├── GameScene.cs
└── TitleScene.cs
├── UI
├── Popup
│ ├── UI_ConfirmPopup.cs
│ ├── UI_DiePopup.cs
│ ├── UI_EqStatPopup.cs
│ ├── UI_InputPopup.cs
│ ├── UI_InvenPopup.cs
│ ├── UI_LoadPopup.cs
│ ├── UI_MenuPopup.cs
│ ├── UI_NumberCheckPopup.cs
│ ├── UI_Popup.cs
│ ├── UI_QuestPopup.cs
│ ├── UI_ShopPopup.cs
│ ├── UI_SkillPopup.cs
│ ├── UI_SlotTipPopup.cs
│ ├── UI_TalkPopup.cs
│ └── UI_UpgradePopup.cs
├── Scene
│ ├── UI_CustomScene.cs
│ ├── UI_PlayScene.cs
│ ├── UI_Scene.cs
│ └── UI_TitleScene.cs
├── SubItem
│ ├── UI_ArmorSlot.cs
│ ├── UI_CustomButton.cs
│ ├── UI_DragSlot.cs
│ ├── UI_Guide.cs
│ ├── UI_InvenSlot.cs
│ ├── UI_ItemDragSlot.cs
│ ├── UI_ItemSlot.cs
│ ├── UI_QuestButton.cs
│ ├── UI_QuestNoticeSlot.cs
│ ├── UI_ShopBuySlot.cs
│ ├── UI_ShopSaleSlot.cs
│ ├── UI_SkillBarSlot.cs
│ ├── UI_SkillPopupSlot.cs
│ ├── UI_SkillSlot.cs
│ ├── UI_Slot.cs
│ ├── UI_UpgradeSlot.cs
│ ├── UI_UseItemSlot.cs
│ └── UI_WeaponSlot.cs
├── UI_Base.cs
├── UI_EventHandler.cs
└── WorldSpace
│ ├── UI_HitEffect.cs
│ ├── UI_HpBar.cs
│ ├── UI_NameBar.cs
│ ├── UI_Navigation.cs
│ └── UI_QuestNotice.cs
└── Utills
├── Define.cs
├── Extension.cs
└── Util.cs
/README.md:
--------------------------------------------------------------------------------
1 | # [Unity 3D] RPG Game Portfolio
2 | ## 1. 소개
3 |
4 |
5 |
6 |

7 |

8 |

9 |

10 |
11 | < 게임 플레이 사진 >
12 |
13 |
14 |
15 | + Unity 3D RPG 게임입니다.
16 |
17 | + 게임 개발자가 되기 위해 스스로 역량을 쌓고 처음으로 제작한 RPG 포트폴리오입니다.
18 |
19 | + 현재 Repository에는 유료에셋 사용으로 인해 소스코드만 등록되어 있습니다.
20 |
21 | + 개발기간: 2023.05.10 ~ 2023.08.18 ( 약 3개월 )
22 |
23 | + 형상관리: Git SourceTree
24 |
25 |
26 |
27 | ## 2. 개발 환경
28 | + Unity 2021.3.21f1 LTS
29 |
30 | + C#
31 |
32 | + Window 10
33 |
34 |
35 |
36 | ## 3. 사용 기술
37 | | 기술 | 설명 |
38 | |:---:|:---|
39 | | 디자인 패턴 | ● **싱글톤** 패턴을 사용하여 Manager 관리
● **State** 패턴을 사용하여 캐릭터의 기능을 직관적으로 관리 |
40 | | SkinnedMeshRender | 캐릭터의 얼굴을 꾸미고, 장비 장착 시 캐릭터의 의상이 변경되도록 구현 |
41 | | GoogleSheet | 구글 스프레드 시트를 사용해 데이터 관리 |
42 | | Save | 게임 데이터를 모두 json으로 변환하여 관리 ( Dictionary 포함 ) |
43 | | Object Pooling | 자주 사용되는 객체는 Pool 관리하여 재사용 |
44 | | UI 자동화 | 유니티 UI 상에서 컴포넌트로 Drag&Drop되는 일을 줄이기 위한 편의성 |
45 |
46 |
47 |
48 | ## 4. 구현 기능
49 | + Object
50 | - 플레이어:
51 | - 전사
52 | - 일반 몬스터:
53 | - 해골 검사
54 | - 날렵한 해골 검사
55 | - 강력한 해골 검사
56 | - 보스 몬스터:
57 | - 암흑 기사 ( 패턴 : 기본 공격 2개, 이동 공격 3개, 스킬 2개 )
58 | - NPC:
59 | - Shop NPC (포션, 장신구, 방어구, 무기)
60 | - Upgrade NPC
61 | - Quest NPC
62 | - 아이템:
63 | - HP 회복 물약, MP 회복 물약
64 | - 무기, 방어구, 장신구
65 | + UI
66 | - Scene:
67 | - PlayScene : 게임 진행 시 사용
68 | ( 플레이어 스탯, 미니맵, 퀘스트 알림, 스킬 퀵슬롯, 소비 아이템 퀵슬롯 )
69 | - CustomScene : 캐릭터 커스텀 시 사용
70 | ( 커스텀 부위 변경 버튼, 확인 버튼, 나가기 버튼 )
71 | - TitleScene : 게임 접속 시 사용
72 | ( 게임 시작 버튼, 세이브 로드 버튼, 나가기 버튼 )
73 | - Popup:
74 | - 인벤토리창, 장비창, 스킬창, 퀘스트창, 상점창, 강화창, 대화창, 메뉴창
75 | - 확인창, 개수 입력창, 메뉴창, 부활창, 슬롯Tip창, Scene Load창
76 | - WorldSpace
77 | - 피격 데미지 Effect, 체력 Bar, 이름 Bar, 네비게이션, 퀘스트 Icon
78 |
79 |
80 |
81 | ## 5. 기술 문서
82 | + https://docs.google.com/presentation/d/1jDW8nCpSjZaHVW5mElcxNQNMD1Ai76vOAgrC0b9hbXA/edit?usp=sharing
83 |
84 | ## 6. History 블로그
85 | + https://lhuhyeon.github.io/categories/3d-unity-my-rpg/
86 |
87 | ## 7. 플레이 영상
88 | + https://www.youtube.com/watch?v=mVkvd95_ezo
89 |
90 | ## 8. 게임 다운로드
91 | + https://drive.google.com/drive/folders/1X-CLIYkAuRs7gpSAvIbH40HmISIl5l-7
92 |
--------------------------------------------------------------------------------
/Scripts/Contents/CharacterCustom.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 | using UnityEngine.EventSystems;
5 |
6 | /*
7 | * File : CharacterCustom.cs
8 | * Desc : 캐릭터 커스텀 제어 ( 커스텀 부위 : 머리카락, 눈썹, 수염, 얼굴 문신, 상체, 하체 )
9 | *
10 | & Functions
11 | & [Public]
12 | & : NextPart() - 캐릭터의 부위를 다음 파츠로 변경
13 | & : SaveCustom() - 현재 부위들을 저장
14 | &
15 | & [Private]
16 | & : CharaterRotation() - 캐릭터 회전
17 | & : ChangePart() - 파츠 부위 변경
18 | & : SetSkinned() - SkinnedMeshRenderer 데이터 저장
19 | *
20 | */
21 |
22 | public class CharacterCustom : MonoBehaviour
23 | {
24 | public bool stopRotation = false; // 회전 제어
25 |
26 | [SerializeField]
27 | private float rotationSpeed = 3.5f; // 회전 속도
28 | private float currentRotation_Y = 0.01f; // 캐릭터 Y 회전값
29 |
30 | // 부위별 파츠 리스트
31 | [SerializeField] List hairList = new List();
32 | [SerializeField] List headList = new List();
33 | [SerializeField] List eyebrowsList = new List();
34 | [SerializeField] List facialHairList = new List();
35 | [SerializeField] List torsoList = new List();
36 | [SerializeField] List hipsList = new List();
37 |
38 | // 부위별 현재 List index
39 | private int currentHairIndex = 0;
40 | private int currentHeadIndex = 0;
41 | private int currentEyebrowsIndex = 0;
42 | private int currentFacialHairIndex = 0;
43 | private int currentTorsoIndex = 0;
44 | private int currentHipsIndex = 0;
45 |
46 | private void Update()
47 | {
48 | CharaterRotation();
49 | }
50 |
51 | // ~ UI_CustomButton.cs 에서 파츠 변경 버튼을 누를 때 호출
52 | public void NextPart(Define.DefaultPart partType, bool isNext)
53 | {
54 | // 부위 타입에 맞게 변경
55 | switch (partType)
56 | {
57 | case Define.DefaultPart.Hair:
58 | ChangePart(hairList, ref currentHairIndex, isNext);
59 | break;
60 | case Define.DefaultPart.Head:
61 | ChangePart(headList, ref currentHeadIndex, isNext);
62 | break;
63 | case Define.DefaultPart.Eyebrows:
64 | ChangePart(eyebrowsList, ref currentEyebrowsIndex, isNext);
65 | break;
66 | case Define.DefaultPart.FacialHair:
67 | ChangePart(facialHairList, ref currentFacialHairIndex, isNext);
68 | break;
69 | case Define.DefaultPart.Torso:
70 | ChangePart(torsoList, ref currentTorsoIndex, isNext);
71 | break;
72 | case Define.DefaultPart.Hips:
73 | ChangePart(hipsList, ref currentHipsIndex, isNext);
74 | break;
75 | }
76 | }
77 |
78 | // ~ UI_CustomButton.cs 에서 확인 버튼을 누를 때 호출
79 | public void SaveCustom()
80 | {
81 | // 딕셔너리 생성
82 | Managers.Game.DefaultPart = new Dictionary();
83 |
84 | // GameManager의 데이터에 저장
85 | Managers.Game.DefaultPart.Add(Define.DefaultPart.Hair, SetSkinned(hairList[currentHairIndex]));
86 | Managers.Game.DefaultPart.Add(Define.DefaultPart.Head, SetSkinned(headList[currentHeadIndex]));
87 | Managers.Game.DefaultPart.Add(Define.DefaultPart.Eyebrows, SetSkinned(eyebrowsList[currentEyebrowsIndex]));
88 | Managers.Game.DefaultPart.Add(Define.DefaultPart.FacialHair, SetSkinned(facialHairList[currentFacialHairIndex]));
89 | Managers.Game.DefaultPart.Add(Define.DefaultPart.Torso, SetSkinned(torsoList[currentTorsoIndex]));
90 | Managers.Game.DefaultPart.Add(Define.DefaultPart.Hips, SetSkinned(hipsList[currentHipsIndex]));
91 | }
92 |
93 | // 캐릭터 회전 (Update)
94 | private void CharaterRotation()
95 | {
96 | // 회전 제어
97 | if (stopRotation == true)
98 | return;
99 |
100 | // UI를 클릭하면 회전 X
101 | if (Input.GetMouseButtonDown(0) == true || Input.GetMouseButtonDown(1) == true)
102 | {
103 | if (EventSystem.current.IsPointerOverGameObject())
104 | return;
105 | }
106 |
107 | // A Key, ◀ Key : 왼쪽으로 회전
108 | // D Key, ▶ Key : 오른쪽으로 회전
109 | if (Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.D) ||
110 | Input.GetKey(KeyCode.LeftArrow) || Input.GetKey(KeyCode.RightArrow))
111 | {
112 | SetRotation(-Input.GetAxis("Horizontal"));
113 | }
114 | else if (Input.GetMouseButton(0) || Input.GetMouseButton(1))
115 | {
116 | SetRotation(-Input.GetAxis("Mouse X"));
117 | }
118 | }
119 |
120 | // 회전 설정
121 | private void SetRotation(float horizontal)
122 | {
123 | currentRotation_Y += horizontal * rotationSpeed;
124 |
125 | transform.localRotation = Quaternion.Euler(0f, currentRotation_Y, 0f);
126 | }
127 |
128 | // 파츠 부위 변경
129 | private void ChangePart(List partList, ref int currentIndex, bool isNext)
130 | {
131 | // 현재 부위 비활성화
132 | partList[currentIndex].SetActive(false);
133 |
134 | // ( ▶ ) Button
135 | if (isNext == true)
136 | {
137 | currentIndex++;
138 |
139 | // 다음버튼 눌렀을 때 현재 인덱스가 마지막이라면 처음으로 이동
140 | if (currentIndex >= partList.Count)
141 | currentIndex = 0;
142 | }
143 | // ( ◀ ) Button
144 | else
145 | {
146 | currentIndex--;
147 |
148 | // 뒤로버튼 눌렀을 때 현재 인덱스가 처음이라면 마지막으로 이동
149 | if (currentIndex < 0)
150 | currentIndex = partList.Count - 1;
151 | }
152 |
153 | // 변경된 부위 활성화
154 | partList[currentIndex].SetActive(true);
155 | }
156 |
157 | // SkinnedMeshRenderer 필요 정보 저장
158 | private SkinnedData SetSkinned(GameObject skinnedObject)
159 | {
160 | // SkinnedMeshRenderer 컴포넌트 받기
161 | SkinnedMeshRenderer skinnedMesh = skinnedObject.GetComponent();
162 |
163 | // 이름, localBounds, rootBone을 저장
164 | SkinnedData skinned = new SkinnedData(){
165 | sharedMeshName = skinnedMesh.name,
166 | bounds = skinnedMesh.localBounds,
167 | rootBoneName = skinnedMesh.rootBone.name,
168 | };
169 |
170 | // bones 저장
171 | skinned.bones = new List();
172 | foreach(Transform child in skinnedMesh.bones)
173 | skinned.bones.Add(child.name);
174 |
175 | return skinned;
176 | }
177 | }
178 |
--------------------------------------------------------------------------------
/Scripts/Contents/Effect.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 |
5 | /*
6 | * File : Effect.cs
7 | * Desc : Effect 관련 스크립트의 부모이다.
8 | */
9 |
10 | public class Effect : MonoBehaviour
11 | {
12 | void OnEnable() { GetComponent().Play(); }
13 | void OnDisable() { GetComponent().Stop(); }
14 | }
15 |
--------------------------------------------------------------------------------
/Scripts/Contents/EffectData.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 |
5 | /*
6 | * File : EffectData.cs
7 | * Desc : Effect의 id로 사용되며
8 | * : Effect가 부모로부터 떨어져야 되는 상황 (잔해물, 잔상 등)이라면
9 | * : EffectDisableDelayTime() 코루틴을 실행
10 | *
11 | & Functions
12 | & [Public]
13 | & : EffectDisableDelay() - 이펙트 비활성화 딜레이
14 | *
15 | */
16 |
17 | public class EffectData : Effect
18 | {
19 | public int id;
20 | public float disableDelayTime = 0; // effect 전용 비활성화 딜레이
21 |
22 | private bool isEffect = false; // 이펙트가 실행 중인가?
23 |
24 | // ~ PlayerController.cs 에서 스킬 이펙트 비활성화를 위해 호출
25 | public void EffectDisableDelay()
26 | {
27 | // 딜레이 시간이 0이라면 바로 비활성화
28 | if (disableDelayTime == 0)
29 | {
30 | gameObject.SetActive(false);
31 | return;
32 | }
33 |
34 | // 이펙트가 실행 중이 아니면
35 | if (isEffect == false)
36 | {
37 | // disableDelayTime 동안 부모와 상속 해제
38 | StopCoroutine(EffectDisableDelayTime());
39 | StartCoroutine(EffectDisableDelayTime());
40 | }
41 | }
42 |
43 | // 플레이어가 움직이더라도 스킬 이펙트가 활성화되야 한다면 사용
44 | IEnumerator EffectDisableDelayTime()
45 | {
46 | isEffect = true;
47 |
48 | Transform effectParent = transform.parent; // 이펙트 부모
49 | Vector3 effectPos = transform.localPosition; // 이펙트 위치
50 |
51 | // 부모 빠져나오기
52 | transform.SetParent(null);
53 |
54 | // 이펙트 비활성화 기다리기
55 | yield return new WaitForSeconds(disableDelayTime);
56 |
57 | // 원위치 이동 후 비활성화
58 | transform.SetParent(effectParent);
59 | transform.localPosition = effectPos;
60 | transform.localRotation = Quaternion.identity;
61 |
62 | isEffect = false;
63 |
64 | gameObject.SetActive(false);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/Scripts/Contents/EffectParticle.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using UnityEngine;
5 |
6 | /*
7 | * File : EffectParticle.cs
8 | * Desc : ParticleSystem의 물리적 접촉이 필요할 때 사용
9 | *
10 | & Functions
11 | & [Public]
12 | & : SetInfo() - 설정
13 | &
14 | & [Private]
15 | & : ParticleCollider() - 파티클 접촉 시 호출
16 | & : OnParticleCollision() - 파티클 접촉 확인
17 | *
18 | */
19 |
20 | public class EffectParticle : Effect
21 | {
22 | Action _onParticleCollider; // 파티클 접촉 시 실행시킬 기능 저장
23 |
24 | // 설정
25 | public void SetInfo(Action onParticleCollider)
26 | {
27 | _onParticleCollider = onParticleCollider;
28 | }
29 |
30 | // 파티클 접촉 시 호출
31 | private void ParticleCollider()
32 | {
33 | if (_onParticleCollider.IsNull() == false)
34 | {
35 | _onParticleCollider.Invoke();
36 | _onParticleCollider = null;
37 | }
38 | }
39 |
40 | // 파티클 물리적 접촉 확인
41 | private void OnParticleCollision(GameObject other)
42 | {
43 | // 플레이어가 접촉하면 True
44 | if (other.CompareTag("Player"))
45 | ParticleCollider();
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Scripts/Contents/ItemPickUp.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 |
5 | /*
6 | * File : ItemPickUp.cs
7 | * Desc : 땅에 떨어진 아이템의 정보를 가지고 있으며 플레이어와 가까우면 이름 생성
8 | *
9 | & Functions
10 | & : Start() - 이름바 생성
11 | & : FixedUpdate() - 플레이어 근접 시 이름바 활성화
12 | *
13 | */
14 |
15 | public class ItemPickUp : MonoBehaviour
16 | {
17 | public ItemData item;
18 | public int itemCount = 1; // 아이템 전용 개수
19 |
20 | private float scanRange = 5f; // 플레이어 스캔 거리
21 |
22 | private UI_NameBar nameBarUI = null;
23 |
24 | void Start()
25 | {
26 | // 이름바 생성 및 자식으로 배치
27 | nameBarUI = Managers.UI.MakeWorldSpaceUI(transform);
28 | if (itemCount > 1)
29 | nameBarUI.nameText = item.itemName + $" ({itemCount})";
30 | else
31 | nameBarUI.nameText = item.itemName;
32 |
33 | nameBarUI.nameText += " [F]";
34 | }
35 |
36 | void FixedUpdate()
37 | {
38 | // 이름바 Null Check
39 | if (nameBarUI.IsNull() == false)
40 | {
41 | // 플레이어 Null Check
42 | if (Managers.Game.GetPlayer().IsNull() == true)
43 | return;
44 |
45 | // 플레이어와 거리 체크
46 | float distance = (Managers.Game.GetPlayer().transform.position - transform.position).magnitude;
47 |
48 | // scanRange만큼 가까우면 활성화
49 | if (distance <= scanRange)
50 | nameBarUI.gameObject.SetActive(true);
51 | else
52 | nameBarUI.gameObject.SetActive(false);
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/Scripts/Contents/MonsterStat.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 |
5 | /*
6 | * File : MonsterStat.cs
7 | * Desc : 몬스터 기본 정보, 피격, 드랍 아이템을 관리
8 | *
9 | & Functions
10 | & [Public]
11 | & : OnAttacked() - 공격 받을 때 호출
12 | &
13 | & [Protected]
14 | & : OnDead() - 사망 시 호출
15 | &
16 | & [Private]
17 | & : OnDropItem() - 드랍 아이템 관리
18 | & : HitEffect() - 피격 이펙트 생성
19 | *
20 | */
21 |
22 | public class MonsterStat : MonoBehaviour
23 | {
24 | [SerializeField] protected int _id;
25 | [SerializeField] protected string _name = "Monster";
26 | [SerializeField] protected int _hp;
27 | [SerializeField] protected int _maxHp;
28 | [SerializeField] protected int _attack;
29 | [SerializeField] protected int _dropExp;
30 | [SerializeField] protected int _dropGold;
31 | [SerializeField] protected int _dropItemId = 0;
32 | [SerializeField] protected float _movespeed;
33 |
34 | public int Id { get { return _id; } set { _id = value; } }
35 | public string Name { get { return _name; } set { _name = value; } }
36 | public int Hp { get { return _hp; } set { _hp = Mathf.Clamp(value, 0, MaxHp); } }
37 | public int MaxHp { get { return _maxHp; } set { _maxHp = value; Hp = MaxHp; } }
38 | public int Attack { get { return _attack; } set { _attack = value; } }
39 | public int DropExp { get { return _dropExp; } set { _dropExp = value; } }
40 | public int DropGold { get { return _dropGold; } set { _dropGold = value; } }
41 | public int DropItemId { get { return _dropItemId; } set { _dropItemId = value; } }
42 | public float MoveSpeed { get { return _movespeed; } set { _movespeed = value; } }
43 |
44 | void Start()
45 | {
46 | _monster = GetComponent();
47 |
48 | GameObject go;
49 | if (Managers.Data.Monster.TryGetValue(Id, out go) == true)
50 | {
51 | MonsterStat stat = go.GetComponent();
52 | _name = stat.Name;
53 | _hp = stat.Hp;
54 | _maxHp = stat.MaxHp;
55 | _attack = stat.Attack;
56 | _dropExp = stat.DropExp;
57 | _dropGold = stat.DropGold;
58 | _dropItemId = stat.DropItemId;
59 | _movespeed = stat.MoveSpeed;
60 | }
61 | }
62 |
63 | // 공격을 받았을 때
64 | MonsterController _monster;
65 | public virtual void OnAttacked(int skillAttack=0)
66 | {
67 | // 일반 몬스터만 피격 상태 진행
68 | if (_monster.monsterType == Define.MonsterType.Normal)
69 | _monster.State = Define.State.Hit;
70 |
71 | // Scene UI에 몬스터 정보 활성화
72 | Managers.Game._playScene.OnMonsterBar(this);
73 |
74 | int damage;
75 | // 스킬 데미지 확인
76 | if (skillAttack != 0)
77 | damage = Mathf.Max(0, skillAttack);
78 | else
79 | damage = Mathf.Max(0, Managers.Game.Attack);
80 |
81 | // 체력 차감
82 | Hp -= damage;
83 |
84 | // 피격 이펙트 생성
85 | HitEffect(damage);
86 |
87 | // 체력이 0보다 작으면 사망
88 | if (Hp <= 0)
89 | {
90 | Hp = 0;
91 | OnDead();
92 | }
93 | }
94 |
95 | // 죽었을 때
96 | protected virtual void OnDead()
97 | {
98 | _monster.State = Define.State.Die;
99 |
100 | // 보상 반영
101 | Managers.Game.Exp += _dropExp;
102 | Managers.Game.Gold += _dropGold;
103 |
104 | // 퀘스트 정보 반영
105 | Managers.Game._playScene._quest.QuestTargetCount(gameObject);
106 |
107 | // Scene UI 몬스터 정보 비활성화
108 | Managers.Game._playScene.CloseMonsterBar();
109 |
110 | // 아이템 드랍
111 | OnDropItem();
112 |
113 | // 전투 종료
114 | _monster.BattleClose();
115 | }
116 |
117 | // 드랍 아이템
118 | private void OnDropItem()
119 | {
120 | // DataManager에서 DropItem List가져오기
121 | List itemList = Managers.Data.DropItem[_dropItemId];
122 |
123 | // 아이탬 개수 0~2 + Luk (최대 5개까지)
124 | int maxCount = Mathf.Clamp(2 + Managers.Game.LUK, 0, 5);
125 |
126 | for(int i=0; i();
137 | goData.item = item;
138 |
139 | // 드랍 위치 설정
140 | float ranPos = Random.Range(-0.5f, 0.5f);
141 | go.transform.position = new Vector3(transform.position.x + ranPos, 0f, transform.position.z + ranPos);
142 | }
143 | }
144 |
145 | // 피격 데미지 출력
146 | private void HitEffect(int damage)
147 | {
148 | // UI_HitEffect 생성 후 데미지 text 넣기
149 | UI_HitEffect hitObject = Managers.UI.MakeWorldSpaceUI(gameObject.transform);
150 | hitObject.hitText.text = damage.ToString();
151 |
152 | // 생성 위치 설정
153 | float randomX = Random.Range(-0.5f, 0.5f);
154 | float valueY = GetComponent().bounds.size.y * 0.8f;
155 | hitObject.transform.position = transform.position + new Vector3(randomX, valueY, 0);
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/Scripts/Contents/Portal.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 |
5 | /*
6 | * File : Portal.cs
7 | * Desc : 포탈 생성 및 Scene 이동
8 | *
9 | & Functions
10 | & : OnTriggerEnter() - 플레이어 Enter 확인 후 포탈 활성화
11 | & : OnTriggerStay() - 플레이어와 근접하면 Scene Load
12 | & : OnTriggerExit() - 플레이어 Exit 확인 후 포탈 비활성화
13 | *
14 | */
15 |
16 | public class Portal : MonoBehaviour
17 | {
18 | private float scanRange = 3.2f; // 플레이어 스캔 거리
19 | private bool isPortal = false; // 포탈 접촉 여부
20 |
21 | [SerializeField]
22 | private Define.Scene sceneType; // Load할 Scene 타입
23 |
24 | [SerializeField]
25 | private GameObject portalObject; // 포탈 객체
26 |
27 | void OnTriggerEnter(Collider other)
28 | {
29 | // 플레이어 체크
30 | if (other.CompareTag("Player"))
31 | {
32 | isPortal = false;
33 | portalObject.SetActive(true);
34 | }
35 | }
36 |
37 | void OnTriggerStay(Collider other)
38 | {
39 | // 포탈 활성화 체크
40 | if (portalObject.activeSelf == true && isPortal == false)
41 | {
42 | // 플레이어와 포탈이 근접한지 체크
43 | float distance = (Managers.Game.GetPlayer().transform.position - portalObject.transform.position).magnitude;
44 | if (distance <= scanRange)
45 | {
46 | isPortal = true;
47 |
48 | // 플레이어 정지
49 | Managers.Game.StopPlayer();
50 |
51 | // 현재 Scene이 Game Scene라면
52 | if (Managers.Scene.CurrentScene.SceneType == Define.Scene.Game)
53 | {
54 | // 확인 Popup 활성화
55 | UI_ConfirmPopup confirmPopup = Managers.UI.ShowPopupUI();
56 | if (confirmPopup.IsNull() == true)
57 | return;
58 |
59 | // 확인 Popup 설정
60 | confirmPopup.SetInfo(()=>
61 | {
62 | // 게임 세이브
63 | Managers.Game.SaveGame();
64 |
65 | // 씬 이동 전 위치 저장
66 | Managers.Game.CurrentPos += Vector3.forward * (-3f);
67 |
68 | // 씬 로드
69 | Managers.Scene.LoadScene(sceneType);
70 | }, Define.DungeonMessage);
71 | }
72 | else
73 | Managers.Scene.LoadScene(sceneType);
74 | }
75 | }
76 | }
77 |
78 | void OnTriggerExit(Collider other)
79 | {
80 | if (other.CompareTag("Player"))
81 | {
82 | portalObject.SetActive(false);
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/Scripts/Contents/SpawningPool.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 | using UnityEngine.AI;
5 |
6 | /*
7 | * File : SpawningPool.cs
8 | * Desc : 몬스터 생성
9 | *
10 | & Functions
11 | & [Public]
12 | & : AddMonsterCount() - 몬스터 수 증가
13 | & : SetKeepMonsterCount() - 최대 몬스터 지정
14 | &
15 | & [Private]
16 | & : ReserveSpawn() - 몬스터 스폰 코루틴
17 | *
18 | */
19 |
20 | public class SpawningPool : MonoBehaviour
21 | {
22 | public GameObject _spawnMonsterNumber; // 몬스터 Prefab
23 |
24 | [SerializeField]
25 | private Vector3 _spawnPos; // 스폰 위치
26 |
27 | [SerializeField]
28 | private float _spawnRedius = 5f; // 스폰 최대 거리
29 |
30 | [SerializeField]
31 | private float _spawnTime = 5f; // 스폰 최대 시간
32 |
33 | [SerializeField]
34 | private int _monsterCount = 0; // 현재 몬스터 수
35 | private int _reserveCount = 0; // 임시 변수 (에러 방지)
36 |
37 | [SerializeField]
38 | private int _keepMonsterCount = 0; // 최대 몬스터 수
39 |
40 | // 몬스터 수 증가
41 | public void AddMonsterCount(Transform parent, int value)
42 | {
43 | // 스포너 부모 체크
44 | if (transform == parent)
45 | this._monsterCount += value;
46 | }
47 | // 최대 몬스터 지정
48 | public void SetKeepMonsterCount(int count) { this._keepMonsterCount = count; }
49 |
50 | void Start()
51 | {
52 | Managers.Game.OnSpawnEvent -= AddMonsterCount;
53 | Managers.Game.OnSpawnEvent += AddMonsterCount;
54 | }
55 |
56 | void Update()
57 | {
58 | // 최대 몬스터 수 만큼 생성
59 | while((_reserveCount + _monsterCount) < _keepMonsterCount)
60 | StartCoroutine("ReserveSpawn");
61 | }
62 |
63 | // 몬스터 스폰 설정
64 | private IEnumerator ReserveSpawn()
65 | {
66 | // 코루틴이 시작하자마자 시간을 기다리므로 _monsterCount가 증가하지 못해 Update의 while에서 Error가 발생할 수 있다.
67 | // 그러므로 _reserveCount를 사용해 while을 끝내도록 한다.
68 | _reserveCount++;
69 |
70 | yield return new WaitForSeconds(Random.Range(1, _spawnTime));
71 |
72 | // 몬스터 생성
73 | GameObject obj = Managers.Game.Spawn(Define.WorldObject.Monster, _spawnMonsterNumber, transform);
74 | NavMeshAgent nav = obj.GetOrAddComponent();
75 |
76 | Vector3 randPos;
77 |
78 | // 소환 가능한 위치를 찾을 때까지 루프
79 | while(true)
80 | {
81 | Vector3 randDir = Random.insideUnitSphere * _spawnRedius; // 원 형태 랜덤 벡터 지정
82 | randDir.y = 0;
83 | randPos = _spawnPos + randDir;
84 |
85 | NavMeshPath path = new NavMeshPath();
86 | if (nav.CalculatePath(randPos, path)) // randPos 위치에 소환 가능 여부 확인
87 | {
88 | obj.transform.position = randPos;
89 | break;
90 | }
91 | }
92 |
93 | // 위치 설정
94 | nav.nextPosition = randPos;
95 | obj.GetComponent().spawnPos = randPos;
96 |
97 | // while의 에러 예방 목적인 변수이므로 코루틴이 끝날땐 --를 해준다.
98 | _reserveCount--;
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/Scripts/Controllers/BaseController.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 |
5 | /*
6 | * File : BaseController.cs
7 | * Desc : 상태 패턴을 사용하며 상태에 따라 Animation CrossFade를 실행한다.
8 | * 모든 Controller는 BaseController를 상속받는다.
9 | *
10 | & Functions
11 | & [Public]
12 | & : Init() - 초기 설정 (abstract)
13 | &
14 | & [protected]
15 | & : AnimAttack() - 공격 Animation 설정
16 | & : UpdateMoving() - 움직일 시 Update
17 | & : UpdateDiveRoll() - 구르기 시 Update
18 | & : UpdateIdle() - 멈춤일 시 Update
19 | & : UpdateAttack() - 공격할 시 Update
20 | & : UpdateSkill() - 스킬사용 시 Update
21 | & : UpdateHit() - 공격 받을 시 Update
22 | & : UpdateDie() - 죽을 시 Update
23 | *
24 | */
25 |
26 | public abstract class BaseController : MonoBehaviour
27 | {
28 | [SerializeField]
29 | public Define.WorldObject WorldObjectType { get; protected set; } = Define.WorldObject.Unknown;
30 |
31 | [SerializeField]
32 | protected GameObject _lockTarget; // 마우스로 타겟한 오브젝트 담는 변수
33 |
34 | [SerializeField]
35 | protected Vector3 _destPos; // 도착 좌표
36 |
37 | [SerializeField]
38 | protected Define.State _state = Define.State.Idle; // 상태 변수
39 |
40 | protected int attackNumber = 1; // 일반 공격 콤보 체크
41 |
42 | protected Animator anim;
43 | protected RaycastHit hit;
44 |
45 | // 캐릭터 상태에 따라 애니메이션 작동
46 | public virtual Define.State State
47 | {
48 | get { return _state; }
49 | set {
50 | _state = value;
51 |
52 | switch (_state)
53 | {
54 | case Define.State.Moving:
55 | anim.CrossFade("RUN", 0.1f);
56 | break;
57 | case Define.State.Idle:
58 | anim.CrossFade("WAIT", 0.4f);
59 | break;
60 | case Define.State.DiveRoll:
61 | anim.CrossFade("DIVEROLL", 0.1f, -1, 0);
62 | break;
63 | case Define.State.Attack:
64 | AnimAttack();
65 | break;
66 | case Define.State.Hit:
67 | anim.CrossFade("HIT", 0.1f, -1, 0);
68 | break;
69 | case Define.State.Down:
70 | anim.CrossFade("DOWN", 0.1f, -1, 0);
71 | break;
72 | case Define.State.Die:
73 | anim.CrossFade("DIE", 0.1f, -1, 0);
74 | break;
75 | }
76 | }
77 | }
78 |
79 | void Start()
80 | {
81 | Init();
82 | _lockTarget = null;
83 | }
84 |
85 | // Playe, NPC 전용 ( 키 입력이 필요한 경우 )
86 | void Update()
87 | {
88 | if (WorldObjectType == Define.WorldObject.Monster)
89 | return;
90 |
91 | switch (State)
92 | {
93 | case Define.State.Moving: // 움직임
94 | UpdateMoving();
95 | break;
96 | case Define.State.DiveRoll: // 구르기
97 | UpdateDiveRoll();
98 | break;
99 | case Define.State.Idle: // 가만히 있기
100 | UpdateIdle();
101 | break;
102 | case Define.State.Attack: // 일반 공격
103 | UpdateAttack();
104 | break;
105 | case Define.State.Skill: // 스킬
106 | UpdateSkill();
107 | break;
108 | case Define.State.Hit: // 피격
109 | UpdateHit();
110 | break;
111 | case Define.State.Die: // 죽음
112 | UpdateDie();
113 | break;
114 | }
115 | }
116 |
117 | // Monster 전용
118 | void FixedUpdate()
119 | {
120 | if (WorldObjectType != Define.WorldObject.Monster)
121 | return;
122 |
123 | switch (State)
124 | {
125 | case Define.State.Moving: // 움직임
126 | UpdateMoving();
127 | break;
128 | case Define.State.Idle: // 가만히 있기
129 | UpdateIdle();
130 | break;
131 | case Define.State.Attack: // 일반 공격
132 | UpdateAttack();
133 | break;
134 | case Define.State.Skill: // 스킬
135 | UpdateSkill();
136 | break;
137 | case Define.State.Hit: // 피격
138 | UpdateHit();
139 | break;
140 | case Define.State.Die: // 죽음
141 | UpdateDie();
142 | break;
143 | }
144 | }
145 |
146 | public abstract void Init();
147 |
148 | // 기본 공격 애니메이션
149 | protected virtual void AnimAttack()
150 | {
151 | anim.CrossFade("ATTACK"+attackNumber, 0.1f, -1, 0);
152 |
153 | if (attackNumber == 1)
154 | attackNumber = 2;
155 | else if (attackNumber == 2)
156 | attackNumber = 1;
157 | }
158 |
159 | protected virtual void UpdateMoving() {}
160 | protected virtual void UpdateDiveRoll() {}
161 | protected virtual void UpdateIdle() {}
162 | protected virtual void UpdateAttack() {}
163 | protected virtual void UpdateSkill() {}
164 | protected virtual void UpdateHit() {}
165 | protected virtual void UpdateDie() {}
166 | }
167 |
--------------------------------------------------------------------------------
/Scripts/Controllers/Monster/MonsterAttackCollistion.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 |
5 | /*
6 | * File : MonsterAttackCollistion.cs
7 | * Desc : 몬스터 콜라이더 공격
8 | *
9 | & Functions
10 | & [Public]
11 | & : IsCollider() - 콜라이더 여부 설정
12 | &
13 | & [Private]
14 | & : OnTriggerEnter() - 플레이어와 접촉 시 데미지 반영
15 | *
16 | */
17 |
18 | public class MonsterAttackCollistion : MonoBehaviour
19 | {
20 | public int damage;
21 |
22 | [SerializeField]
23 | private BoxCollider boxCollider;
24 |
25 | public void IsCollider(bool isActive) { boxCollider.enabled = isActive; }
26 |
27 | void OnTriggerEnter(Collider other)
28 | {
29 | if (other.CompareTag("Player") == true)
30 | Managers.Game.OnAttacked(damage);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Scripts/Controllers/Npc/NpcController.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 |
5 | /*
6 | * File : NpcController.cs
7 | * Desc : NPC 기본 기능 및 모든 NPC의 부모
8 | * 모든 NPC는 플레이어와 상호작용하기 때문에 OnInteract(), OpenPopup(), ExitPopup()를 상속 받아 구현
9 | *
10 | & Functions
11 | & [Public]
12 | & : Init() - 초기 설정
13 | & : GetInteract() - 상호작용 외부 접근
14 | &
15 | & [Protected]
16 | & : UpdateIdle() - 멈춤일 때 Update (플레이어 감지)
17 | & : OnInteract() - 상호작용 기능
18 | & : OpenPopup() - Popup 활성화
19 | & : ExitPopup() - Popup 비활성화
20 | &
21 | & [Private]
22 | & : InteractCheck() - 상호작용 확인
23 | *
24 | */
25 |
26 | public abstract class NpcController : BaseController
27 | {
28 | [SerializeField]
29 | protected string npcName; // npc 이름
30 | [SerializeField]
31 | protected float scanRange; // 플레이어 스캔 사거리
32 |
33 | protected UI_NameBar nameBarUI; // 이름바 UI
34 |
35 | public override void Init()
36 | {
37 | // 이름바 생성 및 이름 설정
38 | nameBarUI = Managers.UI.MakeWorldSpaceUI(transform);
39 | nameBarUI.nameText = npcName + " [G]";
40 | }
41 |
42 | // 상호작용 외부 접근
43 | public void GetInteract() { OnInteract(); }
44 |
45 | protected override void UpdateIdle()
46 | {
47 | // 플레이어 Null Check
48 | if (Managers.Game.GetPlayer().IsNull() == true)
49 | return;
50 |
51 | // 플레이어와의 거리 좌표
52 | Vector3 direction = Managers.Game.GetPlayer().transform.position - transform.position;
53 |
54 | // 거리 체크
55 | if (direction.magnitude <= scanRange)
56 | {
57 | // 상호작용 체크
58 | InteractCheck();
59 |
60 | _lockTarget = Managers.Game.GetPlayer(); // 타겟 설정
61 | nameBarUI.gameObject.SetActive(true); // 이름바 활성화
62 |
63 | // 방향 설정
64 | direction.y = 0;
65 | transform.rotation = Quaternion.LookRotation(direction);
66 | }
67 | else
68 | {
69 | _lockTarget = null;
70 | nameBarUI.gameObject.SetActive(false);
71 | }
72 | }
73 |
74 | // 상호작용 기능
75 | protected virtual void OnInteract()
76 | {
77 | Managers.Game.IsInteract = !Managers.Game.IsInteract;
78 |
79 | // 상호작용 시작
80 | if (Managers.Game.IsInteract)
81 | {
82 | // 모든 팝업 비활성화 및 플레이어 정지
83 | Managers.UI.CloseAllPopupUI();
84 | Managers.Game.StopPlayer();
85 |
86 | OpenPopup(); // Popup Open
87 | }
88 | else
89 | ExitPopup(); // Popup Exit
90 | }
91 |
92 | // Popup Open/Exit
93 | protected virtual void OpenPopup() {}
94 | protected virtual void ExitPopup() {}
95 |
96 | // 플레이어가 가까이 있다면 상호작용 가능
97 | private void InteractCheck()
98 | {
99 | if (Input.GetKeyDown(KeyCode.G))
100 | OnInteract();
101 |
102 | // 상호작용 중이라면
103 | if (Managers.Game.IsInteract == true)
104 | {
105 | // Esc Key 상호작용 종료
106 | if (Input.GetKeyDown(KeyCode.Escape))
107 | OnInteract();
108 | }
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/Scripts/Controllers/Npc/QuestNpcController.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 |
5 | /*
6 | * File : QuestNpcController.cs
7 | * Desc : 퀘스트 NPC 기능 구현
8 | * 대화의 종류는 일반, 퀘스트 시작, 퀘스트 거절, 퀘스트 수락, 퀘스트 진행, 퀘스트 성공으로 총 6가지 존재.
9 | * 퀘스트를 가져오는 방법은 questId, talkId를 미리 유니티상의 컴포넌트에서 id를 입력시켜준다.
10 | * 입력된 Id를 가지고 DataManager에 보내어 퀘스트를 받아온다.
11 | *
12 | & Functions
13 | & [Public]
14 | & : Init() - 초기 설정
15 | &
16 | & [Protected]
17 | & : OnInteract() - 상호작용 기능 구현
18 | & : OpenPopup() - Popup 활성화 (TalkCheck() 호출)
19 | &
20 | & [Private]
21 | & : TalkCheck() - 대화 확인
22 | & : OnTalk() - 대화 시작
23 | & : NextQuestCheck() - 다음 퀘스트 확인
24 | & : NoticeUpdate() - 알람 업데이트
25 | *
26 | */
27 |
28 | public class QuestNpcController : NpcController
29 | {
30 | public Define.QuestType questType; // 퀘스트 타입
31 |
32 | [SerializeField]
33 | private int[] questId; // 퀘스트 id Array
34 | [SerializeField]
35 | private int[] talkId; // 대화 id Array
36 |
37 | private int nextQuestIndex; // 다음 퀘스트 Index
38 |
39 | private bool isQuest; // 퀘스트가 존재하는가?
40 |
41 | private QuestData currentQuest; // 현재 퀘스트
42 | private TalkData currentTalk; // 현재 대화
43 |
44 | private List questDataList; // 퀘스트 List
45 | private List talkDataList; // 대화 List
46 |
47 | private UI_QuestNotice noticeObject; // 알람 Prefab
48 |
49 | public override void Init()
50 | {
51 | base.Init();
52 |
53 | questDataList = new List();
54 | talkDataList = new List();
55 |
56 | // id에 맞게 퀘스트, 대화 데이터 가져오기
57 | for(int i=0; i= currentQuest.targetCount)
112 | {
113 | // 인벤토리가 꽉찼는지 확인
114 | if (Managers.Game._playScene._inventory.IsInvenMaxSize() == true)
115 | {
116 | // 경고 안내문 생성
117 | Managers.UI.MakeSubItem().SetInfo("인벤토리가 가득 찼습니다.", Color.red);
118 | return;
119 | }
120 |
121 | // Scene UI 알람이 등록된 퀘스트라면 삭제
122 | Managers.Game._playScene._quest.CloseQuestNotice(currentQuest);
123 |
124 | OnTalk(currentTalk.clearTalk); // 클리어 대화 시작
125 | currentQuest.QuestClear(); // 퀘스트 클리어 (보상 지급)
126 |
127 | Managers.Game.RefreshQuest(); // 클리어 퀘스트에 등록
128 | NextQuestCheck(); // 다음 퀘스트 확인
129 | }
130 | else
131 | OnTalk(currentTalk.procTalk); // 퀘스트 진행 대화 시작
132 |
133 | return;
134 | }
135 |
136 | // 레벨이 되면 퀘스트 대화 시작
137 | if (currentQuest.minLevel <= Managers.Game.Level)
138 | {
139 | OnTalk(currentTalk);
140 | return;
141 | }
142 |
143 | // 여기까지 오면 받을 퀘스트가 없기에 기본 대화 진행
144 | OnTalk(currentTalk.basicsTalk);
145 | }
146 |
147 | // 기본 하나의 대화 시작
148 | private void OnTalk(string text)
149 | {
150 | UI_TalkPopup talkPopup = Managers.Game._playScene._talk;
151 |
152 | Managers.UI.OnPopupUI(talkPopup); // Popup 활성화
153 | talkPopup.SetInfo(text, npcName); // 대화 세팅
154 | }
155 |
156 | // TalkData를 그대로 보낼 시 퀘스트 시작으로 판정
157 | private void OnTalk(TalkData texts)
158 | {
159 | UI_TalkPopup talkPopup = Managers.Game._playScene._talk;
160 |
161 | Managers.UI.OnPopupUI(talkPopup); // Popup 활성화
162 | talkPopup.SetInfo(texts, currentQuest, npcName); // 대화 세팅
163 | }
164 |
165 | // 다음 퀘스트
166 | private void NextQuestCheck()
167 | {
168 | nextQuestIndex++;
169 |
170 | // 퀘스트 개수 확인
171 | if (nextQuestIndex == questDataList.Count)
172 | {
173 | isQuest = false;
174 | return;
175 | }
176 |
177 | // 퀘스트, 대화 적용
178 | currentQuest = questDataList[nextQuestIndex];
179 | currentTalk = talkDataList[nextQuestIndex];
180 |
181 | isQuest = true;
182 | }
183 |
184 | // 알람 업데이트 ( 실시간 npc 위의 퀘스트 상태 표시 )
185 | private void NoticeUpdate()
186 | {
187 | // Null Check
188 | if (noticeObject.IsNull() == true)
189 | return;
190 |
191 | // 레벨 확인
192 | if (currentQuest.minLevel > Managers.Game.Level)
193 | return;
194 |
195 | // 이미 퀘스트를 클리어 했는가? or 퀘스트가 없거나
196 | if (currentQuest.isClear == true || isQuest == false)
197 | {
198 | noticeObject.SetInfo("", transform.position);
199 | return;
200 | }
201 |
202 | // 퀘스트 목표 달성 확인
203 | if (currentQuest.currnetTargetCount >= currentQuest.targetCount)
204 | {
205 | noticeObject.SetInfo("?");
206 | return;
207 | }
208 |
209 | // 퀘스트 수락 확인
210 | if (currentQuest.isAccept == true)
211 | noticeObject.SetInfo("", transform.position);
212 | else
213 | noticeObject.SetInfo("!", transform.position);
214 | }
215 |
216 | private void DelayInit()
217 | {
218 | // 현재 클리어한 퀘스트가 있는지
219 | for(int i=0; i();
236 | }
237 | }
238 |
--------------------------------------------------------------------------------
/Scripts/Controllers/Npc/ShopNpcController.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 |
5 | /*
6 | * File : ShopNpcController.cs
7 | * Desc : 상점 Npc 기능 구현
8 | *
9 | & Functions
10 | & [Protected]
11 | & : OpenPopup() - Popup 활성화 (OpenShop() 호출)
12 | & : ExitPopup() - Popup 비활성화 (ExitShop() 호출)
13 | &
14 | & [Private]
15 | & : OpenShop() - 상점 Popup 열기
16 | & : ExitShop() - 상점 Popup 나가기
17 | *
18 | */
19 |
20 | public class ShopNpcController : NpcController
21 | {
22 | public Define.ShopType shopType = Define.ShopType.Unknown;
23 |
24 | [SerializeField]
25 | private int shopBuyId; // Shop Npc 구매 목록 Id
26 |
27 | protected override void OpenPopup() { OpenShop(); }
28 | protected override void ExitPopup() { ExitShop(); }
29 |
30 | private void OpenShop()
31 | {
32 | // 상점 Popup 활성화
33 | Managers.UI.OnPopupUI(Managers.Game._playScene._shop);
34 | Managers.Game._playScene._shop.RefreshUI(this, shopBuyId);
35 |
36 | // 인벤토리 Popup 활성화
37 | Managers.UI.OnPopupUI(Managers.Game._playScene._inventory);
38 | Managers.Game._playScene._inventory.ResetPos();
39 | }
40 |
41 | private void ExitShop()
42 | {
43 | Managers.Game._playScene._shop.ExitShop();
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Scripts/Controllers/Npc/UpgradeNpcController.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 |
5 | /*
6 | * File : UpgradeNpcController.cs
7 | * Desc : 장비 강화 NPC 기능 구현
8 | *
9 | & Functions
10 | & [Protected]
11 | & : OpenPopup() - Popup 활성화 (OpenUpgrade() 호출)
12 | & : ExitPopup() - Popup 비활성화 (ExitUpgrade() 호출)
13 | &
14 | & [Private]
15 | & : OpenUpgrade() - 업그레이드 Popup 열기
16 | & : ExitUpgrade() - 업그레이드 Popup 나가기
17 | *
18 | */
19 |
20 | public class UpgradeNpcController : NpcController
21 | {
22 | protected override void OpenPopup() { OpenUpgrade(); }
23 | protected override void ExitPopup() { ExitUpgrade(); }
24 |
25 | private void OpenUpgrade()
26 | {
27 | // 업그레이드 Popup 활성화
28 | Managers.UI.OnPopupUI(Managers.Game._playScene._upgrade);
29 |
30 | // 인벤토리 Popup 활성화
31 | Managers.UI.OnPopupUI(Managers.Game._playScene._inventory);
32 | Managers.Game._playScene._inventory.ResetPos();
33 | }
34 |
35 | private void ExitUpgrade()
36 | {
37 | Managers.Game._playScene._upgrade.ExitUpgrade();
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Scripts/Controllers/Player/CameraController.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 |
5 | /*
6 | * File : CameraController.cs
7 | * Desc : 플레이어를 쿼터뷰 모드로 따라다니는 카메라 기능
8 | *
9 | & Functions
10 | & [Public]
11 | & : SetPlayer() - 플레이어 Prefab 받기
12 | &
13 | & [Private]
14 | & : QuarterViewUpdate() - 쿼터뷰 모드로 플레이어 따라가기
15 | *
16 | */
17 |
18 | public class CameraController : MonoBehaviour
19 | {
20 | [SerializeField]
21 | private Define.CameraMode _mode = Define.CameraMode.QuarterView;
22 | [SerializeField]
23 | private Vector3 _delta;
24 | [SerializeField]
25 | private GameObject _player = null;
26 |
27 | private RaycastHit hit;
28 |
29 | public void SetPlayer(GameObject go) { _player = go; }
30 |
31 | void LateUpdate()
32 | {
33 | QuarterViewUpdate();
34 | }
35 |
36 | // 카메라 위치 이동을 마지막 업데이트에 실행함으로 써 떨림현상 완화
37 | private void QuarterViewUpdate()
38 | {
39 | if (_mode == Define.CameraMode.QuarterView)
40 | {
41 | if (_player.isValid() == false)
42 | return;
43 |
44 | // 플레이어가 오브젝트에 가려져있다면 가깝게 이동
45 | if (Physics.Raycast(_player.transform.position, _delta, out hit, _delta.magnitude, 1 << 10)) // 10 : Block
46 | {
47 | float dist = (hit.point - _player.transform.position).magnitude * 0.8f;
48 | transform.position = (_player.transform.position + Vector3.up) + _delta.normalized * dist;
49 | }
50 | else
51 | {
52 | transform.position = _player.transform.position + _delta;
53 | transform.LookAt(_player.transform);
54 | }
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/Scripts/Controllers/Player/CursorController.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 |
5 | /*
6 | * File : CursorController.cs
7 | * Desc : 마우스 커서 icon을 상황마다 바꿔주는 기능
8 | *
9 | & Functions
10 | & [Private]
11 | & : CursorUpdate() - 상황마다 마우스 커서 Update
12 | *
13 | */
14 |
15 | public class CursorController : MonoBehaviour
16 | {
17 | // 마우스 커서 상태
18 | public enum CursorType
19 | {
20 | None,
21 | Attack,
22 | Hand,
23 | Loot,
24 | }
25 |
26 | private CursorType _cursorType = CursorType.None;
27 |
28 | private RaycastHit hit;
29 | private Texture2D _attackIcon; // 공격 icon
30 | private Texture2D _handIcon; // 기본 icon
31 | private Texture2D _lootIcon; // npc icon
32 |
33 | private int _mask = (1 << (int)Define.Layer.Ground) | (1 << (int)Define.Layer.Monster) | (1 << (int)Define.Layer.Npc);
34 |
35 | void Start()
36 | {
37 | // 커서 텍스쳐 가져오기
38 | _attackIcon = Managers.Resource.Load("Textures/Cursor/Attack");
39 | _handIcon = Managers.Resource.Load("Textures/Cursor/Hand");
40 | _lootIcon = Managers.Resource.Load("Textures/Cursor/Loot");
41 |
42 | // Hand icon 커서에 적용
43 | Cursor.SetCursor(_handIcon, new Vector2(_handIcon.width / 3.1f, 0), CursorMode.Auto);
44 | _cursorType = CursorType.Hand;
45 | }
46 |
47 | void Update()
48 | {
49 | CursorUpdate();
50 | }
51 |
52 | private void CursorUpdate()
53 | {
54 | // 꾹 누르면 아이콘 유지
55 | if (Input.GetMouseButton(0))
56 | return;
57 |
58 | // 마우스 포인트 가져오기
59 | Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
60 |
61 | if (Physics.Raycast(ray, out hit, 100f, _mask))
62 | {
63 | // NPC
64 | if (hit.collider.gameObject.layer == (int)Define.Layer.Npc)
65 | {
66 | if (_cursorType != CursorType.Loot)
67 | {
68 | Cursor.SetCursor(_lootIcon, new Vector2(_lootIcon.width / 4.5f, _lootIcon.height / 2), CursorMode.Auto);
69 | _cursorType = CursorType.Loot;
70 | }
71 | return;
72 | }
73 | // Monster
74 | else if (hit.collider.gameObject.layer == (int)Define.Layer.Monster)
75 | {
76 | if (_cursorType != CursorType.Attack)
77 | {
78 | Cursor.SetCursor(_attackIcon, new Vector2(_attackIcon.width / 3.9f, 0), CursorMode.Auto);
79 | _cursorType = CursorType.Attack;
80 | }
81 | return;
82 | }
83 | // Default
84 | else if (_cursorType != CursorType.Hand)
85 | {
86 | Cursor.SetCursor(_handIcon, new Vector2(_handIcon.width / 3.1f, 0), CursorMode.Auto);
87 | _cursorType = CursorType.Hand;
88 | }
89 | }
90 | }
91 | }
--------------------------------------------------------------------------------
/Scripts/Controllers/Player/MapCameraController.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 |
5 | /*
6 | * File : MapCameraController.cs
7 | * Desc : 미니맵 전용 카메라
8 | *
9 | & Functions
10 | & : FixedUpdate() - 하늘에서 플레이어 추격
11 | *
12 | */
13 |
14 | public class MapCameraController : MonoBehaviour
15 | {
16 | [SerializeField]
17 | private float height;
18 |
19 | void FixedUpdate()
20 | {
21 | if (Managers.Game.GetPlayer().isValid() == false)
22 | return;
23 |
24 | // 플레이어 따라다니기
25 | transform.position = Managers.Game.GetPlayer().transform.position + (Vector3.up * height);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Scripts/Controllers/Player/PlayerAnimEvent.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 |
5 | /*
6 | * File : PlayerAnimEvent.cs
7 | * Desc : 플레이어의 공격, 스킬 등의 애니메이션 Event 관리 (공격마다 각 사거리가 다르므로 하드코딩)
8 | *
9 | ^ Tip : OnParticleCollision() 통해 파티클로도 접촉을 확인할 수 있기 때문에 다음 부터는 이걸 사용해야겠다.
10 | *
11 | */
12 |
13 | public class PlayerAnimEvent : MonoBehaviour
14 | {
15 | [SerializeField]
16 | private CapsuleCollider capsuleCollider;
17 |
18 | private int nextSkillIndex = 0; // 스킬 콤보 체크용
19 |
20 | private const int X_Axis = 0, Y_Axis = 1, Z_Axis = 2;
21 |
22 | #region 공격 사거리
23 |
24 | // 공격 사이즈 클래스
25 | private class AttackSize
26 | {
27 | public float x;
28 | public float y;
29 | public float z;
30 | public float redius;
31 | public float height;
32 | public int direction; // x: 0, y: 1, z: 2
33 | }
34 |
35 | // Id 101 공격 범위 (트리플 슬래쉬)
36 | private AttackSize skill101 = new AttackSize()
37 | {
38 | x = 0, y = 0, z = -0.35f, redius = 2.35f, height = 4.5f, direction = Y_Axis,
39 | };
40 |
41 | // Id 102 공격 범위 (라이징 슬래쉬)
42 | private AttackSize[] skill102 = new AttackSize[]
43 | {
44 | new AttackSize()
45 | {
46 | x = 0.3f, y = 0, z = 1.23f, redius = 0.5f, height = 3.5f, direction = Z_Axis,
47 | },
48 | new AttackSize()
49 | {
50 | x = 0.43f, y = 0.56f, z = 1f, redius = 0.93f, height = 3.6f, direction = Y_Axis,
51 | },
52 | new AttackSize()
53 | {
54 | x = 0, y = 0, z = 0f, redius = 2.35f, height = 4.5f, direction = Y_Axis,
55 | },
56 | };
57 |
58 | // Id 103 공격 범위 (회전의 칼날)
59 | private AttackSize skill103 = new AttackSize()
60 | {
61 | x = -0.4f, y = 0, z = -0.6f, redius = 4.4f, height = 8.7f, direction = Y_Axis,
62 | };
63 |
64 | // Id 104 공격 범위 (어둠의 칼날)
65 | private AttackSize skill104 = new AttackSize()
66 | {
67 | x = 0, y = 0, z = -0.4f, redius = 2.8f, height = 5.5f, direction = Y_Axis,
68 | };
69 |
70 | // Id 105 공격 범위 (궁극의 일격)
71 | private AttackSize skill105 = new AttackSize()
72 | {
73 | x = 0, y = 0, z = 6.1f, redius = 1.2f, height = 11.7f, direction = Z_Axis,
74 | };
75 |
76 | // Id 106 공격 범위 (칼날 섬멸)
77 | private AttackSize[] skill106 = new AttackSize[]
78 | {
79 | new AttackSize()
80 | {
81 | x = 0f, y = 0, z = 0.13f, redius = 1.23f, height = 3.4f, direction = X_Axis,
82 | },
83 | new AttackSize()
84 | {
85 | x = 0f, y = 0f, z = 3.5f, redius = 3f, height = 9.4f, direction = X_Axis,
86 | },
87 | new AttackSize()
88 | {
89 | x = 0f, y = 0f, z = 3.5f, redius = 3f, height = 9.4f, direction = X_Axis,
90 | },
91 | new AttackSize()
92 | {
93 | x = 0f, y = 0f, z = 3.5f, redius = 3f, height = 9.4f, direction = X_Axis,
94 | },
95 | };
96 |
97 | // Id 107 공격 범위 (궁극의 칼날)
98 | private AttackSize skill107 = new AttackSize()
99 | {
100 | x = 0f, y = 0f, z = -0.4f, redius = 7f, height = 0f, direction = Y_Axis,
101 | };
102 |
103 | #endregion
104 |
105 | // 기본 검 공격
106 | private void OnBasicAttack()
107 | {
108 | capsuleCollider.gameObject.SetActive(true);
109 | }
110 |
111 | // skill 101 : 트리플 슬래쉬
112 | private void OnTripleSlash()
113 | {
114 | OnSize(skill101);
115 | }
116 |
117 | // skill 102 : 라이징 슬래쉬
118 | private void OnRisingSlash()
119 | {
120 | OnSize(skill102[nextSkillIndex]);
121 |
122 | ++nextSkillIndex;
123 | if (nextSkillIndex == skill102.Length)
124 | nextSkillIndex = 0;
125 | }
126 |
127 | // skill 103 : 회전의 칼날
128 | private void OnRotationBlade()
129 | {
130 | OnSize(skill103);
131 | }
132 |
133 | // skill 104 : 어둠의 칼날
134 | private void OnDarkBlade()
135 | {
136 | OnSize(skill104);
137 | }
138 |
139 | // skill 105 : 궁극의 일격
140 | private void OnBigSwordSlash()
141 | {
142 | OnSize(skill105);
143 | }
144 |
145 | // skill 106 : 칼날 섬멸
146 | private void OnBladeAnnihilation()
147 | {
148 | OnSize(skill106[nextSkillIndex]);
149 |
150 | ++nextSkillIndex;
151 | if (nextSkillIndex == skill106.Length)
152 | nextSkillIndex = 0;
153 | }
154 |
155 | // skill 107 : 궁극의 칼날
156 | private void OnEventualityBlade()
157 | {
158 | OnSize(skill107);
159 | }
160 |
161 | private void OnSize(AttackSize size)
162 | {
163 | capsuleCollider.gameObject.SetActive(true);
164 | SetSize(size);
165 | }
166 |
167 | private void SetSize(AttackSize size)
168 | {
169 | capsuleCollider.direction = size.direction;
170 | // capsuleCollider.center.Set(size.x, size.y, size.z);
171 | capsuleCollider.center = new Vector3(size.x, size.y, size.z);
172 | capsuleCollider.radius = size.redius;
173 | capsuleCollider.height = size.height;
174 | }
175 | }
--------------------------------------------------------------------------------
/Scripts/Controllers/Player/PlayerAttackCollistion.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 |
5 | /*
6 | * File : PlayerAttackCollistion.cs
7 | * Desc : 플레이어 전방에 Collider를 활성화하여 접촉된 몬스터에게 데미지 반영
8 | *
9 | & Functions
10 | & : OnEnable() - 활성화 시 0.1f 후 비활성화 (DelayActiveFalse() Invoke 호출)
11 | & : OnDisable() - 비활성화 시 스킬 콤보 체크 및 콜라이더 사이즈 초기화
12 | & : OnTriggerEnter() - 몬스터 접촉 시 데미지 반영
13 | &
14 | & [Private]
15 | & : DelayActiveFalse() - 비활성화 딜레이
16 | & : BasicColliderSize() - 콜라이더 기본 사이즈 초기화
17 | *
18 | */
19 |
20 | public class PlayerAttackCollistion : MonoBehaviour
21 | {
22 | private int skillIndex = 0; // 스킬 콤보 공격력 List index
23 |
24 | private CapsuleCollider capsuleCollider;
25 |
26 | [SerializeField]
27 | private PlayerController player;
28 |
29 | void Start()
30 | {
31 | capsuleCollider = GetComponent();
32 |
33 | skillIndex = 0;
34 |
35 | gameObject.SetActive(false);
36 | }
37 |
38 | void OnEnable()
39 | {
40 | Invoke("DelayActiveFalse", 0.1f);
41 | }
42 |
43 | void OnDisable()
44 | {
45 | BasicColliderSize();
46 |
47 | // 마지막 스킬 공격이라면 index 초기화
48 | if (player.currentSkill.IsNull() == false)
49 | {
50 | if (skillIndex == player.currentSkill.powerList.Count - 1)
51 | skillIndex = 0;
52 | else
53 | skillIndex++;
54 | }
55 | }
56 |
57 | void OnTriggerEnter(Collider other)
58 | {
59 | if (other.CompareTag("Monster"))
60 | {
61 | if (player.State == Define.State.Skill)
62 | {
63 | if (player.currentSkill.powerList.Contains(skillIndex) == false)
64 | skillIndex = 0;
65 |
66 | // 스킬 공격
67 | int skillDamage = player.currentSkill.powerList[skillIndex] * (Managers.Game.Attack / 2);
68 | other.GetComponent().OnAttacked(skillDamage);
69 | }
70 | else
71 | other.GetComponent().OnAttacked(); // 기본 공격
72 | }
73 | }
74 |
75 | // Invoke 호출
76 | private void DelayActiveFalse()
77 | {
78 | gameObject.SetActive(false);
79 | }
80 |
81 | // 기본 콜라이더 사이즈
82 | private void BasicColliderSize()
83 | {
84 | capsuleCollider.center = new Vector3(0, 0, 0.4f);
85 | capsuleCollider.radius = 1.2f;
86 | capsuleCollider.height = 2.4f;
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/Scripts/Data/ArmorItemData.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using UnityEngine;
5 |
6 | /*
7 | * File : ArmorItemData.cs
8 | * Desc : 방어 아이템 스탯 데이터 (방어도, 체력, 마나, 속도)
9 | *
10 | & Functions
11 | & : ArmorClone() - 방어구 깊은 복사
12 | *
13 | */
14 |
15 | [Serializable]
16 | public class ArmorItemData : EquipmentData
17 | {
18 | public Define.ArmorType armorType = Define.ArmorType.Unknown;
19 |
20 | // 기본 스탯
21 | public int defnece=0;
22 | public int hp=0;
23 | public int mp=0;
24 | public int moveSpeed=0;
25 |
26 | // 강화 시 추가 스탯
27 | public int addDefnece=0;
28 | public int addHp=0;
29 | public int addMp=0;
30 | public int addMoveSpeed=0;
31 |
32 | [NonSerialized]
33 | public List charEquipment; // 캐릭터 파츠 활성화
34 |
35 | // 깊은 복사용
36 | public ArmorItemData ArmorClone()
37 | {
38 | ArmorItemData armor = new ArmorItemData();
39 |
40 | (this as EquipmentData).EquipmentClone(armor);
41 |
42 | armor.armorType = this.armorType;
43 | armor.defnece = this.defnece;
44 | armor.hp = this.hp;
45 | armor.mp = this.mp;
46 | armor.moveSpeed = this.moveSpeed;
47 |
48 | armor.charEquipment = this.charEquipment;
49 |
50 | return armor;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/Scripts/Data/EquipmentData.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 |
5 | /*
6 | * File : EquipmentData.cs
7 | * Desc : 모든 장비 아이템의 부모
8 | *
9 | & Functions
10 | & : EquipmentClone() - 장비 깊은 복사
11 | *
12 | */
13 |
14 | public class EquipmentData : ItemData
15 | {
16 | public int minLevel; // 최소 장착 레벨
17 | public int upgradeValue = 0; // +1 강화마다 올라가는 수치
18 | public int upgradeCount = 0; // 업그레이드 횟수
19 |
20 | public void EquipmentClone(EquipmentData equip)
21 | {
22 | equip.minLevel = this.minLevel;
23 | equip.upgradeValue = this.upgradeValue;
24 | equip.upgradeCount = this.upgradeCount;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Scripts/Data/ItemData.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using UnityEngine;
5 |
6 | /*
7 | * File : ItemData.cs
8 | * Desc : 모든 아이템의 부모
9 | *
10 | & Functions
11 | & : ItemClone() - 아이템 깊은 복사
12 | *
13 | */
14 |
15 | [Serializable]
16 | public abstract class ItemData
17 | {
18 | public int id;
19 | public string itemName;
20 | public Define.ItemType itemType = Define.ItemType.Unknown;
21 | public Define.itemGrade itemGrade = Define.itemGrade.Common;
22 | public int itemPrice;
23 | public int itemMaxCount = 99;
24 |
25 | public GameObject itemObject;
26 |
27 | [TextArea]
28 | public string itemDesc;
29 |
30 | public Sprite itemIcon;
31 |
32 | // Deep Copy (깊은 복사)
33 | public ItemData ItemClone()
34 | {
35 | if (this is EquipmentData)
36 | {
37 | if (this is ArmorItemData)
38 | {
39 | return AddItemValue((this as ArmorItemData).ArmorClone());
40 | }
41 | else if (this is WeaponItemData)
42 | {
43 | return AddItemValue((this as WeaponItemData).WeaponClone());
44 | }
45 | }
46 | else if (this is UseItemData)
47 | {
48 | return AddItemValue((this as UseItemData).UseClone());
49 | }
50 |
51 | return null;
52 | }
53 |
54 | T AddItemValue(T item) where T : ItemData
55 | {
56 | item.id = this.id;
57 | item.itemName = this.itemName;
58 | item.itemType = this.itemType;
59 | item.itemGrade = this.itemGrade;
60 | item.itemPrice = this.itemPrice;
61 | item.itemMaxCount = this.itemMaxCount;
62 | item.itemObject = this.itemObject;
63 | item.itemDesc = this.itemDesc;
64 | item.itemIcon = this.itemIcon;
65 |
66 | return item;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/Scripts/Data/LevelData.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using UnityEngine;
5 |
6 | /*
7 | * File : LevelData.cs
8 | * Desc : 레벨 데이터
9 | */
10 |
11 | public class LevelData
12 | {
13 | public int level;
14 | public int totalExp;
15 | public int statPoint;
16 | public int maxHp;
17 | public int maxMp;
18 | }
19 |
--------------------------------------------------------------------------------
/Scripts/Data/QuestData.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using UnityEngine;
5 |
6 | /*
7 | * File : QuestData.cs
8 | * Desc : 퀘스트 데이터
9 | *
10 | & Functions
11 | & : QuestClear() - 퀘스트 성공 보상 지급
12 | *
13 | */
14 |
15 | [Serializable]
16 | public class RewardItem
17 | {
18 | public int ItemId;
19 | public int itemCount;
20 | }
21 |
22 | [Serializable]
23 | public class QuestData
24 | {
25 | public int id;
26 | public string titleName;
27 | public Define.QuestType questType;
28 | public int minLevel;
29 | public int targetId;
30 | public int targetCount;
31 | public int currnetTargetCount;
32 | public int rewardGold;
33 | public int rewardExp;
34 | public List rewardItems;
35 | public string description;
36 | public string targetDescription;
37 | public Vector3 targetPos;
38 |
39 | public bool isAccept = false; // 수락 상태
40 | public bool isClear = false; // 클리어 상태
41 |
42 | // 퀘스트 성공
43 | public void QuestClear()
44 | {
45 | isClear = true;
46 |
47 | // 보상 지급
48 | foreach(RewardItem rewardItem in rewardItems)
49 | Managers.Game._playScene._inventory.AcquireItem(Managers.Data.CallItem(rewardItem.ItemId), rewardItem.itemCount);
50 |
51 | Managers.Game.Gold += rewardGold;
52 | Managers.Game.Exp += rewardExp;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Scripts/Data/SkillData.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using UnityEngine;
5 |
6 | /*
7 | * File : SkillData.cs
8 | * Desc : 스킬 데이터
9 | */
10 |
11 | [Serializable]
12 | public class SkillData
13 | {
14 | public int skillId;
15 | public string skillName;
16 | public int minLevel;
17 | public int skillCoolDown;
18 | public int skillConsumMp;
19 | public bool isCoolDown = false;
20 | public bool isLock = true;
21 | public string discription;
22 | public Sprite skillSprite;
23 | public List powerList;
24 | }
--------------------------------------------------------------------------------
/Scripts/Data/SkinnedData.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using UnityEngine;
5 |
6 | /*
7 | * File : SkinnedData.cs
8 | * Desc : 커스텀이나 세이브를 불러올 때 사용될 데이터
9 | * [SkinnedMeshRenderer 교체 방법]: https://lhuhyeon.github.io/posts/Unity-SkinnedMeshRenderer-Change/
10 | */
11 |
12 | [Serializable]
13 | public class SkinnedData
14 | {
15 | public string sharedMeshName;
16 | public Bounds bounds;
17 | public List bones;
18 | public string rootBoneName;
19 | }
20 |
--------------------------------------------------------------------------------
/Scripts/Data/StartData.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using System.Xml.Serialization;
4 | using UnityEngine;
5 |
6 | /*
7 | * File : StartData.cs
8 | * Desc : 시작 기본 데이터
9 | */
10 |
11 | public class StartData
12 | {
13 | public int Id;
14 | public int totalExp;
15 | public int exp;
16 | public int level;
17 | public int maxHp;
18 | public int maxMp;
19 | public int STR;
20 | public int MoveSpeed;
21 | public int LUK;
22 | public int gold;
23 | }
--------------------------------------------------------------------------------
/Scripts/Data/TalkData.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 |
5 | /*
6 | * File : TalkData.cs
7 | * Desc : 대화 데이터
8 | */
9 |
10 | public class TalkData
11 | {
12 | public int id;
13 | public string basicsTalk;
14 | public List questStartTalk;
15 | public string acceptTalk;
16 | public string refusalTalk;
17 | public string procTalk;
18 | public string clearTalk;
19 | }
20 |
--------------------------------------------------------------------------------
/Scripts/Data/UseItemData.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using UnityEngine;
5 |
6 | /*
7 | * File : UseItemData.cs
8 | * Desc : 소비 아이템 데이터
9 | *
10 | & Functions
11 | & : UseItem() - 아이템 사용 (체력 or 마나)
12 | & : UseClone() - 소비 아이템 깊은 복사
13 | *
14 | */
15 |
16 | [Serializable]
17 | public class UseItemData : ItemData
18 | {
19 | public Define.UseType useType = Define.UseType.Unknown;
20 | public int useValue = 0;
21 | public int itemCount = 0;
22 |
23 | public bool UseItem(ItemData item)
24 | {
25 | if ((item is UseItemData) == false)
26 | return false;
27 |
28 | UseItemData useItem = item as UseItemData;
29 |
30 | if (useItem.useType == Define.UseType.Hp)
31 | Managers.Game.Hp += useItem.useValue;
32 | else if (useItem.useType == Define.UseType.Mp)
33 | Managers.Game.Mp += useItem.useValue;
34 |
35 | return true;
36 | }
37 |
38 | public UseItemData UseClone()
39 | {
40 | UseItemData useItem = new UseItemData();
41 | useItem.useType = this.useType;
42 | useItem.useValue = this.useValue;
43 | useItem.itemCount = this.itemCount;
44 |
45 | return useItem;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Scripts/Data/WeaponItemData.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using UnityEngine;
5 |
6 | /*
7 | * File : WeaponItemData.cs
8 | * Desc : 무기 아이템 데이터 (공격력)
9 | *
10 | & Functions
11 | & : WeaponClone() - 무기 아이템 깊은 복사
12 | *
13 | */
14 |
15 | [Serializable]
16 | public class WeaponItemData : EquipmentData
17 | {
18 | public Define.WeaponType weaponType = Define.WeaponType.Unknown;
19 |
20 | public int attack=0;
21 | public int addAttack=0;
22 |
23 | [NonSerialized]
24 | public GameObject charEquipment;
25 |
26 | public WeaponItemData WeaponClone()
27 | {
28 | WeaponItemData weapon = new WeaponItemData();
29 |
30 | (this as EquipmentData).EquipmentClone(weapon);
31 |
32 | weapon.weaponType = this.weaponType;
33 | weapon.attack = this.attack;
34 | weapon.charEquipment = this.charEquipment;
35 |
36 | return weapon;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Scripts/Manager/Core/InputManager.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 | using UnityEngine.EventSystems;
5 | using System;
6 |
7 | /*
8 | * File : InputManager.cs
9 | * Desc : 플레이어의 모든 입력(마우스, 키보드)을 확인하고 반환
10 | * [ Rookiss의 MMORPG Game Part 3 참고. ]
11 | */
12 |
13 | public class InputManager
14 | {
15 | // 키 입력 메소드들을 한번에 실행하기 위한 변수
16 | public Action KeyAction = null;
17 | public Action MouseAction = null;
18 |
19 | bool _leftPressed = false;
20 | bool _rightPressed = false;
21 |
22 | float _leftPressedTime;
23 | float _rightPressedTime;
24 |
25 | public void OnUpdate()
26 | {
27 | // 상호작용 확인
28 | if (Managers.Game.IsInteract == true || Managers.Game.isPopups[Define.Popup.Talk] == true)
29 | return;
30 |
31 | // 키입력 메소드가 KeyAction안에 존재하는가?
32 | if (Input.anyKey && KeyAction.IsNull() == false)
33 | KeyAction.Invoke();
34 |
35 | // UI를 클릭했을 때
36 | if (EventSystem.current.IsPointerOverGameObject())
37 | return;
38 |
39 | if (MouseAction.IsNull() == false)
40 | {
41 | if (Input.GetMouseButton(1))
42 | {
43 | if (!_rightPressed)
44 | {
45 | MouseAction.Invoke(Define.MouseEvent.RightDown);
46 | _rightPressedTime = Time.time;
47 | }
48 | MouseAction.Invoke(Define.MouseEvent.RightPress);
49 | _rightPressed = true;
50 | }
51 | else{
52 | if (_rightPressed)
53 | {
54 | if (Time.time < _rightPressedTime * 0.2f)
55 | MouseAction.Invoke(Define.MouseEvent.RightClick);
56 |
57 | MouseAction.Invoke(Define.MouseEvent.RightUp);
58 | }
59 | _rightPressed = false;
60 | _rightPressedTime = 0f;
61 | }
62 |
63 | if (Input.GetMouseButtonDown(0))
64 | {
65 | if (!_leftPressed)
66 | MouseAction.Invoke(Define.MouseEvent.LeftDown);
67 | }
68 |
69 | if (Input.GetMouseButton(0))
70 | {
71 | _leftPressedTime = Time.time;
72 | MouseAction.Invoke(Define.MouseEvent.LeftPress);
73 | _leftPressed = true;
74 | }
75 | else{
76 | if (_leftPressed)
77 | {
78 | if (Time.time < _leftPressedTime * 0.2f)
79 | MouseAction.Invoke(Define.MouseEvent.LeftClick);
80 |
81 | MouseAction.Invoke(Define.MouseEvent.LeftUp);
82 | }
83 | _leftPressed = false;
84 | _leftPressedTime = 0f;
85 | }
86 | }
87 | }
88 |
89 | public void Clear()
90 | {
91 | KeyAction = null;
92 | MouseAction = null;
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/Scripts/Manager/Core/PoolManager.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 |
5 | /*
6 | * File : PoolManager.cs
7 | * Desc : Poolable 컴포넌트가 있는 오브젝트를 Pool 관리해준다 ( ResourceManager의 보조역할 )
8 | * [ Rookiss의 MMORPG Game Part 3 참고. ]
9 | */
10 |
11 | public class PoolManager
12 | {
13 | class Pool
14 | {
15 | public GameObject Original { get; private set; } // Pool을 진행할 대표 변수
16 | public Transform Root { get; set; }
17 |
18 | Stack _poolStack = new Stack();
19 |
20 | // Pool 초기 설정
21 | public void Init(GameObject original, int count = 5)
22 | {
23 | Original = original;
24 | Root = new GameObject().transform; // Pool을 담을 Root Object
25 | Root.name = $"{original.name}_Root";
26 |
27 | for (int i = 0; i < count; i++) // count 만큼 pool Object 생성 후 Stack에 push
28 | Push(Create());
29 | }
30 |
31 | Poolable Create()
32 | {
33 | GameObject go = Object.Instantiate(Original); // Original 복제
34 | go.name = Original.name;
35 | return go.GetOrAddComponent(); // Poolable 컴포넌트 생성
36 | }
37 |
38 | public void PushCreate(int count = 5)
39 | {
40 | for (int i = 0; i < count; i++)
41 | Push(Create());
42 | }
43 |
44 | // 객체 생성 메소드
45 | public void Push(Poolable poolable)
46 | {
47 | if (poolable.IsNull() == true)
48 | return;
49 |
50 | poolable.transform.SetParent(Root);
51 | poolable.gameObject.SetActive(false);
52 | poolable.IsUsing = false;
53 |
54 | _poolStack.Push(poolable);
55 | }
56 |
57 | // 객체 반환 메소드
58 | public Poolable Pop(Transform parent = null)
59 | {
60 | Poolable poolable;
61 |
62 | if (_poolStack.Count > 0)
63 | poolable = _poolStack.Pop();
64 | else
65 | poolable = Create();
66 |
67 | poolable.gameObject.SetActive(true);
68 |
69 | // DontDestroyOnLoad 해제 용도 (SceneManager 객체를 이용)
70 | if (parent.IsNull() == true)
71 | poolable.transform.SetParent(Managers.Scene.CurrentScene.transform);
72 |
73 | poolable.transform.SetParent(parent);
74 | poolable.IsUsing = true;
75 |
76 | return poolable;
77 | }
78 | }
79 |
80 | Dictionary _pool = new Dictionary(); // pool 객체 저장
81 | Transform _root; // 오브젝트 생성 경로
82 |
83 | public void Init()
84 | {
85 | // Pool Object를 담을 부모 객체(_root) 경로 설정
86 | if (_root.IsNull() == true)
87 | {
88 | _root = new GameObject { name = "@Pool_Root" }.transform;
89 | Object.DontDestroyOnLoad(_root);
90 | }
91 | }
92 |
93 | // 새로운 pool 생성 후 저장
94 | public void CreatePool(GameObject original, int count = 5)
95 | {
96 | // 이미 풀에 존재하면 생성 취소
97 | if (_pool.ContainsKey(original.name) == true)
98 | return;
99 |
100 | AddCreatePool(original, count);
101 | }
102 |
103 | public void AddCreatePool(GameObject original, int count = 5)
104 | {
105 | if (_pool.ContainsKey(original.name) == true)
106 | {
107 | _pool[original.name].PushCreate(count);
108 | return;
109 | }
110 |
111 | Pool pool = new Pool();
112 | pool.Init(original, count); // Pool 생성
113 | pool.Root.SetParent(_root); // _root(@Pool_Root)를 부모 객체로 설정
114 |
115 | _pool.Add(original.name, pool);
116 | }
117 |
118 | // 기존 pool 저장 메소드
119 | public void Push(Poolable poolable)
120 | {
121 | string name = poolable.gameObject.name;
122 |
123 | if (_pool.ContainsKey(name) == false)
124 | {
125 | GameObject.Destroy(poolable.gameObject);
126 | return;
127 | }
128 |
129 | _pool[name].Push(poolable);
130 | }
131 |
132 | // pool 반환 메소드
133 | public Poolable Pop(GameObject original, Transform parent = null)
134 | {
135 | // Poolable 컴포넌트가 붙은 객체인데 저장된 Key가 없을 경우 생성
136 | if (_pool.ContainsKey(original.name) == false)
137 | CreatePool(original);
138 |
139 | return _pool[original.name].Pop(parent); // Pop 진행
140 | }
141 |
142 | // pool 객체 읽기 메소드
143 | public GameObject GetOriginal(string name)
144 | {
145 | if (_pool.ContainsKey(name) == false)
146 | return null;
147 |
148 | return _pool[name].Original;
149 | }
150 |
151 | // mmorpg에서 지역마다 쓰는 객체다 다를 수도 있기 때문에 일단 구현!
152 | public void Clear()
153 | {
154 | // @Pool_Root 안에 있는 객체 모두 제거
155 | foreach(Transform child in _root)
156 | GameObject.Destroy(child.gameObject);
157 |
158 | _pool.Clear(); // Pool 초기화
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/Scripts/Manager/Core/Poolable.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 |
5 | /*
6 | * File : Poolable.cs
7 | * Desc : Poolable 스크립트가 객체에 컴포넌트로 추가되어 있는지 확인하여 Pool 여부를 체크한다.
8 | * [ Rookiss의 MMORPG Game Part 3 참고. ]
9 | */
10 |
11 | public class Poolable : MonoBehaviour
12 | {
13 | public bool IsUsing; // 아무것도 없어 허전하니 풀 중인지 알려주는 변수를 넣어주었다.
14 | }
15 |
--------------------------------------------------------------------------------
/Scripts/Manager/Core/ResourceManager.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 |
5 | /*
6 | * File : ResourceManager.cs
7 | * Desc : Resource 폴더 사용 및 유니티에서 제공하는 명령어를 더 쉽게 사용하기 위한 매니저
8 | * [ Rookiss의 MMORPG Game Part 3 참고. ]
9 | */
10 |
11 | public class ResourceManager
12 | {
13 | // 객체 읽기
14 | public T Load(string path) where T : Object
15 | {
16 | // 찾으려는 타입이 GameObject일 경우 Pool에서 찾음.
17 | if (typeof(T) == typeof(GameObject))
18 | {
19 | string name = path;
20 | int index = name.LastIndexOf('/'); // '/' 문자까지의 문자열 개수 반환
21 | if (index >= 0)
22 | name.Substring(index + 1); // name을 index+1 문자열 위치에서 반환
23 |
24 | GameObject go = Managers.Pool.GetOriginal(name);
25 | if (go.IsNull() == false)
26 | return go as T;
27 | // return go.GetComponent();
28 | }
29 |
30 | // Pool에서 찾지 못하면 Resources 경로에서 가져옴.
31 | // [유니티에서 제공하는 Resources 폴더 안에 해당 경로의 프리팹을 읽어오는 클래스]
32 | return Resources.Load(path);
33 | }
34 |
35 | public GameObject Instantiate(GameObject obj, Transform parent = null)
36 | {
37 | if (obj.IsNull() == true)
38 | {
39 | Debug.Log("객체가 존재하지 않습니다.");
40 | return null;
41 | }
42 |
43 | // 풀링이 적용된 객체인지 확인
44 | if (obj.GetComponent().IsNull() == false)
45 | return Managers.Pool.Pop(obj, parent).gameObject;
46 |
47 | // 해당 original 프리팹을 parent의 자식 객체로 생성하기
48 | GameObject go = Object.Instantiate(obj, parent);
49 | go.name = obj.name; // (Clone) 이름을 없애기 위한 코드
50 |
51 | return go;
52 | }
53 |
54 | // 프리팹 생성
55 | public GameObject Instantiate(string path, Transform parent = null)
56 | {
57 | // original 프리팹 객체 읽어오기.
58 | GameObject original = Load($"Prefabs/{path}");
59 |
60 | if (original.IsNull() == true)
61 | {
62 | Debug.Log($"Failed to load prefab : {path}");
63 | return null;
64 | }
65 |
66 | // 풀링이 적용된 객체인지 확인
67 | if (original.GetComponent().IsNull() == false)
68 | {
69 | // UI 팝업이면 하나만 생성
70 | if (original.GetComponent().IsNull() == false)
71 | Managers.Pool.CreatePool(original, 1);
72 |
73 | return Managers.Pool.Pop(original, parent).gameObject;
74 | }
75 |
76 | // 해당 original 프리팹을 parent의 자식 객체로 생성하기
77 | GameObject go = Object.Instantiate(original, parent);
78 | go.name = original.name; // (Clone) 이름을 없애기 위한 코드
79 |
80 | return go;
81 | }
82 |
83 | // 오브젝트 삭제
84 | public void Destroy(GameObject go)
85 | {
86 | if (go.IsNull() == true)
87 | return;
88 |
89 | // 만약에 풀링이 필요한 아이라면 PoolManager한테 위탁
90 | Poolable poolable = go.GetComponent();
91 | if (poolable.IsNull() == false)
92 | {
93 | Managers.Pool.Push(poolable);
94 | return;
95 | }
96 |
97 | Object.Destroy(go);
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/Scripts/Manager/Core/SceneManagerEx.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 | using UnityEngine.UI;
5 | using UnityEngine.SceneManagement;
6 |
7 | /*
8 | * File : SceneManagerEx.cs
9 | * Desc : 씬 로드 매니저
10 | * [ Rookiss의 MMORPG Game Part 3 참고. ]
11 | */
12 |
13 | public class SceneManagerEx
14 | {
15 | public BaseScene CurrentScene { get { return GameObject.FindObjectOfType(); } }
16 |
17 | public void LoadScene(Define.Scene type)
18 | {
19 | Managers.Clear();
20 | SceneManager.LoadScene(GetSceneName(type));
21 | }
22 |
23 | public AsyncOperation LoadAsynScene(Define.Scene type)
24 | {
25 | AsyncOperation operation = SceneManager.LoadSceneAsync(Managers.Scene.GetSceneName(type));
26 | operation.allowSceneActivation = false;
27 |
28 | return operation;
29 | }
30 |
31 | string GetSceneName(Define.Scene type)
32 | {
33 | string name = System.Enum.GetName(typeof(Define.Scene), type);
34 | return name;
35 | }
36 |
37 | public void Clear()
38 | {
39 | CurrentScene.Clear();
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Scripts/Manager/Core/UIManager.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 |
5 | /*
6 | * File : UIManager.cs
7 | * Desc : UI의 Scene, Popup, WorldSpace 생성/제거를 도와주는 매니저
8 | * [ Rookiss의 MMORPG Game Part 3 참고. ]
9 | */
10 |
11 | public class UIManager
12 | {
13 | int _order = 10;
14 |
15 | List _popupList = new List();
16 | UI_Scene _sceneUI = null;
17 |
18 | // 프리팹 오브젝트 부모 (하이라이커 깔끔하게 정리하려고 사용)
19 | public GameObject Root
20 | {
21 | get{
22 | GameObject root = GameObject.Find("@UI_Root");// 오브젝트 찾기
23 |
24 | if (root.IsNull() == true)
25 | root = new GameObject{name = "@UI_Root"}; // 오브젝트 이름 설정
26 |
27 | return root;
28 | }
29 | }
30 |
31 | // 오브젝트에 Canvas를 추가하고 order을 설정
32 | public void SetCanvas(GameObject go, bool sort = true)
33 | {
34 | Canvas canvas = Util.GetOrAddComponent