Unity开发微信小游戏完整流程,从Unity版本选择,到服务器开发,最后到小游戏的发布,所有流程都会详细介绍,包括一些注意事项和问题解决。
准备工作:
Unity版本选择问题
如果使用Unity团结引擎,那么可以直接进行开发(团结引擎版本,打包出去后小游戏右下角有水印,如果能接受就无所谓了),如果不使用团结引擎的话,Unity的版本选择2021版,22版本URP项目导出的小游戏会报Shader错误
Unity微信工具下载
地址:https://game.weixin.qq.com/cgi-bin/gamewxagwasmsplitwap/getunityplugininfo?download=1
云服务器和CDN(如果有云服务器和CDN需求,先备案域名!!!)
提前说明,工信部域名备案审核需要20天!!!,所以有需求的话,先把域名备案了。
云服务器网上一搜一大推,看自己选择,我使用的是华为云,以下所有服务器配置之类的都是基于华为云,别的大差不大,也可以借鉴。!
UnityWebSocket(没有后端需求可以忽略)
地址:UnityWebSocket: UnityWebSocket
因为微信小游戏仅支持wss://方式链接后端,所以需要使用UnityWebSocket
微信公众平台注册和微信开发者工具下载
链接:微信公众平台
注册账号相关信息,下载微信开发工具
Unity开发:
先导入准备工作中下载的Unity微信工具,和UnityWebSocket(没有后端需求可以忽略)。
UnityWebSocket导入时,会有一个案例,可以看看,很简单的
注意事项
第一,项目中不要使用多线程相关,不支持,可以使用Unity的协同程序或者异步函数
第二,文件系统System.IO不支持,需要使用微信SDK的文件系统,本地只读文件可以使用Resources.lod加载,Json读取数据,或者可以直接使用ScriptableObject。(这里有一点就是,导入Unity微信工具后,这个插件里面自带LitJson)
第三,System.Net不支持,改为UnityWebSocket(也支持UnityWebRequest)
第四,所有客户端能用到的URL都需要在微信公众平台-开发管理-服务器域名里面进行填写,
包括下载头像(填写一级域名即可),如果域名后有端口号,那就把端口号也一起写上
第五,微信小游戏也是需要备案的,和域名备案的时间差不多,也是20天左右。如果手里有美术设计图之类的,可以提前备案。微信公众平台-左下角-账号设置-小程序备案。
微信文件系统API使用
微信文件系统:FileSystemManager | 微信开放文档
//创建全局唯一文件管理器
public static WXFileSystemManager WXFileManager = new WXFileSystemManager();
//微信提供的一个可以让用户读写的根目录
private string rootPath = WX.env.USER_DATA_PATH;
//这里举一个例子,是我存储音乐和音效声音大小到本地的方法
private string SettingData = "Setting.xxl";
private string fileName = "/Setting";
public void GetMusicSoundVolume_WX(out float music, out float sound)
{
byte[] bytes = new byte[8];
//判断文件夹/或者文件是否存。如果存在,返回"access:ok"
if (WXFileManager.AccessSync(rootPath + fileName) != "access:ok")
{
//mkdirParam 是一个结构体,用于定义创建目录时所需的参数
MkdirParam mkdirParam = new MkdirParam();
//fileName为自定义文件夹名 注意:前面必须加@符号,不然无法创建目录
mkdirParam.dirPath = rootPath + @fileName;
//recursive MkdirParam 只能创建单一层级的目录,如果你需要创建多层级的目录(即递归创建目录),则需要设置recursive = trur
mkdirParam.recursive = true;
//创建目录
WXFileManager.Mkdir(mkdirParam);
music = sound = 1f;
BitConverter.GetBytes(music).CopyTo(bytes, 0);
BitConverter.GetBytes(sound).CopyTo(bytes, 4);
//写入文件
WXFileManager.WriteFileSync(rootPath+ fileName+ "/"+ SettingData, bytes);
}
else
{
//如果文件存在,直接读取就行
bytes = WXFileManager.ReadFileSync(rootPath + fileName + "/" + SettingData);
music = BitConverter.ToSingle(bytes, 0);
sound = BitConverter.ToSingle(bytes, 4);
}
}
获取自己的微信信息(名称、头像)
注:先到微信公众平台-左下角小程序名称-账号设置 找到服务内容说明

