【unity游戏开发——网络】unity对接steam,并上传发布游戏版本——Steamworks.NET

文章目录

一、前言

Steamworks.NET 是 Valve 的 Steamworks API 的 C# 封装,可用于 Unity 或基于 C# 的应用程序。

Steamworks.NET 的设计尽可能接近原始 C++ API,因此 Valve 提供的文档主要涵盖了 Steamworks.NET 的使用。可以在 Steamworks.NET 之上轻松实现一些便捷功能和 C# 特有的用法。

Steamworks.NET 完全支持 Windows(32 位和 64 位)、OSX 和 Linux。目前基于 Steamworks SDK 1.63 进行构建。

开发文档:https://steamworks.github.io/

二、插件下载安装

https://github.com/rlabrecque/Steamworks.NET/releases/tag/2025.162.1

示例:https://github.com/rlabrecque/Steamworks.NET-Example

三、快速入门

1、启动steam

注意:后续测试都要记得要先启动steam再运行,不然可能会报错:[Steamworks.NET] SteamAPI_Init() failed. Refer to Valve's documentation or the comment above this line for more information.UnityEngine.Debug:LogError (object,UnityEngine.Object) SteamManager:Awake () (at Assets/Scripts/Steamworks.NET/SteamManager.cs:124)

安装steam地址:https://store.steampowered.com/about/

2、添加SteamManager 脚本

SteamManager 脚本为您的项目提供了理想的起点,我强烈建议使用它,这将大大减少上手所需的时间。

只需在第一个场景中创建一个新的空 GameObject 并将 SteamManager 脚本附加到它上面。

现在,当您启动游戏时,Steam 应该会显示您正处于游戏中。

3、获取 Steam 用户的显示名称

接下来,在确认基本功能工作后,我建议尝试一个简单的 SteamAPI 方法调用。

创建一个名为 SteamScript.cs 的新脚本:

csharp 复制代码
using Steamworks;
using UnityEngine;

public class SteamScript : MonoBehaviour
{
    void Start() {
		if(SteamManager.Initialized) {
			// 获取 Steam 用户的显示名称
			string name = SteamFriends.GetPersonaName();
			Debug.Log(name);
		}
	}
}

请注意:在调用任何 Steamworks 函数之前,我们必须始终通过检查 SteamManager.Initialized 来确保

Steam 已初始化。

现在只需将此脚本添加到一个 GameObject 上并尝试运行!应该会打印出你的steam名字

如果遇到任何问题,请查看 常见问题解答 ,看看是否已有解决方案!

4、Steam 回调

回调是 Steamworks 最重要的方面,它们允许您从 Steam 异步获取数据,而不会锁定您的游戏。

您很可能希望使用的一个回调是 GameOverlayActivated_t。顾名思义,每当 Steam 覆盖层被激活或停用时,它都会向您发送一个回调。

我们将继续使用之前的脚本来演示如何使用它。

要在 Steamworks.NET 中使用回调,首先必须在类作用域内声明一个受保护的 Callback<> 作为成员变量。

csharp 复制代码
public class SteamScript : MonoBehaviour {
    protected Callback<GameOverlayActivated_t> m_GameOverlayActivated;
}

然后,我们通过调用 Callback<>.Create() 来创建回调,并将其分配给 m_GameOverlayActivated。这可以防止回调被垃圾回收。

我们通常在 OnEnable 中执行此操作,因为这允许我们在 Unity 重新加载程序集后重新创建回调。

csharp 复制代码
public class SteamScript : MonoBehaviour {
    protected Callback<GameOverlayActivated_t> m_GameOverlayActivated;

    private void OnEnable() {
        if (SteamManager.Initialized) {
            m_GameOverlayActivated = Callback<GameOverlayActivated_t>.Create(OnGameOverlayActivated);
        }
    }
}

最后一块拼图是 OnGameOverlayActivated 函数。

csharp 复制代码
using Steamworks;
using UnityEngine;

public class SteamScript : MonoBehaviour {
    protected Callback<GameOverlayActivated_t> m_GameOverlayActivated;

    private void OnEnable() {
        if (SteamManager.Initialized) {
            m_GameOverlayActivated = Callback<GameOverlayActivated_t>.Create(OnGameOverlayActivated);
        }
    }

