TapTap单机小游戏《TouchStar》的联网功能扩展

TapTap链接:https://www.taptap.cn/app/190392

游戏介绍

《TouchStar》于2022年上架TapTap游戏中心,如今已有4万+的下载量。游戏是一款轻量偏休闲向的单机手游,玩法简单,没有复杂的操作系统,无广告无氪金。游戏的主界面非常简单,只有模式选择和开始按钮。
image
游戏有三种模式:“持久模式”、”爆发模式“、”动点模式“,在持久模式中,玩家需要在60秒内以尽可能快的速度点击屏幕上的白色方块,60秒时间结束后将无法继续点击,游戏将实时反馈玩家的点击次数和平均点击速率。

image
(注:此处为游戏bug,实际应为持久模式)
在爆发模式中,该时间被缩短为15秒。

image
而动点模式是另一种玩法,在主界面选择动点模式并开始游戏后,屏幕上会随机出现绿色的圆点,同一时刻最多出现三个,在玩家点击圆点后此处的圆点会立刻消失,并随机在另一个位置出现新的圆点,玩家需要在60秒内点击尽可能多的圆点。游戏会实时反馈玩家的平均点击速率。
image
游戏玩法简单而不失趣味性,玩法相当完善,也逐渐形成了自己的玩家生态,在TapTap上获得了玩家的高度评价(评分9.2)。如今TapTap论坛上超过万人参加讨论,在论坛中纷纷展晒出自己的手速,表达对游戏的喜欢以及给开发者的反馈意见。

联网功能扩展

在TapTap玩家论坛中,玩家们晒出自己的最终成绩,使作为一款单机游戏的《TouchStar》拥有了一定的竞技性质,玩家间得分的相互对比,让我萌生了为该游戏内添加在线排行榜的想法,于是我联系了游戏开发者,并得到了游戏的源代码用作学习和二次开发,由于此项目并不开源,因而无法在此放出源码,敬请谅解。
游戏为Unity工程,代码封装完美,具有良好的扩展性。由于游戏单机玩法已经相当完善,因此我没有在玩法上做过多改动。在游戏本身的基础上,我为游戏创建了账户系统,并接入TapTap登录,调用TapTap账号的用户名等信息完成在线排行榜的搭建。而对于不想联网的玩家,我也为其实现了游客登录功能,不需要网络也能进入游戏并正常游玩,但对于游客登录的玩家,其得分不能上传至在线排行榜,并且无法获取到在线排行榜数据。
代码中有些信息不便展示,敬请谅解。
添加代码如下:

账户系统类:

public class AccountManager : MonoBehaviour
{
public AccountManager instance;
private static SUser user; //存储当前用户信息

public class SUser //用户信息类
{
public TDSUser tdsUser;
public string nickname;
public bool isOnline;
public SUser(TDSUser tdsUser, string nickname, bool isOnline)
{
this.tdsUser = tdsUser;
this.nickname = nickname;
this.isOnline = isOnline;
}
}

private void Awake()
{
//初始化SDK
var config = new TapConfig.Builder()
.ClientID("...")
.ClientToken("...")
.ServerURL("...")
.RegionType(RegionType.CN)
.ConfigBuilder();
TapBootstrap.Init(config);

//初始化单例
if(instance == null)
{
instance = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
}

// Start is called before the first frame update
void Start()
{
//用户信息初始化
user = new SUser(null, null, false);
}

//获取用户信息接口
public static SUser GetUser()
{
return user;
}

region Login

//TapTap登录接口
public static async Task Login_WithTaptap()
{
var currentUser = await TDSUser.GetCurrent();
if(currentUser == null)
{
try
{
var tdsUser = await TDSUser.LoginWithTapTap();
user.tdsUser = tdsUser;
user.nickname = tdsUser["nickname"].ToString();
user.isOnline = true;
}
catch { }
}
else
{
if(currentUser.IsAnonymous)
{
await TDSUser.Logout();
await Login_WithTaptap();
}
else
{
user.tdsUser = currentUser;
user.nickname = currentUser["nickname"].ToString();
user.isOnline = true;
}
}
}

//游客登录接口
public static async Task Login_WithGuest()
{
var currentUser = await TDSUser.GetCurrent();
if (currentUser == null)
{
try
{
var tdsUser = await TDSUser.LoginAnonymously();
user.tdsUser = tdsUser;
user.nickname = "游客用户";
user.isOnline = false;
}
catch { }
}
else
{
await TDSUser.Logout();
await Login_WithGuest(); ;
}
}

endregion

}

排行榜系统类:

public class LeaderboardManager : MonoBehaviour
{
public static LeaderboardManager leaderboardManager;
private static List leaderboard; //本地存储排行榜数据

public class UserScore //用户得分数据类
{
public string name;
public int score;
public UserScore(string name, int score)
{
this.name = name;
this.score = score;
}
}
public List GetLeaderboard() //获取排行榜接口
{
return leaderboard;
}

//用于向服务端发送用户得分信息
public const string url = "...";
public const string privateCode = "...";
public const string publicCode = "...";

private void Awake()
{
//初始化单例
if(leaderboardManager == null)
{
leaderboardManager = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
}

void Start()
{
//本地排行榜初始化
leaderboard = new List();
}

//向服务端发送分数数据
public static IEnumerator UplodeScore(int score)
{
string name = AccountManager.GetUser().nickname;
UnityWebRequest request = new UnityWebRequest(url + privateCode + "/add/" + UnityWebRequest.EscapeURL(name) + "/" + score);
yield return request.SendWebRequest();
}

//从服务端下载xml数据并更新本地排行榜
[System.Obsolete]
public static IEnumerator UpdateLeaderboard()
{
leaderboard.Clear();
UnityWebRequest request = UnityWebRequest.Get(url + publicCode + "/xml");
yield return request.SendWebRequest();
string str = request.downloadHandler.text;
//Debug.Log(request.downloadHandler.text);
XmlDocument xml = new XmlDocument();
xml.LoadXml(str);
XmlNodeList xmlNodeList = xml.SelectSingleNode("dreamlo").ChildNodes;
foreach(XmlNode xmlNode in xmlNodeList)
{
if(xmlNode.Name == "entry")
{
string name = "";
int score = -1;
foreach(XmlNode xmlData in xmlNode.ChildNodes)
{
if(xmlData.Name == "name")
{
name = xmlData.InnerText;
Debug.Log(name);
}
if(xmlData.Name == "score")
{
score = int.Parse(xmlData.InnerText);
}
}
leaderboard.Add(new UserScore(name, score));
}
}
}
}

底层代码完成后,我为上述联网功能添加了尽可能符合原作风格的UI界面,并对UI进行了轻度修改,使其看起来更加符合人的直觉。
登录界面:
image
主界面:
image
UI界面相关代码如下:

按钮管理:

public class LoginController : MonoBehaviour
{
public Button taptap;
public Button guest;
public Button play;

public async void TaptapLogin()
{
await AccountManager.Login_WithTaptap();
if(AccountManager.GetUser().tdsUser != null)
{
taptap.gameObject.SetActive(false);
guest.gameObject.SetActive(false);
play.gameObject.SetActive(true);
}
}
public async void GuestLogin()
{
await AccountManager.Login_WithGuest();
taptap.gameObject.SetActive(false);
guest.gameObject.SetActive(false);
play.gameObject.SetActive(true);
}
public void EnterGame()
{
SceneManager.LoadScene("Home");
}
}

排行榜效果:
(图中数据皆为测试数据)
持久模式:
image
动点模式:
image
顺便修复了一下原作持久模式游戏下模式显示为爆发模式的bug:
image

经多次运行调试后,代码并无重大bug,可以正常运行
本二次开发代码量较小,所作大部分修改都在Unity Editor中完成,因版权原因恕不能在此展示,再次表示抱歉。

总结

本次开发中我遇到了很多以前没有遇到过的问题,其中最大的问题便是Unity的版本不一致导致的各种兼容性bug,排查这类bug花费了我大量的时间,并无数次请求原作者的帮助,好在最终基本解决了这些问题。其次,在调试的过程中,我遇到过一些由于Unity或第三方包本身的问题导致的报错,这类问题基本很难解决甚至无法解决,只能临时更换方案。事实上,在我最开始的设想中,服务端获取数据时采用json格式来传输,但由于字符编码问题一直无法运行,我搜索了无数方法,最终没有解决,不得已才更换了我并不熟悉的xml格式来传输数据。在调试过程中,我学到了很多关于兼容性和异性屏适配问题的解决思路,这会对我未来的学习产生莫大的帮助。在此特别感谢游戏原作者提供的项目工程文件,感谢原作者的支持。

posted @ 2024-03-06 00:35  xoxkom  阅读(253)  评论(0)    收藏  举报