把这两个设置了
Unity中创建个界面,就是UI登录界面,加个开始按钮,自己随意设置,按钮点击事件绑定函数StartGame
using System.Collections;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;
using WeChatWASM;
public class LoaderTest : MonoBehaviour
{
/// <summary>
/// 用户信息
/// 获取到用户信息后可根据需要存云端或本地持久存储
/// </summary>
public WXUserInfo userInfo;
/// <summary>
/// 是否获取用户信息
/// </summary>
private bool infoFlag = false;
/// <summary>
/// 头像材质
/// </summary>
public Texture2D avatarTexture;
// Start is called before the first frame update
void Start()
{
// 初始化微信SDK
WX.InitSDK((code) =>
{
Debug.Log("init WXSDK code: " + code);
// 加载用户信息
this.LoaderWXMess();
});
}
/// <summary>
/// 加载微信授权相关信息
/// </summary>
private void LoaderWXMess()
{
// 1. 询问隐私协议授权情况
WX.GetPrivacySetting(new GetPrivacySettingOption()
{
success = (res) =>
{
/**
* needAuthorization - 含义
* 是否需要用户授权隐私协议(如果开发者没有在[mp后台-设置-服务内容声明-用户隐私保护指引]中声明隐私收集类型则会返回false;
* 如果开发者声明了隐私收集,且用户之前同意过隐私协议则会返回false;
* 如果开发者声明了隐私收集,且用户还没同意过则返回true;
* 如果用户之前同意过、但后来小程序又新增了隐私收集类型也会返回true)
*/
// 询问成功
if (res.needAuthorization)
{
// 有隐私协议,且未授权
// 2. 发起隐私协议授权
// 弹出隐私协议询问弹窗
WX.RequirePrivacyAuthorize(new RequirePrivacyAuthorizeOption()
{
success = (res) =>
{
Debug.Log("同意隐私协议:" + JsonUtility.ToJson(res, true));
// 用户同意隐私协议
// 3. 获取用户信息
this.GetScopeInfoSetting();
// 将 信息获取标志 标记为true
this.infoFlag = true;
},
fail = (err) =>
{
Debug.Log("拒绝隐私协议:" + JsonUtility.ToJson(res, true));
},
complete = (res) =>
{
Debug.Log("询问隐私协议结束");
}
});
}
},
fail = (err) => { },
complete = (res) =>
{
// 处理询问隐私协议失败或之前已经同意但未授权用户信息的情况
if (!this.infoFlag)
{
this.GetScopeInfoSetting();
}
}
});
}
/// <summary>
/// 点击开始游戏
/// </summary>
public void StartGame()
{
Debug.Log("start game");
Debug.Log("用户信息:" + JsonUtility.ToJson(this.userInfo, true));
}
/// <summary>
/// 获取授权
/// </summary>
private void GetScopeInfoSetting()
{
// 询问用户信息授权情况
WX.GetSetting(new GetSettingOption()
{
success = (res) =>
{
Debug.Log("获取用户信息授权情况成功: " + JsonUtility.ToJson(res.authSetting, true));
// 判断用户信息的授权情况
if (!res.authSetting.ContainsKey("scope.userInfo") || !res.authSetting["scope.userInfo"])
{
// 3.1 未授权,创建授权按钮区
// 需引导用户点击所创建的区域,这里的做法是将开始游戏的按钮放在该区域
this.CreateUserInfoButton();
}
else
{
// 3.2 已授权,直接获取用户信息
this.GetUserInfo();
// 这里也可以先不获取,留到点击开始游戏按钮再获取,但没必要,先获取后存起来即可
}
},
fail = (err) =>
{
Debug.Log("获取用户信息授权情况失败:" + JsonUtility.ToJson(err, true));
}
});
}
/// <summary>
/// 创建用户信息授权点击区域
/// </summary>
private void CreateUserInfoButton()
{
Debug.Log("create userinfo button area");
/**
* 方法一:创建用户信息获取按钮,在底部区域创建一个300高度的 透明!!! 区域
* 首次获取会弹出用户授权窗口, 可通过右上角-设置-权限管理用户的授权记录
* 可根据需要设置不同高度,使用屏幕还有其它点击热区的情况
*/
// 获取屏幕信息
// var systemInfo = WX.GetSystemInfoSync();
// var canvasWith = (int)(systemInfo.screenWidth * systemInfo.pixelRatio);
// var canvasHeight = (int)(systemInfo.screenHeight * systemInfo.pixelRatio);
// var buttonHeight = (int)(canvasWith / 1080f * 300f);
// 很容易被误导,与其叫按钮,不如叫热区
// WXUserInfoButton btn = WX.CreateUserInfoButton(0, canvasHeight - buttonHeight, canvasWith, buttonHeight, "zh_CN", false);
/**
* 方法二:创建布满整个屏幕的授权按钮区
*/
WXUserInfoButton btn = WX.CreateUserInfoButton(0, 0, Screen.width, Screen.height, "zh_CN", false);
// 监听授权区域的点击
btn.OnTap((res) =>
{
Debug.Log("click userinfo btn: " + JsonUtility.ToJson(res, true));
if (res.errCode == 0)
{
// 用户已允许获取个人信息,返回的 res.userInfo 即为用户信息
Debug.Log("userinfo: " + JsonUtility.ToJson(res.userInfo, true));
// 将用户信息存入成员变量,以待后用
this.userInfo = res.userInfo;
// 展示,只是为了测试看到
this.ShowUserInfo(res.userInfo.avatarUrl, res.userInfo.nickName);
}
else
{
Debug.Log("用户拒绝获取个人信息");
}
// 最后隐藏授权区域,防止阻塞游戏继续
btn.Hide();
Debug.Log("已隐藏热区");
});
}
/// <summary>
/// 调用Api获取用户信息
/// </summary>
private void GetUserInfo()
{
WX.GetUserInfo(new GetUserInfoOption()
{
lang = "zh_CN",
success = (res) =>
{
Debug.Log("获取用户信息成功(API): " + JsonUtility.ToJson(res.userInfo, true));
// 将用户信息存入成员变量,或存入云端,方便后续使用
this.userInfo = this.ConvertUserInfo(res.userInfo);
this.ShowUserInfo(res.userInfo.avatarUrl, res.userInfo.nickName);
},
fail = (err) =>
{
Debug.Log("获取用户信息失败(API): " + JsonUtility.ToJson(err, true));
}
});
}
/// <summary>
/// 展示用户信息,对头像、昵称展示的整合
/// ps: 测试用
/// </summary>
/// <param name="avatarUrl"></param>
/// <param name="nickName"></param>
private void ShowUserInfo(string avatarUrl, string nickName)
{
StartCoroutine(LoadAvatar(avatarUrl));
showNickname(nickName);
}
/// <summary>
/// 展示用户头像
/// ps: 测试用
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
IEnumerator LoadAvatar(string url)
{
// 加载头像图片
UnityWebRequest request = new UnityWebRequest(url);
DownloadHandlerTexture texture = new DownloadHandlerTexture(true);
request.downloadHandler = texture;
yield return request.SendWebRequest();
if (string.IsNullOrEmpty(request.error))
{
avatarTexture = texture.texture;
}
Sprite sprite = Sprite.Create(avatarTexture, new Rect(0, 0, avatarTexture.width, avatarTexture.height), new Vector2(0.5f, 0.5f));
// 场景中图片对象名称为Avatar
Image tempImage = GameObject.Find("Avatar").GetComponent<Image>();
tempImage.sprite = sprite;
}
/// <summary>
/// 展示用户昵称
/// ps: 测试用
/// </summary>
/// <param name="name"></param>
void showNickname(string name)
{
// 场景中文本对象名称为Nickname
Text nickname = GameObject.Find("Nickname").GetComponent<Text>();
nickname.text = name;
}
/// <summary>
/// 将UserInfo对象转为WXUserInfo
/// ps: 不知为何,相同结构要搞两个对象
/// </summary>
/// <param name="userInfo"></param>
/// <returns></returns>
WXUserInfo ConvertUserInfo(UserInfo userInfo)
{
return new WXUserInfo()
{
nickName = userInfo.nickName,
avatarUrl = userInfo.avatarUrl,
country = userInfo.country,
province = userInfo.province,
city = userInfo.city,
language = userInfo.language,
gender = (int)userInfo.gender
};
}
}
其实就是运行时候,会主动弹出一个是否允许授权的提示窗,还有一个是否允许获取个人信息的提示窗,都允许后,就可以获取到个人微信信息了。开始按钮只是方便切换到下一个界面。
这里需要注意的就是,如果需要获取用户OpenID(使用小游戏的唯一用户ID),需要先获取Code
//该函数在用户授权后调用就行
private void GetWXLoginCode()
{
Debug.Log("****** GetCode ");
LoginOption loginOption = new LoginOption();
loginOption.complete = (e) =>
{
Debug.Log("****** e.complete " + e);
};
loginOption.success = ((e) =>
{
//成功获取到用户Code
string code = e.code;
});
loginOption.fail = ((e) =>
{
Debug.Log("****** e.errMsg " + e.errMsg);
});
WX.Login(loginOption);
}
获取到Code后,最好通过后端服务器获取OpenID在进行返回,这样安全系数比较高。
//下面三个参数分别为 小游戏AppID,小程序秘钥,和以获取到的Code
string openIDURL = string.Format("https://api.weixin.qq.com/sns/jscode2session?appid={0}&secret={1}&js_code={2}&grant_type=authorization_code", WXAppID, WXSecret,WXCode);
HttpWebRequest req = HttpWebRequest.Create(new Uri(url)) as HttpWebRequest;
req.Method = WebRequestMethods.Http.Get;
req.Timeout = 3000;
HttpWebResponse res = req.GetResponse() as HttpWebResponse;
if (res.StatusCode == HttpStatusCode.OK)
{
Stream downLoadStream = res.GetResponseStream();
StreamReader reader = new StreamReader(downLoadStream);
string json = reader.ReadToEnd();
JObject jsonObject = JObject.Parse(json);
//成功获取到用户OpenID
string openid = jsonObject["openid"].ToString();
res.Close();
reader.Close();
downLoadStream.Close();
}
注:小程序ID和秘钥,都可以在微信公众平台里面找到。