    private void OnGameOverlayActivated(GameOverlayActivated_t pCallback) {
        if(pCallback.m_bActive != 0) {
            Debug.Log("Steam 覆盖层已被激活");
        }
        else {
            Debug.Log("Steam 覆盖层已被关闭");
        }
    }
}

搞定!

GameOverlayActivated 回调一个流行且推荐的用例是:当覆盖层打开时暂停游戏。比如打开背包,打开设置面板等等。

5、Steam 调用结果

调用结果与回调非常相似,但它们是特定函数调用的异步结果,而不是像回调那样的全局事件接收器。

您可以通过检查函数的返回值来识别提供调用结果的函数。如果它返回 SteamAPICall_t,那么您必须设置一个调用结果。

Steamworks.NET 中,设置调用结果与回调几乎相同!

比如我们获取正在玩您游戏的玩家数量

csharp 复制代码
using Steamworks;
using UnityEngine;

public class SteamScript : MonoBehaviour {
    private CallResult<NumberOfCurrentPlayers_t> m_NumberOfCurrentPlayers;

    //在 OnEnable 中执行此操作,以便每次 Unity 重新加载程序集时都会重新创建它。
    private void OnEnable() {
        if (SteamManager.Initialized) {
            //创建调用结果
            m_NumberOfCurrentPlayers = CallResult<NumberOfCurrentPlayers_t>.Create(OnNumberOfCurrentPlayers);
        }
    }
    
    private void Update() {
    	// 我们需要调用一个返回调用结果的函数,然后将其返回的 SteamAPICall_t 句柄与我们的调用结果关联起来。
        if(Input.GetKeyDown(KeyCode.Space)) {
            SteamAPICall_t handle = SteamUserStats.GetNumberOfCurrentPlayers();
            m_NumberOfCurrentPlayers.Set(handle);
            Debug.Log("已调用 GetNumberOfCurrentPlayers()");
        }
    }

    //创建异步调用的函数,调用结果的函数签名与回调略有不同,增加了 bool bIOFailure 参数。
    private void OnNumberOfCurrentPlayers(NumberOfCurrentPlayers_t pCallback, bool bIOFailure) {
        if (pCallback.m_bSuccess != 1 || bIOFailure) {
            Debug.Log("检索玩家数量时出错。");
        }
        else {
            Debug.Log("正在玩您游戏的玩家数量: " + pCallback.m_cPlayers);
        }
    }
}

运行,按空格,查看结果

对于回调和调用结果,您都必须定期调用 SteamAPI.RunCallbacks。当然这在 SteamManager 中已经帮你完成。

现在您已经熟悉了 Steamworks 的基础构建模块,请查看 Steamworks.NET 示例应用程序,了解 Steamworks 统计和成就的工业级实现!

Steamworks.NET-Test 项目对于了解如何在 Steamworks.NET 中完成各种任务非常有价值。

还有一个 Steamworks.NET-GameServerTest 项目可用,它实现了 Steamworks 游戏服务器接口的一些基本功能。

四、SteamManager

1、SteamManager 脚本的工作原理

SteamManager 脚本是我们认为的 Steamworks "用户模式"端。它提供了一些基本逻辑来设置和维护与 Steam 的连接,并为您提供了一个良好的起点以供构建。

您很可能需要自行修改 SteamManager 脚本,了解其工作原理是全面掌握 Steamworks 的重要一步。

或者,如果您已经拥有平台抽象层,可以编写自己的实现。

您可以在 GitHub 上找到最新版本的 SteamManager 脚本:https://github.com/rlabrecque/Steamworks.NET-SteamManager/blob/master/SteamManager.cs

请注意:如果不使用类似 SteamManager 的脚本,Steamworks.NET 将完全无法工作。


2、样板代码

以下所有代码都包装在一个 MonoBehavior 类中,以便可以添加到 GameObject 上。

csharp 复制代码
using UnityEngine;
using System.Collections;
using Steamworks;

class SteamManager : MonoBehaviour {

}

3、持久化 GameObject/单例逻辑

SteamManager 脚本依赖于创建一次并在整个游戏过程中持续存在。这涉及到一些相当复杂的逻辑来与 Unity 的 GameObject 系统集成。

我们使用"自创建持久单例"模式来实现这一点。

