1、侧边栏复访功能「必接」:
详情见:「必接」侧边栏复访能力技术指南_小游戏_抖音开放平台
小游戏主页面设置按钮 ‘入口有礼’ btnSidebar 、‘入口有礼对话框’ ndSidebar 、‘进入侧边栏按钮’ btnGotoSidebar ,和‘领取奖励按钮’ btnGetAward
private isFromSidebar = false //状态,表示是否从侧边栏进入
@property({ type: cc.Button })
public btnSidebar: cc.Button | null = null; //入口有礼按钮
@property({ type: cc.Node })
public ndSidebar: cc.Node | null = null; //入口有礼对话框
@property({ type: cc.Button })
public btnGotoSidebar: cc.Button | null = null; //去侧边栏按钮
@property({ type: cc.Button })
public btnGetAward: cc.Button | null = null; //领取奖励按钮
start(){
if(!window.tt){
return;
}
tt.onShow((res) => {
//判断用户是否从侧边栏进来
this.isFromSidebar = (res.launch_from == 'homepage' && res.location == 'sidebar_card')
if(this.isFromSidebar){
//从侧边栏进来,显示‘领取奖励按钮’,隐藏‘去侧边栏按钮’
this.btnGetAward.node.active = true
this.btnGotoSidebar.node.active = false
}
else{
//否则反之
this.btnGetAward.node.active = false
this.btnGotoSidebar.node.active = true
}
});
//判断用户是否支持侧边栏进入功能,不支持就把‘入口有礼’隐藏
tt.checkScene({
scene: "sidebar",
success: (res) => {
let curtime = new Date().getTime();
if(StringUtil.getTimeFormat(curtime,"YY.MM.DD") != PlayerMgr.getLoginDate()){
this.btnSidebar.node.active = true;
}
},
fail: (res) => {
this.btnSidebar.node.active = false
}
});
}
//以下方法分别绑定对应按钮的事件:
//点击‘入口有礼按钮’,显示‘入口有礼对话框’
onbtnOpenSideBarDialog(){
this.ndSidebar.active = true
}
//关闭‘入口有礼对话框’
onbtnCloseSideBarDialog(){
this.ndSidebar.active = false
}
//‘去侧边栏按钮’逻辑
onbtnGotoSidebarClick(){
tt.navigateToScene({
scene: "sidebar",
success: (res) => {
console.log("navigate to scene success!");
// 跳转成功回调逻辑
},
fail: (res) => {
console.log("navigate to scene fail: ", res);
this.showWarning('跳转失败,请稍后再试!')
// 跳转失败回调逻辑
},
});
}
//‘领取奖励按钮’的逻辑
onbtnGetAwardClick(){
//获取奖励的逻辑,自己写
console.log("getAward!");
let curtime = new Date().getTime();
if(StringUtil.getTimeFormat(curtime,"YY.MM.DD") != PlayerMgr.getLoginDate()){
PlayerMgr.changeItem({1:1,2:1});
UIHelp.ShowGainItemTips({1:1,2:1});
PlayerMgr.updateLoginDate();
//领取奖励后‘入口有礼’按钮隐藏
this.btnSidebar.node.active = false;
}
}
2、登录,并获取用户信息:
api:tt.login 、tt.getUserInfo
详情见:
start(){
this.login()
}
//登录
login() {
let _this = this;
if(window.tt){
//是抖音环境:
tt.login({
force: true,
success(res) {
console.log(`login 调用成功${res.code} ${res.anonymousCode}`);
// 获取用户信息
tt.getUserInfo({
success(resUser) {
console.log(`getUserInfo 调用成功`, resUser.userInfo);
//编写调用成功逻辑...
//初始化用户信息
_this.getUserInfo(resUser.userInfo);
},
fail(resUser) {
console.log(`getUserInfo 调用失败`, resUser.errMsg);
//编写调用失败逻辑...
//初始化用户信息
let data = {nickName:'1111',avatarUrl:""}
_this.getUserInfo(data);
},
});
},
fail(res) {
console.log(`login 调用失败`);
},
});
}else{
//不是抖音环境,自己写一个测试用的data
let data = {nickName:'1111',avatarUrl:""}
_this.getUserInfo(data); //初始化用户信息
}
}
//初始化用户信息
getUserInfo(userData ?: any){
//自己写:
if(userData){
Define.isTest = false;
PlayerMgr.setUserPlayerInfo({
appid:"",
nickname:userData.nickName,
avatar:userData.avatarUrl
});
}
}
3、插屏广告接入:
api:tt.createInterstitialAd
详情见:tt.createInterstitialAd_小游戏_抖音开放平台
插屏广告注意事项,详情见:插屏广告注意事项_小游戏_抖音开放平台
对于插屏广告的展示,有一定的频率控制,具体如下:
1. 小游戏启动后的前30s(秒),不能展示插屏广告。
2. 已经展示一次插屏广告后,第二次展示需要距离上一次展示60s。
3. 展示过一次激励视频广告后,后续需要展示插屏广告的情况下,需要与激励视频广告的展示间隔60s。
发送播放插屏广告消息:
//判断上次播放插屏广告时间
if(this.info.onLineTime-this.info.interstitialTime >= 60){
this.info.interstitialTime = this.info.onLineTime;
// 获取当前场景的名称,只在主页放插屏广告,游戏中不放
let sceneName = cc.director.getScene().name;
console.log("当前场景名称:", sceneName);
if(sceneName === 'Home'){
cc.director.emit('playInterstitialAd'); //发送播放插屏广告信息
}
}
主页播放插屏广告:
onLoad () {
//别处发送的播放插屏广告信息,这里接收
if(window.tt){
cc.director.on('playInterstitialAd',this.playInterstitialAd,this);
}
}
playInterstitialAd(){
console.log('插屏广告准备中...');
const obj ={ adUnitId: "xxxx...你自己的广告id,不知道的话,在抖音开放平台->控制台->商业化->流量主->广告管理 上看怎么做" };
const interstitiaAd = tt.createInterstitialAd(obj);
let canReTry = true;
interstitiaAd.onLoad(onLoadHandle); //创建会自动load
function onLoadHandle() {
interstitiaAd.show().then(() => {
console.log("插屏广告展示成功");
});
}
interstitiaAd.onError(onErrorHandle); // 自动load 的失败会走到这里
function onErrorHandle(err) {
// 等待一定时间后,或者等待下次需要再展示,不能一直尝试。
if (canReTry) {
canReTry = false;
setTimeout(() => {
interstitiaAd.load(); // 重新尝试加载广告
}, 8000); // 8秒后重试,可以根据实际需求调整等待时间
} else {
tt.showToast({
title: "暂无广告",
icon: "none",
});
}
}
}
onDestroy(){
cc.director.off('playInterstitialAd',this.playInterstitialAd,this);
}
4、激励视频接入:
api:tt.createRewardedVideoAd
详情见:tt.createRewardedVideoAd_小游戏_抖音开放平台
export default class PlatFormMgr{
//激励视频实现:
static playVideo(num ?: number){
//todo:插屏广告等待播放的计时暂停...
//...
return new Promise<void>((resolve, reject) => {
if(window.tt){
cc.director.pause();
if(num){
let videoAd = tt.createRewardedVideoAd({
adUnitId: "xxxxxxx",
multiton: true,
multitionRewardMsg: ['更多奖励1','更多奖励2','更多奖励3'],
multitionRewardTimes: num,
});
videoAd.show().catch(() => {
// 失败重试
videoAd.load()
.then(() => videoAd.show())
.catch(err => {
console.log('激励视频 广告显示失败')
cc.director.resume();
//todo:插屏广告等待播放的计时恢复...
resolve();
})
})
videoAd.onClose(res => {
// 用户点击了【关闭广告】按钮
// 小于 2.1.0 的基础库版本,res 是一个 undefined
if (res && res.isEnded || res === undefined) {
// 正常播放结束,可以下发游戏奖励的逻辑
console.log(`watch ${res.count} video`);
cc.director.resume();
//todo:插屏广告等待播放的计时恢复...
resolve();
}
else {
// 播放中途退出,不下发游戏奖励
cc.director.resume();
//todo:插屏广告等待播放的计时恢复...
reject();
}
});
}else{
let videoAd = tt.createRewardedVideoAd({
adUnitId: "xxxxxxx",
});
videoAd.show().catch(() => {
// 失败重试
videoAd.load()
.then(() => videoAd.show())
.catch(err => {
console.log('激励视频 广告显示失败')
cc.director.resume();
//todo:插屏广告等待播放的计时恢复...
resolve();
})
})
videoAd.onClose(res => {
// 用户点击了【关闭广告】按钮
if (res && res.isEnded || res === undefined) {
// 正常播放结束,可以下发游戏奖励的逻辑
console.log(`watch ${res.count} video`);
cc.director.resume();
//todo:插屏广告等待播放的计时恢复...
resolve();
}
else {
// 播放中途退出,不下发游戏奖励
cc.director.resume();
//todo:插屏广告等待播放的计时恢复...
reject();
}
});
}
}else{
//todo:插屏广告等待播放的计时恢复...
resolve();
}
})
}
}
//示例:别处调用
export default class UIGetAwardPanel extends cc.Component {
//看广告双倍获得
onBtnGetDouble()
{
PlatFormMgr.playVideo().then(()=>{
//补充:奖励翻倍并获得逻辑...
})
}
}
5、分享给好友:
api:tt.shareAppMessage
详情见:tt.shareAppMessage_小游戏_抖音开放平台
//分享按钮
onShareTT(){
if(window.tt){
tt.shareAppMessage({
channel: "invite", // 拉起邀请面板分享游戏好友
title: "一起来吃瓜吧!",
desc: "快来加入我们!",
imageUrl: "",
query: "",
success(res) {
console.log("分享成功" + JSON.stringify(res));
},
fail(e) {
console.log("分享失败");
},
});
}
}
6、排行榜:
抖音排行榜方案:
api:tt.setImRankData、tt.getImRankList
详情见:tt.setImRankData_小游戏_抖音开放平台
static commitScore(score,callBack?:(data:any)=>void){
if(window.tt){
// 提交分数到抖音平台
console.log('tt.setImRankData')
tt.setImRankData({
dataType: 0, // 成绩为数字类型
value: score.toString(), // 将分数转化为字符串
priority: 0, // dataType为数字类型,不需要权重,直接传0
extra: 'extra', // 可以包含额外的用户信息
zoneId: 'test', // 分区ID,例如'test'
success(res) {
console.log(`setImRankData success res: ${res}`);
callBack&&callBack(res);
},
fail(res) {
console.log(`setImRankData fail res: ${res.errMsg}`);
//
},
});
}
}
static getRankInfo(gameType:e_GameType,rankType:e_RankType,callBack:(data:any)=>void){
if(window.tt){
//moba游戏自动生成段位榜
tt.getImRankList({
relationType: "default", //好友榜、总榜都展示
dataType: 1, //只圈选type为枚举类型的数据进行排序
rankType: "day", //每天凌晨0点更新,只对当天0点到现在写入的数据进行排序
suffix: "", //为空或不填,一般枚举类型不需要填后缀
rankTitle: "rankTitle", //标题
zoneId: 'default',
success(res) {
console.log(`getImRankData success res: ${res}`);
callBack&&callBack(res);
},
fail(res) {
console.log(`getImRankData fail res: ${res.errMsg}`);
},
});
}
}
自接服务器方案:
不推荐
const pushlishUrl = "xxxxxxxxxxxxxx";
const testUrl = "xxxxxxxxxxxxx"
/**
* Timer管理器
*/
export default class NetMgr{
static getUrl(){
return pushlishUrl;
}
static sendReq(data,callBack?:(data:any)=>void,failCallBack?:()=>void){
let url = this.getUrl();
let xhr = new XMLHttpRequest();
xhr.open("POST",url+data.postUrl);
xhr.setRequestHeader("Content-Type","text/plain;charset=UTF-8");
xhr.responseType="json";
xhr.onreadystatechange = function () {
if (xhr.readyState == 4){
if(xhr.status >= 200 && xhr.status <= 207) {
if(callBack){
callBack(xhr.response);
}
}
}
};
xhr.ontimeout = function(){
if(failCallBack){
failCallBack();
}
}
xhr.onerror = function(){
if(failCallBack){
failCallBack();
}
}
let appId = ""
if(data.gameType == 0){
appId = Define.gameId +"-normal"
}else{
appId = Define.gameId +"-compete"
}
data.appId = appId;
delete data.postUrl;
delete data.gameType;
xhr.send(JSON.stringify(data));
}
static getServerRankType(rankType){
if(rankType == 'today'{
return 1;
}else if(rankType == 'month'){
return 3;
}if(rankType == 'total'){
return 4;
}
}
static commitScore(score,callBack?:(data:any)=>void){
let userPlayerInfo = {};
NetMgr.sendReq({
postUrl:"/xxxx/",
score:score,
nickname:userPlayerInfo.nickname,
avatar:userPlayerInfo.avatar,
gameType:0
},(data)=>{
if(callBack){
callBack(data.data);
}
})
}
static getRankInfo(gameType:e_GameType,rankType:e_RankType,callBack:(data:any)=>void){
this.sendReq({
postUrl:"/xxxxx/",
type:this.getServerRankType(rankType),
gameType:gameType
},(data)=>{
if(callBack){
callBack(data.data);
}
})
}
}