还有一点需要注意的是,如果需要头像下载到本地使用,把保存下来的头像地址,使用replace方法,把头像URL中的https://thirdwx.qlogo.cn,替换为https://wx.qlogo.cn,包括其他用户的头像地址,都是一样的操作,不然可能会出现头像下载不下来的问题,记得把https://wx.qlogo.cn这个域名添加了
UnityWebSocket链接服务器相关
这个基本就没啥可说的了,用导入时那个案例的代码进行修改就可以,需要注意的就是,微信小游戏只支持wss://IP+端口 方式链接,不过本地测试的话可以使用ws://
ws/wss类似http/https
屏幕触摸相关
可以使用Unity的Input.Touch(不过总感觉那里怪怪的,后面换成微信提供的触摸相关API了)
微信触摸API使用:wx.onTouchStart(function listener) | 微信开放文档
//添加监听
private void Input_ON()
{
WX.OnTouchStart(Input_WX_Start);
WX.OnTouchMove(Input_WX_Move);
WX.OnTouchEnd(Input_WX_End);
}
//移除监听
private void Input_OffWX()
{
WX.OffTouchStart(Input_WX_Start);
WX.OffTouchMove(Input_WX_Move);
WX.OffTouchEnd(Input_WX_End);
}
注:手指移动监听函数,不知道为什么,会出现掉帧的情况(包括Unity中的Input.Touch也是),就是进行手指移动距离检测的时候,会出现某一帧为0的情况,这个就根据实际情况修改一下代码逻辑。
打包相关

