[VR] GroundZero 개발

[4Idle - Gazzlers] Player Data, Gun Data, Enemy Data 정리 1

meltingmelvin 2023. 11. 27. 00:28

 

PlayerData

-PlayerUsername

- Player HP: (적에게 총알 맞으면 감소, 체력 아이템 쥐면 풀 충전)
     =>적 총알: 한 발에 -8점
- 획득점수(Score)
- 방패의 Gauge : 1초에 20%씩 감소

-현재 소지한 총의 종류(gunId)

 

https://communityforums.atmeta.com/t5/Unity-VR-Development/How-do-i-access-oculus-username/td-p/889007

 

How do i access oculus username?

I've searched everywhere and i have not been able to find a single post about how to get a string of text which contains the local player's username. Does anyone know how to do it with v33, and if so can you go into detail a bit, thankyou.

communityforums.atmeta.com

-랭킹시스템이 있으므로 플레이어는 각자의 기록을 기록할때 오큘러스의 username으로 기록하게된다.

=> 즉, 각각의 플레이어는 게임이 시작되면 본인의 username으로 로그인하게 되는 셈이다.

=> 서버에는 username, score가 묶여서 저장된다. 이를 이용해 랭킹을 기록한다.

 

 

 

GunData

-총 id(gunId)

-총 typeName

-총 material

-파괴력(Damage)

-최대 탄창수(Energy)

 

 

 

EnemyData

-Enemy종류

-Enemy HP
-플레이어 공격 데미지(Attack Damage)

 

 

 

Player가 Enemy 공격시 데이터 연동

-먼저 적 공격 시 데이터 연동을 하려한다. 다음 2가지 내용이 각각 구현되어야 한다.

-----------------------------------------------------------------------------------------------------------------------------------------------------------------

1. 플레이어가 총을 쏴서 몬스터에게 맞췄을 때에 DamageText를 띄우는데, 이 때 표시되는 데미지는 GunData의 Damage에 의해 결정된다. 

=> 이 때 PlayerData의 GunId에 따라 GunData의 값을 찾아야한다.

 

2. Enemy Data에 있는 적의 HP는 플레이어에게 타격될 때 GunData의 Damage에 따라 그만큼 감소한다.

=> 이 HP의 값에 따라 적의 HP Bar의 슬라이더 값이 조절된다.

-----------------------------------------------------------------------------------------------------------------------------------------------------------------

위의 정리내용을 토대로 json을 만들어준다.

-엑셀로 table 생성 후 json 변환

PlayerData
GunData / EnemyData

https://shancarter.github.io/mr-data-converter/

 

Mr. Data Converter

 

shancarter.github.io

https://jsonviewer.stack.hu/

 

Online JSON Viewer and Formatter

 

jsonviewer.stack.hu

 

 

 

-추가한 json 파일들을 Resources/Data 폴더안에 넣어준다.

 

Newtonsoft Json 패키지 추가

-Newtonsoft Json 패키지가 추가되어있는지 확인해주었다.

 

https://gofogo.tistory.com/64

 

[unity/json]Newtonsoft Json 라이브러리 손쉽게 추가하는법(package manager 이용)

[unity/json]Newtonsoft Json 라이브러리 손쉽게 추가하는법(package manager 이용) [핵심] com.unity.nuget.newtonsoft-json [windows]->[package manager]로 이동 후 [add pakaage by name...] 선택 com.unity.nuget.newtonsoft-json 를 입력 후

gofogo.tistory.com

 

-playerData, gunData, enemyData 클래스를 생성한다.

 

 

-DataManager.cs를 생성하여 구조화한다.

 

데이터 로드

DataManager

-DataManager.cs를 생성하여 데이터를 로드하는 Load 메서드와 로드한 데이터를 받는 Get함수를 작성했다.

-DataManager에서는 딕셔너리를 생성하여 json에서 데이터를 역직렬화한 다음 저장한다.

-로드해서 저장된 데이터는 Get 메서드에서 List로 반환된다.

GameMain

-빈 오브젝트로 GameMain을 만들고 GameMain.cs를 넣어준다.

-이 메인에서 데이터를 로드하고, Get하여 잘 가져왔는지 확인해보았다.

=> 플레이어가 현재 들고있는 총을 GunData에서 찾아 정보를 가져오는 것이다.

