레벨업

CJB_ny·2022년 1월 19일
0

Mini_MMO_RPG

목록 보기
14/16
post-thumbnail

자 이렇게 해서 프로젝트 초기의 어느정도 기본적인 모습은 갖추어 주었다.

1. 수정하고싶은 부분들 수정 + (비트마스크)

그래서 지금은 Player가 죽으면 없어지는데 이렇게 하지는 말고

Palyer가 어디서 리스폰되게하고 몬스터는 멋이게 죽으면서 경험치를 주는 그런 방식으로 구현을 함 해보자.

그런데 지금 죽은 상태에서 클릭을하면 경고가 뜨는데

지금 anim.CrossFade를 해주고있기 때문에 경고가 뜨고있는 중이다.

우리가 지금 PlayerController는

이렇게 OnMouseEvent로 callback을 받아서 처리를 하고있었다.


이부분은

레이케스팅을 해가지고

State를 바꾸려고하는데

지금 State를 바꾸는 부분이 문제가 되고있는 것이다.

지금 상태가 죽으면 UbityCahn Root산하로 들어가게되서 꺼진 상태인데

anim을 바꾸려고해서 문제가 발생하게 되는 것이다.

그리고 지금

유니티짱을 꺼둔 상태(enable)이라고 하더라도

여전히

OnMouseEvent는 실행이 되고있다.

그래서 여전히 실행이 되는 것도 문제라고 볼 수 있다.

우리가 이런 callback방식이 아니라

Update와 같은 방식으로 처리를 했다라고 하면은

해당 Player가 off(enable)상태라고하면은 event가 안들어 오기 때문에

아무것도 호출이 안되었을 텐데

이 callback을 받는 객체가 메모리에서 날라가기 전까지는

계속 콜백이 호출이 되는 상황이다.

그래서 일단 이 부분은 넘어가고

2. 카메라 수정

지금 건물에 가리면 카메라가 앞으로 스르륵 안나오는데 이 부분을 수정을 좀 해보도록 하자.

그래서 유니티 툴에서


Build들 같은 경우에는 Layer를 Block으로 다 변경했었었다.

그래서 cameraController로 가보면은 이전에는 "Wall"로 된 Layer일 경우 앞으로 스스륵 가게해놓았었다.

이부분인데

Block으로 바꾸어 보자.

그리고 지금 Raycast의 마지막 인자를

LayerMask.GetMask("Block");으로 해주었는데

이렇게 해도되고


1 << (int)Define.Layer.Block)
마지막 인자를 이렇게 해도 똑같이 계산이 된다.

2. 비트마스크

************************************************** (쉬프트 연산자인데 1 << (int)Define.Layer.Block) 지금 이부분은 bitMask이다. Define.Layer.Block 이 지금

Define에 가보면은

10인데 1을 쉬프트 연산자로 10칸 왼쪽으로 옮기면

1024(정수 값)이다.

그러면 RayCast에서 Block을 클릭한 부분이 1024인 정수 값이라고하면은

해당 코드가 실행되는 그런 식이다.


그래서 코드를 이렇게 수정을 좀 해주고

이상태에서 건물뒤로 가게되면은


건물 뒤에있는데 이렇게 카메라가 앞으로 나오게된다.

와우같은 게임은 뭐 카메라가 부드럽게 스르르륵 움직이게 되는데

그 부분은 중요한 부분은 일단 아니니까 넘어가자

3. 몬스터가 죽는 부분 구현

일단 MonsterController로 가보면은

이전에

구현을 하다 만 부분이 있는데 TODO

나중에 MonsterManager가 생기면 이부분을 구현을 하기로 했었는데

양심에 걸리니까 구현을 해보도록 하자.

이 Manager란 지난시간에 만든 GameManagerEx를 말하는 것이다.

그래서 지금 이

_player를 가지고오게 인터페이스만 구현을 해보도록하자.


이렇게 만들어주고

이렇게 만들어 주었으면은 이제

MonsterController로 가가지고


이렇게 FindGameObjectWithTag로 하면은 느리니까


이렇게 해주면은 조금이나마 성능 향상이 있을것이다.

4. 질문한것


근데 지금 MonasterController에서는 FindObjectWithTag로 찾았는데

GameManagerEx에서는 그냥 GameObject를 반환하는 GetPlayer의 인터페이스만 구현을 했는데 어떻게 FindObjectWithTag와 같은 기능을 할 수 있는지..??