颜色空间要选为Gamme

选择生成并转换
这里需要注意的是:
游戏AppID就是你注册微信小游戏时候的ID
项目前期,只是进行测试的话,游戏资源可以本地加载,游戏资源CDN地址填写 http://本机的IP地址:8001,可以通过win+R,打开运行,输入cmd,在输入ipconfig查看自己的IP地址(V4)。
首包资源加载方式选择小游戏包内。还有就是,如果没有好友关系链的需求,可以取消勾选,如果有好友功能需求,运行时可能会出现报错,点击报错链接,会出现一个插件应用的提示,启用下就好了。
压缩首包资源,这个选项可以勾选,测试没问题,不过到后面如果需要把资源放到CDN上面,记得在Web服务器上添加请求文件类型规则(MIME),因为勾选压缩首包资源,打包出来的文件后缀是.br,如果不添加可访问的文件类型,会出现404找不到指定文件错误。
打包后,在导出路径下,会生成两个文件

minigame是上传到微信的,webgl后面可以传到CDN上面,如果是使用本地测试,可以将webgl放入到minigame文件夹里,方便在微信开发者工具里读取资源
然后打开微信开发者工具,导入刚刚打包的项目,选择minigame文件夹
查看微信开发者工具的本地资源地址:

微信开发者工具第一次运行时候,可能会报插件错误,直接点启用就好了
几个注意点
1、素材的分辨率,宽高是4次幂就行,压缩模式选择ASTC(开发者工具可能会报一堆提示,不用理会)
2、项目运行测试时,最好是把那个不校验合法域名的选项去掉勾选,这样能及时发现没有在微信公众平台中添加的域名,添加后,在微信开发者工具,详情-本地设置,刷新一下。
3、真机调试,如果没有启用代码分包的话,一般都会报代码包太大的错误。启用代码分包,在微信开发者工具,设置-扩展设置-找到wasmCodeSplit进行安装,安装后

