TapTap連結:https://www.taptap.cn/app/190392
遊戲介紹
《TouchStar》於2022年上架TapTap遊戲中心,如今已有4萬+的下載量。遊戲是一款輕量偏休閒向的單機手遊,玩法簡單,沒有複雜的作業系統,無廣告無氪金。遊戲的主介面非常簡單,只有模式選擇和開始按鈕。
遊戲有三種模式:“持久模式”、”爆發模式“、”動點模式“,在持久模式中,玩家需要在60秒內以儘可能快的速度點選螢幕上的白色方塊,60秒時間結束後將無法繼續點選,遊戲將實時反饋玩家的點選次數和平均點選速率。
(注:此處為遊戲bug,實際應為持久模式)
在爆發模式中,該時間被縮短為15秒。
而動點模式是另一種玩法,在主介面選擇動點模式並開始遊戲後,螢幕上會隨機出現綠色的圓點,同一時刻最多出現三個,在玩家點選圓點後此處的圓點會立刻消失,並隨機在另一個位置出現新的圓點,玩家需要在60秒內點選儘可能多的圓點。遊戲會實時反饋玩家的平均點選速率。
遊戲玩法簡單而不失趣味性,玩法相當完善,也逐漸形成了自己的玩家生態,在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<UserScore> leaderboard; //本地儲存排行榜資料
public class UserScore //使用者得分資料類
{
public string name;
public int score;
public UserScore(string name, int score)
{
this.name = name;
this.score = score;
}
}
public List<UserScore> 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<UserScore>();
}
//向服務端傳送分數資料
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進行了輕度修改,使其看起來更加符合人的直覺。
登入介面:
主介面:
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");
}
}
排行榜效果:
(圖中資料皆為測試資料)
持久模式:
動點模式:
順便修復了一下原作持久模式遊戲下模式顯示為爆發模式的bug:
經多次執行除錯後,程式碼並無重大bug,可以正常執行
本二次開發程式碼量較小,所作大部分修改都在Unity Editor中完成,因版權原因恕不能在此展示,再次表示抱歉。
總結
本次開發中我遇到了很多以前沒有遇到過的問題,其中最大的問題便是Unity的版本不一致導致的各種相容性bug,排查這類bug花費了我大量的時間,並無數次請求原作者的幫助,好在最終基本解決了這些問題。其次,在除錯的過程中,我遇到過一些由於Unity或第三方包本身的問題導致的報錯,這類問題基本很難解決甚至無法解決,只能臨時更換方案。事實上,在我最開始的設想中,服務端獲取資料時採用json格式來傳輸,但由於字元編碼問題一直無法執行,我搜尋了無數方法,最終沒有解決,不得已才更換了我並不熟悉的xml格式來傳輸資料。在除錯過程中,我學到了很多關於相容性和異性屏適配問題的解決思路,這會對我未來的學習產生莫大的幫助。在此特別感謝遊戲原作者提供的專案工程檔案,感謝原作者的支援。