SteamWorksPy
まず挙げられる方法としてはラッパを使用することだと思います。
こんなのがあります
https://github.com/philippj/SteamworksPy/tree/master
マッチメイキング機能から、基本的なsteamの情報を取得する機能までがある。
使い方はこの動画がとても参考になりました。
https://www.youtube.com/watch?v=Ka9LIGYm_b0
ただしp2p方式の通信機能までは備わっていなかったので(もしかしたらあるのかもしれない)
今後の拡張性を考慮して、C++から拡張しPythonで使えるようにする。
VS
VSを使う
まずはプロジェクトのプロパティーから、SteamSDKを読み込む
https://partner.steamgames.com/downloads/list
SteamSDKはこのサイトからダウンロードできる
- VC++ディレクトリー -> ライブラリディレクトリー -> C:\steamworks_sdk\redistributable_bin\win64\steam_api64.lib
... ライブラリの読 - C/C++ -> 追加のインクルードディレクトリ -> C:\steamworks_sdk\public
これでおそらく、cppファイルで#include が読み込めるようになった。 - C/C++ -> SDLチェック -> いいえ
これはC言語の低レベルな関数を使うためにチェックを外す必要がある...らしい?(これがTrueだとstring.hが読み込めなかった) - リンカー -> 追加の依存ファイル -> steam_api64.lib
- リンカー -> 全般 -> 使いのライブラリディレクトリー -> C:\steamworks_sdk\redistributable_bin\win64
これでデバッグ時に依存ファイルをチェックしてくれる。
憶測なので確実な情報じゃないよ
また実行ファイルと同じディレクトリーにsteam_api64.dllをセットしておく必要がある。
スクリプトも例を乗せておく
#include <iostream>
#include <chrono>
#include <thread>
#include <steam/steam_api.h>
#include <vector>
#include <string>
static std::vector<uint64_t> friendLobbyIDs;
static std::vector<uint64_t> publicLobbyIDs;
class LobbyCreatedCallback {
public:
STEAM_CALLBACK(LobbyCreatedCallback, OnLobbyCreated, LobbyCreated_t);
CSteamID createdLobbyID;
bool success = false;
};
void LobbyCreatedCallback::OnLobbyCreated(LobbyCreated_t* pCallback) {
if (pCallback->m_eResult == k_EResultOK) {
createdLobbyID.SetFromUint64(pCallback->m_ulSteamIDLobby);
success = true;
}
else {
success = false;
}
}
class LobbyChatUpdateCallback {
public:
STEAM_CALLBACK(LobbyChatUpdateCallback, OnLobbyChatUpdate, LobbyChatUpdate_t);
uint64 lastJoinedSteamID = 0;
uint64 lastLeftSteamID = 0;
uint64 lastLobbyID = 0;
bool joinedUpdated = false;
bool leftUpdated = false;
};
void LobbyChatUpdateCallback::OnLobbyChatUpdate(LobbyChatUpdate_t* pCallback) {
lastLobbyID = pCallback->m_ulSteamIDLobby;
// **参加したプレイヤー**
if (pCallback->m_rgfChatMemberStateChange & k_EChatMemberStateChangeEntered) {
lastJoinedSteamID = pCallback->m_ulSteamIDUserChanged;
joinedUpdated = true;
std::cout << "Player " << lastJoinedSteamID << " joined lobby " << lastLobbyID << std::endl;
}
// **退室したプレイヤー**
if (pCallback->m_rgfChatMemberStateChange & k_EChatMemberStateChangeLeft) {
lastLeftSteamID = pCallback->m_ulSteamIDUserChanged;
leftUpdated = true;
std::cout << "Player " << lastLeftSteamID << " left lobby " << lastLobbyID << std::endl;
}
}
class LobbyListCallback {
public:
STEAM_CALLBACK(LobbyListCallback, OnLobbyMatchList, LobbyMatchList_t);
int lobbyCount = 0;
bool updated = false;
};
void LobbyListCallback::OnLobbyMatchList(LobbyMatchList_t* pCallback) {
lobbyCount = pCallback->m_nLobbiesMatching;
updated = true;
}
class LobbyEnterCallback {
public:
STEAM_CALLBACK(LobbyEnterCallback, OnLobbyEnter, LobbyEnter_t);
bool lobbyEnterSuccess = false;
CSteamID joinedLobbyID;
EChatRoomEnterResponse response;
};
void LobbyEnterCallback::OnLobbyEnter(LobbyEnter_t* pCallback) {
joinedLobbyID = pCallback->m_ulSteamIDLobby;
response = static_cast<EChatRoomEnterResponse>(pCallback->m_EChatRoomEnterResponse);
if (response == k_EChatRoomEnterResponseSuccess) {
lobbyEnterSuccess = true;
std::cout << "[LobbyEnter] Successfully joined lobby "
<< joinedLobbyID.ConvertToUint64() << std::endl;
}
else {
lobbyEnterSuccess = false;
std::cout << "[LobbyEnter] Failed to enter lobby (Error code: "
<< static_cast<int>(response) << ")" << std::endl;
}
}
LobbyEnterCallback lobbyEnterCallback;
// **グローバル変数としてコールバックオブジェクトを登録**
LobbyChatUpdateCallback chatUpdateCallback;
// ロビー検索用のコールバックオブジェクト
LobbyListCallback lobbyListCallback;
// ロビー作成用のコールバックオブジェクト
LobbyCreatedCallback lobbyCallback;
extern "C" {
// **P2P セッションリクエストを受け入れる**
__declspec(dllexport) bool AcceptP2PSession(uint64 steamID) {
return SteamNetworking()->AcceptP2PSessionWithUser(CSteamID(steamID));
}
__declspec(dllexport) void RunSteamCallbacks() {
SteamAPI_RunCallbacks();
}
// Steam API の初期化
__declspec(dllexport) bool InitializeSteam() {
return SteamAPI_Init();
}
// Steam ID の取得
__declspec(dllexport) uint64 GetSteamID() {
if (SteamUser()) {
CSteamID steamID = SteamUser()->GetSteamID();
return steamID.ConvertToUint64();
}
return 0;
}
// P2P メッセージ送信
__declspec(dllexport) bool SendP2PMessage(uint64 steamID, const char* message) {
CSteamID targetID = CSteamID(steamID);
SteamNetworking()->AllowP2PPacketRelay(true);
return SteamNetworking()->SendP2PPacket(targetID, message, strlen(message) + 1, k_EP2PSendReliable);
}
// P2P メッセージ受信
__declspec(dllexport) bool ReceiveP2PMessage(char* buffer, int bufferSize, uint64* senderSteamID) {
uint32 msgSize;
CSteamID senderID;
SteamNetworking()->AllowP2PPacketRelay(true);
if (SteamNetworking()->IsP2PPacketAvailable(&msgSize)) {
SteamNetworking()->ReadP2PPacket(buffer, bufferSize, &msgSize, &senderID);
*senderSteamID = senderID.ConvertToUint64();
return true;
}
return false;
}
// **PING メッセージを送信**
__declspec(dllexport) bool SendPing(uint64 steamID) {
const char* pingMessage = "PING";
return SteamNetworking()->SendP2PPacket(CSteamID(steamID), pingMessage, 4, k_EP2PSendReliable);
}
// **フレンドのロビーを検索→一覧を保存
__declspec(dllexport) int RefreshFriendLobbies()
{
friendLobbyIDs.clear(); // 前回結果をクリア
// 1) ロビーリストをリクエスト
lobbyListCallback.updated = false;
SteamMatchmaking()->AddRequestLobbyListResultCountFilter(100);
SteamMatchmaking()->RequestLobbyList();
// 2) コールバック完了まで待機
for (int i = 0; i < 200; i++) {
SteamAPI_RunCallbacks();
if (lobbyListCallback.updated) {
break;
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
// 3) 結果を取得してフレンドチェック
int count = lobbyListCallback.lobbyCount;
for (int i = 0; i < count; i++) {
CSteamID lobbyID = SteamMatchmaking()->GetLobbyByIndex(i);
CSteamID ownerID = SteamMatchmaking()->GetLobbyOwner(lobbyID);
// ownerが0なら無効
if (ownerID.ConvertToUint64() == 0) continue;
// オーナーがフレンドなら保存
if (SteamFriends()->HasFriend(ownerID, k_EFriendFlagImmediate)) {
friendLobbyIDs.push_back(lobbyID.ConvertToUint64());
}
}
// 4) 見つかった数を返す
return static_cast<int>(friendLobbyIDs.size());
}
//** 公開lobbyを検索して一覧を保存**
__declspec(dllexport) int RefreshPublicLobbies()
{
publicLobbyIDs.clear();
lobbyListCallback.updated = false;
SteamMatchmaking()->AddRequestLobbyListResultCountFilter(300);
SteamMatchmaking()->RequestLobbyList();
for (int i = 0; i < 200; i++) {
SteamAPI_RunCallbacks();
if (lobbyListCallback.updated) {
break;
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
int count = lobbyListCallback.lobbyCount;
for (int i = 0; i < count; i++) {
CSteamID lobbyID = SteamMatchmaking()->GetLobbyByIndex(i);
CSteamID ownerID = SteamMatchmaking()->GetLobbyOwner(lobbyID);
// オーナー不在なら無視
if (ownerID.ConvertToUint64() == 0) continue;
// ここで必要があれば、さらにロビー人数などをチェックして除外もOK
//int numMembers = SteamMatchmaking()->GetNumLobbyMembers(lobbyID);
publicLobbyIDs.push_back(lobbyID.ConvertToUint64());
}
return static_cast<int>(publicLobbyIDs.size());
}
// **一般lobby一覧を取り出す
__declspec(dllexport) uint64 GetPublicLobbyIDByIndex(int index)
{
if (index < 0 || index >= (int)publicLobbyIDs.size()) {
return 0;
}
return publicLobbyIDs[index];
}
// **フレンドlobby一覧を取り出す**
__declspec(dllexport) uint64 GetFriendLobbyIDByIndex(int index)
{
if (index < 0 || index >= (int)friendLobbyIDs.size()) {
return 0;
}
return friendLobbyIDs[index];
}
// **指定したオーナーのロビー ID を取得**
__declspec(dllexport) uint64 FindLobbyByOwner(uint64 ownerID) {
lobbyListCallback.updated = false;
SteamMatchmaking()->RequestLobbyList();
// **コールバックが完了するまで待つ**
for (int i = 0; i < 100; i++) {
SteamAPI_RunCallbacks();
if (lobbyListCallback.updated) {
break;
}
std::this_thread::sleep_for(std::chrono::milliseconds(10)); // 10ミリ秒待機
}
// **取得したロビーの数を確認**
int count = lobbyListCallback.lobbyCount;
for (int i = 0; i < count; i++) {
CSteamID lobbyID = SteamMatchmaking()->GetLobbyByIndex(i);
uint64 lobbyOwnerID = SteamMatchmaking()->GetLobbyOwner(lobbyID).ConvertToUint64();
if (lobbyOwnerID == ownerID) {
return lobbyID.ConvertToUint64(); // 該当するロビーの ID を返す
}
}
return 0; // 該当するロビーが見つからなかった
}
// **ロビー ID をインデックスから取得**
__declspec(dllexport) uint64 GetLobbyByIndex(int index) {
if (index < 0 || index >= SteamMatchmaking()->GetFavoriteGameCount()) {
return 0; // 無効なインデックス
}
return SteamMatchmaking()->GetLobbyByIndex(index).ConvertToUint64();
}
// **ロビーのオーナー(ホスト)の Steam ID を取得**
__declspec(dllexport) uint64 GetLobbyOwner(uint64 lobbyID) {
CSteamID lobby(lobbyID);
CSteamID owner = SteamMatchmaking()->GetLobbyOwner(lobby);
return owner.ConvertToUint64();
}
// **ロビーのメンバー数を取得**
__declspec(dllexport) int GetNumLobbyMembers(uint64 lobbyID) {
CSteamID lobby(lobbyID);
return SteamMatchmaking()->GetNumLobbyMembers(lobby);
}
// **指定インデックスのロビーメンバーの Steam ID を取得**
__declspec(dllexport) uint64 GetLobbyMemberByIndex(uint64 lobbyID, int index) {
CSteamID lobby(lobbyID);
CSteamID member = SteamMatchmaking()->GetLobbyMemberByIndex(lobby, index);
return member.ConvertToUint64();
}
// **SteamID からプレイヤー名を取得**
__declspec(dllexport) void GetSteamName(uint64 steamID, char* buffer, int bufferSize) {
CSteamID playerID(steamID);
const char* name = SteamFriends()->GetFriendPersonaName(playerID);
strncpy(buffer, name, bufferSize - 1);
buffer[bufferSize - 1] = '\0'; // Null 終端
}
// **ロビーを作成**
__declspec(dllexport) uint64 CreateLobby(int lobbyType, int maxPlayers) {
lobbyCallback.success = false;
SteamMatchmaking()->CreateLobby((ELobbyType)lobbyType, maxPlayers);
// 一定時間待機してロビーが作成されたかチェック
for (int i = 0; i < 100; i++) {
SteamAPI_RunCallbacks(); // コールバックを処理
if (lobbyCallback.success) {
return lobbyCallback.createdLobbyID.ConvertToUint64();
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
return 0; // 作成失敗
}
// ロビーに参加
__declspec(dllexport) bool JoinLobby(uint64 lobbyID) {
lobbyEnterCallback.lobbyEnterSuccess = false;
SteamMatchmaking()->JoinLobby(CSteamID(lobbyID));
// 参加完了を待機 (OnLobbyEnter コールバック)
for (int i = 0; i < 100; i++) {
SteamAPI_RunCallbacks();
if (lobbyEnterCallback.lobbyEnterSuccess) {
// 成功
return true;
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
// 失敗
return false;
}
// **ロビー参加を検出**
__declspec(dllexport) bool CheckLobbyJoin(uint64* joinedSteamID, uint64* lobbyID) {
SteamAPI_RunCallbacks(); // **Steam コールバックを処理**
if (chatUpdateCallback.joinedUpdated) {
*joinedSteamID = chatUpdateCallback.lastJoinedSteamID;
*lobbyID = chatUpdateCallback.lastLobbyID;
chatUpdateCallback.joinedUpdated = false;
return true;
}
return false;
}
// **ロビー退室を検出**
__declspec(dllexport) bool CheckLobbyLeave(uint64* leftSteamID, uint64* lobbyID) {
SteamAPI_RunCallbacks(); // **Steam コールバックを処理**
if (chatUpdateCallback.leftUpdated) {
*leftSteamID = chatUpdateCallback.lastLeftSteamID;
*lobbyID = chatUpdateCallback.lastLobbyID;
chatUpdateCallback.leftUpdated = false;
return true;
}
return false;
}
// ========================
// Rich Presence 関連
// ========================
// 自分が所属するロビーIDを Rich Presence にセット
__declspec(dllexport) void SetLobbyRichPresence(uint64 lobbyID) {
// key="steam_player_group" にロビーIDを文字列で入れるのが慣例
char lobbyStr[32];
sprintf(lobbyStr, "%llu", (unsigned long long)lobbyID);
// ※ "steam_player_group" と "connect" の2つに同じ値をセットする手法が多い
// "connect" を見て参加しようとするタイトルもあるため
SteamFriends()->SetRichPresence("steam_player_group", lobbyStr);
SteamFriends()->SetRichPresence("connect", lobbyStr);
// 任意でステータス文言をセットしてもOK
SteamFriends()->SetRichPresence("status", "In a lobby!");
std::cout << "[SetLobbyRichPresence] lobbyID=" << lobbyStr << std::endl;
}
// Rich Presence をクリア (ゲーム終了時やロビー退出時など)
__declspec(dllexport) void ClearRichPresence() {
SteamFriends()->ClearRichPresence();
std::cout << "[ClearRichPresence] All presence data cleared.\n";
}
// フレンドの Rich Presence から "steam_player_group" を取得
// (フレンドがロビーIDをセットしていれば、それが返ってくる)
__declspec(dllexport) uint64 GetFriendLobbyRichPresence(uint64 friendSteamID) {
CSteamID friendID(friendSteamID);
const char* presence = SteamFriends()->GetFriendRichPresence(friendID, "steam_player_group");
if (presence && presence[0] != '\0') {
return std::stoull(presence);
}
return 0; // 見つからない場合は 0
}
__declspec(dllexport) int RefreshFriendLobbiesRichPresence()
{
friendLobbyIDs.clear();
// (1) 今のフレンド数を取得
int friendCount = SteamFriends()->GetFriendCount(k_EFriendFlagImmediate);
if (friendCount < 1) {
std::cout << "[RefreshFriendLobbiesRichPresence] No friends found.\n";
return 0;
}
// (2) 各フレンドを走査し、Rich Presenceの "steam_player_group" をチェック
for (int i = 0; i < friendCount; i++) {
CSteamID friendID = SteamFriends()->GetFriendByIndex(i, k_EFriendFlagImmediate);
if (!friendID.IsValid()) continue;
// ここでキー"steam_player_group"の値を取得 (ロビーIDが入っている可能性がある)
const char* presence = SteamFriends()->GetFriendRichPresence(friendID, "steam_player_group");
if (presence && presence[0] != '\0') {
// 文字列 -> uint64 に変換
uint64_t lobbyID = std::strtoull(presence, nullptr, 10);
if (lobbyID != 0) {
friendLobbyIDs.push_back(lobbyID);
std::cout << "[RefreshFriendLobbiesRichPresence] Friend "
<< friendID.ConvertToUint64()
<< " has lobbyID=" << lobbyID << "\n";
}
}
}
// フレンドが参加中のロビー数を返す
return static_cast<int>(friendLobbyIDs.size());
}
__declspec(dllexport) uint64_t GetFriendLobbyIDByIndex_RichPresence(int index)
{
if (index < 0 || index >= static_cast<int>(friendLobbyIDs.size())) {
return 0; // 無効な場合は0
}
return friendLobbyIDs[index];
}
}
仮のスクリプトですので、整理する必要があります。
私はこんな感じで分けてみました。
source - SteamLobby.cpp SteamNetworking.cpp SteamCallbacks.cpp
header - SteamLobby.h 以下同じ
cl.exe /LD /I "C:\steamworks_sdk\public" SteamCallbacks.cpp SteamNetworking.cpp SteamLobby.cpp "C:\steamworks_sdk\redistributable_bin\win64\steam_api64.lib" /FeSteamNetworkingWrapper.dll
Pythonではこんな感じで読み込みます。
import ctypes
import os
# DLL をロード
dll_path = os.path.abspath("./SteamNetworkingWrapper.dll")
steam_dll = ctypes.CDLL(dll_path)
# SteamCallbacks を実行
run_steam_callbacks = steam_dll.RunSteamCallbacks
run_steam_callbacks.restype = None
コールバックしかないんですけど全部こんな感じでかけます。
フレンドのルーム検索は二つあって、私のおすすめは、RichPresenceです