这个图标就是代码分包
服务器搭建和部署:
配置:
需要先购买云服务器和域名,服务器大概一百左右一年,域名看自己喜欢,.top的域名一年才几块钱。域名的购买和备案都可以直接在华为云完成。
如果需要CDN服务,也可以顺便购买上,也是一百左右。
关于CDN。其实就是把你打包出来的那个webgl文件缓存在全国服务器上面。这样可以减轻源服务器的压力和带宽,也可以加速资源下载。源服务器就是你购买的云服务器。
说的更直白一些,就是首先在你的云服务器上部署一个web网页,只是这个网页用来下载文件,CDN就是帮你把原本需要从云服务器上下载的东西,缓存到全国各地的服务器上,所以,CDN地址你直接填云服务器地址都可以,只要你能承受云服务器的流量刷刷的就没了就行
注意事项:
服务器购买后,远程登录服务器的话,需要先配置安全组,以华为云为例,登录后点击控制台

然后在左侧列表中选择安全组,添加3389、80、443规则,其它规则按需添加即可。
域名看自己需求,一级域名备案后,可以在弄个二级域名,方便区分web和服务器应用。二级域名不需要备案
一级域名和二级域名都是直接在云解析服务 DNS中创建添加即可。
ssl证书一定要申请,因为很多地方都能用到。如果是华为云的话,直接搜索证书就出来了。申请后下载保存。
然后CDN的配置,如果要使用CDN,那么域名必须先备案,备案后可以添加域名,域名添加后,进行设置。
源站就是你的云服务器。看需要什么填写什么就行,如果需要https请求,就把你下载下来的证书,解压,找到后缀为LLS的那个文件,里面两个文件,一个是证书内容,一个是秘钥,在HTTPS设置里面进行复制就行。
域名添加完成后,会给你一个CNAME地址,把这个地址复制,然后再 云解析服务DNS中添加域名解析


添加成功后,返回CDN管理

