文章目录
前言
当前手游市场每天可能有几十上百款游戏上线,竞争这么激烈的情况下,流量对游戏的重要性不言而喻。
今天就在此记录整理一下我最近在项目中的解决方案:邀请拉新
流程
需求大概如下。
- 为每个玩家生成唯一邀请码。
- 将邀请码与链接地址生成二维码。
- 生成游戏落地页。
- 分享到微信好友/朋友圈。
- 使用OpenInstall传递启动参数。
- 识别启动参数,进入邀请流程。
1.为每个玩家生成唯一邀请码。
服务器用node写的,根据玩家在数据库的唯一id转换成MASK长度进制的邀请码。起初是26个字母+10个数字来的,不过某些字母跟数字不容易区分,就剔除掉了。
全用大写是因为真正输入的时候不断的切换大小写对玩家体验非常不友好。
const BIND_MASK = ["A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0"];
function GetBindCode(value) {
var length = BIND_MASK.length;
var arr = [];
while (true) {
if (value < length) {
arr.push(value);
break;
}
var intDivide = value % length;
value = (value - intDivide) / length;
arr.push(intDivide);
}
arr.reverse();
var bindCode = "";
for (let item of arr) {
var index = item;
if (index == 0) {
index = length;
}
bindCode += BIND_MASK[index - 1];
}
return bindCode;
}
2.将邀请码与链接地址生成二维码。
网上解决方案参考比较多。参考跳转
1. Zxing插件。
Zxing是一款本地生成二维码的插件,使用简单易上手。前端是untiy,实现如下。
代码搬运工~~
public Texture2D CreateQRCode(string content, Rect rect)
{
Texture2D encoded = new Texture2D((int)rect.width, (int)rect.height);
if (content != null)
{
Color32[] color32 = Encode(content, (int)rect.width, (int)rect.height);
encoded.SetPixels32(color32); //根据转换来的32位颜色值来计算二维码的像素
encoded.Apply(); //生成二维码
}
try
{
byte[] pngData = encoded.EncodeToPNG(); //将Texture2D转码成png格式的字节数据
File.WriteAllBytes(path, pngData);
}
catch (Exception ioe)
{
Debug.LogException(ioe); //输出图片保存异常信息
}
return encoded;
}
private static Color32[] Encode(string textForEncoding, int width, int height)
{
BarcodeWriter writer = new BarcodeWriter
{
Format = BarcodeFormat.QR_CODE,
Options = new QrCodeEncodingOptions
{
Height = height,
Width = width,
CharacterSet = "UTF-8",
}
};
return writer.Write(textForEncoding);
}
结果如下
但是在打包到安卓端的时候生成报错
看样子是插件内部的问题,最终不了了之。使用了第二种方法。
[补充]:应该是某个版本的Zxing有问题,我从网上又试了几个版本,测试这个版本是可以用的
网盘链接:https://pan.baidu.com/s/12rQ7vuen9JiO9p5VMag18w
提取码:i1qc
2. 通过草料的API接口获取。
不过这种方式的缺点也很明显。
- 用户体验不好,需要有网络支持。
- 经测试,text=后如果携带多个参数,草料只会判断第一个参数有效。
比如"https://cli.im/api/qrcode/code?text=" + “http://baidu.com?name=1&pwd=2&bindCode=3”。最终生成的二维码的链接是"https://cli.im/api/qrcode/code?text=" + “http://baidu.com?name=1”。第一个之后的参数
&pwd=2&bindCode=3"
都被忽略掉了。
下面的其实就是用 | 把各个字段拼接了一下。
bindData.ToEntryString()
var url = "https://cli.im/api/qrcode/code?text=" + url + $"?bindData={bindData.ToEntryString()}";
UnityWebRequest uwr = UnityWebRequest.Get(url);
yield return uwr.SendWebRequest();
if (uwr.isHttpError || uwr.isNetworkError)
{
Debug.Log(uwr.error);
yield break;
}
else
{
string s = uwr.downloadHandler.text.Substring(uwr.downloadHandler.text.IndexOf("<img src=") + 12, uwr.downloadHandler.text.Length - (uwr.downloadHandler.text.IndexOf("<img src=") + 12));
string result = s.Substring(0, s.IndexOf("\""));
UnityWebRequest newu = UnityWebRequest.Get(result);
newu.downloadHandler = new DownloadHandlerTexture(true);
yield return newu.SendWebRequest();
var dlh = newu.downloadHandler as DownloadHandlerTexture;
File.WriteAllBytes(localPath, dlh.texture.EncodeToPNG());
Debug.Log(localPath);
img_qrCode.texture = dlh.texture;
}
3.生成游戏落地页。
二维码生成后,怎么把二维码放到落地页又成了一个问题。
有看到说是根据像素,把二维码放到预先生成好的图片里。个人感觉不是很直观。
用了一个很老土的办法,把二维码渲染到一个rawImage中,然后落地页的样子,用单独的正交摄像机去拍照。
代码也是非常简单
private IEnumerator TakePhoto()
{
Debug.Log("开始拍照");
yield return new WaitForEndOfFrame();
RenderTexture.active = mCamera.targetTexture;
var sharePost = new Texture2D(mCamera.targetTexture.width, mCamera.targetTexture.height);
sharePost.ReadPixels(new Rect(0, 0, sharePost.width, sharePost.height), 0, 0);
sharePost.Apply();
if (!Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
}
File.WriteAllBytes(path, sharePost.EncodeToPNG());
Debug.Log("拍照完毕");
}
4.分享到微信好友/朋友圈。
上链接~微信官方文档
PS:首先要做的就是根据官方文档把该配置的都配置完毕,否则出了问题之后,定位问题的时间都比重新接入要久。
【安卓】
注意这句话
当时忽略了这点,发现在小米10上测试的时候提示要用fileProvider的方式去分享,后来改了之后就OK了。
在论坛里看到有一些同学不知道怎么用fileProvider。我把自己的实现贴在下面。
//scene 0-分享到对话 1-分享到朋友圈 2-分享到收藏 imgPath = "share.png"
public void ShareToWechat(int scene, String imgPath) {
String filePath = getExternalFilesDir(null) + "/sharedata/" + imgPath;
File file = new File(filePath);
String contentPath = getFileUri(this, file);
WXEntryActivity.ShareToWechat(scene, filePath, contentPath);
}
public String getFileUri(Context context, File file) {
if (file == null || !file.exists()) {
return null;
}
Uri contentUri = FileProvider.getUriForFile(context,
"com.abcd.qmddx.fileProvider", // 要与`AndroidManifest.xml`里配置的`authorities`一致,假设你的应用包名为com.example.app
file);
// 授权给微信访问路径
context.grantUriPermission("com.tencent.mm", // 这里填微信包名
contentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
return contentUri.toString(); // contentUri.toString() 即是以"content://"开头的用于共享的路径
}
//scene 0-分享到对话 1-分享到朋友圈 2-分享到收藏
public static void ShareToWechat(int scene, String imgPath, String uri) {
//imgPath是真实路径
Bitmap bmp = BitmapFactory.decodeFile(imgPath);
//初始化 WXImageObject 和 WXMediaMessage 对象
WXImageObject imgObj = new WXImageObject();
if (checkVersionValid() && checkAndroidNotBelowN()) {
//这里是uri路径。
imgObj.setImagePath(uri);
} else {
imgObj.setImagePath(imgPath);
}
WXMediaMessage msg = new WXMediaMessage();
msg.mediaObject = imgObj;
msg.description = "XXXXXXXXXXX";
msg.title = "XXXXXXXX";
//设置缩略图
Bitmap thumbBmp = Bitmap.createScaledBitmap(bmp, 75, 134, true);
bmp.recycle();
msg.thumbData = Bitmap2Bytes(thumbBmp);
//构造一个Req
SendMessageToWX.Req req = new SendMessageToWX.Req();
req.transaction = String.valueOf(System.currentTimeMillis());
req.message = msg;
req.scene = scene;
// req.userOpenId = getOpenId();
//调用api接口,发送数据到微信
MainApplication.sApi.sendReq(req);
}
【IOS】
void _shareToWechatSession(){
NSLog(@"_shareToWechatSession");
//要先获取这个地址
NSString *documentPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *filePath = [documentPath stringByAppendingPathComponent:@"/sharedata/share.png"];
UIImage *image = [UIImage imageNamed:filePath];
NSData* imageData = UIImageJPEGRepresentation(image, 0.7);
WXImageObject *imageObject = [WXImageObject object];
imageObject.imageData = imageData;
WXMediaMessage *message = [WXMediaMessage message];
//thumbData不能太大
message.thumbData = UIImageJPEGRepresentation(image, 0.1);
message.title = @"XXXXX";
message.description = @"XXXXX";
message.mediaObject = imageObject;
SendMessageToWXReq *req = [[SendMessageToWXReq alloc] init];
req.bText = NO;
req.message = message;
req.scene = WXSceneSession;
[WXApi sendReq:req completion:^(BOOL success) { NSLog(@"唤起微信:%@", success ? @"成功" : @"失败"); }];
}
5.使用OpenInstall传递启动参数。
带参数的落地页生成好了,怎么让玩家在扫码下载游戏之后携带启动参数进入游戏呢?本着不要重复造轮子的想法,我找到一个第三方插件openInstall。接入文档
简单易上手。因为我用的是Unity开发,就下载了unity的SDK。
接入后我们只需要关系 getInstallFinish这个回调函数。
因为之前我通过 | 将多参数合并在一起了。现在需要再拆分开。所以有了下面的字符串替换的部分。
// callback
public void getInstallFinish(OpenInstallData installData)
{
Debug.Log("OpenInstallSample getInstallFinish : 渠道编号=" + installData.channelCode + ",自定义数据=" + installData.bindData);
if (string.IsNullOrEmpty(installData.bindData))
{
Debug.Log("没有启动参数");
return;
}
var str = installData.bindData.Replace("{", "").Replace("}", "").Replace("\"","");
var arr = str.Split('|');
ApprData.BindData bindData = new ApprData.BindData() { channel = arr[0], bindCode = arr[1] };
LoginManager.bindData = bindData;
}
注意上传包之后,官网会进行检测,之后会给你生成对应的参数以及修改意见,按照修改意见接入,测试。
【安卓端】
安卓端只需要按照官方文档配置AndroidManifest.xml文件即可。
【IOS】
根据官方文档接入,理想情况下 配置参数即可。
不理想情况比较多。因为按照我个人接入习惯来说,一般都要重写AppController的,尽量做到与导出工程解耦。这时候就要注意下面这句话,官方这里也有特别说明。
要注意的是这里用的
OpenInstallUnity3DCallBack defaultManager
我当时就没有注意这点,根据官方文档接入了,最后一直获取不到启动参数。注意不是下面的
OpenInstallSDK continueUserActivity
【H5落地页】
OpenInstall很好的一点是傻瓜式接入。甚至连H5落地页都写好了。我把自己的落地页放进来,供学习交流。
安卓与IOS都接入完毕后,打开落地页,在安卓平台下就会打开/下载游戏,IOS打开/App Store跳转 游戏。
启动参数可以从官方SDK的getWakeupFinish中获取。
public void getWakeupFinish(OpenInstallData wakeupData)
{
Debug.Log("OpenInstallSample getWakeupFinish : 渠道编号=" + wakeupData.channelCode + ", 自定义数据=" + wakeupData.bindData);
//wakeupResult.text = "拉起参数:" + JsonUtility.ToJson(wakeupData);
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta http-equiv="X-UA-Compatible" content="chrome=1,IE=edge">
<title>一起来学习</title>
<style>
* {
margin: 0;
padding: 0;
}
</style>
</head>
<!-- 以下为openinstall集成代码,建议在html文档中尽量靠前放置,加快初始化过程 -->
<!-- 强烈建议直接引用下面的cdn加速链接,以得到最及时的更新,我们将持续跟踪各种主流浏览器的变化,提供最好的服务;不推荐将此js文件下载到自己的服务器-->
<script type="text/javascript" charset="UTF-8" src="https://web.cdn.openinstall.io/openinstall.js"></script>
<body>
<div id=full>
<!-- <img src='./share.png'> -->
<button type="button" onclick="DownloadGame()" name="downloadButton" id=download></button>
</div>
<style type="text/css">
#full {
width: 100%;
/* height: 2200px; */
height: 100%;
position: absolute;
margin: 0px;
padding: 0px;
background: url("./share.png") center center no-repeat;
background-size: cover;
}
</style>
<style type="text/css">
#download {
width: 500px;
height: 250px;
text-align: center;
position: absolute;
left: 50%;
top: 50%;
/* top: 300px; */
/* margin: 50% 50% 50% 50% ; */
transform: translate(-50%, -50%);
-webkit-transform: translate(-50%, -50%);
background-image: url('./download.png');
background-repeat: no-repeat;
background-size: 500px 250px;
background-color: transparent;
border: 0px;
}
</style>
</body>
<script type="text/javascript">
var DownloadGame = function () {
console.log("尚未加载成功");
};
//OpenInstall初始化时将与openinstall服务器交互,应尽可能早的调用
/*web页面向app传递的json数据(json string/js Object),应用被拉起或是首次安装时,通过相应的android/ios api可以获取此数据*/
var data = OpenInstall.parseUrlParams();///openinstall.js中提供的工具函数,解析url中的所有查询参数
console.log(data);
new OpenInstall({
/*appKey必选参数,openinstall平台为每个应用分配的ID*/
appKey: "idididid",
/*直接指定渠道编号,默认通过当前页url中的channelCode参数自动检测渠道编号*/
channelCode: "LaXin",
/*邀请码*/
bindData: data.bindData,
/*自定义遮罩的html*/
// mask: function () {
// return "<div id='_shadow' style='position:fixed;left:0;top:0;background:rgba(0,255,0,0.5);filter:alpha(opacity=50);width:100%;height:100%;z-index:10000;'></div>"
// },
/*OpenInstall初始化完成的回调函数,可选*/
onready: function () {
/*在app已安装的情况尝试拉起app*/
this.schemeWakeup();
/*用户点击某个按钮时(假定按钮id为downloadButton),安装app*/
var m = this;
DownloadGame = function () {
console.log("clicked");
// m.wakeupOrInstall();
/*跳过scheme拉起,直接安装*/
m.install();
return false;
}
// m.wakeupOrInstall();
}
}, data);
</script>
<script type="text/javascript" charset="UTF-8"
src="https://web.openinstall.io/web/banner.js?id=111111111111111"></script>
</html>
结束
欢迎各位同学一起交流~