Unity+ArkTS:自定义“好友排行榜“原子化组件
·
引言
在现代游戏开发中,社交功能已成为提升用户留存和活跃度的重要手段。好友排行榜作为核心社交功能之一,其实现方式和用户体验直接影响玩家互动质量。本文将介绍如何结合Unity游戏引擎和ArkTS(鸿蒙系统的声明式开发语言)创建高性能、可复用的"好友排行榜"原子化组件,实现跨平台一致的用户体验。
技术选型优势
Unity+ArkTS组合方案的优势:
- 性能优化:ArkTS的声明式UI渲染效率高
- 跨平台能力:一套代码适配手机、平板等多设备
- 开发效率:原子化组件可复用,减少重复开发
- 生态融合:无缝接入鸿蒙系统社交能力
排行榜数据结构设计
首先定义排行榜数据模型:
// models/RankingData.ets
export class FriendRankItem {
uid: string = '';
nickname: string = '';
avatar: string = '';
score: number = 0;
rank: number = 0;
isOnline: boolean = false;
// 用于增量更新检查
isDataChanged(newData: FriendRankItem): boolean {
return this.score !== newData.score ||
this.isOnline !== newData.isOnline;
}
}
export class RankingListData {
version: number = 0; // 数据版本号
selfRank: FriendRankItem = new FriendRankItem();
friends: FriendRankItem[] = [];
// 更新数据并返回是否有变化
updateWith(newData: RankingListData): boolean {
let changed = false;
if (this.selfRank.isDataChanged(newData.selfRank)) {
this.selfRank = {...newData.selfRank};
changed = true;
}
if (this.friends.length !== newData.friends.length) {
this.friends = [...newData.friends];
return true;
}
for (let i = 0; i < this.friends.length; i++) {
if (this.friends[i].isDataChanged(newData.friends[i])) {
this.friends[i] = {...newData.friends[i]};
changed = true;
}
}
return changed;
}
}
ArkTS原子化组件实现
1. 单项排行榜组件
// components/FriendRankItemView.ets
@Component
export struct FriendRankItemView {
@Prop item: FriendRankItem
@Link isExpanded: boolean
build() {
Row() {
// 排名显示
Text(this.item.rank.toString())
.fontSize(16)
.fontWeight(FontWeight.Bold)
.width(40)
.textAlign(TextAlign.Center)
// 头像
Image(this.item.avatar)
.width(40)
.height(40)
.borderRadius(20)
.objectFit(ImageFit.Contain)
Column() {
// 昵称和分数
Row() {
Text(this.item.nickname)
.fontSize(16)
.maxLines(1)
.textOverflow({overflow: TextOverflow.Ellipsis})
if (this.isExpanded) {
Text(`分数: ${this.item.score}`)
.fontSize(14)
.margin({left: 10})
.opacity(0.8)
}
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
// 在线状态
if (this.item.isOnline) {
Text('在线')
.fontSize(12)
.fontColor('#07C160')
}
}
.layoutWeight(1)
.margin({left: 10})
// 展开/收起图标
Image($r('app.media.arrow'))
.width(20)
.height(20)
.rotate(this.isExpanded ? 90 : 0)
.animation({duration: 200})
}
.padding(10)
.borderRadius(8)
.backgroundColor('#FFFFFF')
.shadow({radius: 2, color: '#10000000', offsetX: 1, offsetY: 1})
.onClick(() => {
this.isExpanded = !this.isExpanded;
})
}
}
2. 完整排行榜组件
// components/FriendRankingList.ets
@Component
export struct FriendRankingList {
@State rankingData: RankingListData = new RankingListData();
@State isExpanded: boolean = false;
@State isLoading: boolean = false;
// 从Unity接收数据更新
@Watch('onDataUpdate')
@Consume('unityData') @Prop unityData?: string;
// 定时刷新
private timer: number = 0;
onDataUpdate() {
if (this.unityData) {
try {
const newData = JSON.parse(this.unityData) as RankingListData;
if (this.rankingData.updateWith(newData)) {
this.rankingData = {...this.rankingData};
}
} catch (e) {
console.error('Parse ranking data error:', e);
}
}
}
onPageShow() {
this.startAutoRefresh();
this.loadData();
}
onPageHide() {
this.stopAutoRefresh();
}
startAutoRefresh() {
this.timer = setInterval(() => {
this.loadData();
}, 30000); // 30秒刷新一次
}
stopAutoRefresh() {
if (this.timer) {
clearInterval(this.timer);
this.timer = 0;
}
}
async loadData() {
if (this.isLoading) return;
this.isLoading = true;
try {
// 调用Unity接口获取数据
const result = await callUnity('GetFriendRanking');
const newData = JSON.parse(result) as RankingListData;
this.rankingData.updateWith(newData);
} finally {
this.isLoading = false;
}
}
build() {
Column() {
// 标题栏
Row() {
Text('好友排行榜')
.fontSize(20)
.fontWeight(FontWeight.Bold)
Button(this.isLoading ? '加载中...' : '刷新')
.fontSize(14)
.margin({left: 20})
.onClick(() => this.loadData())
}
.width('100%')
.padding(15)
// 自己的排名
FriendRankItemView({
item: this.rankingData.selfRank,
isExpanded: this.isExpanded
})
.backgroundColor('#F0F9FF')
// 好友列表
List({ space: 10 }) {
ForEach(this.rankingData.friends, (item: FriendRankItem) => {
ListItem() {
FriendRankItemView({
item: item,
isExpanded: this.isExpanded
})
}
}, (item: FriendRankItem) => item.uid)
}
.layoutWeight(1)
.divider({strokeWidth: 1, color: '#F0F0F0'})
}
.width('100%')
.height('100%')
.backgroundColor('#F8F8F8')
.padding(10)
}
}
Unity端集成方案
1. 数据桥接接口
// UnityRankingBridge.cs
using UnityEngine;
using System;
using System.Runtime.InteropServices;
public class UnityRankingBridge : MonoBehaviour
{
// 单例模式
public static UnityRankingBridge Instance { get; private set; }
// ArkTS调用的回调
public delegate string GetRankingDataDelegate();
public static event GetRankingDataDelegate OnGetRankingData;
private void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
RegisterUnityCallbacks();
}
else
{
Destroy(gameObject);
}
}
[DllImport("__Internal")]
private static extern void RegisterUnityCallbacks();
// 供ArkTS调用的方法
[AOT.MonoPInvokeCallback(typeof(GetRankingDataDelegate))]
public static string GetFriendRanking()
{
try
{
var data = new RankingListData()
{
version = 1,
selfRank = new FriendRankItem()
{
uid = "user_123",
nickname = PlayerPrefs.GetString("nickname", "我"),
avatar = "default_avatar",
score = PlayerPrefs.GetInt("high_score", 0),
rank = 5,
isOnline = true
},
friends = GetMockFriendsData()
};
return JsonUtility.ToJson(data);
}
catch (Exception e)
{
Debug.LogError($"GetFriendRanking error: {e}");
return "{}";
}
}
private static FriendRankItem[] GetMockFriendsData()
{
// 模拟数据,实际项目从服务器获取
return new FriendRankItem[]
{
new FriendRankItem { uid="1", nickname="玩家A", score=4500, rank=1, isOnline=true },
new FriendRankItem { uid="2", nickname="玩家B", score=4200, rank=2, isOnline=false },
new FriendRankItem { uid="3", nickname="玩家C", score=3800, rank=3, isOnline=true },
new FriendRankItem { uid="4", nickname="玩家D", score=3600, rank=4, isOnline=true },
new FriendRankItem { uid="6", nickname="玩家F", score=3200, rank=6, isOnline=false },
new FriendRankItem { uid="7", nickname="玩家G", score=3000, rank=7, isOnline=true },
new FriendRankItem { uid="8", nickname="玩家H", score=2800, rank=8, isOnline=false },
new FriendRankItem { uid="9", nickname="玩家I", score=2500, rank=9, isOnline=true },
new FriendRankItem { uid="10", nickname="玩家J", score=2000, rank=10, isOnline=false }
};
}
// 更新ArkTS端数据
public void UpdateArkTSRanking(string jsonData)
{
#if UNITY_ANDROID && !UNITY_EDITOR
UpdateArkTSRankingData(jsonData);
#endif
}
[DllImport("__Internal")]
private static extern void UpdateArkTSRankingData(string jsonData);
}
// 数据结构需与ArkTS端保持一致
[Serializable]
public class FriendRankItem
{
public string uid;
public string nickname;
public string avatar;
public int score;
public int rank;
public bool isOnline;
}
[Serializable]
public class RankingListData
{
public int version;
public FriendRankItem selfRank;
public FriendRankItem[] friends;
}
2. Unity中的排行榜控制器
// RankingController.cs
using UnityEngine;
using System.Collections;
public class RankingController : MonoBehaviour
{
[SerializeField] private float refreshInterval = 30f;
private Coroutine refreshCoroutine;
private void OnEnable()
{
StartAutoRefresh();
}
private void OnDisable()
{
StopAutoRefresh();
}
private void StartAutoRefresh()
{
if (refreshCoroutine != null)
StopCoroutine(refreshCoroutine);
refreshCoroutine = StartCoroutine(RefreshRoutine());
}
private void StopAutoRefresh()
{
if (refreshCoroutine != null)
{
StopCoroutine(refreshCoroutine);
refreshCoroutine = null;
}
}
private IEnumerator RefreshRoutine()
{
while (true)
{
yield return RefreshRankingData();
yield return new WaitForSeconds(refreshInterval);
}
}
public IEnumerator RefreshRankingData()
{
// 实际项目中这里应该从服务器获取数据
yield return new WaitForSeconds(1f); // 模拟网络延迟
// 构造测试数据
var newData = new RankingListData()
{
version = Time.frameCount,
selfRank = new FriendRankItem()
{
uid = "user_123",
nickname = PlayerPrefs.GetString("nickname", "我"),
avatar = "default_avatar",
score = Random.Range(3000, 5000), // 随机分数用于测试
rank = Random.Range(1, 10),
isOnline = true
},
friends = UnityRankingBridge.GetMockFriendsData()
};
// 更新ArkTS端
UnityRankingBridge.Instance.UpdateArkTSRanking(
JsonUtility.ToJson(newData));
}
}
多设备适配方案
1. 响应式布局
// components/FriendRankingList.ets
@Component
export struct FriendRankingList {
@State isLandscape: boolean = false;
onWindowSizeChange() {
this.isLandscape = window.innerWidth > window.innerHeight;
}
build() {
Column() {
// 标题栏适配
Row() {
Text('好友排行榜')
.fontSize(this.isLandscape ? 24 : 20)
Button(this.isLoading ? '加载中...' : '刷新')
.fontSize(this.isLandscape ? 16 : 14)
.margin({left: this.isLandscape ? 30 : 20})
}
.padding(this.isLandscape ? 20 : 15)
// 列表布局适配
if (this.isLandscape) {
this.landscapeLayout()
} else {
this.portraitLayout()
}
}
.onAreaChange(() => this.onWindowSizeChange())
}
@Builder landscapeLayout() {
Row() {
// 左侧-玩家信息
Column() {
FriendRankItemView({
item: this.rankingData.selfRank,
isExpanded: true // 横屏下始终展开
})
// 其他功能区域...
}
.width('40%')
// 右侧-排行榜
Column() {
List({ space: 10 }) {
ForEach(this.rankingData.friends, (item: FriendRankItem) => {
ListItem() {
FriendRankItemView({
item: item,
isExpanded: true // 横屏下始终展开
})
}
})
}
}
.width('60%')
}
}
@Builder portraitLayout() {
// 竖屏布局(原实现)
Column() {
FriendRankItemView({
item: this.rankingData.selfRank,
isExpanded: this.isExpanded
})
List({ space: 10 }) {
ForEach(this.rankingData.friends, (item: FriendRankItem) => {
ListItem() {
FriendRankItemView({
item: item,
isExpanded: this.isExpanded
})
}
})
}
}
}
}
2. 动态样式调整
// 在FriendRankItemView.ets中添加
@Styles function rankTextStyle() {
.fontSize(DeviceInfo.isTablet ? 20 : 16)
.width(DeviceInfo.isTablet ? 60 : 40)
}
@Styles function avatarStyle() {
.width(DeviceInfo.isTablet ? 50 : 40)
.height(DeviceInfo.isTablet ? 50 : 40)
.borderRadius(DeviceInfo.isTablet ? 25 : 20)
}
// 使用样式
Text(this.item.rank.toString())
.rankTextStyle()
Image(this.item.avatar)
.avatarStyle()
性能优化策略
1. 数据差异更新
// 在FriendRankingList.ets中修改
onDataUpdate() {
if (this.unityData) {
try {
const newData = JSON.parse(this.unityData) as RankingListData;
// 只更新变化的数据项
if (this.rankingData.version !== newData.version) {
const changedItems: FriendRankItem[] = [];
// 检查自身数据
if (this.rankingData.selfRank.isDataChanged(newData.selfRank)) {
changedItems.push(newData.selfRank);
}
// 检查好友列表
for (let i = 0; i < newData.friends.length; i++) {
if (i >= this.rankingData.friends.length ||
this.rankingData.friends[i].isDataChanged(newData.friends[i])) {
changedItems.push(newData.friends[i]);
}
}
// 应用更新
if (changedItems.length > 0) {
this.rankingData = {...newData};
this.playUpdateAnimation(changedItems);
}
}
} catch (e) {
console.error('Parse ranking data error:', e);
}
}
}
// 播放更新动画
private playUpdateAnimation(changedItems: FriendRankItem[]) {
changedItems.forEach(item => {
const itemId = `rank_item_${item.uid}`;
const animator = this.animators[itemId];
if (animator) {
animator.stop();
animator.play({
duration: 300,
iterations: 1,
easing: 'ease-out'
});
}
});
}
2. 列表项复用优化
// 修改List配置
List({ space: 10 }) {
ForEach(this.rankingData.friends, (item: FriendRankItem) => {
ListItem() {
FriendRankItemView({
item: item,
isExpanded: this.isExpanded
})
.transition({ type: TransitionType.InsertDelete })
}
}, (item: FriendRankItem) => item.uid)
}
.scrollBar(BarState.Off)
.cachedCount(5) // 预缓存项数
.edgeEffect(EdgeEffect.None)
完整使用示例
// entry.ets
import { FriendRankingList } from './components/FriendRankingList';
@Entry
@Component
struct GameSocial {
build() {
Column() {
// 游戏顶部导航
GameHeader()
// 好友排行榜组件
FriendRankingList()
.layoutWeight(1)
// 游戏底部导航
GameFooter()
}
.width('100%')
.height('100%')
.backgroundColor('#F0F0F0')
}
}
// 模拟游戏其他组件
@Component
struct GameHeader {
build() {
Row() {
Text('我的游戏')
.fontSize(20)
// 其他头部元素...
}
.padding(10)
.backgroundColor('#FFFFFF')
}
}
@Component
struct GameFooter {
build() {
Row() {
Button('首页').layoutWeight(1)
Button('社交').layoutWeight(1)
Button('设置').layoutWeight(1)
}
.height(50)
.backgroundColor('#FFFFFF')
}
}
总结
通过Unity与ArkTS的结合,我们实现了:
- 高性能好友排行榜:利用ArkTS声明式UI的高效渲染
- 真正的跨平台体验:适配手机、平板等多种设备
- 数据实时同步:Unity与ArkTS的高效数据通信
- 可复用的原子化组件:组件可在不同场景重复使用
- 丰富的交互效果:平滑的动画和过渡效果
这种集成方案充分发挥了Unity的游戏引擎能力和ArkTS的UI优势,为游戏社交功能开发提供了新的技术路径。开发者可以根据实际需求扩展更多功能,如礼物系统、成就展示等,构建更完整的游戏社交生态。
更多推荐


所有评论(0)