-현재에는 start에서 테스트해보고있지만, 나중에 아이템을 적용해서 총이 바뀌는 경우에는 이 코드를 응용해 바꿔줘야한다. 현재 총을 바꿔서 playerGunId를 수정하여 저장하고, 그 id에 따라 GunData를 가져오면된다.

 

EnemyData 로드 - UI Slider값과 EnemyData의 Enemy Hp 연동

-먼저 DataManager에 Enemy 딕셔너리, LoadEnemyDatas()와 GetEnemyDatas()를 추가한다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace hyw
{
    public class DataManager
    {//DataManager.cs manage Datas

        public static readonly DataManager Instance = new DataManager();

        //Dictionaries To save Data
        private Dictionary<string, PlayerData> dicPlayerDatas = new Dictionary<string, PlayerData>();
        private Dictionary<int, GunData> dicGunDatas = new Dictionary<int, GunData>();
        private Dictionary<string, EnemyData> dicEnemyDatas = new Dictionary<string, EnemyData>();
        //method
        public void LoadPlayerDatas()
        {
           
            if (File.Exists("./Assets/Resources/Data/playerData.json"))
            {
                //read file
                string json = File.ReadAllText("./Assets/Resources/Data/playerData.json");
               // Debug.Log(json);

                var playerDatas = JsonConvert.DeserializeObject<PlayerData[]>(json);
                this.dicPlayerDatas = playerDatas.ToDictionary(x => x.playerUserName);

            }
            else
            {
                Debug.Log("File doesn't exist!");
                return;
            }      
            Debug.LogFormat("Loaded Player Data: {0}", this.dicPlayerDatas.Count);
        }

        public void LoadGunrDatas()
        {
            if (File.Exists("./Assets/Resources/Data/gunData.json"))
            { //read file
                string json = File.ReadAllText("./Assets/Resources/Data/gunData.json");
                var gunDatas = JsonConvert.DeserializeObject<GunData[]>(json);
                this.dicGunDatas = gunDatas.ToDictionary(x => x.gunId);

            }
            else
            {
                Debug.Log("File doesn't exist!");
                return;
            }
            Debug.LogFormat("Loaded Player Data: {0}", this.dicGunDatas.Count);
        }
        public void LoadEnemyDatas()
        {
            if (File.Exists("./Assets/Resources/Data/enemyData.json"))
            { //read file
                string json = File.ReadAllText("./Assets/Resources/Data/enemyData.json");
                var enemyDatas = JsonConvert.DeserializeObject<EnemyData[]>(json);
                this.dicEnemyDatas = enemyDatas.ToDictionary(x => x.enemyType);


            }
            else
            {
                Debug.Log("File doesn't exist!");
                return;
            }
            Debug.LogFormat("Loaded Player Data: {0}", this.dicGunDatas.Count);
        }


        public List<PlayerData> GetPlayerDatas()
        {
            return this.dicPlayerDatas.Values.ToList();
        }

        public List<GunData> GetGunDatas()
        {
            return this.dicGunDatas.Values.ToList();
        }
        public List<EnemyData> GetEnemyDatas()
        {
            return this.dicEnemyDatas.Values.ToList();
        }
    }
}

 

- Enemy들은 여러 종류가 있다. EnemyData는 이 여러 종류의 각각의 데이터 정보를 담고있는 역할을한다.

- Enemy가 생성될 때 어떤 종류의 Enemy로 생성될지는 별도의 클래스로 관리해야한다. 

- 생성될 때 정해진 Enemy의 종류를 받아 EnemyData에서 데이터를 찾아서 쓰는 것이다.

예를 들어, FrankenStein이라는 Enemy를 생성했다면 EnemyData에서 FrankenStein의 데이터를 가져온다.

=> Enemy 생성에 관여하는 클래스로 GameEnums.cs를 생성하였다.

 

-빈 오브젝트를 만들고 EnemyGenerator라 하자.

-Enemy Generator는 Enemy Go List에 할당된 프리팹을 담고 있고, Generate 메서드를 통해 Enemy를 생성한다.

-GameObject 프리팹이 필요하므로 FrankenStein 프리팹을 생성하고, 기존에 EnemyController에 작성했던 코드를 GameMain.cs로 옮겨 수정해주었다.

-GamaMain에서 Enemy를 생성하는 Generate메서드를 호출하여 Enemy를 생성하도록 한다.

=> UI 관련된 부분을 GameMain이 담당하게 하였다. 따라서 맞았을 때 데미지 텍스트를 띄우는 것도 GameMain에서 처리한다.