5. 체력 까는 부분 구현

그리고 몬스터를 공격하고 몬스터가 나를 공격하는 부분은 MonsterController 부분에 구현을 해놓았었다.


그리고 체력을 깍는 부분을 이곳에서 구현을 해주고 있었는데

이부분을 조금 개선을 해보도록 하자.

그리고 이렇게 데미지를 입히는 부분은

밖에서 공격하는 사람이 처리를 하기 보다는

피해자한테 던져주는 것이 나중에 보면은 훨씬 더 좋다고 했었다.

나중에 RPG같은 게임은 Player의 버프라던가 온갖 장치들이 있게 될텐데

무적버프라던가 패시브 스킬이라던가 등등... ㅈㄴ 붙어있을 예정임

그런것들을

이렇게 밖으로 빼내와서 처리를 하게되면은

여기서도 똑같이 체크를 해주어야 한다.
(즉 MonsterControlller // 체력깍이 부분 밑에 코드가 엄청나게 길어 질것이다 if문으로 하루종일 체크를 하니까)

그래서 그렇게 하기보다는 상대방한테 던져주어가지고 처리를 하는것이 훨씬더 깰끔하다.

그래서 생각을 해보면은

Stat에다가 그런 기능을 넣어주면 좋을 거같다.

그래서

Stat.cs에서

이렇게 public virtual void OnAttacked라고 인터페이스만 파주도록 하자.

이렇게 virtual로 만든이유는

혹시라도 Player는 기본적인 이런 OnAttacked가 아니라

좀 다른 방식으로 구현을 하고 싶을 수도 있기 때문에

overrind을 할 수 있도록 열어 주도록 하자.

그리고 공격력을 인자로 받고싶은데

Stat을 받아주도록하자.


이렇게 Stat attacker를 인자로 받아서

attacker.Attack 뭐 이런식으로 사용을 할것인데 굳이 이렇게 받아와서 사용하는 이유는

혹시라도 attacker가 몬스터인지 아니면 Player인지 아니면 다른 정보도 추출을 할 수가 있게 될 수도 있으니까

굳이 이렇게 컴포넌트로 전달을 해준 것이다.

그리고 여기서부터는 MonsterController에서 만들어 주었던 부분이랑 거의 비슷하다.


뭐 이런부분은 똑같다 거의


그래서 일단 이런식으로 추출을 해보도록 하자.

이렇게 되면은 attacker의 Attack에서 나의 Defense를 빼게 된 것이

damage가 될 것이다.

damage를 다시 나의 Hp에서

damage를 깍아 주면 될 것이다.

그런데 이렇게 깍는데 혹시라도 음수가 되는것을 방지 해야하니까


이렇게 처리를 해주고

여기서 이제 if(Hp <0 )죽은 경우인데 이부분을 어떻게 해주어야 할지 살짝 고민이다.

이부분은 Player이냐 몬스터이냐에 따라서 조금 달라질 수 있다.

그래서 이부분에 일단 OnDead만들어 주고

밑에 인터페이스를 하나 파주자.


이렇게 만들어 주고

OnDead의 기본적인 방식은 그냥 사라지게 만들어 주자.


그래서 Managers.Game에 접근을 해 Despawn을 해주는데

지금 Stat을 들고있는 주인님을 없어주기 위해서 gameObject를 인자로 넣어주도록 하자.

그라고 OnDead를 수정을 해주는데

virtaul로 만들어서 Player같은 경우는 당장 없애지는 않고

아무처리도 하지 않도록 바꾸어 보도록 하자(PlayerStat으로 이동)


그리고 이렇게 override로 만들어 주고 일단은 로그만 찍도록 해보자.


그라고 PlayerStat조금 버프 시키는 식으로 수정을 하고

MonsterController로 가가지고


여기서 체력 깍는 부분에서


이부분 날려주고


이렇게 targetStat이 OnAttack되는데

OnAttack인자에 나의 스탯을 넣어주어야 한다

여기서 나는 Monster이니까


이렇게 넣어주면 된다.

그리고 이부분을 사실상 PlayerContoller부분에서도 똑같이 해주면 된다.

Playercontroller로 가서

OnHitEvent에서도 이렇게

되어있는데

MonsterController와 똑같이 해주면 될듯하다.

그래서 if문은

이렇게 두줄로만 됨

그러면 이제 유니티로 돌아가서 테스트를 해보도록 하자.

그리고 테스트를 해보면 잘된다.

ㅈㄴ 패서 몬스터 먼저 뒤짐


1차 커밋 까지 함


6. 경험치 구현

경험치 같은 경우에는

죽었을 때

OnDead에다가 구현을 해놓는 것이 좋아 보인다.

그런데 이곳이 아니라 OnAttacked안에 넣어주어도 크게 상관은 없다.


잠깐 정리

지금 잠깐 정리를 하자면은

PlayerController에 OnAttacked(_stat)을 넣어 주었는데

이렇게되면 내 스탯을 인자로 넣어주어서

Stat.cs로 가보면은

attacker == PlayerStat인데

damage가 0 ~ PlayerStat.Attack - Defense인 값이다.

이것을 현재 Hp에다가 깍아주는데 Hp는 현재 이 컴포넌트를 들고있는 객체의 Hp를 말하는 것이다.

그리고 죽으면 OnDead함수를 호출을 하는 것이다.


아무튼


이렇게 고쳤으면은


PlayerStat에서도 똑같이 넣어 주어야 한다.

그다음 이제 Stat.cs로 돌아와서

이제 여기서 판별을 해야 하는데

나를 죽인애가 Player이냐 아니냐를 판별을 해야되겠죠??

그런데 우리가

여기 Stat에다가는 뭔가를 판별할 수있는 그런것은 넣어놓지는 않았다.

물론

지금 Stat.cs에서 OnDead함수에서 Stat attacker가 PlayerStat이냐 아니냐로 구분을 할 수는 있기는 있지만

그렇게 하지말고

Managers.Game.GetWorldObjectType으로 접근해서 사용하자.


이부분 이전에 질문을 했었는데

이런식으로 할당을 해준다.


그래서 attacker는 Stat컴포넌트이니까 이것를 들고있는 게임 오브젝트를

이렇게 넣어주면 타입이 반환이 될거같다! 라는 생각이 든다.


그래서 Define.WorldObject type으로 받고

type이 Player인 경우에만 경험치를 받도록 해보자.


그래서 attacker.exp이런식으로 접근을 해야되는데

지금 exp를 안타깝게도 PlayerStat에다가만 구현을 해놓아서

캐스팅을 해주어야 한다.


6-1. 캐스팅 하는 방법

내가 아는 캐스팅(형변환)은

int _myNumber = (int)blabla;

이런건데 지금


이런식으로 캐스팅을 해주었는데

이게 가능하노...??

캐스팅을 이딴식으로도 가능하구나



이런식으로 캐스팅을 해주고


null체크를 함 해주고

이 if안에다가 구현을 해주도록 하자.

그런데 지금


이 부분에서

PlayerStat playerStat = attacker as PlayerStat;
이렇게 이미 캐스팅을 해서

if문으로 PlayerStat != null체크를 해주었는데

이렇게되면

Define.WorldObject type = Managers.Game.GetWorldObjectType(attacker.gameObject);
이런식으로 타입을 가져올 필요가 없게된다!

(나는 왜 인지 이해가 안감 어떤타입인지 가져와야 되는거 아닌가...??)


그래서 이제

여기다가 뭔가를 추가를 이렇게 해주면 될것처럼 보인다.

그런데 보통 몬스터마다 경험치가 다 다를 것이다.

7. 경험치 Data

그래서 이렇게 다 경험치가 다를 경우에는 data로 관리를 할 경우가 굉장히 높다!


여기서 처럼

Resource > Data > StatData 이렇게 만들어 주었었는데

이런식으로

StatData뿐만 아니라 나중에 MonsterData도 있어서 여기서 뭐 아이템이나 주는 경험치도 다 적혀져 있을 것이다.

그런데 지금은 MonsterData까지 만들면 버거우니까 일단은 고정값으로 놔두도록 하자.

그대신 이 StatData는 작업을 해보도록 하자.


2차 커밋 22.01.17


22.01.18 22:41 Start


여기 이렇게 Stat Data로 와가지고

여기서 exp를 추가를 해서 exp를 초과를 하면은 레벨업이되는 부분을 구현을 해보도록 하자.

그래서 경험치를 1렙에서 2랩으로 넘어가는 경험치를 넣어줄지

아니면 누적 경험치인 tatal경험치를 줄지는 딱히 상관은 없지만

total exp를 넣어주면 조금 작업이 쉬우니까

tatal exp를 넣어 주도록 하자.


이렇게 10을 얻으면 레벨업이 되고

20을 얻으면 넘어가는 방식으로 하자.


그리고 스탯부분도 이렇게 좀 버프를 시켜주도록 하자.

일단 데이터부터 이렇게 만들어주면은 데이터 구조가 어떻게 될지는 예상이 간다.

그러면 이제 반대로 Data를 정의하는 파일이 있었다.

그게 바로

여기 Data.Contents에서 관리를 하고있었다.


새 세로 문서 그룹으로 만들어서 동시에 비교를 하도록 하겠다.


이런식인데


이렇게 하면은 기존의 스탯이 똑같이 긁어질거 같다.

그리고 여기까지 로딩이 잘되었는지 유니티를 실행해서 테스트를 해보도록 하자.

그래서 Stat.cs로 돌아와보면 스탯을 강제로

이렇게 셋팅을 하고있었었다.

Player만 우리가 세로 JSON파일에 작성한 것을 바꿔치기를 해보도록 하자.

그래서 PlayerStat에가서

Start부분에서

Managers.Data 접근을 해서 StatDict에서 뭔가를 추출을 해줘야 할 것이다.


이렇게 StatData를 가져오는데 레벨과 스텟이 매핑이 되어있었다.

확인해보면은

이게 현재 JSON파일이고

이것을 읽어 오는 부분이

Data.content파일에서

Stat이라는 class만들고

StatData라는 클래스 만들고 ILoader<int, Stat>을 상속받게 클래스를 만들고

List인데 Stat을 받는 리스트인 stats = new List< Stat >();

만들어주고

Dictionary를 반환해주는 MakeDic() 인터페이스를 파준다.

이 MakeDic안에서 key = int, value = Stat을 받는 dict라는 Dictionary만들어주고

이 stats에 foreach로 접근을 해서 dict안에 key값으로 레벨을 넣고
value값으로 stat을 Add해주고 dict를 반환을 하는 방식이다.

그리고

DataManager에서

StatDict에다가 LoadJson을 통해서 key값에
Data.contents에서 매핑해준 stats.level을 넣고

value값에 stats을 넣어준다.

그래서 다시 PlayerStat에서

레벨은 키 값을

data[1]. 을 찍어보면 이렇게 우리가 Data에서 정의한 것들이 뜨는데

이녀석들을 꺼내주면 된다.

일단 maxHp를 꺼내가지고

근데 이전에 꺼내는 작업이 반복이 되니까

미리 정의를 해주자.


이렇게 Data.Stat stat = dict[1]로 정의를 해주고

밑에 _hp 시리즈들을

이렇게


구현을 해주도록하자.

지금 defense, movespeed의 경우 내가 Data.contents로가서 다 수정을 한것이다 JSON파일도 같이.

그런데 지금


이부분들이 레벨이 올라갈때마다 호출이 되야 한다는 부분이니까

이녀석도 함수를 하나를 만들어 주도록 하자.


이렇게 해주고 이제 나머지 부분들을 설정을 해주는 것이 될것이다.

그래서 SetStat함수안에 Data로 관리 하는 부분만 긁어 오자

이렇게.

그런데 _exp의 경우

굳이 SetStat함수에 안 넣어 주더라도

레벨이 변동이 되었으면

애당초 레벨에 맞는 exp가 채워지니까

_exp = 0;으로 수정하고 밑줄 친 부분 없애고 SetStat을 넣어주면된다.

그래서
이렇게 수정을 하자.

그다음에

Stat.cs로 가가지고


이제 이부분을 수정을 할 수 있는데

한마리만 잡아도 레벨업이 되도록 경험치를 15로 수정을 해보도록 하자.


그러면 여기 Exp가 다음 경험치에 해당을 하는지 체크를 해야한다.


OnDead같은 경우는 죽은 몬스터 한테서 호출이 되는 함수인데


그아이가 이렇게 PlayerStat에 접근을 해서 셋팅을 하는게 뭔가 이상하기는 하다.

그렇게 하지말고 PlayerStat에서 Exp를 건드리는 부분에서 이 작업을 해보도록 하자.

그래서 PlayerStat으로 간다음


여기 밑줄을 친 부분에다가 뭔가를 넣어 주도록 하자.


참고


이렇게 Stat.cs에서 외부에서 접근을해서 Stat, exp etc... 를 바꾸는 경우(사람도 있는데)

이렇게 될때의 문제점은 무엇이냐하면은

나중에 경험치를 추가해주는 부분이 이래저래 많이 추가가 될 것이다.

예를들면은 경험치 복구 이벤트 라거나

어떤 아이템을 먹었더니 경험치를 받는 다거나

어떤 퀘스트를 깻더니 경험치를 받는다거나 하는 그런 부분들이 항상 있겠죠? RPG게임을 하다보면은

그런데 그럴때마다 이렇게

하드코딩을 해가지고


여기 아래부분에다가 레벨업 체크를 하는 부분을 넣어 놓는다고 하면은

레벨업 체크를 하는 부분을 일일히 똑같이 복사해서 다 넣어 주어야 할 것이다.

그런데 그것이 아니라 애당초

PlayerStat에서 경험치를 건드리는 부분에서

동시에 체크를 하는 부분을 넣어놨다라고 하면은

다 건드릴 필요 없이 깔끔하게 체크를 하게 되겠죠??

그래서 이렇게 한번씩 미래를 생각해보면서 작업을 해보는 것이 좋다는 말이다.


그래서

이렇게 셋팅을 한다음에


여기서 레벨업 체크를 하면 된다는 말이 된다.

그래서 이부분을 작업을 하기전에

이렇게 띄워놓고 작업을 해보도록 하자.


그래서 우리가 해야할 것은

어떤 레벨을 기준으로

Player의 경험치가 늘어났다고 가정을 했을 때

그 다음레벨로 갈 수 있는지를 체크를 해야할 것이다.

그런데 현재 레벨 기준으로

다음 레벨의 경험치만 체크를 하면 된다고 생각할 수 있는데

아주 예외적인 경우가 경우에 따라서 어마어마한 양의 exp를 얻었다고 하면은

예를들어 레벨1에서 경험치를 100을 얻었다라고 하면은

레벨2로 가는 것이 아니라 레벨3으로 뛰어 넘어야 정상적인 상황이 될 것이다.

그래서 그부분을 조금 유의해서 만들어 보면 될 것이다.

현재 레벨을 일단

level에서 시작한다고 가정을 할 때

level이

이부분에서 어디까지 갈 수 있는지 확인을 해보면 된다.

그래서 일단은 while문을 돌껀데


이렇게 Managers.Data.Statdict에서 stat을 가져올 수 있었다.


그래서 TryGetValue로 값(stat들)을 얻어 올 수 있는데


다음레벨에 해당하는 스탯을 얻어 오기 위해 level + 1, stat을 TryGetValue에다가 넣어주자.

그런데 stat을 얻어 와야하는데


이렇게 얻어와서 out으로 stat을 넘겨주자.

그런데 지금 만약 Managers.Data.StatDict.TryGetValue(level + 1, out stat) == false라면은

다음 레벨이 없다라는 것이니까


여기서 끝내면 된다.

그런데 만약 다른 경우가 있다

라고하면은 예를들어

1레벨에서 2레벨로 가는 것을 뽑아 온 것이다.

그러면

지금 내 현재 경험치



새로 뽑아온 다음레벨의 total경험치 보다 큰지를 이렇게 체크를 해주어야 한다.

먄약 다음레벨의 total경험치보다 작다라고하면은 레벨업을 못한 것이니까

break;를 해주면 되고

그것이 아니라 if문을 다 통과를 했다면

level++;를 해주어서 다음 레벨로 넘어가주도록 하겠다.


이부분을 while문으로 돌기 시작하면은


여기서 지금 내가 갈 수 있는 레벨까지 증가가 되어서


여기 level변수에 레벨이 저장이 되어있을 것이다.

그리고 만약에


우리가 새로 계산한 새level이

현재 내가 가지고있는 현재 Level변수랑 다르다면

현재 레벨에 변화가 일어났다는 얘기가 되니까

현재 레벨을

Level(현재레벨)을 = level로 바꿔주고

SetStat(Level)을 넣어주자!

그다음에 이제 유니티로가서 다시 테스트를 해보도록 하자.

잘 된다

그리고

Stat.cs에서


+= 수치들을 조절해가면서 예외사항은 없는지 테스트를 해주도록 하자.

그래서

이렇게 경험치를 셋팅하는 순간에

이런식으로 다시 레벨업 여부를 체크를 하면은

이제 누구든지 경험치를 건드는 순간 레벨업을 할지 안할지 결정하게 될 것이다.

-끝-

profile
https://cjbworld.tistory.com/ <- 이사중

0개의 댓글