안녕하세요.
<가디언즈 오브 레스토레이션>을 1인 개발하고 있는 창작고래 입니다.
올해 정식 출시를 목표로 열심히 작업하고 있습니다.
최근에 스팀의 업적과 스팀덱 진동을 구현을 적용하였습니다.
스팀 업적 부분은 쉽게 적용되었지만 스팀덱 진동에서 많은 시행착오를 격은 거 같습니다.
부족한 스크립트이지만 스팀덱 진동을 구현하시는 분들께 도움이 되었으면 하여 작성한 스크립트를 공유해 드립니다.
UniTask 와 Steamworks .net(ver 2024.8.0)을 사용하였습니다.
스팀덱의 진동의 세기가 6dB이 기본으로 적용되어 있는데 설정에서 12dB로 변경하면 진동이 약하게 체감됩니다.
스팀덱 관련 진동만 스팀 input api를 사용하였습니다.
================================================================================================
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using Cysharp.Threading.Tasks;
#if USE_STEAM
using Steamworks;
#endif
public class SteamSDKManager : MonoBehaviour
{
#if USE_STEAM
public static SteamSDKManager Instance;
// 스팀 덱 여부
public static bool IsSteamDeck;
private void Awake()
{
if (Instance == null) Instance = this;
}
bool isInitiallized = false;
// 실패 시 종료 창 관련 액션
UnityAction<string> messageAction;
// 스팀덱 동작 확인 시 스팀덱 핸들 저장용
InputHandle_t inputHandle;
/// <summary>
/// 스팀 초기화 진행
/// </summary>
/// <param name="action">실패 시 메시지 창 내용 전달 액션</param>
/// <returns></returns>
public async UniTask Initialize(UnityAction<string> action)
{
messageAction = action;
//스팀 클라이언트 실행 여부 확인
if (!SteamAPI.IsSteamRunning())
{
// 초기화를 실패하여 게임을 종료합니다. {0}s
FailGameQuit(Localize.GetLocalizedString(Const.LOCALIZE_COMMON_SYSTEM,
"Start_Init_Fail")).Forget();
return;
}
//스팀 API 초기화 진행
if (!SteamAPI.Init())
{
// 초기화를 실패하여 게임을 종료합니다. {0}s
FailGameQuit(Localize.GetLocalizedString(Const.LOCALIZE_COMMON_SYSTEM,
"Start_Init_Fail")).Forget();
return;
}
// 스팀 API 초기화 성공
isInitiallized = true;
// 현재 게임이 사용자의 계정에 있는지 확인
bool isPurchased = SteamApps.BIsSubscribed();
if (!isPurchased)
{
// 구매 확인 실패로 게임 종료
FailGameQuit(Localize.GetLocalizedString(Const.LOCALIZE_COMMON_SYSTEM,
"Purchased_Fail")).Forget();
return;
}
// 스팀 Input API 초기화 진행
SteamInput.Init(false);
// 스팀 Input 동작 체크
SteamInput.RunFrame();
// 스팀덱 동작 여부 체크
IsSteamDeck = SteamUtils.IsSteamRunningOnSteamDeck();
// 스팀덱 동작일 경우 스팀덱 핸들 얻기
if (IsSteamDeck)
{
StartCoroutine(CoSteamInputCheck());
InputHandle_t[] controllers = new InputHandle_t[Constants.STEAM_INPUT_MAX_COUNT];
SteamInput.GetConnectedControllers(controllers);
foreach (var handle in controllers)
{
ESteamInputType temp = SteamInput.GetInputTypeForHandle(handle);
if (temp == ESteamInputType.k_ESteamInputType_SteamDeckController)
{
inputHandle = handle;
break;
}
}
}
// 임의로 대기 0.3초 넣어줌
await UniTask.WaitForSeconds(0.3f);
}
// 매 프레임 스팀 Input 체크
IEnumerator CoSteamInputCheck()
{
while (true)
{
// 중요! 스팀 Input 동작 체크
SteamInput.RunFrame();
yield return null;
}
}
// 실패 시 3초 후 게임 종료 관련
async UniTask FailGameQuit(string message)
{
for (int i = 3; i > 0; i--)
{
messageAction(string.Format(message, i));
await UniTask.WaitForSeconds(1);
}
Application.Quit();
}
bool isSetAchievement; //스팀 업적 변경 여부
bool isUpdateAchievement; //스팀 업적 변경 내용 저장 여부
/// <summary>
/// 스팀 업적 달성 등록
/// </summary>
/// <param name="key">업적 관련 키</param>
/// <returns>등록 성공 여부</returns>
public bool SetAchievement(string key)
{
isSetAchievement = false;
isUpdateAchievement = false;
if (!isInitiallized) return false;
if (SteamUserStats.SetAchievement(key)) isSetAchievement = true;
else isSetAchievement = false;
isUpdateAchievement = SteamUserStats.StoreStats();
return isSetAchievement && isUpdateAchievement;
}
/// <summary>
/// 스팀 업적 달성 확인
/// </summary>
/// <param name="key">업적 관련 키</param>
/// <returns>달성 여부</returns>
public bool CheckAchievement(string key)
{
bool result = false;
if (!isInitiallized) return result;
bool isSuccess = SteamUserStats.GetAchievement(key, out result);
if (result && isSuccess) return true;
else return false;
}
/// <summary>
/// 스팀 업적 초기화
/// </summary>
/// <param name="key">업적 관련 키</param>
public void ClearAchievement(string key)
{
if (!isInitiallized) return;
bool isSuccess = SteamUserStats.ClearAchievement(key);
if (isSuccess)
{
SteamUserStats.StoreStats();
}
}
Coroutine stopRumbleRoutine;
/// <summary>
/// 스팀덱 진동 시작
/// </summary>
/// <param name="lowFrequency"></param>
/// <param name="highFrequency"></param>
/// <param name="duration">지속 시간</param>
public void SetRumble(float lowFrequency, float highFrequency, float duration)
{
if (!isInitiallized) return;
if (!IsSteamDeck) return;
// 스팀덱 진동이 워낙 약해서 최대 값 적용하고 강도를 지속시간으로 처리
SteamInput.TriggerVibration(inputHandle, 65535, 65535);
if (stopRumbleRoutine != null) StopCoroutine(stopRumbleRoutine);
stopRumbleRoutine = StartCoroutine(CoSteamDeckStopRumble(inputHandle, duration));
}
IEnumerator CoSteamDeckStopRumble(InputHandle_t inputHandle, float duration)
{
float timer = 0;
while (timer < duration)
{
timer += Time.unscaledDeltaTime;
yield return null;
}
StopRumble(inputHandle);
stopRumbleRoutine = null;
}
void StopRumble(InputHandle_t inputHandle)
{
SteamInput.TriggerVibration(inputHandle, 0, 0);
}
// 게임 종료 관련 스팀 API 중단
private void OnApplicationQuit()
{
if (isInitiallized)
{
if (stopRumbleRoutine != null)
{
StopCoroutine(stopRumbleRoutine);
if(inputHandle != null) StopRumble(inputHandle);
}
SteamAPI.Shutdown();
isInitiallized = false;
}
}
#endif
}
================================================================================================
SteamInput.RunFrame(); 이 부분을 시작 부분과 매프레임에 적용 안하면
IsSteamDeck = SteamUtils.IsSteamRunningOnSteamDeck();
이 부분에서 스팀덱 가동 여부는 true가 되지만
foreach (var handle in controllers)
{
ESteamInputType temp = SteamInput.GetInputTypeForHandle(handle);
if (temp == ESteamInputType.k_ESteamInputType_SteamDeckController)
{
inputHandle = handle;
break;
}
}
이 부분에서 ESteamInputType.k_ESteamInputType_SteamDeckController을 찾지 못하는 문제가 발생하는거 같았습니다.
마지막으로 현재까지 적용된 게임의 0.8.0.0 버전의 트레일러 영상입니다.
감사합니다~ ^^
오~~ 굿.