-GameMain.cs에서 생성하는 Enemy마다 각각의 HpBar가 있을 것이므로 위치는 EnemyController에서 받아와 각각 업데이트 해주어야한다.

-따라서 Generate할 때 EnemyController를 return하도록 하여 비어있던 리스트는 enemyList에 Add하고, update문에서 리스트를 돌며 position값을 바꿔주도록 코드를 수정하였다.

 

Generate 되는 몬스터들에게 HPBar 할당 - 오브젝트 풀링

-현재에는 하나의 FrankenStein이 Generate되고, start될때 하나의 HPBar가 생성되어 문제가 없지만 여러개를 generate하기 시작하면 hpBar가 부족해지는 문제가 발생한다. 따라서 여러개의 HPBar를 생성해두고 껐다 켰다 할 수 있도록 오브젝트 풀링을 사용하는 코드로 변경해준다.

오브젝트 풀링 / start에서 호출

- GameMain의 start 부분을 수정했다.

- 기존에 hpBar를 Instantiate하는 부분을 주석처리하고, 대신 오브젝트 풀에서 가져오도록한다.

- enemyList에 저장된 적의 수, 즉 새로 생성된 적의 수 만큼 for문을 도는 동안 내부에 data를 검색하도록한다. 

- 데이터의 enemyType과 게임 오브젝트.name이 같은 경우에 i번째의 HPBar에 j에 해당하는 HP값을 넣어준다.

=> i냐 j냐 주의하자. HPBar는 i개 생길테고, 각각의 HPBar에게 해당하는 데이터를 넣어주는 것이다.

 

Action 대리자로 Hit 이벤트 처리

 

-총을 쏠 때, Hit한 오브젝트가 무엇인지 GameMain에 전달되도록 RightHandController를 수정한다.

-매개변수를 통해 hit된 게임 오브젝트를 전달한다.

=> 오브젝트를 enemyList의 몬스터들과 비교해 같은 개체인 경우에 HP를 감소시킨다.

- Damage 값은 gunData에서 가져와야 하므로 다음과 같이 수정해주었다.

- 이벤트가 여러개 받아오는 문제가 발생해 로그를 찍어보니 Input 문제였다.

- OVRInput.Get이 아니라 OVRInput.GetDown으로 수정했다.

 

   OVRInput.Get()  OVRInput.GetDown()
 Update() 에서 호출 누른 상태에서 계속 호출  눌렀을 때 최초 한번만 호출
 Access 방식 결합방식 (Accessed as a Combined Controller) 개별방식 (Accessed as a Individual Controller)
 Parameter OVRInput.[사용하는 버튼] OVRInput.Button.[사용하는 버튼]
 return value  float [사용하는 버튼에 따라 다름]  bool
 사용예시  OVRInput.Get(OVRInput.Axis1D.SecondaryIndexTrigger,
OVRInput.Controller.Touch);
 OVRInput.GetDown(OVRInput.Button.PrimaryIndexTrigger,
OVRInput.Controller.RTouch);

 

-HP 바에 EnemyData의 데이터를 가져와 해당하는 Enemy의 종류에 따라 최대 체력을 정해준다.

-총도 GunData를 가져와 총 종류에 따라 데미지를 다르게 적용한다.

 

데미지 텍스트에 데이터 연동

-데미지 텍스트에도 GunData의 데이터에 따라 해당하는 값을 띄우도록 코드를 작성하였다.