这样就是添加并已开启。
注意:
CDN的域名,只需要添加这一个DNS解析就行。不需要再添加指向云服务器IP的解析,因为你在配置CDN的时候,那个源站地址已经填写过了。
Web域名DNS解析,可以直接指向云服务器的IP,也记得填一下!
服务器环境部署,可以使用宝塔配置,也可以直接下载Nginx和Apache部署,我使用的是Nginx,所以这里的是Nginx的配置
这里主要讲一下,如果对后端和服务器部署不是很了解的,可以把Web和后端服务器应用分开处理,这样相对好一些,我自己是单独下载一个Nginx用来反向代理服务器应用,然后宝塔使用IIS配置Web服务器(CDN),大佬请无视
宝塔的端口为8888,也记得在安全组中填一下
搭建:
Nginx配置
下载地址:nginx: download(Stable version(稳定版),下载稳定版就行)
安装后,在安装目录里面找到conf文件夹,里面有个nginx.conf的配置文件。
worker_processes auto;
error_log D:/nginx/logs/error.log;
events {
worker_connections 1024;
}
http {
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log D:/nginx/logs/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 4096;
default_type application/octet-stream;
server {
listen 需要监听的端口号(就是从外网访问进来的端口) ssl;
server_name 服务器域名;
#下面这两行是配置https证书
ssl_certificate D:/nginx/cert/server.crt;
ssl_certificate_key D:/nginx/cert/server.key;
ssl_session_timeout 20m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_verify_client off;
location / {
proxy_pass http://本地服务器应用绑定的地址和端口号(使用127.0.0.1也行,或者可以查看自己的ip地址,使用本地地址,端口号为服务器绑定的端口,一般自己设置就行,记得别和别的端口冲突);
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
# 可选:如果需要传递客户端的真实IP给后端服务器
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 禁用缓存
proxy_no_cache 1;
proxy_cache_bypass 1;
}
}
}
listen中填的端口号,记得在云服务器的安全组中填一下,后面的ssl代表是https访问方式,需要证书
配置完运行一下nginx.exe服务(如果没有设置开机自启动,每次重启都要手动运行)
注:这里的证书别搞混了,一级域名一个证书,二级域名也有证书。我这里是使用的二级域名代理后端服务器应用,所以证书是二级域名的证书,千万别搞混!!!
后端服务器开发(这里是C#的后端服务器代码,如果你的不是C#写的,可以问GTP4怎么实现,或者查查资料)
注意:后端服务器应用,绑定的地址,必须是http的。因为Nginx配置中就是http://开头。所以服务器URL绑定就需要使用http来监听绑定,不然外网请求没办法通过Nginx反向代理到服务器上
服务器代码:
internal class WebSocketServer_Server
{
private readonly HttpListener _listener;
private readonly ConcurrentDictionary<string, WebSocketClient> _clients;
private string _url;
public static int clientNum;
public WebSocketServer_Server(string url)
{
_listener = new HttpListener();
_listener.Prefixes.Add(url);
_url = url;
_clients = new ConcurrentDictionary<string, WebSocketClient>();
}
// 启动WebSocket服务器
public async Task StartAsync(CancellationToken cancellationToken)
{
_listener.Start();
while (!cancellationToken.IsCancellationRequested)
{
var context = await _listener.GetContextAsync();
if (context.Request.IsWebSocketRequest)
{
await HandleWebSocketRequestAsync(context, cancellationToken);
}
else
{
context.Response.StatusCode = 400; // 不是WebSocket请求
context.Response.Close();
}
}
}
// 处理 WebSocket 请求
private async Task HandleWebSocketRequestAsync(HttpListenerContext context, CancellationToken cancellationToken)
{
WebSocket webSocket = null;
WebSocketClient client = null;
try
{
// 接受 WebSocket 连接
WebSocketContext webSocketContext = await context.AcceptWebSocketAsync(null);
webSocket = webSocketContext.WebSocket;
// 创建客户端对象并加入管理列表
client = new WebSocketClient(webSocket);
_clients.TryAdd(client.ClientId, client);
Console.WriteLine($"Client {client.ClientId} connected.");
++clientNum;
// 持续读取客户端发来的消息
_=client.ListenForMessagesAsync(cancellationToken);
}
catch (Exception ex)
{
Console.WriteLine($"Error handling WebSocket request: {ex.Message}");
}
}
// 停止WebSocket服务器
public void Stop()
{
_listener.Stop();
Console.WriteLine("WebSocket server stopped.");
}
}
WebSocketClient客户端接入代码:
public class WebSocketClient
{
private static int _nextClientId = 0;
private readonly WebSocket _webSocket;
private readonly string _clientId;
private readonly CancellationTokenSource _cancellationTokenSource;
private int cacheNum = 0;
private byte[] cacheBytes = new byte[1024];
private PlayerWXData playerWXData;
public string ClientId => _clientId;
public WebSocketClient(WebSocket webSocket)
{
_webSocket = webSocket;
_clientId = (++_nextClientId).ToString();
_cancellationTokenSource = new CancellationTokenSource();
}
// 向客户端发送消息
public async Task SendMessageAsync(string message)
{
if (_webSocket.State == WebSocketState.Open)
{
byte[] buffer = Encoding.UTF8.GetBytes(message);
await _webSocket.SendAsync(new ArraySegment<byte>(buffer), WebSocketMessageType.Text, true, _cancellationTokenSource.Token);
}
}
public async Task SendMessageAsync(byte[] message)
{
if (_webSocket.State == WebSocketState.Open)
{
await _webSocket.SendAsync(new ArraySegment<byte>(message), WebSocketMessageType.Binary, true, _cancellationTokenSource.Token);
}
}
// 监听客户端发送的消息
public async Task ListenForMessagesAsync(CancellationToken cancellationToken)
{
byte[] buffer = new byte[1024];
try
{
while (_webSocket.State == WebSocketState.Open && !cancellationToken.IsCancellationRequested)
{
var result = await _webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), cancellationToken);
if (result.MessageType == WebSocketMessageType.Binary)
{
//string receivedMessage = Encoding.UTF8.GetString(buffer, 0, result.Count);
//Console.WriteLine($"Client {ClientId} sent: {receivedMessage}");
//// 处理接收到的消息,示例中是回声响应
//await SendMessageAsync($"Echo: {receivedMessage}");
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Error with client {ClientId}: {ex.Message}");
}
finally
{
await CloseConnectionAsync();
}
}
// 处理客户端连接的关闭
public async Task CloseConnectionAsync()
{
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Client disconnected", _cancellationTokenSource.Token);
_webSocket.Dispose();
}
}
开启服务:
//开始服务
private void button1_Click(object sender, EventArgs e)
{
//IP = "http://127.0.0.1:11111/";
var server = new WebSocketServer_Server(IP);
cts = new CancellationTokenSource();
// 启动 WebSocket 服务器
Ranking_RedisServer.Instance.OpenRedisServer();
var serverTask = server.StartAsync(cts.Token);
}
宝塔配置Web(windows系统)
下载地址:宝塔面板下载,免费全能的服务器运维软件
可以本地下载,然后复制到云服务器上进行安装。
云服务器上安装完成后(会默认生成一个用户名和密码,记得先保存)

复制面板地址,然后再自己的电脑上打开链接,输入云服务器安装宝塔后给的用户和密码,然后登录,登录后会有一个提示安装插件的面板,直接关了就行,什么都别安装!
面板左侧,点击网站,选择PHP项目,添加站点,如果弹出选择安装服务,就选择LLS,安装完成后,应该会自动创建一个默认站点,不想用就直接删除就好,然后自己添加个站点,域名就是留给web使用的域名(别和CDN的域名搞混了),目录自己选择,别的不用管,直接创建就行,创建完站点后,有个设置填一下

这里记得填了,这里是上面说的,如果打包的时候,需要压缩首包资源,那么这里必须填写!
填完后重启一下lls服务,然后点击站点名称,在打开的面板中找到ssl,把之前下载的证书配置进去(别搞错证书,这里的是web域名的证书)
剩下的就是测试网站是否正常打开(默认打开后应该会看见Nginx的欢迎,如果看见这个提示就证明网站配置没问题)
注:如果打开网页失败,看看自己宝塔是不是安装了别的什么软件,打开云服务器,查看站点目录是否存在,里面是否有内容。仔细检查下。还有就是域名的DNS解析看看加没加。
都没问题后,宝塔就可以关了,后续也基本用不到了。
打开云服务器,点开站点的文件夹。除了web.config、.htaccess、web_config。这三个文件,别的都可以删了。这三个文件不知道有没有用,我没删,你们可以测试一下,最多就是把站点删了重新创建。
后面在打包,就是那个webgl文件夹,把里面的的5个文件复制到这个目录里面就行了。
不要复制webgl文件夹,点进去,复制里面的文件!
至此结束!个人开发,每一步都是一点一点扣出来的,希望能帮到大家,加油吧!!!
如果有遗漏,可以留言,我再补充

被折叠的 条评论
为什么被折叠?



