PlayerData
-PlayerUsername
- Player HP: (적에게 총알 맞으면 감소, 체력 아이템 쥐면 풀 충전)
=>적 총알: 한 발에 -8점
- 획득점수(Score)
- 방패의 Gauge : 1초에 20%씩 감소
-현재 소지한 총의 종류(gunId)
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 변환
https://shancarter.github.io/mr-data-converter/
Mr. Data Converter
shancarter.github.io
Online JSON Viewer and Formatter
jsonviewer.stack.hu
-추가한 json 파일들을 Resources/Data 폴더안에 넣어준다.
-Newtonsoft Json 패키지가 추가되어있는지 확인해주었다.
[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.cs를 생성하여 데이터를 로드하는 Load 메서드와 로드한 데이터를 받는 Get함수를 작성했다.
-DataManager에서는 딕셔너리를 생성하여 json에서 데이터를 역직렬화한 다음 저장한다.
-로드해서 저장된 데이터는 Get 메서드에서 List로 반환된다.
-빈 오브젝트로 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를 생성해두고 껐다 켰다 할 수 있도록 오브젝트 풀링을 사용하는 코드로 변경해준다.
- 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의 데이터에 따라 해당하는 값을 띄우도록 코드를 작성하였다.
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;
}
}
}
}
'[VR] GroundZero 개발' 카테고리의 다른 글
[4Idle - Gazzlers] Player 이동 기능 + 손 기능 합치기 (0) | 2023.12.01 |
---|---|
[4Idle - Gazzlers] 1차 점검 (0) | 2023.11.30 |
[4Idle - Gazzlers] 플레이어 오른손 기능 구현(총 쏘기+ 데미지 텍스트 효과) (1) | 2023.11.21 |
[4Idle-Gazzlers] 플레이어 왼손 구현 -2. 방패 부착 (1) | 2023.11.14 |
[4Idle-Gazzlers] 플레이어 왼손 구현 -1. 아이템 그랩/사용 (1) | 2023.11.13 |