적 타격시 EnemyHP와 GunData데이터 연동 결과

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace hyw
{
    public class EnemyGenerator : MonoBehaviour
    {
        public List<GameObject> enemyGoList;

        // Start is called before the first frame update
        void Start()
        {
           
        }

        public EnemyController Generate(GameEnums.eEnemyType enemyType, Vector3 initPosition)
        {
            int index = (int)enemyType;
            GameObject enemy = this.enemyGoList[index];
            GameObject go = Instantiate(enemy);
            go.name = enemy.name;

            go.transform.position = initPosition;
            return go.GetComponent<EnemyController>();
        }
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace hyw
{
    public class RightHandController : MonoBehaviour
    {
        public System.Action<Vector3,GameObject> OnHitEnemy;

        [SerializeField] Transform shootTrans;
        [SerializeField] Transform shootDistance;
        [SerializeField] GameObject gunLaserGo;
        [SerializeField] GameObject impactEffectGo;

        private GameObject gunLaserBeamGo;
        private GameObject laserImpactGo;
       
        // Start is called before the first frame update
        void Start()
        {
            this.gunLaserBeamGo = Instantiate<GameObject>(this.gunLaserGo);
            this.laserImpactGo = Instantiate<GameObject>(this.impactEffectGo);
            this.gunLaserBeamGo.SetActive(false);
            this.laserImpactGo.SetActive(false);
        }

        // Update is called once per frame
        void Update()
        {

            if (OVRInput.GetDown(OVRInput.Button.SecondaryIndexTrigger))
            {
                Debug.Log("right Hand Index Trigger");
                StartCoroutine(this.CoLaserBeam());
                StartCoroutine(this.CoCheckImpact());
            }
           



        }

        private IEnumerator CoLaserBeam()
        {
            this.gunLaserBeamGo.transform.position = this.shootTrans.position;
            this.gunLaserBeamGo.SetActive(true);
            this.gunLaserBeamGo.transform.LookAt(this.shootDistance.position);//hit nothing


           
            yield return new WaitForSeconds(0.3f);
            this.gunLaserBeamGo.SetActive(false);
            this.laserImpactGo.SetActive(false);
        }
        private IEnumerator CoCheckImpact()
        {
            //-----------------------------check impact---------------------------------------

           
            Ray ray = new Ray(this.shootTrans.position, this.gunLaserBeamGo.transform.forward);
            Debug.DrawRay(ray.origin, ray.direction * 10f, Color.red, 0.3f);
            var layerMask = 3 << LayerMask.NameToLayer("Monster");
            RaycastHit hit;

            if (Physics.Raycast(ray.origin, ray.direction, out hit, 10.0f))
            {
                // Debug.Log("Hit Monster!!");
                if (!hit.collider.gameObject.CompareTag("Vehicle"))
                { this.CreateImpactEffect(hit.point); }

                if (hit.collider.gameObject.CompareTag("Enemy"))
                {
                    Debug.Log("Enemy");
                    this.OnHitEnemy(hit.point, hit.collider.gameObject);
                }
                var particleSys = this.gunLaserBeamGo.GetComponent<ParticleSystemRenderer>();
                particleSys.lengthScale = hit.distance;
            }
           
            //-----------------------------------------------------------------------------------
            yield return null;
        }
        private void CreateImpactEffect(Vector3 pos)
        {
            this.laserImpactGo.transform.position = pos;
            this.laserImpactGo.SetActive(true);
        }

    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
namespace hyw
{
    public class GameMain : MonoBehaviour
    {
        [SerializeField] EnemyGenerator enemyGen;
        [SerializeField] Canvas worldCanvas;
        [SerializeField] GameObject uiHPBarPrefab;
        [SerializeField] GameObject uiDamageTextPrefab;
        [SerializeField] RightHandController rightHand;

      //  private int maxEnemyCount = 8;
        private GameObject hpBarUIGo;
        private GameObject damageTextUIGo;
        private EnemyGenerator enemyGenerator;
        private string currGunType;
        private int currGunDamage;

        private List<EnemyController> enemyList = new List<EnemyController>();
        private List<GameObject> enemyHpBarPools = new List<GameObject>();
        private List<GameObject> activeHPBar = new List<GameObject>();

        private void Awake()
        {
            this.enemyGenerator = enemyGen;
            //----------------------------------Generate Enemy-----------------------------------------------------------
            this.enemyList.Add(this.enemyGenerator.Generate(GameEnums.eEnemyType.FrankenStein, new Vector3(-0.09f, -1.35f, 4.3f)));
            this.enemyList.Add(this.enemyGenerator.Generate(GameEnums.eEnemyType.FrankenStein, new Vector3(2.41f, -1.35f, 4.3f)));
        }
        // Start is called before the first frame update
        void Start()
        {
           
            DataManager.Instance.LoadPlayerDatas();
            DataManager.Instance.LoadGunrDatas();
            DataManager.Instance.LoadEnemyDatas();

            List<PlayerData> playerData = DataManager.Instance.GetPlayerDatas();
            List<GunData> gunData = DataManager.Instance.GetGunDatas();
            List<EnemyData> enemyData = DataManager.Instance.GetEnemyDatas();

            var playerGunId = playerData[0].currGunId;

            // Debug.Log(playerGunId);
            for (int i = 0; i < gunData.Count; i++)
            {
                if (playerGunId == gunData[i].gunId)
                {//Find player's current gun's Data on GunData 
                    Debug.Log(gunData[i].gunType);
                    this.currGunType = gunData[i].gunType;
                    this.currGunDamage = gunData[i].gunDamage;
                   //gunData[i].gunDamage;
                }
            }


            //this.hpBarUIGo = Instantiate(this.uiHPBarPrefab,this.worldCanvas.transform);
            this.enemyHpBarPool();

            for(int i=0; i < this.enemyList.Count; i++)
            {
                this.activeHPBar.Add(this.CreateHpBar(this.enemyList[i].hpBarPoint.position));
               // enemyList[i].gameObject.
               
              for(int j = 0; j < enemyData.Count; j++)
                {
                    if (this.enemyList[j].name == enemyData[j].enemyType)
                    {
                        //Debug.Log(enemyData[j].enemyType);
                        var slider = this.activeHPBar[i].GetComponent<Slider>();
                        slider.maxValue = enemyData[j].enemyHp;
                        Debug.Log(slider.maxValue);
                        slider.value = slider.maxValue;//reset slider value as full state
                    }
                }
            }

       

            this.damageTextUIGo = Instantiate(this.uiDamageTextPrefab, this.worldCanvas.transform);
            // this.hpBarUIGo.SetActive(false);
            this.damageTextUIGo.SetActive(false);

          
            this.rightHand.OnHitEnemy = (hitPos,hitObject) =>
            {
               // Debug.LogFormat("Hit Enemy! Point: {0}", hitPos);
                StartCoroutine(this.CoShowDamageText(hitPos));
                
                for (int i = 0; i < this.enemyList.Count; i++)
                {
                    if (hitObject == this.enemyList[i].gameObject)
                    {
                        Debug.LogFormat("Object: {0} , Damage : {1}",i,currGunDamage);
                        this.activeHPBar[i].GetComponent<Slider>().value -= currGunDamage;
                    }
            }
             
            };
          

          
        
        }
        private void enemyHpBarPool()
        {
            for(int i = 0; i < this.enemyList.Count; i++)
            {
                GameObject go = Instantiate(this.uiHPBarPrefab, this.worldCanvas.transform);
                go.SetActive(false);
                this.enemyHpBarPools.Add(go);
            }
        }
        private GameObject GetEnemyHpBarInPool()
        {
            foreach(GameObject hpBar in enemyHpBarPools)
            {
                if(hpBar.activeSelf == false)
                {
                    return hpBar;               
                }
            }
            return null;
        }
        private GameObject CreateHpBar(Vector3 position)
        {
            GameObject go = this.GetEnemyHpBarInPool();
            go.transform.position = position;
            go.SetActive(true);
            return go;
        }
        private void UpdateEnemyHPBar(Vector3 worldPos)
        {
            //----------------if Canvas Render Mode is Overlay-------------------------------
            //var screenPos = RectTransformUtility.WorldToScreenPoint(Camera.main, worldPos);
            //Vector2 localPos;
            //RectTransformUtility.ScreenPointToLocalPointInRectangle(
            //    (RectTransform)this.canvas.transform, screenPos, this.uiCam, out localPos);
            //Debug.Log(localPos);
            //this.hpBarUIGo.GetComponent<RectTransform>().localPosition = localPos;
            //-------------------------------------------------------------------------------------
            //---------------if world Position (VR always need world Pos)--------------------------
            // this.hpBarUIGo.transform.position = worldPos;
           
            //-------------------------------------------------------------------------------------
        }
        private IEnumerator CoShowDamageText(Vector3 UIPos)
        {
            this.damageTextUIGo.transform.position = UIPos;
            this.damageTextUIGo.SetActive(true);

            var text = this.damageTextUIGo.GetComponent<TextMeshProUGUI>();
            text.text = string.Format("{0}",this.currGunDamage);
            yield return new WaitForSeconds(0.3f);
            this.damageTextUIGo.SetActive(false);
        }
        // Update is called once per frame
        void Update()
        {
            for (int i = 0; i < this.enemyList.Count; i++)
            {
                //  this.UpdateEnemyHPBar(this.enemyList[i].hpBarPoint.position);
                this.activeHPBar[i].transform.position = this.enemyList[i].hpBarPoint.position;
                

            }
        }
    }
}