功能批量实现 优化 服务端 新浮动小游戏框架 新界面UI

This commit is contained in:
FloatGaming
2026-04-24 13:20:16 +08:00
parent e0bc0bbf08
commit 5eec879582
257 changed files with 38481 additions and 1288 deletions
@@ -0,0 +1,231 @@
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;
using GameServer.Client;
#if !(UNITY_STANDALONE_WIN || UNITY_STANDALONE_LINUX || UNITY_STANDALONE_OSX || STEAMWORKS_WIN || STEAMWORKS_LIN_OSX)
#define FRIEND_DISABLE_STEAMWORKS
#endif
#if !FRIEND_DISABLE_STEAMWORKS
using Steamworks;
#endif
public class friendCardPrefab : MonoBehaviour
{
[SerializeField] private Image profileBorder;
[SerializeField] private Image friendProfile;
[SerializeField] private Text friendName;
[SerializeField] private Text friendStatus;
[SerializeField] private Button friendDetails;
[SerializeField] private Button startPersonalConversation;
[SerializeField] private Toggle isStarFriend;
[SerializeField] private Image friendOffline_blackMask;
[Header("offline heibai")]
[SerializeField] private Material offline_material;
private Material _profileBorderMaterial;
private Material _friendProfileMaterial;
private Coroutine _avatarLoadRoutine;
private string _steamId;
private Action<string, bool> _starChanged;
private Action<string> _openDetails;
private Action<string> _openPrivateConversation;
private bool _bindingToggle;
private void Awake()
{
_profileBorderMaterial = profileBorder != null ? profileBorder.material : null;
_friendProfileMaterial = friendProfile != null ? friendProfile.material : null;
}
private void OnDestroy()
{
if (_avatarLoadRoutine != null)
{
StopCoroutine(_avatarLoadRoutine);
_avatarLoadRoutine = null;
}
}
public void Bind(FriendCardViewData data, Action<string, bool> onStarChanged, Action<string> onOpenDetails, Action<string> onOpenPrivateConversation)
{
_steamId = data != null ? data.SteamId : string.Empty;
_starChanged = onStarChanged;
_openDetails = onOpenDetails;
_openPrivateConversation = onOpenPrivateConversation;
if (friendName != null)
{
friendName.text = data != null ? data.DisplayName ?? string.Empty : string.Empty;
}
if (friendStatus != null)
{
friendStatus.text = data != null ? data.StatusText ?? string.Empty : string.Empty;
}
ApplyOnlineVisualState(data != null && data.IsOnline);
if (isStarFriend != null)
{
isStarFriend.onValueChanged.RemoveAllListeners();
_bindingToggle = true;
isStarFriend.isOn = data != null && data.IsStarred;
_bindingToggle = false;
isStarFriend.onValueChanged.AddListener(HandleStarToggleChanged);
}
if (friendDetails != null)
{
friendDetails.onClick.RemoveAllListeners();
friendDetails.onClick.AddListener(HandleOpenDetails);
}
if (startPersonalConversation != null)
{
startPersonalConversation.onClick.RemoveAllListeners();
startPersonalConversation.onClick.AddListener(HandleStartPrivateConversation);
}
if (_avatarLoadRoutine != null)
{
StopCoroutine(_avatarLoadRoutine);
}
_avatarLoadRoutine = StartCoroutine(LoadAvatarRoutine(data));
}
private void HandleStarToggleChanged(bool isOn)
{
if (_bindingToggle)
{
return;
}
_starChanged?.Invoke(_steamId, isOn);
}
private void HandleOpenDetails()
{
_openDetails?.Invoke(_steamId);
}
private void HandleStartPrivateConversation()
{
_openPrivateConversation?.Invoke(_steamId);
}
private void ApplyOnlineVisualState(bool isOnline)
{
if (friendOffline_blackMask != null)
{
friendOffline_blackMask.gameObject.SetActive(!isOnline);
}
Material borderMaterial = isOnline ? _profileBorderMaterial : offline_material;
Material profileMaterial = isOnline ? _friendProfileMaterial : offline_material;
if (profileBorder != null)
{
profileBorder.material = borderMaterial;
}
if (friendProfile != null)
{
friendProfile.material = profileMaterial;
}
}
private IEnumerator LoadAvatarRoutine(FriendCardViewData data)
{
if (friendProfile == null || data == null)
{
yield break;
}
string resolvedAvatarUrl = NetworkManager.ResolveAvatarUrl(data.AvatarUrl, data.SteamId);
bool loaded = false;
if (NetworkManager.IsUsableAvatarUrl(resolvedAvatarUrl)
&& !resolvedAvatarUrl.StartsWith(NetworkManager.SteamAvatarUrlPrefix, StringComparison.OrdinalIgnoreCase))
{
using (UnityWebRequest request = UnityWebRequestTexture.GetTexture(resolvedAvatarUrl))
{
yield return request.SendWebRequest();
if (request.result == UnityWebRequest.Result.Success)
{
Texture2D texture = DownloadHandlerTexture.GetContent(request);
if (texture != null)
{
friendProfile.sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f));
loaded = true;
}
}
}
}
if (!loaded)
{
yield return LoadSteamAvatarRoutine(data.SteamId);
}
_avatarLoadRoutine = null;
}
private IEnumerator LoadSteamAvatarRoutine(string steamId)
{
#if FRIEND_DISABLE_STEAMWORKS
yield break;
#else
if (!SteamManager.Initialized || string.IsNullOrWhiteSpace(steamId) || friendProfile == null || !ulong.TryParse(steamId, out ulong rawSteamId))
{
yield break;
}
var targetSteamId = new CSteamID(rawSteamId);
SteamFriends.RequestUserInformation(targetSteamId, false);
const float timeout = 5f;
float deadline = Time.realtimeSinceStartup + timeout;
while (Time.realtimeSinceStartup < deadline)
{
int imageId = SteamFriends.GetLargeFriendAvatar(targetSteamId);
if (imageId <= 0)
{
imageId = SteamFriends.GetMediumFriendAvatar(targetSteamId);
}
if (imageId > 0 && SteamUtils.GetImageSize(imageId, out uint width, out uint height) && width > 0 && height > 0)
{
byte[] buffer = new byte[width * height * 4];
if (SteamUtils.GetImageRGBA(imageId, buffer, buffer.Length))
{
friendProfile.sprite = CreateFlippedSteamAvatarSprite(buffer, (int)width, (int)height);
yield break;
}
}
yield return new WaitForSecondsRealtime(0.25f);
}
#endif
}
private static Sprite CreateFlippedSteamAvatarSprite(byte[] rgbaBuffer, int width, int height)
{
Texture2D texture = new Texture2D(width, height, TextureFormat.RGBA32, false);
texture.LoadRawTextureData(rgbaBuffer);
texture.Apply();
Texture2D flipped = new Texture2D(width, height, TextureFormat.RGBA32, false);
for (int y = 0; y < height; y++)
{
Color[] pixels = texture.GetPixels(0, y, width, 1);
flipped.SetPixels(0, height - 1 - y, width, 1, pixels);
}
flipped.Apply();
return Sprite.Create(flipped, new Rect(0, 0, width, height), new Vector2(0.5f, 0.5f));
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 281d6e71d4aa56440a980d4d95af38e4
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 4114343157163b04da9c9640daa57742
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,216 @@
using System;
using System.Collections;
using System.Threading.Tasks;
using GameServer.Client;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;
#if !(UNITY_STANDALONE_WIN || UNITY_STANDALONE_LINUX || UNITY_STANDALONE_OSX || STEAMWORKS_WIN || STEAMWORKS_LIN_OSX)
#define FRIEND_DISABLE_STEAMWORKS
#endif
#if !FRIEND_DISABLE_STEAMWORKS
using Steamworks;
#endif
public class friendRequestPrefab : MonoBehaviour
{
[SerializeField] private Image profileImage;
[SerializeField] private Text friendName;
[SerializeField] private Text friendSourceInfomation;
[SerializeField] private Button acceptButton;
[SerializeField] private Button rejectButton;
private Coroutine _avatarLoadRoutine;
private Coroutine _buttonCooldownRoutine;
private void OnDestroy()
{
if (_avatarLoadRoutine != null)
{
StopCoroutine(_avatarLoadRoutine);
_avatarLoadRoutine = null;
}
if (_buttonCooldownRoutine != null)
{
StopCoroutine(_buttonCooldownRoutine);
_buttonCooldownRoutine = null;
}
}
public void Bind(SocialFriendRequestEntry request, Func<Task> onAccept, Func<Task> onReject, float cooldownSeconds)
{
string steamId = request != null ? request.from_steam_id : string.Empty;
if (friendName != null)
{
friendName.text = request != null
? NetworkManager.ResolveDisplayName(request.from_name, steamId, false)
: string.Empty;
}
if (acceptButton != null)
{
acceptButton.onClick.RemoveAllListeners();
if (onAccept != null)
{
acceptButton.onClick.AddListener(() =>
{
if (_buttonCooldownRoutine != null)
{
return;
}
_ = HandleDecisionClickedAsync(onAccept, cooldownSeconds);
});
}
}
if (rejectButton != null)
{
rejectButton.onClick.RemoveAllListeners();
if (onReject != null)
{
rejectButton.onClick.AddListener(() =>
{
if (_buttonCooldownRoutine != null)
{
return;
}
_ = HandleDecisionClickedAsync(onReject, cooldownSeconds);
});
}
}
if (_avatarLoadRoutine != null)
{
StopCoroutine(_avatarLoadRoutine);
}
if (profileImage != null && request != null)
{
profileImage.sprite = null;
string avatarUrl = NetworkManager.ResolveAvatarUrl(request.from_avatar, steamId);
_avatarLoadRoutine = StartCoroutine(LoadAvatarRoutine(steamId, avatarUrl));
}
}
private async Task HandleDecisionClickedAsync(Func<Task> action, float cooldownSeconds)
{
SetDecisionButtonsInteractable(false);
_buttonCooldownRoutine = StartCoroutine(ButtonCooldownRoutine(cooldownSeconds));
await action();
}
private IEnumerator ButtonCooldownRoutine(float cooldownSeconds)
{
yield return new WaitForSecondsRealtime(cooldownSeconds);
SetDecisionButtonsInteractable(true);
_buttonCooldownRoutine = null;
}
private void SetDecisionButtonsInteractable(bool interactable)
{
if (acceptButton != null)
{
acceptButton.interactable = interactable;
}
if (rejectButton != null)
{
rejectButton.interactable = interactable;
}
}
private IEnumerator LoadAvatarRoutine(string steamId, string avatarUrl)
{
if (profileImage == null)
{
yield break;
}
bool loaded = false;
if (NetworkManager.IsUsableAvatarUrl(avatarUrl)
&& !avatarUrl.StartsWith(NetworkManager.SteamAvatarUrlPrefix, StringComparison.OrdinalIgnoreCase))
{
using (UnityWebRequest request = UnityWebRequestTexture.GetTexture(avatarUrl))
{
yield return request.SendWebRequest();
if (request.result == UnityWebRequest.Result.Success)
{
Texture2D texture = DownloadHandlerTexture.GetContent(request);
if (texture != null)
{
profileImage.sprite = Sprite.Create(
texture,
new Rect(0f, 0f, texture.width, texture.height),
new Vector2(0.5f, 0.5f));
loaded = true;
}
}
}
}
if (!loaded)
{
yield return LoadSteamAvatarRoutine(steamId);
}
_avatarLoadRoutine = null;
}
private IEnumerator LoadSteamAvatarRoutine(string steamId)
{
#if FRIEND_DISABLE_STEAMWORKS
yield break;
#else
if (!SteamManager.Initialized || string.IsNullOrWhiteSpace(steamId) || profileImage == null || !ulong.TryParse(steamId, out ulong rawSteamId))
{
yield break;
}
CSteamID targetSteamId = new CSteamID(rawSteamId);
SteamFriends.RequestUserInformation(targetSteamId, false);
float deadline = Time.realtimeSinceStartup + 5f;
while (Time.realtimeSinceStartup < deadline)
{
int imageId = SteamFriends.GetLargeFriendAvatar(targetSteamId);
if (imageId <= 0)
{
imageId = SteamFriends.GetMediumFriendAvatar(targetSteamId);
}
if (imageId > 0 && SteamUtils.GetImageSize(imageId, out uint width, out uint height) && width > 0 && height > 0)
{
byte[] buffer = new byte[width * height * 4];
if (SteamUtils.GetImageRGBA(imageId, buffer, buffer.Length))
{
profileImage.sprite = CreateFlippedSteamAvatarSprite(buffer, (int)width, (int)height);
yield break;
}
}
yield return new WaitForSecondsRealtime(0.25f);
}
#endif
}
private static Sprite CreateFlippedSteamAvatarSprite(byte[] rgbaBuffer, int width, int height)
{
Texture2D texture = new Texture2D(width, height, TextureFormat.RGBA32, false);
texture.LoadRawTextureData(rgbaBuffer);
texture.Apply();
Texture2D flipped = new Texture2D(width, height, TextureFormat.RGBA32, false);
for (int y = 0; y < height; y++)
{
Color[] pixels = texture.GetPixels(0, y, width, 1);
flipped.SetPixels(0, height - 1 - y, width, 1, pixels);
}
flipped.Apply();
return Sprite.Create(flipped, new Rect(0, 0, width, height), new Vector2(0.5f, 0.5f));
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 801af46c3c30ecc44ad9befbb892dc33
@@ -0,0 +1,769 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &167812896000688069
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 171703084932209816}
- component: {fileID: 7766852143305575412}
- component: {fileID: 2512442011294951853}
m_Layer: 0
m_Name: source
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &171703084932209816
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 167812896000688069}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1887113380917164164}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 30.39, y: -4}
m_SizeDelta: {x: 143.18, y: 12.573}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &7766852143305575412
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 167812896000688069}
m_CullTransparentMesh: 1
--- !u!114 &2512442011294951853
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 167812896000688069}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: bd4be44636ad6c24a9b117a7108a75e9, type: 3}
m_FontSize: 10
m_FontStyle: 0
m_BestFit: 0
m_MinSize: 1
m_MaxSize: 18
m_Alignment: 3
m_AlignByGeometry: 0
m_RichText: 1
m_HorizontalOverflow: 0
m_VerticalOverflow: 0
m_LineSpacing: 1
m_Text: "\u96C5\u8389\u68A6\u7483\u9AD8\u65B0\u4EA7\u4E1A\u56ED Steam2\u533A"
--- !u!1 &171027573892652408
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 5899234148902931492}
- component: {fileID: 752898224887842841}
- component: {fileID: 7879641519199484437}
- component: {fileID: 3532086592632966835}
m_Layer: 0
m_Name: acceptRequest
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &5899234148902931492
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 171027573892652408}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 2373904671374910531}
m_Father: {fileID: 1887113380917164164}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: -9.2046, y: -17.465}
m_SizeDelta: {x: 63.9929, y: 17.071}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &752898224887842841
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 171027573892652408}
m_CullTransparentMesh: 1
--- !u!114 &7879641519199484437
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 171027573892652408}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 0.4198113, g: 1, b: 0.5353115, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 21300000, guid: 7f3b1220317607e44bbb8f0e1998044a, type: 3}
m_Type: 1
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 5
--- !u!114 &3532086592632966835
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 171027573892652408}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Navigation:
m_Mode: 3
m_WrapAround: 0
m_SelectOnUp: {fileID: 0}
m_SelectOnDown: {fileID: 0}
m_SelectOnLeft: {fileID: 0}
m_SelectOnRight: {fileID: 0}
m_Transition: 1
m_Colors:
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
m_ColorMultiplier: 1
m_FadeDuration: 0.1
m_SpriteState:
m_HighlightedSprite: {fileID: 0}
m_PressedSprite: {fileID: 0}
m_SelectedSprite: {fileID: 0}
m_DisabledSprite: {fileID: 0}
m_AnimationTriggers:
m_NormalTrigger: Normal
m_HighlightedTrigger: Highlighted
m_PressedTrigger: Pressed
m_SelectedTrigger: Selected
m_DisabledTrigger: Disabled
m_Interactable: 1
m_TargetGraphic: {fileID: 7879641519199484437}
m_OnClick:
m_PersistentCalls:
m_Calls: []
--- !u!1 &696844997186230125
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1887113380917164164}
- component: {fileID: 8398379083156494532}
- component: {fileID: 885345931350300794}
m_Layer: 0
m_Name: btm
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &1887113380917164164
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 696844997186230125}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 3062756356687769231}
- {fileID: 3192042035776563592}
- {fileID: 171703084932209816}
- {fileID: 5899234148902931492}
- {fileID: 9058676790248205063}
m_Father: {fileID: 7715568052211693546}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 220, y: 70}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &8398379083156494532
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 696844997186230125}
m_CullTransparentMesh: 1
--- !u!114 &885345931350300794
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 696844997186230125}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 21300000, guid: 537c5cf3bf53c2f4a9404248acd2d336, type: 3}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!1 &1234883103932309115
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 3062756356687769231}
- component: {fileID: 7577265852821514236}
- component: {fileID: 322438256967861300}
m_Layer: 0
m_Name: profile
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &3062756356687769231
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1234883103932309115}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1887113380917164164}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: -74.39, y: 1.5}
m_SizeDelta: {x: 55, y: 55}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &7577265852821514236
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1234883103932309115}
m_CullTransparentMesh: 1
--- !u!114 &322438256967861300
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1234883103932309115}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 0}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!1 &1752572000902273007
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 8230912162764402355}
- component: {fileID: 4345137299206345491}
- component: {fileID: 107565700018763461}
m_Layer: 0
m_Name: Text (Legacy)
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &8230912162764402355
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1752572000902273007}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 9058676790248205063}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &4345137299206345491
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1752572000902273007}
m_CullTransparentMesh: 1
--- !u!114 &107565700018763461
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1752572000902273007}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: bd4be44636ad6c24a9b117a7108a75e9, type: 3}
m_FontSize: 10
m_FontStyle: 0
m_BestFit: 0
m_MinSize: 1
m_MaxSize: 40
m_Alignment: 4
m_AlignByGeometry: 0
m_RichText: 1
m_HorizontalOverflow: 0
m_VerticalOverflow: 0
m_LineSpacing: 1
m_Text: "\u62D2\u7EDD\u7533\u8BF7"
--- !u!1 &4212541439505938081
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 7715568052211693546}
- component: {fileID: 2770649873537820046}
m_Layer: 0
m_Name: friendRequestPrefab
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &7715568052211693546
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4212541439505938081}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 1887113380917164164}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 100, y: 100}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &2770649873537820046
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4212541439505938081}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 801af46c3c30ecc44ad9befbb892dc33, type: 3}
m_Name:
m_EditorClassIdentifier:
profileImage: {fileID: 322438256967861300}
friendName: {fileID: 946637269750711889}
friendSourceInfomation: {fileID: 2512442011294951853}
acceptButton: {fileID: 3532086592632966835}
rejectButton: {fileID: 1318607288835086267}
--- !u!1 &4555906629148779692
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 3192042035776563592}
- component: {fileID: 3709975975265226361}
- component: {fileID: 946637269750711889}
m_Layer: 0
m_Name: username
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &3192042035776563592
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4555906629148779692}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1887113380917164164}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 30.39, y: 14}
m_SizeDelta: {x: 143.18, y: 26}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &3709975975265226361
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4555906629148779692}
m_CullTransparentMesh: 1
--- !u!114 &946637269750711889
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4555906629148779692}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: dfa10de3e2846524089b5073175cd151, type: 3}
m_FontSize: 24
m_FontStyle: 0
m_BestFit: 1
m_MinSize: 12
m_MaxSize: 24
m_Alignment: 3
m_AlignByGeometry: 0
m_RichText: 1
m_HorizontalOverflow: 0
m_VerticalOverflow: 0
m_LineSpacing: 1
m_Text: "\u73A9\u5BB6\u540D\u5B57\u653E\u5728\u8FD9\u91CC\u5982\u679C\u5B57\u6570\u5B9E\u5728\u662F\u592A\u591A"
--- !u!1 &7108017819273845759
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 9058676790248205063}
- component: {fileID: 9058361928411038020}
- component: {fileID: 8625764165904734956}
- component: {fileID: 1318607288835086267}
m_Layer: 0
m_Name: rejectRequest
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &9058676790248205063
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7108017819273845759}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 8230912162764402355}
m_Father: {fileID: 1887113380917164164}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 63.383705, y: -17.465}
m_SizeDelta: {x: 62.1074, y: 17.071}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &9058361928411038020
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7108017819273845759}
m_CullTransparentMesh: 1
--- !u!114 &8625764165904734956
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7108017819273845759}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 0.7684825, b: 0.759434, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 21300000, guid: 7f3b1220317607e44bbb8f0e1998044a, type: 3}
m_Type: 1
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 5
--- !u!114 &1318607288835086267
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7108017819273845759}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Navigation:
m_Mode: 3
m_WrapAround: 0
m_SelectOnUp: {fileID: 0}
m_SelectOnDown: {fileID: 0}
m_SelectOnLeft: {fileID: 0}
m_SelectOnRight: {fileID: 0}
m_Transition: 1
m_Colors:
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
m_ColorMultiplier: 1
m_FadeDuration: 0.1
m_SpriteState:
m_HighlightedSprite: {fileID: 0}
m_PressedSprite: {fileID: 0}
m_SelectedSprite: {fileID: 0}
m_DisabledSprite: {fileID: 0}
m_AnimationTriggers:
m_NormalTrigger: Normal
m_HighlightedTrigger: Highlighted
m_PressedTrigger: Pressed
m_SelectedTrigger: Selected
m_DisabledTrigger: Disabled
m_Interactable: 1
m_TargetGraphic: {fileID: 8625764165904734956}
m_OnClick:
m_PersistentCalls:
m_Calls: []
--- !u!1 &8090928456119292382
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 2373904671374910531}
- component: {fileID: 3245753848347248243}
- component: {fileID: 660554859512802687}
m_Layer: 0
m_Name: Text (Legacy)
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &2373904671374910531
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8090928456119292382}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 5899234148902931492}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &3245753848347248243
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8090928456119292382}
m_CullTransparentMesh: 1
--- !u!114 &660554859512802687
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8090928456119292382}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: bd4be44636ad6c24a9b117a7108a75e9, type: 3}
m_FontSize: 10
m_FontStyle: 0
m_BestFit: 0
m_MinSize: 1
m_MaxSize: 40
m_Alignment: 4
m_AlignByGeometry: 0
m_RichText: 1
m_HorizontalOverflow: 0
m_VerticalOverflow: 0
m_LineSpacing: 1
m_Text: "\u63A5\u53D7\u7533\u8BF7"
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: e8ca0ff1de25bed438ccb59d4be1154b
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,218 @@
using System;
using System.Collections;
using System.Threading.Tasks;
using GameServer.Client;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;
#if !(UNITY_STANDALONE_WIN || UNITY_STANDALONE_LINUX || UNITY_STANDALONE_OSX || STEAMWORKS_WIN || STEAMWORKS_LIN_OSX)
#define FRIEND_DISABLE_STEAMWORKS
#endif
#if !FRIEND_DISABLE_STEAMWORKS
using Steamworks;
#endif
public class friendSearchPrefab : MonoBehaviour
{
[SerializeField] private Image profileImage;
[SerializeField] private Text friendName;
[SerializeField] private Text friendSourceInfomation;
[SerializeField] private Button addFriendButton;
[SerializeField] private Text addFriendButtonText;
private Coroutine _avatarLoadRoutine;
private Coroutine _buttonCooldownRoutine;
private string _defaultAddButtonText;
private void Awake()
{
Text buttonText = ResolveAddButtonText();
_defaultAddButtonText = buttonText != null ? buttonText.text : string.Empty;
}
private void OnDestroy()
{
if (_avatarLoadRoutine != null)
{
StopCoroutine(_avatarLoadRoutine);
_avatarLoadRoutine = null;
}
if (_buttonCooldownRoutine != null)
{
StopCoroutine(_buttonCooldownRoutine);
_buttonCooldownRoutine = null;
}
}
public void Bind(ProfileData profile, bool canAdd, string disabledButtonText, Func<Task> onAddClicked, float cooldownSeconds)
{
if (friendName != null)
{
friendName.text = profile != null
? NetworkManager.ResolveDisplayName(profile.display_name, profile.steam_id, false)
: string.Empty;
}
if (addFriendButton != null)
{
addFriendButton.onClick.RemoveAllListeners();
addFriendButton.interactable = canAdd;
Text buttonText = ResolveAddButtonText();
if (buttonText != null)
{
buttonText.text = !canAdd && !string.IsNullOrWhiteSpace(disabledButtonText)
? disabledButtonText
: _defaultAddButtonText;
}
if (canAdd && onAddClicked != null)
{
addFriendButton.onClick.AddListener(() =>
{
if (_buttonCooldownRoutine != null)
{
return;
}
_ = HandleAddClickedAsync(onAddClicked, cooldownSeconds);
});
}
}
if (_avatarLoadRoutine != null)
{
StopCoroutine(_avatarLoadRoutine);
}
if (profileImage != null && profile != null)
{
profileImage.sprite = null;
string avatarUrl = NetworkManager.ResolveAvatarUrl(profile.avatar_url, profile.steam_id);
_avatarLoadRoutine = StartCoroutine(LoadAvatarRoutine(profile.steam_id, avatarUrl));
}
}
private async Task HandleAddClickedAsync(Func<Task> onAddClicked, float cooldownSeconds)
{
if (addFriendButton != null)
{
addFriendButton.interactable = false;
}
_buttonCooldownRoutine = StartCoroutine(ButtonCooldownRoutine(cooldownSeconds));
await onAddClicked();
}
private IEnumerator ButtonCooldownRoutine(float cooldownSeconds)
{
yield return new WaitForSecondsRealtime(cooldownSeconds);
if (addFriendButton != null)
{
addFriendButton.interactable = true;
}
_buttonCooldownRoutine = null;
}
private IEnumerator LoadAvatarRoutine(string steamId, string avatarUrl)
{
if (profileImage == null)
{
yield break;
}
bool loaded = false;
if (NetworkManager.IsUsableAvatarUrl(avatarUrl)
&& !avatarUrl.StartsWith(NetworkManager.SteamAvatarUrlPrefix, StringComparison.OrdinalIgnoreCase))
{
using (UnityWebRequest request = UnityWebRequestTexture.GetTexture(avatarUrl))
{
yield return request.SendWebRequest();
if (request.result == UnityWebRequest.Result.Success)
{
Texture2D texture = DownloadHandlerTexture.GetContent(request);
if (texture != null)
{
profileImage.sprite = Sprite.Create(
texture,
new Rect(0f, 0f, texture.width, texture.height),
new Vector2(0.5f, 0.5f));
loaded = true;
}
}
}
}
if (!loaded)
{
yield return LoadSteamAvatarRoutine(steamId);
}
_avatarLoadRoutine = null;
}
private IEnumerator LoadSteamAvatarRoutine(string steamId)
{
#if FRIEND_DISABLE_STEAMWORKS
yield break;
#else
if (!SteamManager.Initialized || string.IsNullOrWhiteSpace(steamId) || profileImage == null || !ulong.TryParse(steamId, out ulong rawSteamId))
{
yield break;
}
CSteamID targetSteamId = new CSteamID(rawSteamId);
SteamFriends.RequestUserInformation(targetSteamId, false);
float deadline = Time.realtimeSinceStartup + 5f;
while (Time.realtimeSinceStartup < deadline)
{
int imageId = SteamFriends.GetLargeFriendAvatar(targetSteamId);
if (imageId <= 0)
{
imageId = SteamFriends.GetMediumFriendAvatar(targetSteamId);
}
if (imageId > 0 && SteamUtils.GetImageSize(imageId, out uint width, out uint height) && width > 0 && height > 0)
{
byte[] buffer = new byte[width * height * 4];
if (SteamUtils.GetImageRGBA(imageId, buffer, buffer.Length))
{
profileImage.sprite = CreateFlippedSteamAvatarSprite(buffer, (int)width, (int)height);
yield break;
}
}
yield return new WaitForSecondsRealtime(0.25f);
}
#endif
}
private Text ResolveAddButtonText()
{
if (addFriendButtonText != null)
{
return addFriendButtonText;
}
return addFriendButton != null ? addFriendButton.GetComponentInChildren<Text>(true) : null;
}
private static Sprite CreateFlippedSteamAvatarSprite(byte[] rgbaBuffer, int width, int height)
{
Texture2D texture = new Texture2D(width, height, TextureFormat.RGBA32, false);
texture.LoadRawTextureData(rgbaBuffer);
texture.Apply();
Texture2D flipped = new Texture2D(width, height, TextureFormat.RGBA32, false);
for (int y = 0; y < height; y++)
{
Color[] pixels = texture.GetPixels(0, y, width, 1);
flipped.SetPixels(0, height - 1 - y, width, 1, pixels);
}
flipped.Apply();
return Sprite.Create(flipped, new Rect(0, 0, width, height), new Vector2(0.5f, 0.5f));
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e6960d1bacccebf45bc74ca08eab154b
@@ -0,0 +1,567 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &167812896000688069
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 171703084932209816}
- component: {fileID: 7766852143305575412}
- component: {fileID: 2512442011294951853}
m_Layer: 0
m_Name: source
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &171703084932209816
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 167812896000688069}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1887113380917164164}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 30.39, y: -4}
m_SizeDelta: {x: 143.18, y: 12.573}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &7766852143305575412
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 167812896000688069}
m_CullTransparentMesh: 1
--- !u!114 &2512442011294951853
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 167812896000688069}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: bd4be44636ad6c24a9b117a7108a75e9, type: 3}
m_FontSize: 10
m_FontStyle: 0
m_BestFit: 0
m_MinSize: 1
m_MaxSize: 18
m_Alignment: 3
m_AlignByGeometry: 0
m_RichText: 1
m_HorizontalOverflow: 0
m_VerticalOverflow: 0
m_LineSpacing: 1
m_Text: "\u96C5\u8389\u68A6\u7483\u9AD8\u65B0\u4EA7\u4E1A\u56ED Steam2\u533A"
--- !u!1 &171027573892652408
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 5899234148902931492}
- component: {fileID: 752898224887842841}
- component: {fileID: 7879641519199484437}
- component: {fileID: 3532086592632966835}
m_Layer: 0
m_Name: sendRequest
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &5899234148902931492
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 171027573892652408}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 2373904671374910531}
m_Father: {fileID: 1887113380917164164}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 30.389, y: -17.465}
m_SizeDelta: {x: 143.18, y: 17.071}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &752898224887842841
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 171027573892652408}
m_CullTransparentMesh: 1
--- !u!114 &7879641519199484437
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 171027573892652408}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 0.6367924, g: 1, b: 0.9797205, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 21300000, guid: 7f3b1220317607e44bbb8f0e1998044a, type: 3}
m_Type: 1
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 5
--- !u!114 &3532086592632966835
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 171027573892652408}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Navigation:
m_Mode: 3
m_WrapAround: 0
m_SelectOnUp: {fileID: 0}
m_SelectOnDown: {fileID: 0}
m_SelectOnLeft: {fileID: 0}
m_SelectOnRight: {fileID: 0}
m_Transition: 1
m_Colors:
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
m_ColorMultiplier: 1
m_FadeDuration: 0.1
m_SpriteState:
m_HighlightedSprite: {fileID: 0}
m_PressedSprite: {fileID: 0}
m_SelectedSprite: {fileID: 0}
m_DisabledSprite: {fileID: 0}
m_AnimationTriggers:
m_NormalTrigger: Normal
m_HighlightedTrigger: Highlighted
m_PressedTrigger: Pressed
m_SelectedTrigger: Selected
m_DisabledTrigger: Disabled
m_Interactable: 1
m_TargetGraphic: {fileID: 7879641519199484437}
m_OnClick:
m_PersistentCalls:
m_Calls: []
--- !u!1 &696844997186230125
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1887113380917164164}
- component: {fileID: 8398379083156494532}
- component: {fileID: 885345931350300794}
m_Layer: 0
m_Name: btm
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &1887113380917164164
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 696844997186230125}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 3062756356687769231}
- {fileID: 3192042035776563592}
- {fileID: 171703084932209816}
- {fileID: 5899234148902931492}
m_Father: {fileID: 7715568052211693546}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 220, y: 70}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &8398379083156494532
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 696844997186230125}
m_CullTransparentMesh: 1
--- !u!114 &885345931350300794
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 696844997186230125}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 21300000, guid: 537c5cf3bf53c2f4a9404248acd2d336, type: 3}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!1 &1234883103932309115
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 3062756356687769231}
- component: {fileID: 7577265852821514236}
- component: {fileID: 322438256967861300}
m_Layer: 0
m_Name: profile
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &3062756356687769231
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1234883103932309115}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1887113380917164164}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: -74.39, y: 1.5}
m_SizeDelta: {x: 55, y: 55}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &7577265852821514236
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1234883103932309115}
m_CullTransparentMesh: 1
--- !u!114 &322438256967861300
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1234883103932309115}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 0}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!1 &4212541439505938081
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 7715568052211693546}
- component: {fileID: 6174152505969619671}
m_Layer: 0
m_Name: friendSearchPrefab
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &7715568052211693546
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4212541439505938081}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 1887113380917164164}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 100, y: 100}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &6174152505969619671
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4212541439505938081}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: e6960d1bacccebf45bc74ca08eab154b, type: 3}
m_Name:
m_EditorClassIdentifier:
profileImage: {fileID: 322438256967861300}
friendName: {fileID: 946637269750711889}
friendSourceInfomation: {fileID: 2512442011294951853}
addFriendButton: {fileID: 3532086592632966835}
--- !u!1 &4555906629148779692
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 3192042035776563592}
- component: {fileID: 3709975975265226361}
- component: {fileID: 946637269750711889}
m_Layer: 0
m_Name: username
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &3192042035776563592
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4555906629148779692}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1887113380917164164}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 30.39, y: 14}
m_SizeDelta: {x: 143.18, y: 26}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &3709975975265226361
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4555906629148779692}
m_CullTransparentMesh: 1
--- !u!114 &946637269750711889
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4555906629148779692}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: dfa10de3e2846524089b5073175cd151, type: 3}
m_FontSize: 24
m_FontStyle: 0
m_BestFit: 1
m_MinSize: 12
m_MaxSize: 24
m_Alignment: 3
m_AlignByGeometry: 0
m_RichText: 1
m_HorizontalOverflow: 0
m_VerticalOverflow: 0
m_LineSpacing: 1
m_Text: "\u73A9\u5BB6\u540D\u5B57\u653E\u5728\u8FD9\u91CC\u5982\u679C\u5B57\u6570\u5B9E\u5728\u662F\u592A\u591A"
--- !u!1 &8090928456119292382
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 2373904671374910531}
- component: {fileID: 3245753848347248243}
- component: {fileID: 660554859512802687}
m_Layer: 0
m_Name: Text (Legacy)
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &2373904671374910531
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8090928456119292382}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 5899234148902931492}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &3245753848347248243
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8090928456119292382}
m_CullTransparentMesh: 1
--- !u!114 &660554859512802687
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8090928456119292382}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: bd4be44636ad6c24a9b117a7108a75e9, type: 3}
m_FontSize: 10
m_FontStyle: 0
m_BestFit: 0
m_MinSize: 1
m_MaxSize: 40
m_Alignment: 4
m_AlignByGeometry: 0
m_RichText: 1
m_HorizontalOverflow: 0
m_VerticalOverflow: 0
m_LineSpacing: 1
m_Text: "\u70B9\u51FB\u5411\u5176\u53D1\u9001\u597D\u53CB\u7533\u8BF7"
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 473497bd46c599a47a629dd5455a04cd
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+769
View File
@@ -0,0 +1,769 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Bansonic;
using GameServer.Client;
using UnityEngine;
using UnityEngine.UI;
#if !(UNITY_STANDALONE_WIN || UNITY_STANDALONE_LINUX || UNITY_STANDALONE_OSX || STEAMWORKS_WIN || STEAMWORKS_LIN_OSX)
#define FRIEND_DISABLE_STEAMWORKS
#endif
#if !FRIEND_DISABLE_STEAMWORKS
using Steamworks;
#endif
public enum FriendFilterType
{
All,
Online,
Starred,
Offline,
GameFriends,
SteamFriends
}
[Serializable]
public sealed class FriendCardViewData
{
public string SteamId;
public string DisplayName;
public string AvatarUrl;
public string StatusText;
public bool IsOnline;
public bool IsStarred;
public bool IsGameFriend;
public bool IsSteamFriend;
}
public class friendSystem : MonoBehaviour
{
private const string LabelAllFriends = "全部好友";
private const string LabelOnlineFriends = "在线好友";
private const string LabelStarredFriends = "星标好友";
private const string LabelOfflineFriends = "离线好友";
private const string LabelGameFriends = "游戏好友";
private const string LabelSteamFriends = "Steam好友";
private const string StatusOnline = "在线";
private const string StatusOffline = "离线";
private const string StatusPlaying = "游戏中";
private const string StatusInRoom = "房间中";
private const string NoticeFriendDetailsNotReady = "好友详情面板尚未接入";
private const string NoticePrivateChatNotReady = "私聊面板尚未接入";
private const string DefaultStatusLoading = "正在拉取好友信息...";
private const string DefaultStatusEmpty = "你当前没有好友";
private const string DefaultStatusServiceUnavailable = "无法访问服务";
public GameObject friendCardPrefab;
public Transform friendsDisplayContent;
[Header("filter")]
public Dropdown friendsDisplayDropdown;
[Header("text")]
public Text defaultText;
[Header("sync")]
[SerializeField] private float steamFriendResyncIntervalSeconds = 8f;
private readonly Dictionary<string, FriendCardViewData> _mergedFriends =
new Dictionary<string, FriendCardViewData>(StringComparer.Ordinal);
private readonly Dictionary<string, FriendCardViewData> _steamFriendCache =
new Dictionary<string, FriendCardViewData>(StringComparer.Ordinal);
private readonly Dictionary<string, GameObject> _spawnedCards =
new Dictionary<string, GameObject>(StringComparer.Ordinal);
private readonly List<(FriendFilterType filter, string label)> _filters =
new List<(FriendFilterType filter, string label)>
{
(FriendFilterType.All, LabelAllFriends),
(FriendFilterType.Online, LabelOnlineFriends),
(FriendFilterType.Starred, LabelStarredFriends),
(FriendFilterType.Offline, LabelOfflineFriends),
(FriendFilterType.GameFriends, LabelGameFriends),
(FriendFilterType.SteamFriends, LabelSteamFriends)
};
private bool _dropdownInitialized;
private bool _isRefreshing;
private bool _lastRefreshFailed;
private Coroutine _resyncCoroutine;
private static List<string> _startupSteamFriendIds = new List<string>();
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void WarmSteamFriendIdsOnStartup()
{
_startupSteamFriendIds = ReadSteamFriendIdsOnly();
}
private void OnEnable()
{
ClearDisplayedFriends();
SetDefaultText(DefaultStatusLoading);
InitializeDropdown();
if (ArenaRoomService.Instance != null)
{
ArenaRoomService.Instance.OnFriendsListChanged += HandleFriendsListChanged;
}
StartCoroutine(InitializeFriendsRoutine());
if (_resyncCoroutine != null)
{
StopCoroutine(_resyncCoroutine);
}
_resyncCoroutine = StartCoroutine(SteamFriendResyncLoop());
}
private void OnDisable()
{
if (ArenaRoomService.Instance != null)
{
ArenaRoomService.Instance.OnFriendsListChanged -= HandleFriendsListChanged;
}
if (_resyncCoroutine != null)
{
StopCoroutine(_resyncCoroutine);
_resyncCoroutine = null;
}
if (friendsDisplayDropdown != null)
{
friendsDisplayDropdown.onValueChanged.RemoveListener(HandleFilterChanged);
}
}
private void ClearDisplayedFriends()
{
_spawnedCards.Clear();
if (friendsDisplayContent == null)
{
return;
}
for (int index = friendsDisplayContent.childCount - 1; index >= 0; index--)
{
Transform child = friendsDisplayContent.GetChild(index);
if (child != null)
{
Destroy(child.gameObject);
}
}
}
private IEnumerator InitializeFriendsRoutine()
{
if (_isRefreshing)
{
yield break;
}
_isRefreshing = true;
_lastRefreshFailed = false;
SetDefaultText(DefaultStatusLoading);
RefreshSteamFriendCache();
float waitDeadline = Time.realtimeSinceStartup + 3f;
while (NetworkManager.Instance == null && Time.realtimeSinceStartup < waitDeadline)
{
yield return null;
}
NetworkManager network = NetworkManager.Instance;
List<string> steamFriendIds = _steamFriendCache.Keys
.Where(id => !string.IsNullOrWhiteSpace(id))
.Distinct(StringComparer.Ordinal)
.ToList();
if (network != null)
{
var friendsTask = network.GetFriendsList(steamFriendIds);
float timeoutAt = Time.realtimeSinceStartup + 20f;
while (!friendsTask.IsCompleted && Time.realtimeSinceStartup < timeoutAt)
{
yield return null;
}
if (!friendsTask.IsCompleted)
{
_lastRefreshFailed = true;
Debug.LogWarning("[friendSystem] GetFriendsList timed out.");
}
else if (friendsTask.Status == System.Threading.Tasks.TaskStatus.RanToCompletion
&& friendsTask.Result != null
&& friendsTask.Result.success)
{
ReplaceGameFriends(friendsTask.Result.friends);
}
else
{
_lastRefreshFailed = true;
string error = friendsTask.IsFaulted ? friendsTask.Exception?.GetBaseException().Message : "friend list request failed";
Debug.LogWarning($"[friendSystem] Initialize friends failed: {error}");
}
}
else
{
_lastRefreshFailed = true;
Debug.LogWarning("[friendSystem] NetworkManager is unavailable when initializing friends.");
}
Render();
_isRefreshing = false;
}
private IEnumerator SteamFriendResyncLoop()
{
float interval = Mathf.Max(3f, steamFriendResyncIntervalSeconds);
var wait = new WaitForSecondsRealtime(interval);
while (isActiveAndEnabled)
{
yield return wait;
if (_isRefreshing)
{
continue;
}
RefreshSteamFriendCache();
if (!ShouldRetrySteamImport())
{
continue;
}
yield return InitializeFriendsRoutine();
}
}
private void InitializeDropdown()
{
if (friendsDisplayDropdown == null)
{
return;
}
friendsDisplayDropdown.onValueChanged.RemoveListener(HandleFilterChanged);
if (!_dropdownInitialized)
{
friendsDisplayDropdown.ClearOptions();
friendsDisplayDropdown.AddOptions(_filters.Select(item => item.label).ToList());
friendsDisplayDropdown.value = 0;
_dropdownInitialized = true;
}
friendsDisplayDropdown.onValueChanged.AddListener(HandleFilterChanged);
friendsDisplayDropdown.RefreshShownValue();
}
private void HandleFilterChanged(int _)
{
Render();
}
private void HandleFriendsListChanged(IReadOnlyList<SocialFriendEntry> friends)
{
_lastRefreshFailed = false;
RefreshSteamFriendCache();
ReplaceGameFriends(friends);
Render();
}
private void RefreshSteamFriendCache()
{
CacheSteamFriends(ReadSteamFriends().Concat(BuildStartupSteamFriendCards()));
}
private bool ShouldRetrySteamImport()
{
if (_steamFriendCache.Count == 0)
{
return false;
}
foreach (KeyValuePair<string, FriendCardViewData> pair in _steamFriendCache)
{
FriendCardViewData steamFriend = pair.Value;
if (steamFriend == null || string.IsNullOrWhiteSpace(steamFriend.SteamId))
{
continue;
}
if (_mergedFriends.ContainsKey(steamFriend.SteamId))
{
continue;
}
if (steamFriend.IsOnline || string.Equals(steamFriend.StatusText, StatusPlaying, StringComparison.Ordinal))
{
return true;
}
}
return false;
}
private void CacheSteamFriends(IEnumerable<FriendCardViewData> steamFriends)
{
_steamFriendCache.Clear();
if (steamFriends == null)
{
return;
}
foreach (FriendCardViewData steamFriend in steamFriends)
{
if (steamFriend == null || string.IsNullOrWhiteSpace(steamFriend.SteamId))
{
continue;
}
steamFriend.IsStarred = LoadStarredState(steamFriend.SteamId);
_steamFriendCache[steamFriend.SteamId] = steamFriend;
}
}
private void ReplaceGameFriends(IEnumerable<SocialFriendEntry> gameFriends)
{
_mergedFriends.Clear();
if (gameFriends == null)
{
return;
}
foreach (SocialFriendEntry gameFriend in gameFriends)
{
if (gameFriend == null || string.IsNullOrWhiteSpace(gameFriend.friend_steam_id))
{
continue;
}
string steamId = gameFriend.friend_steam_id.Trim();
_steamFriendCache.TryGetValue(steamId, out FriendCardViewData steamFriend);
bool steamSaysPlaying = steamFriend != null && string.Equals(steamFriend.StatusText, StatusPlaying, StringComparison.Ordinal);
bool mergedOnline = gameFriend.online || (steamFriend != null && steamFriend.IsOnline) || steamSaysPlaying;
string mergedStatus = MapServerPresenceToStatusText(gameFriend.status, steamFriend, mergedOnline);
FriendCardViewData merged = new FriendCardViewData
{
SteamId = steamId,
DisplayName = ResolvePreferredDisplayName(
steamFriend != null ? steamFriend.DisplayName : string.Empty,
NetworkManager.ResolveDisplayName(gameFriend.friend_name, steamId, false),
steamId),
AvatarUrl = ResolvePreferredAvatarUrl(
steamFriend != null ? steamFriend.AvatarUrl : string.Empty,
NetworkManager.ResolveAvatarUrl(gameFriend.friend_avatar, steamId),
steamId),
StatusText = mergedStatus,
IsOnline = mergedOnline,
IsStarred = LoadStarredState(steamId),
IsGameFriend = true,
IsSteamFriend = steamFriend != null
};
_mergedFriends[steamId] = merged;
}
}
private async System.Threading.Tasks.Task HydrateRegisteredSteamFriendsAsync(NetworkManager network)
{
if (network == null || _steamFriendCache.Count == 0)
{
return;
}
foreach (KeyValuePair<string, FriendCardViewData> pair in _steamFriendCache)
{
string steamId = pair.Key;
FriendCardViewData steamFriend = pair.Value;
if (string.IsNullOrWhiteSpace(steamId) || _mergedFriends.ContainsKey(steamId))
{
continue;
}
ProfileData profile;
try
{
profile = await network.GetProfile(steamId);
}
catch
{
continue;
}
if (profile == null || string.IsNullOrWhiteSpace(profile.steam_id))
{
continue;
}
bool steamSaysPlaying = steamFriend != null && string.Equals(steamFriend.StatusText, StatusPlaying, StringComparison.Ordinal);
bool mergedOnline = steamFriend != null && steamFriend.IsOnline;
string mergedStatus = steamSaysPlaying ? StatusPlaying : (mergedOnline ? StatusOnline : StatusOffline);
_mergedFriends[steamId] = new FriendCardViewData
{
SteamId = steamId,
DisplayName = ResolvePreferredDisplayName(
steamFriend != null ? steamFriend.DisplayName : string.Empty,
NetworkManager.ResolveDisplayName(profile.display_name, steamId, false),
steamId),
AvatarUrl = ResolvePreferredAvatarUrl(
steamFriend != null ? steamFriend.AvatarUrl : string.Empty,
NetworkManager.ResolveAvatarUrl(profile.avatar_url, steamId),
steamId),
StatusText = mergedStatus,
IsOnline = mergedOnline,
IsStarred = LoadStarredState(steamId),
IsGameFriend = true,
IsSteamFriend = true
};
}
}
private void Render()
{
if (friendsDisplayContent == null || friendCardPrefab == null)
{
SetDefaultText(DefaultStatusServiceUnavailable);
return;
}
List<FriendCardViewData> target = ApplyFilter(_mergedFriends.Values)
.OrderBy(friend => GetSortBucket(friend))
.ThenBy(friend => friend.DisplayName ?? string.Empty, StringComparer.OrdinalIgnoreCase)
.ThenBy(friend => friend.SteamId ?? string.Empty, StringComparer.OrdinalIgnoreCase)
.ToList();
HashSet<string> targetIds = new HashSet<string>(target.Select(friend => friend.SteamId), StringComparer.Ordinal);
foreach (KeyValuePair<string, GameObject> pair in _spawnedCards.ToList())
{
if (targetIds.Contains(pair.Key))
{
continue;
}
Destroy(pair.Value);
_spawnedCards.Remove(pair.Key);
}
for (int index = 0; index < target.Count; index++)
{
FriendCardViewData friend = target[index];
if (!_spawnedCards.TryGetValue(friend.SteamId, out GameObject cardObject) || cardObject == null)
{
cardObject = Instantiate(friendCardPrefab, friendsDisplayContent);
_spawnedCards[friend.SteamId] = cardObject;
}
cardObject.transform.SetSiblingIndex(index);
friendCardPrefab card = cardObject.GetComponent<friendCardPrefab>();
if (card != null)
{
card.Bind(friend, HandleStarChanged, HandleOpenDetails, HandleOpenPrivateConversation);
}
}
UpdateDefaultTextState(target.Count);
}
private void UpdateDefaultTextState(int visibleCount)
{
if (_lastRefreshFailed)
{
SetDefaultText(DefaultStatusServiceUnavailable);
return;
}
if (visibleCount > 0)
{
SetDefaultText(string.Empty);
return;
}
SetDefaultText(DefaultStatusEmpty);
}
private void SetDefaultText(string message)
{
if (defaultText == null)
{
return;
}
defaultText.text = message ?? string.Empty;
}
private IEnumerable<FriendCardViewData> ApplyFilter(IEnumerable<FriendCardViewData> source)
{
FriendFilterType filter = GetSelectedFilter();
switch (filter)
{
case FriendFilterType.All:
return source;
case FriendFilterType.Online:
return source.Where(friend => friend.IsOnline);
case FriendFilterType.Starred:
return source.Where(friend => friend.IsStarred);
case FriendFilterType.Offline:
return source.Where(friend => !friend.IsOnline);
case FriendFilterType.GameFriends:
return source.Where(friend => friend.IsGameFriend);
case FriendFilterType.SteamFriends:
return source.Where(friend => friend.IsSteamFriend);
default:
return source;
}
}
private FriendFilterType GetSelectedFilter()
{
if (friendsDisplayDropdown == null || friendsDisplayDropdown.value < 0 || friendsDisplayDropdown.value >= _filters.Count)
{
return FriendFilterType.All;
}
return _filters[friendsDisplayDropdown.value].filter;
}
private void HandleStarChanged(string steamId, bool isOn)
{
if (string.IsNullOrWhiteSpace(steamId))
{
return;
}
PlayerPrefs.SetInt(BuildStarKey(steamId), isOn ? 1 : 0);
PlayerPrefs.Save();
if (_mergedFriends.TryGetValue(steamId, out FriendCardViewData friend) && friend != null)
{
friend.IsStarred = isOn;
}
if (_steamFriendCache.TryGetValue(steamId, out FriendCardViewData steamFriend) && steamFriend != null)
{
steamFriend.IsStarred = isOn;
}
Render();
}
private void HandleOpenDetails(string steamId)
{
if (string.IsNullOrWhiteSpace(steamId))
{
return;
}
gNotice.warning.display(NoticeFriendDetailsNotReady);
}
private void HandleOpenPrivateConversation(string steamId)
{
if (string.IsNullOrWhiteSpace(steamId))
{
return;
}
globalChatSystem.OpenPrivateConversation(steamId);
}
private static int GetSortBucket(FriendCardViewData friend)
{
if (friend == null)
{
return int.MaxValue;
}
if (friend.IsStarred && friend.IsOnline)
{
return 0;
}
if (friend.IsStarred && !friend.IsOnline)
{
return 1;
}
if (!friend.IsStarred && friend.IsOnline)
{
return 2;
}
return 3;
}
private static string ResolvePreferredDisplayName(string currentDisplayName, string newDisplayName, string steamId)
{
if (NetworkManager.IsUsableDisplayName(newDisplayName, steamId))
{
return newDisplayName;
}
if (NetworkManager.IsUsableDisplayName(currentDisplayName, steamId))
{
return currentDisplayName;
}
return string.Empty;
}
private static string ResolvePreferredAvatarUrl(string currentAvatarUrl, string newAvatarUrl, string steamId)
{
if (NetworkManager.IsUsableAvatarUrl(newAvatarUrl))
{
return newAvatarUrl;
}
if (NetworkManager.IsUsableAvatarUrl(currentAvatarUrl))
{
return currentAvatarUrl;
}
return NetworkManager.ResolveAvatarUrl(string.Empty, steamId);
}
private static string BuildStarKey(string steamId)
{
return $"friend_star_{steamId}";
}
private static bool LoadStarredState(string steamId)
{
if (string.IsNullOrWhiteSpace(steamId))
{
return false;
}
return PlayerPrefs.GetInt(BuildStarKey(steamId), 0) == 1;
}
private static List<FriendCardViewData> ReadSteamFriends()
{
var result = new List<FriendCardViewData>();
#if FRIEND_DISABLE_STEAMWORKS
return result;
#else
if (!SteamManager.Initialized)
{
return result;
}
int count = SteamFriends.GetFriendCount(EFriendFlags.k_EFriendFlagImmediate);
for (int index = 0; index < count; index++)
{
CSteamID friendSteamId = SteamFriends.GetFriendByIndex(index, EFriendFlags.k_EFriendFlagImmediate);
string steamId = friendSteamId.m_SteamID.ToString();
SteamFriends.RequestUserInformation(friendSteamId, false);
string displayName = SteamFriends.GetFriendPersonaName(friendSteamId);
EPersonaState personaState = SteamFriends.GetFriendPersonaState(friendSteamId);
FriendGameInfo_t gameInfo;
bool isPlaying = SteamFriends.GetFriendGamePlayed(friendSteamId, out gameInfo);
bool isOnline = personaState != EPersonaState.k_EPersonaStateOffline;
string statusText = isPlaying ? StatusPlaying : (isOnline ? StatusOnline : StatusOffline);
result.Add(
new FriendCardViewData
{
SteamId = steamId,
DisplayName = NetworkManager.ResolveDisplayName(displayName, steamId, false),
AvatarUrl = NetworkManager.ResolveAvatarUrl(string.Empty, steamId),
StatusText = statusText,
IsOnline = isOnline,
IsSteamFriend = true,
IsGameFriend = false,
IsStarred = LoadStarredState(steamId)
}
);
}
return result;
#endif
}
private static IEnumerable<FriendCardViewData> BuildStartupSteamFriendCards()
{
if (_startupSteamFriendIds == null || _startupSteamFriendIds.Count == 0)
{
yield break;
}
foreach (string steamId in _startupSteamFriendIds)
{
if (string.IsNullOrWhiteSpace(steamId))
{
continue;
}
yield return new FriendCardViewData
{
SteamId = steamId,
DisplayName = NetworkManager.ResolveDisplayName(string.Empty, steamId, false),
AvatarUrl = NetworkManager.ResolveAvatarUrl(string.Empty, steamId),
StatusText = string.Empty,
IsOnline = false,
IsSteamFriend = true,
IsGameFriend = false,
IsStarred = LoadStarredState(steamId)
};
}
}
private static List<string> ReadSteamFriendIdsOnly()
{
var result = new List<string>();
#if FRIEND_DISABLE_STEAMWORKS
return result;
#else
if (!SteamManager.Initialized)
{
return result;
}
int count = SteamFriends.GetFriendCount(EFriendFlags.k_EFriendFlagImmediate);
for (int index = 0; index < count; index++)
{
CSteamID friendSteamId = SteamFriends.GetFriendByIndex(index, EFriendFlags.k_EFriendFlagImmediate);
string steamId = friendSteamId.m_SteamID.ToString();
if (!string.IsNullOrWhiteSpace(steamId))
{
result.Add(steamId);
}
}
return result.Distinct(StringComparer.Ordinal).ToList();
#endif
}
private static string MapServerPresenceToStatusText(string status, FriendCardViewData steamFriend, bool mergedOnline)
{
if (string.Equals(status, "IN_GAME", StringComparison.OrdinalIgnoreCase))
{
return StatusPlaying;
}
if (string.Equals(status, "IN_ROOM", StringComparison.OrdinalIgnoreCase))
{
return StatusInRoom;
}
if (string.Equals(status, "ONLINE", StringComparison.OrdinalIgnoreCase))
{
return StatusOnline;
}
if (string.Equals(status, "OFFLINE", StringComparison.OrdinalIgnoreCase))
{
return StatusOffline;
}
if (steamFriend != null && string.Equals(steamFriend.StatusText, StatusPlaying, StringComparison.Ordinal))
{
return StatusPlaying;
}
return mergedOnline ? StatusOnline : StatusOffline;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e90a1965bd8aeff459ac3039e97c3df3
@@ -0,0 +1,668 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Bansonic;
using GameServer.Client;
using UnityEngine;
using UnityEngine.UI;
public class friendsManager : MonoBehaviour
{
private const float RequestCooldownSeconds = 3f;
public InputField searchFriend_iptfd;
public Button searchButton;
[Header("FriendSearchObjects")]
public GameObject friendSearchPrefab;
public Transform friendSearchParent;
[Header("FriendRequestObjects")]
public GameObject friendRequestPrefab;
public GameObject friendRequestParent;
public Button quickAcceptAll;
public Button quickRejectAll;
[Header("Default Texts")]
public Text defaultFriendSearchText;
public Text defaultFriendRequestText;
private readonly Dictionary<long, GameObject> _spawnedRequestCards = new Dictionary<long, GameObject>();
private readonly Dictionary<string, float> _actionCooldownUntil = new Dictionary<string, float>();
private readonly HashSet<string> _pendingActions = new HashSet<string>();
private GameObject _spawnedSearchCard;
private bool _isSearching;
private bool _isBatchProcessing;
private Transform RequestParentTransform => friendRequestParent != null ? friendRequestParent.transform : null;
private void OnEnable()
{
ClearSearchParentChildren();
ClearRequestParentChildren();
SetSearchDefaultVisible(false);
SetRequestDefaultVisible(true);
BindUiEvents();
if (ArenaRoomService.Instance != null)
{
ArenaRoomService.Instance.OnFriendRequestsChanged += HandleFriendRequestsChanged;
}
_ = InitializeAsync();
}
private void OnDisable()
{
if (ArenaRoomService.Instance != null)
{
ArenaRoomService.Instance.OnFriendRequestsChanged -= HandleFriendRequestsChanged;
}
UnbindUiEvents();
}
private async Task InitializeAsync()
{
ArenaRoomService arena = ArenaRoomService.Instance;
if (arena == null)
{
SetRequestDefaultVisible(true);
return;
}
try
{
IReadOnlyList<SocialFriendRequestEntry> requests = await arena.GetFriendRequestsAsync(true);
RenderFriendRequests(requests);
}
catch (Exception ex)
{
gNotice.error.display(ex.Message);
SetRequestDefaultVisible(true);
}
}
private void BindUiEvents()
{
if (searchButton != null)
{
searchButton.onClick.RemoveListener(HandleSearchClicked);
searchButton.onClick.AddListener(HandleSearchClicked);
}
if (searchFriend_iptfd != null)
{
searchFriend_iptfd.onEndEdit.RemoveListener(HandleSearchEndEdit);
searchFriend_iptfd.onEndEdit.AddListener(HandleSearchEndEdit);
}
if (quickAcceptAll != null)
{
quickAcceptAll.onClick.RemoveListener(HandleQuickAcceptAll);
quickAcceptAll.onClick.AddListener(HandleQuickAcceptAll);
}
if (quickRejectAll != null)
{
quickRejectAll.onClick.RemoveListener(HandleQuickRejectAll);
quickRejectAll.onClick.AddListener(HandleQuickRejectAll);
}
}
private void UnbindUiEvents()
{
if (searchButton != null)
{
searchButton.onClick.RemoveListener(HandleSearchClicked);
}
if (searchFriend_iptfd != null)
{
searchFriend_iptfd.onEndEdit.RemoveListener(HandleSearchEndEdit);
}
if (quickAcceptAll != null)
{
quickAcceptAll.onClick.RemoveListener(HandleQuickAcceptAll);
}
if (quickRejectAll != null)
{
quickRejectAll.onClick.RemoveListener(HandleQuickRejectAll);
}
}
private void HandleFriendRequestsChanged(IReadOnlyList<SocialFriendRequestEntry> requests)
{
RenderFriendRequests(requests);
}
private void HandleSearchClicked()
{
string actionSignature = BuildSearchSignature();
if (!TryBeginAction(actionSignature))
{
return;
}
BeginButtonCooldown(searchButton);
_ = SearchExactPlayerAsync(actionSignature);
}
private void HandleSearchEndEdit(string value)
{
if (!isActiveAndEnabled)
{
return;
}
if (Input.GetKeyDown(KeyCode.Return) || Input.GetKeyDown(KeyCode.KeypadEnter))
{
string actionSignature = BuildSearchSignature();
if (!TryBeginAction(actionSignature))
{
return;
}
BeginButtonCooldown(searchButton);
_ = SearchExactPlayerAsync(actionSignature);
}
}
private async Task SearchExactPlayerAsync(string actionSignature)
{
if (_isSearching || string.IsNullOrWhiteSpace(actionSignature))
{
EndAction(actionSignature);
return;
}
_isSearching = true;
SetSearchDefaultVisible(false);
ClearSearchParentChildren();
try
{
string query = searchFriend_iptfd != null ? searchFriend_iptfd.text?.Trim() : string.Empty;
ProfileData profile = await LookupProfileExactAsync(query);
if (!IsValidProfile(profile))
{
SetSearchDefaultVisible(true);
return;
}
if (friendSearchPrefab == null || friendSearchParent == null)
{
return;
}
_spawnedSearchCard = Instantiate(friendSearchPrefab, friendSearchParent, false);
friendSearchPrefab prefab = _spawnedSearchCard.GetComponent<friendSearchPrefab>();
if (prefab != null)
{
bool alreadyFriend = IsAlreadyFriend(profile.steam_id);
bool canSendRequest = CanSendFriendRequest(profile.steam_id);
prefab.Bind(
profile,
canSendRequest,
alreadyFriend ? "你们已经是好友" : string.Empty,
() => SendFriendRequestAsync(profile.steam_id),
RequestCooldownSeconds);
}
}
catch (Exception ex)
{
gNotice.error.display(ex.Message);
SetSearchDefaultVisible(true);
}
finally
{
_isSearching = false;
EndAction(actionSignature);
}
}
private async Task<ProfileData> LookupProfileExactAsync(string query)
{
if (string.IsNullOrWhiteSpace(query))
{
return null;
}
NetworkManager network = NetworkManager.Instance;
if (network == null)
{
throw new Exception("NetworkManager is not initialized");
}
string trimmed = query.Trim();
if (trimmed.All(char.IsDigit))
{
if (trimmed.Length >= 16)
{
return await SafeGetProfileBySteamId(network, trimmed);
}
if (int.TryParse(trimmed, out int uid) && uid > 0)
{
return await SafeGetProfileByUid(network, uid);
}
}
return null;
}
private static async Task<ProfileData> SafeGetProfileBySteamId(NetworkManager network, string steamId)
{
try
{
return await network.GetProfile(steamId);
}
catch
{
return null;
}
}
private static async Task<ProfileData> SafeGetProfileByUid(NetworkManager network, int uid)
{
try
{
return await network.GetPlayerByUid(uid);
}
catch
{
return null;
}
}
private static bool IsValidProfile(ProfileData profile)
{
return profile != null && !string.IsNullOrWhiteSpace(profile.steam_id);
}
private bool CanSendFriendRequest(string targetSteamId)
{
if (string.IsNullOrWhiteSpace(targetSteamId))
{
return false;
}
NetworkManager network = NetworkManager.Instance;
if (network != null && string.Equals(network.SteamId, targetSteamId, StringComparison.Ordinal))
{
return false;
}
return !IsAlreadyFriend(targetSteamId);
}
private bool IsAlreadyFriend(string targetSteamId)
{
if (string.IsNullOrWhiteSpace(targetSteamId))
{
return false;
}
ArenaRoomService arena = ArenaRoomService.Instance;
return arena != null && arena.GetFriendsSnapshot().Any(friend =>
friend != null && string.Equals(friend.friend_steam_id, targetSteamId, StringComparison.Ordinal));
}
private async Task SendFriendRequestAsync(string targetSteamId)
{
string actionSignature = $"send:{targetSteamId}";
if (!TryBeginAction(actionSignature))
{
return;
}
ArenaRoomService arena = ArenaRoomService.Instance;
if (arena == null)
{
gNotice.error.display("好友服务未初始化");
EndAction(actionSignature);
return;
}
try
{
await arena.SendFriendRequestAsync(targetSteamId);
ClearSearchParentChildren();
SetSearchDefaultVisible(false);
}
catch (Exception ex)
{
gNotice.error.display(ex.Message);
}
finally
{
EndAction(actionSignature);
}
}
private void RenderFriendRequests(IReadOnlyList<SocialFriendRequestEntry> requests)
{
Transform parent = RequestParentTransform;
if (parent == null || friendRequestPrefab == null)
{
SetRequestDefaultVisible(true);
return;
}
ClearRequestParentChildren();
List<SocialFriendRequestEntry> normalized = (requests ?? Array.Empty<SocialFriendRequestEntry>())
.Where(request => request != null && request.id > 0 && !string.IsNullOrWhiteSpace(request.from_steam_id))
.OrderByDescending(request => request.created_at ?? string.Empty, StringComparer.Ordinal)
.ThenByDescending(request => request.id)
.ToList();
for (int index = 0; index < normalized.Count; index++)
{
SocialFriendRequestEntry request = normalized[index];
GameObject cardObject = Instantiate(friendRequestPrefab, parent, false);
_spawnedRequestCards[request.id] = cardObject;
cardObject.transform.SetSiblingIndex(index);
friendRequestPrefab prefab = cardObject.GetComponent<friendRequestPrefab>();
if (prefab != null)
{
prefab.Bind(
request,
() => AcceptRequestAsync(request.id),
() => RejectRequestAsync(request.id),
RequestCooldownSeconds);
}
}
SetRequestDefaultVisible(normalized.Count == 0);
}
private async Task AcceptRequestAsync(long requestId)
{
string actionSignature = $"accept:{requestId}";
if (!TryBeginAction(actionSignature))
{
return;
}
ArenaRoomService arena = ArenaRoomService.Instance;
if (arena == null)
{
gNotice.error.display("好友服务未初始化");
EndAction(actionSignature);
return;
}
try
{
await arena.AcceptFriendRequestAsync(requestId);
}
catch (Exception ex)
{
gNotice.error.display(ex.Message);
}
finally
{
EndAction(actionSignature);
}
}
private async Task RejectRequestAsync(long requestId)
{
string actionSignature = $"reject:{requestId}";
if (!TryBeginAction(actionSignature))
{
return;
}
ArenaRoomService arena = ArenaRoomService.Instance;
if (arena == null)
{
gNotice.error.display("好友服务未初始化");
EndAction(actionSignature);
return;
}
try
{
await arena.RejectFriendRequestAsync(requestId);
}
catch (Exception ex)
{
gNotice.error.display(ex.Message);
}
finally
{
EndAction(actionSignature);
}
}
private void HandleQuickAcceptAll()
{
const string actionSignature = "batch:accept";
if (_isBatchProcessing || !TryBeginAction(actionSignature))
{
return;
}
BeginButtonCooldown(quickAcceptAll);
_ = ProcessAllRequestsAsync(accept: true, actionSignature);
}
private void HandleQuickRejectAll()
{
const string actionSignature = "batch:reject";
if (_isBatchProcessing || !TryBeginAction(actionSignature))
{
return;
}
BeginButtonCooldown(quickRejectAll);
_ = ProcessAllRequestsAsync(accept: false, actionSignature);
}
private async Task ProcessAllRequestsAsync(bool accept, string actionSignature)
{
ArenaRoomService arena = ArenaRoomService.Instance;
if (arena == null)
{
gNotice.error.display("好友服务未初始化");
EndAction(actionSignature);
return;
}
_isBatchProcessing = true;
SetBatchButtonsInteractable(false);
try
{
List<long> requestIds = arena.GetFriendRequestsSnapshot()
.Where(request => request != null && request.id > 0)
.Select(request => request.id)
.ToList();
foreach (long requestId in requestIds)
{
if (accept)
{
await arena.AcceptFriendRequestAsync(requestId);
}
else
{
await arena.RejectFriendRequestAsync(requestId);
}
}
}
catch (Exception ex)
{
gNotice.error.display(ex.Message);
}
finally
{
_isBatchProcessing = false;
EndAction(actionSignature);
StartCoroutine(ReenableBatchButtonsAfterCooldown());
}
}
private string BuildSearchSignature()
{
string query = searchFriend_iptfd != null ? searchFriend_iptfd.text?.Trim() : string.Empty;
return string.IsNullOrWhiteSpace(query) ? string.Empty : $"search:{query}";
}
private bool TryBeginAction(string actionSignature)
{
if (string.IsNullOrWhiteSpace(actionSignature))
{
return false;
}
float now = Time.unscaledTime;
CleanupExpiredActions(now);
if (_pendingActions.Contains(actionSignature))
{
return false;
}
if (_actionCooldownUntil.TryGetValue(actionSignature, out float cooldownUntil) && cooldownUntil > now)
{
return false;
}
_pendingActions.Add(actionSignature);
_actionCooldownUntil[actionSignature] = now + RequestCooldownSeconds;
return true;
}
private void EndAction(string actionSignature)
{
if (string.IsNullOrWhiteSpace(actionSignature))
{
return;
}
_pendingActions.Remove(actionSignature);
}
private void CleanupExpiredActions(float now)
{
List<string> expiredKeys = null;
foreach (KeyValuePair<string, float> entry in _actionCooldownUntil)
{
if (_pendingActions.Contains(entry.Key) || entry.Value > now)
{
continue;
}
expiredKeys ??= new List<string>();
expiredKeys.Add(entry.Key);
}
if (expiredKeys == null)
{
return;
}
foreach (string key in expiredKeys)
{
_actionCooldownUntil.Remove(key);
}
}
private void BeginButtonCooldown(Button button)
{
if (button == null)
{
return;
}
StartCoroutine(ButtonCooldownRoutine(button));
}
private IEnumerator ButtonCooldownRoutine(Button button)
{
button.interactable = false;
yield return new WaitForSecondsRealtime(RequestCooldownSeconds);
if (button != null)
{
button.interactable = true;
}
}
private IEnumerator ReenableBatchButtonsAfterCooldown()
{
yield return new WaitForSecondsRealtime(RequestCooldownSeconds);
SetBatchButtonsInteractable(true);
}
private void SetBatchButtonsInteractable(bool interactable)
{
if (quickAcceptAll != null)
{
quickAcceptAll.interactable = interactable;
}
if (quickRejectAll != null)
{
quickRejectAll.interactable = interactable;
}
}
private void ClearSearchParentChildren()
{
Transform parent = friendSearchParent;
if (parent == null)
{
return;
}
for (int index = parent.childCount - 1; index >= 0; index--)
{
Transform child = parent.GetChild(index);
if (child != null)
{
Destroy(child.gameObject);
}
}
_spawnedSearchCard = null;
}
private void ClearRequestParentChildren()
{
Transform parent = RequestParentTransform;
if (parent != null)
{
for (int index = parent.childCount - 1; index >= 0; index--)
{
Transform child = parent.GetChild(index);
if (child != null)
{
Destroy(child.gameObject);
}
}
}
_spawnedRequestCards.Clear();
}
private void SetSearchDefaultVisible(bool visible)
{
if (defaultFriendSearchText != null)
{
defaultFriendSearchText.gameObject.SetActive(visible);
}
}
private void SetRequestDefaultVisible(bool visible)
{
if (defaultFriendRequestText != null)
{
defaultFriendRequestText.gameObject.SetActive(visible);
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: cfee62e88f648294aa2f71387a1e58b5