⚠️이 글은 API 문서를 보고 구현 한걸 내용 정리한겁니다. ↓
https://kb.heathen.group/assets/steamworks/unity-engine/sample-scenes/stats-and-achievements
1. UI로 Stat과 Achievement 가시화 구현
원래 유니티에서 steamworks Inspector로 stat과 achivement 뿐 아니라 다양한 데이터를 볼 수 있습니다.
Heathen api 인스펙터로 stat 및 achievement 한눈에 보기 ↓
그러나 인게임 화면에서도 볼 수 있게 하고 싶었고 무엇보다도 UI로 데이터 접근을 구현하다보면,
인게임의 다른 부분(상점, 결과 화면 등)들을 구현할 때에도 에서도 쉽게 구현 할 수 있을 것이라고 생각합니다.
#1-1 업적 구현
우선 업적부터 구현해봅시다.
먼저 코드를 아래와 같이 작성합니다. ↓
using System.Collections.Generic;
using HeathenEngineering.SteamworksIntegration;
using UnityEngine;
public class AchievementsManager : MonoBehaviour
{
public SteamworksBehaviour steamworksBehaviour;
// AchievementData를 써도 됨
// public AchievementData a;
//* 아니면 AchievementObject 스크립터블 오브젝트 써도 됨
//* 이건 AchievementData 구조체를 Unity의 ScriptableObject로 나타냄
//* 이를 통해 스크립트에서 업적을 참조하고, 업적을 드래그 앤 드롭 가능
//* 또한, 업적이 잠금 상태인지 해제 상태인지를 관리하는 UnityEvent를 처리 가능
public List<AchievementObject> achievementObjects;
//* +추가) 업적 패널에 표기할 것들
public GameObject achievementsPanelGroup;
void Awake()
{
if (steamworksBehaviour == null)
{
steamworksBehaviour = FindAnyObjectByType<SteamworksBehaviour>();
}
}
//* SteamworkBehaviour이 초기화 되면 업뎃하기
public void UpdateAchievements()
{
achievementObjects = steamworksBehaviour.settings.achievements;
for (int i = 0; i < achievementsPanelGroup.transform.childCount; i++)
{
if (achievementObjects[i] != null)
{
achievementObjects[i].StatusChanged?.Invoke(achievementObjects[i].IsAchieved);
}
}
}
public void AchievementTrue(AchievementObject achievementObject)
{
achievementObject.IsAchieved = true;
}
public void AchievementReset(AchievementObject achievementObject)
{
achievementObject.IsAchieved = false;
}
}
코드에서 주목할 부분은 다음과 같습니다.
1. steamworksBehaviour가 초기화 되면 이벤트 발생할 UpdateAchivements() 메서드를 구현함.
2. steamworksBehaviour.settings.achievements모든 업적을 achievementObjects(list 타입)에 저장.
3. AchievementTrue()와 AchievementReset()은 UI 버튼으로 업적을 달성하고 초기화 하기 위함.
4. 이외에 achievementsPanelGroup 옵젝은 그냥 눈으로 보이는 UI 업적들 갯수를 얻기 위함입니다.
위에서 말했듯이 steamworksBehaviour가 초기화 끝나면 UpdateAchivements() 메서드 실행해야합니다.
그렇기 때문에 인스펙터에서 UpdateAchivements() 메서드를 이벤트에 등록합시다.
이벤트에 등록한 후 업적 UI 버튼들로 각각 달성과 초기화도 할 수 있게 해두었습니다.
#1-2 Stat 구현
Stat부분도 업적이랑 같은 원리로 구현했습니다.
우선 코드부터 보시죠 ↓
using System.Collections.Generic;
using HeathenEngineering.SteamworksIntegration;
using UnityEngine;
public class StatsManager : MonoBehaviour
{
public SteamworksBehaviour steamworksBehaviour;
public List<StatObject> userStatObjects;
//* +추가) 스탯 패널에 표기할 것들
public GameObject statPanelGroup;
public GameObject statVisualObj;
//* 업적이랑 연결하기 위해 참조
public AchievementsManager achievementsManager;
void Awake()
{
if (steamworksBehaviour == null)
{
steamworksBehaviour = FindAnyObjectByType<SteamworksBehaviour>();
}
}
//* SteamworkBehaviour이 초기화 되면 업뎃하기
public void UpdateStats()
{
userStatObjects = steamworksBehaviour.settings.stats;
GenerateAllStatVisuals();
}
private void GenerateAllStatVisuals()
{
for (int i = 0; i < userStatObjects.Count; i++)
{
//* stat 패널에 비주얼 프리팹 생성
StatVisual statVisual = Instantiate<StatVisual>(statVisualObj.GetComponent<StatVisual>(), Vector3.zero
, statPanelGroup.transform.rotation, statPanelGroup.transform);
statVisual.selfStatObejct = userStatObjects[i];
//* statVisual의 버튼들에 업적 갱신 함수 추가
statVisual.SetBtn.onClick.AddListener(achievementsManager.UpdateAchievements);
statVisual.ResetBtn.onClick.AddListener(achievementsManager.UpdateAchievements);
statVisual.UpdateStatVisual();
}
}
}
코드에서 주목할 부분은 다음과 같습니다.
1. steamworksBehaviour가 초기화 되면 이벤트 발생할 UpdateStats() 메서드를 구현함.
2. steamworksBehaviour.settings.stats의 모든 stats을 userStatObjects(list타입)에 저장해둠.
3. GenerateAllStatVisuals()는 모든 stats을 가시화 하기 위한 UI를 생성하는 함수입니다.
4. StatVisual이 눈에 띄는데 아래에서 자세히 설명할 예정
위에서 말했듯이 steamworksBehaviour가 초기화 끝나면 UpdateAchivements() 메서드 실행해야합니다.
그렇기 때문에 인스펙터에서 UpdateAchivements() 메서드를 이벤트에 등록합시다.
이제 위에 코드에서 StatVisual이 보이실텐데 이건 수정 가능한 UI 객체를 위해 구현했습니다.
사진에서 보이는 것 처럼 StatVisual이 각각 데이터 설정과 초기화 기능을 가지고 있습니다.
또한 Inputfield로 데이터 값을 입력할 부분도 구현했습니다.
아래는 StatVisual의 코드입니다. ↓
using UnityEngine;
using UnityEngine.UI;
using HeathenEngineering.SteamworksIntegration;
using TMPro;
using System.Text.RegularExpressions;
using Steamworks;
public class StatVisual : MonoBehaviour
{
[Header("프리팹 생성시 지정해줘야함")]
public StatObject selfStatObejct;
[Header("스탯 표기 텍스트 필요")]
public TextMeshProUGUI statNameText;
public TextMeshProUGUI statValueText;
[Header("버튼 참조 필요")]
public Button SetBtn;
public Button ResetBtn;
private TMP_InputField inputField;
private int intTypeValue;
private float flaotTypeValue;
void Awake()
{
inputField = transform.GetChild(0).GetComponent<TMP_InputField>();
}
//* 설정 버튼 누르면 값이 설정됨
public void SaveValue()
{
//* 정규식으로 inputField 값 제한
// 영문자 a-z A-Z 0-9
inputField.text = Regex.Replace(inputField.text, @"[^0-9.]", "");
if (!string.IsNullOrEmpty(inputField.text))
if (selfStatObejct.Type == StatObject.DataType.Int)
{
if (int.TryParse(inputField.text, out intTypeValue))
{
selfStatObejct.SetIntStat(intTypeValue);
// stat 값 저장 (모든 스탯들 다 저장됨)
selfStatObejct.StoreStats();
}
else
Debug.Log("int가 아닙니다.");
}
else
if (selfStatObejct.Type == StatObject.DataType.Float)
{
if (float.TryParse(inputField.text, out flaotTypeValue))
{
selfStatObejct.SetFloatStat(flaotTypeValue);
// stat 값 저장 (모든 스탯들 다 저장됨)
selfStatObejct.StoreStats();
}
else
Debug.Log("float가 아닙니다.");
}
UpdateStatVisual();
}
public void ResetValue()
{
inputField.text = "0";
SaveValue();
}
//* 표기 갱신
public void UpdateStatVisual()
{
statNameText.text = selfStatObejct.name;
if (selfStatObejct.Type == StatObject.DataType.Int)
{
if (selfStatObejct.GetValue(UserData.Me, out intTypeValue))
{
statValueText.text = intTypeValue.ToString();
}
}
else
if (selfStatObejct.Type == StatObject.DataType.Float)
{
if (selfStatObejct.GetValue(UserData.Me, out flaotTypeValue))
{
statValueText.text = flaotTypeValue.ToString();
}
}
}
}
구현하기 위해 api 문서 보면서 알아 낸 사실을 종합해보자면,
1. Stat은 현재 서버에서 플레이 중인 클라이언트에 대해서만 자동 업데이트됨.
2. 그렇기 때문에 각각 다른 사용자의 경우 데이터를 새로 고치려면 다시 호출해야 함.
3. 그러기 위해 존재하는 것이 아래 RequestUserStats 메서드 코드다.
RequestUserStats 메서드를 알아봅시다.
#1-3 RequestUserStats 메서드
public void RequestUserStats(UserData user,
Action<UserStatsReceived_t, bool> callback)
위 메서드의 사용 방법은 특정 stat 하나를 지정하고,
StatObject selfStatObejct;
아래 처럼 람다식으로 Request 콜백을 구현할 수 있습니다. ↓
selfStatObejct.RequestUserStats(UserData.Me, (r, e) =>
{
//* 람다식으로 구현했는데 밑에 HandleCallback으로도 구현 가능
if (!e && r.m_eResult == EResult.k_EResultOK)
{
// 해당 사용자의 i번째 stats을 성공적으로 받음
Debug.Log(selfStatObejct.name + ", stats 불러오기 성공");
}
else
{
// 해당 사용자의 stats을 받는데 실패함
Debug.Log(selfStatObejct.name + ", stats 불러오기 실패");
}
});
+대체제) 그리고 람다식 아니여도 콜백 헨들러 구현 가능합니다. ↓
//* 람다식 대신 이렇게 구현해도 됨
void HandleCallback(UserStatsReceived_t results, bool IOError)
{
if (!IOError && results.m_eResult == EResult.k_EResultOK)
{
// 해당 사용자의 stats을 성공적으로 받음
Debug.Log("stats 불러오기 성공");
}
else
{
// 해당 사용자의 stats을 받는데 실패함
Debug.Log("stats 불러오기 실패");
}
}
추가로 Request 해보니깐 콜백 때문에 하나씩 처리해야해서 여러개를 빠르게 처리가 안되는 것 같습니다.
#1-4 Stat 타입은 어떻게 알아내고 처리함?
문서를 읽다보면 데이터 타입을 INT, FLOAT, AVGRATE로 나눠서 처리하라는데,
public bool GetValue(UserData user, out int value)
public bool GetValue(UserData user, out float value)
이렇게 int랑 float 타입 GetValue 메서드만 던져줘서 처음에 헤맸습니다.
그래서 제가 직접 만들면서 두가지 방법을 고안해봤습니다.
우선 위에서 StatVisual 부분 설명에서 사용한 코드를 기반으로 두고 만들었습니다.
1. 특정 StatObject를 인스펙터나 코드로 참조해서 준비해둡니다.
StatObject selfStatObejct;
방법 1. 타입에 맞게 GetValue() 대신 GetFloatValue()와 GetIntValue() 사용함
private void ExampleCheckTypeOne()
{
//* 타입에 맞게 값을 받아서 string으로 추출함.
if (selfStatObejct.Type == HeathenEngineering.SteamworksIntegration.StatObject.DataType.Float)
selfStatObejct.GetFloatValue().ToString();
else
selfStatObejct.GetIntValue().ToString();
//* 여기다가 뭐 text UI에 string 값을 그대로 넣어주는거 구현하면 됨
}
방법 2. 타입에 맞게 switch문 이용하기
private void ExampleCheckTypeTwo()
{
var newName = "새로운 값";
if (!string.IsNullOrEmpty(newName) && newName != selfStatObejct.data)
{
selfStatObejct.data = newName;
switch (selfStatObejct.Type)
{
case StatObject.DataType.Int:
selfStatObejct.name = "[Stat Int] " + newName;
break;
case StatObject.DataType.Float:
selfStatObejct.name = "[Stat Float] " + newName;
break;
case StatObject.DataType.AvgRate:
selfStatObejct.name = "[Stat AvgRate] " + newName;
break;
}
}
}
이렇게 두가지 방법으로 stat 타입에 맞게 다뤄봤습니다.
api를 더 뜯어보니 얘네도 자기들 멋대로 쓰더라고요.
그냥 편한대로 구현해봅시다!
'스팀웍스SteamWorks > Heathen API' 카테고리의 다른 글
스팀 인벤토리 구조 Steam Inventory Schema (0) | 2023.06.07 |
---|---|
Heathen API 디버깅 툴 Debugging Tools (0) | 2023.05.19 |
Heathen API Steamworks Behaviour 스팀웍스 Behaviour (0) | 2023.05.16 |
Heathen API 게임 오브젝트 초기화 GameObject Initialization (0) | 2023.05.16 |