Unity 接入微信小游戏后常用的功能
1. 前言
近期项目需要将游戏投放到国内各个小游戏平台,微信小游戏,抖音小游戏,快手小游戏,支付宝小游戏等,各种小游戏平台,之前有处理过国内安卓 谷歌 IOS 平台的打包,后边会一一说明各小游戏平台用到的不同的功能。
关于游戏上架微信的博客有很多,此篇主要提一下我上到微信后用到的微信方面相关的功能,分享,邀请,订阅,朋友圈,振动,好友排行等,后台等相关应用,具体还要根据大家项目自行修改。
2.微信相关文档
普通的Unity版本可以参考下方链接接入
3.关于打包微信的应用
1.处理掉烦人的LOGO
- 代码处理
[Preserve]
public class SkipUnityLogo
{
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSplashScreen)]
private static void BeforeSplashScreen()
{
#if UNITY_WEBGL
Application.focusChanged += Application_focusChanged;
#else
System.Threading.Tasks.Task.Run(AsyncSkip);
#endif
}
#if UNITY_WEBGL
private static void Application_focusChanged(bool obj)
{
Application.focusChanged -= Application_focusChanged;
SplashScreen.Stop(SplashScreen.StopBehavior.StopImmediate);
}
#else
private static void AsyncSkip()
{
SplashScreen.Stop(SplashScreen.StopBehavior.StopImmediate);
}
#endif
}
#endif
- 插件处理
如果不是团结引擎的话,可以考虑用些工具处理 ( ̄︶ ̄)↗
类似下边这个 - https://github.com/tylearymf/UniHacker
2.微信分享或邀请
微信当中可以自定义参数,来获取来进行分享或邀请
GetShareInfo(type, WeChatConfig.ShareLocationInvite, (info) =>
{
LY.Analytics.ShareEvent(WeChatConfig.ShareLocationMenu, type, DataAnalytics.ShareAction.show, info.combineShareId);
string str = $"sharelocation={
info.location}&combineShareId={
info.combineShareId}&action=normal"; //分享
string str = $"sharelocation={
info.location}&combineShareId={
info.combineShareId}&inviter={
LY.SDK.GetOpenId()}&action=invite"; //邀请
_inviteCallback = cb;
_shareStartTime = DateTime.Now;
ShareAppMessageOption shareAppMessageOption = new ShareAppMessageOption();
shareAppMessageOption.title = info.text;
shareAppMessageOption.imageUrl = info.imageUrl; //自己CDN配置的图片地址
shareAppMessageOption.query = str;
WX.ShareAppMessage(shareAppMessageOption); //调用
}, (error) =>
{
cb?.Invoke(false);
});
微信也会返回受邀新用户数量,可以用来做邀请有礼等游戏内容
3.游戏圈
游戏圈的难点,基本就在入口的位置计算上,另外就是我们UI还是用的美术出的效果图,但是向微信上传的是一张空白图
- 第一步计算生成位置
public static void CreateClubBtn(Transform tra, Camera camera = null)
{
#if !UNITY_EDITOR
if (camera == null)
{
camera = Camera.main;
}
WeChatWASM.SystemInfo systemInfo = WX.GetSystemInfoSync(); ;
float ux = Screen.width;
float uy = Screen.height;
float wx = (float)systemInfo.screenWidth;
float wy = (float)systemInfo.screenHeight;
float sx = wx / ux;
float sy = wy / uy;
Vector3 screenPoint = RectTransformUtility.WorldToScreenPoint(camera, tra.position);
Rect r = Utility.UGUI.GetSize(tra.gameObject);
float bw = r.width * sx;
float bh = r.height * sy;
float bx = screenPoint.x * sx - bw * 0.5f;
float by = (Screen.height - screenPoint.y) * sy - bh * 0.5f;
Debug.Log($"CreateClubBtn++++ux:{
ux} uy:{
uy} wx:{
wx} wy:{
wy} sx:{
sx} sy:{
sy} bw:{
bw} bh:{
bh} bx:{
bx} by:{
by} screenX:{
screenPoint.x} screenY:{
screenPoint.y}");
CreateClubBtn(new Rect(bx,by,bw,bh));
#endif
}
- 生成出对应功能
public static void CreateClubBtn(Rect rect)
{
#if !UNITY_EDITOR
if (clubBtn != null)
{
return;
}
clubBtn = WX.CreateGameClubButton(new WXCreateGameClubButtonParam()
{
type = GameClubButtonType.image,
text = "游戏圈",
image = WeChatConfig.ClubImageUrl,
style = new GameClubButtonStyle()
{
left = (int)rect.x,
top = (int)rect.y,
width = (int)rect.width,
height = (int)rect.height,
}
});
clubBtn.Show();
#endif
}
- 控制朋友圈按钮显示,隐藏,销毁
public static void ShowClubkBtn()
{
#if LIEYOU_WECHAT
clubBtn?.Show();
#endif
}
public static void HideClubkBtn()
{
#if LIEYOU_WECHAT
clubBtn?.Hide();
#endif
}
public static void DestroyClubkBtn()
{
#if LIEYOU_WECHAT
clubBtn?.Destroy();
clubBtn = null;
#endif
}
- 可以将奖励发放,放在 OnTap 中
public static void OnTapAc(Action action)
{
#if LIEYOU_WECHAT && !UNITY_EDITOR
clubBtn.OnTap(() => {
if (action != null)
{
action();
}
});
#endif
}
4.微信振动
根据需求调用长振动还是短振动就可以
public override void VibrateShort()
{
base.VibrateShort();
VibrateShortOption result = new VibrateShortOption();
WX.VibrateShort(result);
}
public override void VibrateLong()
{
base.VibrateLong();
VibrateShortOption result = new VibrateShortOption();
result.type = "heavy";
WX.VibrateShort(result);
}
5.微信个人信息获取
你可以通过 微信返回的配置信息 WeChatWASM.GetSettingOption,来判断有没有授权个人信息,当然也有简单的办法,我在下边会先进行个人信息获取,如果没有获取到,创建弹窗授权获取。
public override void GetUserInfo(Action<DataStruct.UserInfo> suc, Action<int, string> fail = null)
{
base.GetUserInfo(suc, fail);
if (_userInfo != null)
{
suc?.Invoke(_userInfo);
return;
}
GetUserInfoOption getUserInfoOption = new GetUserInfoOption();
getUserInfoOption.success += (GetUserInfoSuccessCallbackResult res) =>
{
_userInfo = new DataStruct.UserInfo();
_userInfo.nickName = res.userInfo.nickName;
_userInfo.avatarUrl = res.userInfo.avatarUrl;
suc?.Invoke(_userInfo);
};
getUserInfoOption.fail += (GeneralCallbackResult res) =>
{
WXUserInfoButton btn = WX.CreateUserInfoButton(0, 0, 5000, 5000, "en", false);
btn.OnTap((WXUserInfoResponse res) =>
{
if (res.errCode == 0)
{
_userInfo = new DataStruct.UserInfo();
_userInfo.nickName = res.userInfo.nickName;
_userInfo.avatarUrl = res.userInfo.avatarUrl;
suc?.Invoke(_userInfo);
}
else
{
fail?.Invoke(res.errCode, res.errMsg);
}
btn.Destroy();
});
};
WX.GetUserInfo(getUserInfoOption);
}
6.微信订阅功能
订阅可能需要处理的比较麻烦些,由于Unity WebGL发布的多点触控存在问题, 导致在微信中多点触控存在粘连的情况,所以需要使用WX的触控接口重新覆盖Unity的BaseInput关于触控方面的接口,通过设置StandaloneInputModule.inputOverride的方式来实现,这块儿比较多,感觉能单独写一篇,下边简单提要一些。
可以写一个 WXTouchInputOverride : BaseInput 脚本进行管理
private void OnWxTouchStart(OnTouchStartListenerResult touchEvent)
{
foreach (var wxTouch in touchEvent.changedTouches)
{
var data = FindOrCreateTouchData(wxTouch.identifier);
data.touch.phase = TouchPhase.Began;
data.touch.position = new Vector2(wxTouch.clientX, wxTouch.clientY);
data.touch.rawPosition = data.touch.position;
data.timeStamp = touchEvent.timeStamp;
// Debug.Log($"OnWxTouchStart:{wxTouch.identifier}, {data.touch.phase}");
}
}
private void OnWxTouchMove(OnTouchStartListenerResult touchEvent)
{
foreach (var wxTouch in touchEvent.changedTouches)
{
var data = FindOrCreateTouchData(wxTouch.identifier);
UpdateTouchData(data, new Vector2(wxTouch.clientX, wxTouch.clientY), touchEvent.timeStamp, TouchPhase.Moved);
}
}
private void OnWxTouchEnd(OnTouchStartListenerResult touchEvent)
{
foreach (var wxTouch in touchEvent.changedTouches)
{
TouchData data = FindTouchData(wxTouch.identifier);
if (data == null)
{
Debug.LogError($"OnWxTouchEnd, error identifier:{
wxTouch.identifier}");
return;
}
if (data.touch.phase == TouchPhase.Canceled || data.touch.phase == TouchPhase.Ended)
{
Debug.LogWarning($"OnWxTouchEnd, error phase:{
wxTouch.identifier}, phase:{
data.touch.phase}");
}
// Debug.Log($"OnWxTouchEnd:{wxTouch.identifier}");
UpdateTouchData(data, new Vector2(wxTouch.clientX, wxTouch.clientY), touchEvent.timeStamp, TouchPhase.Ended);
}
GameObject selectedObject = EventSystem.current.currentSelectedGameObject;
if (selectedObject != null)
{
Button button = selectedObject.GetComponent<Button>();
if (button != null)
{
int clickListenerCount = button.onClick.GetPersistentEventCount();
if (clickListenerCount > 0) {
button.onClick.SetPersistentListenerState(0, UnityEventCallState.EditorAndRuntime);
button.onClick.Invoke();
button.onClick.SetPersistentListenerState(0, UnityEventCallState.Off);
}
}
}
}
private void OnWxTouchCancel(OnTouchStartListenerResult touchEvent)
{
foreach (var wxTouch in touchEvent.changedTouches)
{
TouchData data = FindTouchData(wxTouch.identifier);
if (data == null)
{
Debug.LogError($"OnWxTouchCancel, error identifier:{
wxTouch.identifier}");
return;
}
if (data.touch.phase == TouchPhase.Canceled || data.touch.phase == TouchPhase.Ended)
{
Debug.LogWarning($"OnWxTouchCancel, error phase:{
wxTouch.identifier}, phase:{
data.touch.phase}");
}
// Debug.Log($"OnWxTouchCancel:{wxTouch.identifier}");
UpdateTouchData(data, new Vector2(wxTouch.clientX, wxTouch.clientY), touchEvent.timeStamp, TouchPhase.Canceled);
}
}
进行方法绑定
private void RegisterWechatTouchEvents()
{
WX.OnTouchStart(OnWxTouchStart);
WX.OnTouchMove(OnWxTouchMove);
WX.OnTouchEnd(OnWxTouchEnd);
WX.OnTouchCancel(OnWxTouchCancel);
}
private void UnregisterWechatTouchEvents()
{
WX.OffTouchStart(OnWxTouchStart);
WX.OffTouchMove(OnWxTouchMove);
WX.OffTouchEnd(OnWxTouchEnd);
WX.OffTouchCancel(OnWxTouchCancel);
}
然后,判定是否有订阅权限
public void HasPermission(string templateId, Action suc, Action<string> fail)
{
#if UNITY_EDITOR
fail?.Invoke("not support");
#elif LIEYOU_WECHAT
WeChatWASM.GetSettingOption option = new WeChatWASM.GetSettingOption();
option.withSubscriptions = true;
option.success += (WeChatWASM.GetSettingSuccessCallbackResult r) =>
{
WeChatWASM.SubscriptionsSetting set = r.subscriptionsSetting;
Dictionary<string, string> dict = set.itemSettings;
if (r.subscriptionsSetting != null && r.subscriptionsSetting.mainSwitch && dict.ContainsKey(templateId) && dict[templateId].Equals("accept"))
{
suc?.Invoke();
}
else
{
fail?.Invoke("No permission");
}
};
option.fail += (error) =>
{
fail.Invoke(error.errMsg);
};
WeChatWASM.WX.GetSetting(option);
#endif
}
紧接着, 请求版本更新权限
public void RequestVersionUpdate(Action suc, Action<string> fail)
{
#if UNITY_EDITOR
fail?.Invoke("not support");
#elif LIEYOU_WECHAT
WeChatWASM.RequestSubscribeSystemMessageOption request = new WeChatWASM.RequestSubscribeSystemMessageOption();
string[] list = {
"SYS_MSG_TYPE_WHATS_NEW" };
request.msgTypeList = list;
request.success += (rr) =>
{
Utility.Debug.LogSDK("SubscribeManager.RequestVersionUpdate.suc");
suc?.Invoke();
};
request.fail += (rr) =>
{
fail?.Invoke(rr.errMsg);
};
WeChatWASM.WX.RequestSubscribeSystemMessage(request);
#endif
}
顺带提一下 申请订阅权限,一次不能超过3个
public void RequestSubscribeMessage(string[] tmplIds, Action suc, Action<string> fail)
{
Utility.Debug.LogSDK("SubscribeManager.RequestSubscribeMessage");
#if UNITY_EDITOR
fail?.Invoke("not support");
#elif LIEYOU_WECHAT
WeChatWASM.RequestSubscribeMessageOption request = new WeChatWASM.RequestSubscribeMessageOption();
request.tmplIds = tmplIds;
request.fail += (r) =>
{
Utility.Debug.LogSDK("RequestSubscribeMessage.fail = " + r.errMsg);
fail?.Invoke(r.errMsg);
};
request.success += (r) =>
{
Utility.Debug.LogSDK("RequestSubscribeMessage.suc");
suc?.Invoke();
};
WeChatWASM.WX.RequestSubscribeMessage(request);
#endif
}
7.微信好友排行榜
微信好友排行默认使用 Layout布局,且需要用到 JS引擎,由于项目组人少且并无前端,周期不允许,用自带open-data无法满足展示多样性界面的需求,但也是有别的办法,这里只能粗略说一下,具体实现布局啥的没办法详细展开,内容也比较多。
目前可使用TS来实现布局,界面的搭建以及与主域的交互跟微信的交互
布局相关
基础组件相关
npm run build 来生成界面出来直接替换掉wx自带的开放域
需要提一下,字体是需要主域共享给开放域的,无法在开放域中添加
8.微信广告
微信广告用到的基本上包括 激励,插屏,横幅,简单写一下实现
- 激励广告
void LoadReward()
{
if (_rewardAd == null)
{
WXCreateRewardedVideoAdParam param = new WXCreateRewardedVideoAdParam();
param.adUnitId = WeChatConfig.RewardId;
_rewardAd = WX.CreateRewardedVideoAd(param);
_rewardAd.OnError((r) =>
{
Utility.Debug.LogSDK("WXReward.OnError = " + r.errMsg);
IsRewardSuc = false;
RewardAdFail?.Invoke();
});
_rewardAd.OnLoad((r) =>
{
_isLoadedReward = true;
//其他
});
_rewardAd.OnClose((r) =>
{
if (r != null && r.isEnded)
{
IsRewardSuc = true;
RewardAdSuc?.Invoke();
if (_videoIsInters)
{
SDKEvent.SpotVideoFinishCall?.Invoke();
}
}
else
{
IsRewardSuc = false;
RewardAdFail?.Invoke();
}
SDKEvent.RewardClose?.Invoke(IsRewardSuc);
_videoIsInters = false;
});
_rewardAd.Load();
}
}
- 插屏广告
void LoadInters()
{
if (_interstitialAd == null)
{
WXCreateInterstitialAdParam param = new WXCreateInterstitialAdParam();
param.adUnitId = WeChatConfig.IntersId;
_interstitialAd = WX.CreateInterstitialAd(param);
_interstitialAd.OnLoad((r) =>
{
_isLoadedInters = true;
//数据打点
});
_interstitialAd.OnClose(() =>
{
InterstitialCallback?.Invoke(true);
SDKEvent.IntersClose?.Invoke();
_interstitialAd.Load();
//数据打点
});
_interstitialAd.OnError((r) =>
{
InterstitialCallback?.Invoke(false);
SDKEvent.IntersClose?.Invoke();
Utility.Debug.LogSDK(r.errMsg);
});
_interstitialAd.Load();
}
}
- Banner广告
void LoadBanner(Rect rect)
{
if (rect == default)
{
rect = WeChatConfig.BannerStyle;
}
//数据打点
WXCreateBannerAdParam param = new WXCreateBannerAdParam();
param.adUnitId = WeChatConfig.BannerId;
Style style = new Style();
style.height = (int)rect.height;
style.width = (int)rect.width;
style.top = (int)rect.y;
style.left = (int)rect.x;
param.style = style;
//_bannerAd = WX.CreateBannerAd(param);
_bannerAd = WX.CreateFixedBottomMiddleBannerAd(param.adUnitId, 30, style.height);
_bannerAd.OnLoad((r) =>
{
Utility.Debug.LogSDK("MPWeChat.LoadBanner.OnLoad top = " + style.top + " left = " + style.left + " height = " + style.height + " width = " + style.width);
});
_bannerAd.OnError((r) =>
{
Utility.Debug.LogSDK(r.errMsg);
});
}
8.微信充值
微信充值这里要分一下,ios跟安卓,双端充值流程与补单流程并不一样,只简单粗略提一下
/// ios 充值
private void CallPayIOS(DataStruct.GoodsData d, Action<DataStruct.GoodsData> suc, Action<string> fail)
{
//Debug.Log(" 支付测试 开始启用ios弹窗 ISO_openCustomerService");
OpenCustomerServiceConversationOption obj = new OpenCustomerServiceConversationOption();
//会话来源
obj.sessionFrom = "{'Title': '游戏充值','productId':" + "'" + d.orderId + "'" + "}";
//是否显示会话内消息卡片,
//设置此参数为 true,
//用户进入客服会话会在右下角显示"可能要发送的小程序"提示,
//用户点击后可以快速发送小程序消息
obj.showMessageCard = true;
obj.sendMessageTitle = "游戏充值";
//会话内消息卡片路径
obj.sendMessagePath = d.orderId;
//会话内消息卡片图片路径
obj.sendMessageImg = WeChatConfig.IosPayMessageImg;
obj.success = (response) =>
{
IOSPayQuery(d, suc, fail);
};
obj.fail = (err) =>
{
Utility.Debug.LogSDK("LYWeChat.CallPayIOS.fail:" + err.errMsg);
fail?.Invoke(err.errMsg);
};
WX.OpenCustomerServiceConversation (obj);
}
如果没有道具到账,但是进行了扣款,就需要主动进行补单
安卓,需要借助米大师来实现充值补单的流程, 需要先进行米大师余额的查询与扣除
/// <summary>
/// 米大师支付
/// </summary>
/// <param name="d"></param>
/// <param name="suc"></param>
/// <param name="fail"></param>
private void MidasPayment(DataStruct.GoodsData d, Action<DataStruct.GoodsData> suc, Action<string> fail)
{
RequestMidasPaymentOption callBack = new RequestMidasPaymentOption();
callBack.mode = "game";//支付的类型,不同的支付类型有各自额外要传的附加参数。
callBack.env = 0; //环境配置
callBack.offerId = WeChatConfig.OfferID;//在米大师侧申请的应用 id
callBack.currencyType = "CNY";//币种
callBack.platform = "android";//申请接入时的平台,platform 与应用 id 有关。
callBack.buyQuantity = (int)(d.price * WeChatConfig.BuyQuantityScale);//购买数量。mode=game 时必填。购买数量。详见 buyQuantity 限制说明。
callBack.zoneId = "1";//分区 ID
callBack.success = (callResult) =>
{
SetOrderStatus(d.orderId, LY_OrderStatus.buy, () =>
{
suc?.Invoke(d);
SDKEvent.PaySuc?.Invoke(d);
}, fail);
};
callBack.fail = (err) =>
{
fail?.Invoke(err.errMsg);
};
WX.RequestMidasPayment(callBack);
}
4.总结
此篇讲了一些接入微信后可能用到的功能,当然还有些基础功能,WxHide,WxShow,微信锁帧,后台,常亮等等,比较简单,就不做整理了,希望对大家能有一些帮助。