通过此模式,您可以从游戏中的任何场景使用 SteamManager,而无需在每个场景中手动放置 SteamManager GameObject。与往常一样,请避免在其他脚本的 Awake()OnDestroy() 中与 SteamManager 交互,因为执行顺序无法保证。

如果您的游戏中已经有维护全局状态的方法,您可能希望用您自己的方法替换此逻辑,以确保以正确的顺序设置。

csharp 复制代码
private static SteamManager s_instance;
private static SteamManager Instance {
	get {
		return s_instance ?? new GameObject("SteamManager").AddComponent<SteamManager>();
	}
}

private void Awake() {
	if (s_instance != null) {
		Destroy(gameObject);
		return;
	}
	s_instance = this;

	DontDestroyOnLoad(gameObject);
}

private void OnEnable() {
	if (s_instance == null) {
		s_instance = this;
	}
}

private void OnDestroy() {
	if (s_instance != this) {
		return;
	}
	s_instance = null;
}

4、完整性检查

Steamworks.NET 提供了几个非必需的完整性检查,以确保正确使用 Steamworks.NET

Packsize.Test() 确保 Steamworks.NET 在正确的平台下运行。在 Unity 正常操作下,这永远不会返回 false。

DllCheck.Test() 检查确保 Steamworks 可再发行二进制文件版本正确。这在您升级 Steamworks.NET 时特别有用,尤其是在不使用 Steamworks.NET 编辑器脚本的情况下。使用错误的 steam_api.dll 运行 Steamworks.NET 可能会导致问题。(当前仅检查 steam_api[64].dll

csharp 复制代码
if (!Packsize.Test()) {
	Debug.LogError("[Steamworks.NET] Packsize 测试返回 false,此平台上正在运行错误版本的 Steamworks.NET。", this);
}

if (!DllCheck.Test()) {
	Debug.LogError("[Steamworks.NET] DllCheck 测试返回 false,一个或多个 Steamworks 二进制文件版本错误。", this);
}

5、SteamAPI.RestartAppIfNecessary

脚本调用的第一个 Steamworks 函数是 SteamAPI.RestartAppIfNecessary((AppId)480)。请将 480 替换为您自己的 AppId。

SteamAPI.RestartAppIfNecessary() 检查 Steam 客户端是否正在运行,如果没有则启动它。

如果返回 true,则会在需要时启动 Steam 客户端并通过它重新启动您的游戏,然后您应尽快手动关闭应用程序。这实际上是运行 steam://run/[AppId],因此可能不会重新启动调用它的确切可执行文件。

如果返回 false,则表示您的游戏是由 Steam 客户端启动的,正常继续执行。

如果当前工作目录中存在 steam_appid.txt 文件,则 SteamAPI_RestartAppIfNecessary() 将返回 false。这允许您进行开发而无需每次都通过 Steam 重新启动。

由于这是调用的第一个 Steamworks 函数,因此是确保 steam_api.dll 确实可以加载的理想位置。这是通过将此函数调用包装在 try..catch 块中以捕获 DllNotFoundException 来实现的。

csharp 复制代码
private void Awake() {
	try {
		if (SteamAPI.RestartAppIfNecessary((AppId)480)) {
			Application.Quit();
			return;
		}
	}
	catch (System.DllNotFoundException e) {
		Debug.LogError("[Steamworks.NET] 无法加载 [lib]steam_api.dll/so/dylib。它可能不在正确的位置。有关更多详细信息,请参阅 README。\n" + e, this);

		Application.Quit();
		return;
	}
}

6、SteamAPI.Init

应该调用的第二个 Steamworks 函数是 SteamAPI.Init(),这会启动 SteamAPI,并且必须在调用任何其他 Steamworks 函数之前调用。

如果 SteamAPI.Init() 返回 true,则表示已设置好一切以继续使用 Steamworks.NET

否则,返回值为 false 由以下三个问题之一引起:

  1. Steam 客户端未运行。需要运行的 Steam 客户端来提供各种 Steamworks 接口的实现。
  2. Steam 客户端无法确定游戏的 AppID。确保游戏目录中有 steam_appid.txt。当通过 Steam 下载启动游戏时,这永远不会发生,因为 SteamAPI.RestartAppIfNecessary() 将通过 Steam 重新启动它。
  3. 确保您的应用程序在与 Steam 客户端相同的用户上下文(包括管理员权限)下运行。

如果遇到 Init 问题,请尝试在启动前运行 Microsoft 的 DbgView 以获取 Steam 的内部输出。

SteamManager 公开了 Initialized 属性,您可以从其他脚本中使用它来确保在调用任何 Steamworks 函数之前 SteamAPI 已初始化。

csharp 复制代码
private bool m_bInitialized;
public static bool Initialized {
	get {
		return Instance.m_bInitialized;
	}
}

private void Awake() {
	m_bInitialized = SteamAPI.Init();
	if (!m_bInitialized) {
		Debug.LogError("[Steamworks.NET] SteamAPI_Init() 失败。有关更多信息,请参阅 Valve 的文档或此行上方的注释。", this);

		return;
	}
}

7、SteamAPIWarningMessageHook

通过使用函数委托调用 SteamClient.SetWarningMessageHook(),我们可以在某些情况下拦截来自 Steam 的警告消息。

请注意,在调用任何 Steamworks 函数之前,我们确保 Steam API 已初始化。

我们在 OnEnable 中调用此函数,以便在 Unity 执行程序集重新加载(例如重新编译脚本时)后重新创建它。

要从 Steam 接收警告消息,您必须在启动参数中使用 -debug_steamapi 启动游戏。

csharp 复制代码
private SteamAPIWarningMessageHook_t m_SteamAPIWarningMessageHook;
private static void SteamAPIDebugTextHook(int nSeverity, System.Text.StringBuilder pchDebugText) {
	Debug.LogWarning(pchDebugText);
}

private void OnEnable() {
	if (!m_bInitialized) {
		return;
	}

	if (m_SteamAPIWarningMessageHook == null) {
		m_SteamAPIWarningMessageHook = new SteamAPIWarningMessageHook_t(SteamAPIDebugTextHook);
		SteamClient.SetWarningMessageHook(m_SteamAPIWarningMessageHook);
	}
}

8、SteamAPI.RunCallbacks

要使回调和调用结果系统能够分发事件,必须频繁调用 SteamAPI.RunCallbacks()。调用之间的时间越长,从 Steam API 接收事件或结果的潜在延迟就越大。

如果通过将 Time.timeScale 设置为 0 来暂停游戏,则 Update() 函数将不再运行。您需要考虑替代方案,以确保即使游戏暂停时 SteamAPI.RunCallbacks() 也在运行。协程可能是一个不错的选择。

请注意,在调用任何 Steamworks 函数之前,我们确保 Steam API 已初始化。

csharp 复制代码
private void Update() {
	if (!m_bInitialized) {
		return;
	}

	// 运行 Steam 客户端回调
	SteamAPI.RunCallbacks();
}

9、SteamAPI.Shutdown

SteamManager 将进行的最终调用是 SteamAPI.Shutdown(),它会清理 SteamAPI 并让 Steam 知道您正在准备关闭。

使用 OnDestroy,因为这是关闭时最后调用的内容。

由于 SteamManager 应该是持久化的且永远不会被禁用或销毁,我们可以使用 OnDestroy 来关闭 SteamAPI。

csharp 复制代码
private void OnDestroy() {
	if (!m_bInitialized) {
		return;
	}

	SteamAPI.Shutdown();
}

五、对接自己的应用

前面测试你会发现我们都没有设置自己的游戏信息,默认使用的都是480测试appid

要修改成自己的,我们需要

  • 找到工程目录中的steam_appid.txt文件,默认是480,是UnitySpaceWar的id,把480改成你自己的appid(appid在Steamworks 创建应用的时候会分配)

  • 找到SteamManager.cs 中的SteamAPI.RestartAppIfNecessary并修改为SteamAPI.RestartAppIfNecessary(new AppId_t(你的appid))

  • 保存,重启unity

  • 我们可以打印AppID测试一下,看看对不对

    csharp 复制代码
    using Steamworks;
    using UnityEngine;
    
    public class SteamScript : MonoBehaviour
    {
    	void Start()
    	{
    		if (SteamManager.Initialized)
    		{
    			// 获取当前运行的AppID
    			AppId_t currentAppId = SteamUtils.GetAppID();
    			uint appIdValue = currentAppId.m_AppId;
    			Debug.Log($"当前AppID: {appIdValue}");
    		}
    	}
    }

六、设置Steamwork商店Depot

生成分支和Depot到底是啥?咋上传不同语言?可以参考:https://www.bilibili.com/video/BV1xamnYbED6/

简单来说,Depot可以理解为你的游戏不同版本

1、找到所有应用程序

2、找到你在Steam上花100美元申请的AppID的应用程序,点击Steamworks管理员

3、找到SteamPipe中的Depot

4、添加新的depot,因为我这里已经添加过了因此有一个depot

5、添加depot的名称和选中depotID,这个depotid默认是比appid+1的,比如你的appid是480,depotid就应该是481,然后点击保存 ,这里的depot就设置完成了。

七、上传游戏版本

1、下载SteamworkSDK

Steamwork的网址将SDK下载下来,解压到一个最好是英文的目录中

2、\sdk\tools\ContentBuilder\scripts里面默认有4个文件,我们首先打开第一个app_build_1000这个文件

注意文件名要跟着改

3、将自己的游戏打包出来,将整个打包的游戏文件拷贝到sdk/tools/ContentBuilder/content下面

注意查看steam_appid.txt文件,是否填好正确的appid

4、编辑depot_build_1001文件

5、然后就通过SteamPipeGUI工具将自己的exe包进行上传了

出现success,则成功了,如果你是第一次上传会需要邮箱进行验证,等待验证成功即可

如果上传失败,检测刚刚那两个文件的参数是否正确,可以在output文件夹中看到错误输出日志

这里一个坑,Steam有一个命令行工具,run_build这个工具也能成功上传,但是虽然显示成功,但提示是preview,要修改run_build里面的账号密码为自己的,再双击运行,可以把末尾的quit删掉,命令行界面就不会自动关闭了

八、Steamwork商店配置发布

1、找到Steamworks管理员,然后找到SteamPile,点击生成版本,我们可以看到刚刚上传然后生成的版本,这里已经上传过很多次了,因此你第一次上传的话应该是只有一个的,点击default分支,点击预览更改

2、立即将生成版本设置上线

3、设置通用安装文件夹以及启动项

4、点击发布

5、之前没有上线过的等待审核,审核通过了的界面就是这样的,可以安装自己的游戏测试

参考

https://blog.csdn.net/qq_41884036/article/details/134667607


专栏推荐

地址
【unity游戏开发入门到精通------C#篇】
【unity游戏开发入门到精通------unity通用篇】
【unity游戏开发入门到精通------unity3D篇】
【unity游戏开发入门到精通------unity2D篇】
【unity实战】
【制作100个Unity游戏】
【推荐100个unity插件】
【实现100个unity特效】
【unity框架/工具集开发】
【unity游戏开发------模型篇】
【unity游戏开发------InputSystem】
【unity游戏开发------Animator动画】
【unity游戏开发------UGUI】
【unity游戏开发------联网篇】
【unity游戏开发------优化篇】
【unity游戏开发------shader篇】
【unity游戏开发------编辑器扩展】
【unity游戏开发------热更新】
【unity游戏开发------网络】

完结

好了,我是向宇,博客地址:https://xiangyu.blog.csdn.net,如果学习过程中遇到任何问题,也欢迎你评论私信找我。

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!

相关推荐
武藤一雄2 小时前
彻底吃透.NET中序列化反序列化
xml·微软·c#·json·.net·.netcore
陈言必行2 小时前
Unity 实战:屏蔽移动平台 UI 点击检测的“坑”与解决之道
ui·unity·游戏引擎
CreasyChan2 小时前
unity-向量数学:由浅入深详解
unity·c#
nnsix2 小时前
Unity Terrain获取关联的TerrainData
unity·游戏引擎
秦奈2 小时前
Unity复习学习笔记(七):NGUI
笔记·学习·unity
Sui_Network2 小时前
社交游戏 Super-B 登陆 Epic 游戏商店抢先体验
人工智能·游戏·rpc·区块链·量子计算
切糕师学AI2 小时前
.NET 中常见的内存泄漏场景及解决方案
.net·内存泄漏
老朱佩琪!2 小时前
Unity适配器模式
unity·设计模式·游戏引擎·适配器模式
Laravel技术社区3 小时前
用PHP8实现斗地主游戏,实现三带一,三带二,四带二,顺子,王炸功能(第二集)
前端·游戏·php