csharp
// 获取属性
public T GetProperty<T>(string key, T defaultValue = default)
{
if (properties.TryGetValue(key, out object value))
{
try
{
return (T)Convert.ChangeType(value, typeof(T));
}
catch
{
return defaultValue;
}
}
return defaultValue;
}
}
// 虚拟土地资产
[Serializable]
public class VirtualLandAsset : BlockchainAsset
{
public VirtualLandAsset(string id, string owner) : base(id, owner, "VirtualLand")
{
}
// 设置土地坐标
public void SetCoordinates(int x, int y)
{
SetProperty("x", x);
SetProperty("y", y);
}
// 设置土地尺寸
public void SetSize(int width, int height)
{
SetProperty("width", width);
SetProperty("height", height);
}
// 获取土地坐标X
public int GetX()
{
return GetProperty<int>("x");
}
// 获取土地坐标Y
public int GetY()
{
return GetProperty<int>("y");
}
// 获取土地宽度
public int GetWidth()
{
return GetProperty<int>("width", 1);
}
// 获取土地高度
public int GetHeight()
{
return GetProperty<int>("height", 1);
}
}
// 虚拟物品资产
[Serializable]
public class VirtualItemAsset : BlockchainAsset
{
public VirtualItemAsset(string id, string owner) : base(id, owner, "VirtualItem")
{
}
// 设置物品类型
public void SetItemType(string itemType)
{
SetProperty("itemType", itemType);
}
// 设置物品稀有度
public void SetRarity(string rarity)
{
SetProperty("rarity", rarity);
}
// 获取物品类型
public string GetItemType()
{
return GetProperty<string>("itemType", "unknown");
}
// 获取物品稀有度
public string GetRarity()
{
return GetProperty<string>("rarity", "common");
}
}
// 区块链交易记录
[Serializable]
public class BlockchainTransaction
{
[SerializeField] private string transactionId;
[SerializeField] private string fromAddress;
[SerializeField] private string toAddress;
[SerializeField] private string assetId;
[SerializeField] private string transactionType;
[SerializeField] private string timestamp;
[SerializeField] private Dictionary<string, object> metadata = new Dictionary<string, object>();
public string TransactionId => transactionId;
public string FromAddress => fromAddress;
public string ToAddress => toAddress;
public string AssetId => assetId;
public string TransactionType => transactionType;
public string Timestamp => timestamp;
public Dictionary<string, object> Metadata => metadata;
public BlockchainTransaction(string id, string from, string to, string asset, string type)
{
transactionId = id;
fromAddress = from;
toAddress = to;
assetId = asset;
transactionType = type;
timestamp = DateTime.UtcNow.ToString("o");
}
// 添加元数据
public void AddMetadata(string key, object value)
{
metadata[key] = value;
}
// 获取交易签名
public string GetSignature()
{
// 构建签名数据
string dataToSign = $"{transactionId}|{fromAddress}|{toAddress}|{assetId}|{transactionType}|{timestamp}|{JsonConvert.SerializeObject(metadata)}";
// 简化的签名实现
byte[] dataBytes = Encoding.UTF8.GetBytes(dataToSign);
using (SHA256 sha256 = SHA256.Create())
{
byte[] hashBytes = sha256.ComputeHash(dataBytes);
return Convert.ToBase64String(hashBytes);
}
}
}
// 区块链服务接口
public interface IBlockchainService
{
Task<bool> InitializeAsync(string userAddress, string privateKey);
Task<BlockchainAsset> GetAssetAsync(string assetId);
Task<bool> TransferAssetAsync(string assetId, string toAddress);
Task<BlockchainAsset> CreateAssetAsync(string assetType, Dictionary<string, object> properties);
Task<bool> UpdateAssetAsync(string assetId, Dictionary<string, object> properties);
Task<List<BlockchainAsset>> GetUserAssetsAsync(string userAddress);
Task<List<BlockchainTransaction>> GetAssetHistoryAsync(string assetId);
}
// 模拟区块链服务实现
public class MockBlockchainService : IBlockchainService
{
private string _userAddress;
private string _privateKey;
private Dictionary<string, BlockchainAsset> _assets = new Dictionary<string, BlockchainAsset>();
private Dictionary<string, List<BlockchainTransaction>> _transactions = new Dictionary<string, List<BlockchainTransaction>>();
// 初始化区块链服务
public Task<bool> InitializeAsync(string userAddress, string privateKey)
{
_userAddress = userAddress;
_privateKey = privateKey;
return Task.FromResult(true);
}
// 获取资产
public Task<BlockchainAsset> GetAssetAsync(string assetId)
{
if (_assets.TryGetValue(assetId, out BlockchainAsset asset))
{
return Task.FromResult(asset);
}
return Task.FromResult<BlockchainAsset>(null);
}
// 转移资产
public Task<bool> TransferAssetAsync(string assetId, string toAddress)
{
if (_assets.TryGetValue(assetId, out BlockchainAsset asset))
{
// 检查所有权
if (asset.OwnerAddress != _userAddress)
{
return Task.FromResult(false);
}
// 创建新资产以更新所有者
BlockchainAsset newAsset;
if (asset is VirtualLandAsset landAsset)
{
newAsset = new VirtualLandAsset(assetId, toAddress);
((VirtualLandAsset)newAsset).SetCoordinates(landAsset.GetX(), landAsset.GetY());
((VirtualLandAsset)newAsset).SetSize(landAsset.GetWidth(), landAsset.GetHeight());
}
else if (asset is VirtualItemAsset itemAsset)
{
newAsset = new VirtualItemAsset(assetId, toAddress);
((VirtualItemAsset)newAsset).SetItemType(itemAsset.GetItemType());
((VirtualItemAsset)newAsset).SetRarity(itemAsset.GetRarity());
}
else
{
newAsset = new BlockchainAsset(assetId, toAddress, asset.AssetType);
}
// 复制所有属性
foreach (var pair in asset.Properties)
{
newAsset.SetProperty(pair.Key, pair.Value);
}
// 更新资产
_assets[assetId] = newAsset;
// 记录交易
string transactionId = Guid.NewGuid().ToString();
BlockchainTransaction transaction = new BlockchainTransaction(
transactionId, _userAddress, toAddress, assetId, "Transfer");
if (!_transactions.ContainsKey(assetId))
{
_transactions[assetId] = new List<BlockchainTransaction>();
}
_transactions[assetId].Add(transaction);
return Task.FromResult(true);
}
return Task.FromResult(false);
}
// 创建资产
public Task<BlockchainAsset> CreateAssetAsync(string assetType, Dictionary<string, object> properties)
{
string assetId = Guid.NewGuid().ToString();
BlockchainAsset asset;
if (assetType == "VirtualLand")
{
asset = new VirtualLandAsset(assetId, _userAddress);
if (properties.TryGetValue("x", out object x) && properties.TryGetValue("y", out object y))
{
((VirtualLandAsset)asset).SetCoordinates(Convert.ToInt32(x), Convert.ToInt32(y));
}
if (properties.TryGetValue("width", out object width) && properties.TryGetValue("height", out object height))
{
((VirtualLandAsset)asset).SetSize(Convert.ToInt32(width), Convert.ToInt32(height));
}
}
else if (assetType == "VirtualItem")
{
asset = new VirtualItemAsset(assetId, _userAddress);
if (properties.TryGetValue("itemType", out object itemType))
{
((VirtualItemAsset)asset).SetItemType(itemType.ToString());
}
if (properties.TryGetValue("rarity", out object rarity))
{
((VirtualItemAsset)asset).SetRarity(rarity.ToString());
}
}
else
{
asset = new BlockchainAsset(assetId, _userAddress, assetType);
}
// 添加剩余的属性
foreach (var pair in properties)
{
if (pair.Key != "x" && pair.Key != "y" && pair.Key != "width" && pair.Key != "height" &&
pair.Key != "itemType" && pair.Key != "rarity")
{
asset.SetProperty(pair.Key, pair.Value);
}
}
// 存储资产
_assets[assetId] = asset;
// 记录创建交易
string transactionId = Guid.NewGuid().ToString();
BlockchainTransaction transaction = new BlockchainTransaction(
transactionId, "0x0000000000000000000000000000000000000000", _userAddress, assetId, "Create");
if (!_transactions.ContainsKey(assetId))
{
_transactions[assetId] = new List<BlockchainTransaction>();
}
_transactions[assetId].Add(transaction);
return Task.FromResult(asset);
}
// 更新资产
public Task<bool> UpdateAssetAsync(string assetId, Dictionary<string, object> properties)
{
if (_assets.TryGetValue(assetId, out BlockchainAsset asset))
{
// 检查所有权
if (asset.OwnerAddress != _userAddress)
{
return Task.FromResult(false);
}
// 更新属性
foreach (var pair in properties)
{
asset.SetProperty(pair.Key, pair.Value);
}
// 记录更新交易
string transactionId = Guid.NewGuid().ToString();
BlockchainTransaction transaction = new BlockchainTransaction(
transactionId, _userAddress, _userAddress, assetId, "Update");
foreach (var pair in properties)
{
transaction.AddMetadata(pair.Key, pair.Value);
}
if (!_transactions.ContainsKey(assetId))
{
_transactions[assetId] = new List<BlockchainTransaction>();
}
_transactions[assetId].Add(transaction);
return Task.FromResult(true);
}
return Task.FromResult(false);
}
// 获取用户资产
public Task<List<BlockchainAsset>> GetUserAssetsAsync(string userAddress)
{
List<BlockchainAsset> userAssets = new List<BlockchainAsset>();
foreach (var asset in _assets.Values)
{
if (asset.OwnerAddress == userAddress)
{
userAssets.Add(asset);
}
}
return Task.FromResult(userAssets);
}
// 获取资产历史
public Task<List<BlockchainTransaction>> GetAssetHistoryAsync(string assetId)
{
if (_transactions.TryGetValue(assetId, out List<BlockchainTransaction> history))
{
return Task.FromResult(history);
}
return Task.FromResult(new List<BlockchainTransaction>());
}
}
// 实际区块链服务实现 (与具体区块链集成)
public class EthereumBlockchainService : IBlockchainService
{
private string _userAddress;
private string _privateKey;
private string _nodeUrl;
private string _contractAddress;
// 构造函数
public EthereumBlockchainService(string nodeUrl, string contractAddress)
{
_nodeUrl = nodeUrl;
_contractAddress = contractAddress;
}
// 初始化区块链服务
public async Task<bool> InitializeAsync(string userAddress, string privateKey)
{
_userAddress = userAddress;
_privateKey = privateKey;
// 检查连接性
bool isConnected = await CheckConnectionAsync();
return isConnected;
}
// 检查连接性
private async Task<bool> CheckConnectionAsync()
{
// 构建API请求检查节点连接性
using (UnityWebRequest request = UnityWebRequest.Get(_nodeUrl))
{
await request.SendWebRequest();
return !request.isNetworkError && !request.isHttpError;
}
}
// 获取资产
public async Task<BlockchainAsset> GetAssetAsync(string assetId)
{
// 构建API请求以获取资产数据
string url = $"{_nodeUrl}/api/assets/{assetId}";
using (UnityWebRequest request = UnityWebRequest.Get(url))
{
await request.SendWebRequest();
if (request.isNetworkError || request.isHttpError)
{
Debug.LogError($"Failed to fetch asset: {request.error}");
return null;
}
string json = request.downloadHandler.text;
return JsonConvert.DeserializeObject<BlockchainAsset>(json);
}
}
// 转移资产
public async Task<bool> TransferAssetAsync(string assetId, string toAddress)
{
// 构建交易数据
Dictionary<string, object> transactionData = new Dictionary<string, object>
{
{ "from", _userAddress },
{ "to", toAddress },
{ "assetId", assetId },
{ "action", "transfer" }
};
// 序列化交易数据
string json = JsonConvert.SerializeObject(transactionData);
// 构建API请求
string url = $"{_nodeUrl}/api/transactions";
using (UnityWebRequest request = UnityWebRequest.Post(url, "POST"))
{
byte[] bodyRaw = Encoding.UTF8.GetBytes(json);
request.uploadHandler = new UploadHandlerRaw(bodyRaw);
request.downloadHandler = new DownloadHandlerBuffer();
request.SetRequestHeader("Content-Type", "application/json");
request.SetRequestHeader("Authorization", $"Bearer {_privateKey}");
await request.SendWebRequest();
if (request.isNetworkError || request.isHttpError)
{
Debug.LogError($"Transfer failed: {request.error}");
return false;
}
return true;
}
}
// 创建资产
public async Task<BlockchainAsset> CreateAssetAsync(string assetType, Dictionary<string, object> properties)
{
// 构建创建资产的数据
Dictionary<string, object> assetData = new Dictionary<string, object>
{
{ "owner", _userAddress },
{ "type", assetType },
{ "properties", properties }
};
// 序列化数据
string json = JsonConvert.SerializeObject(assetData);
// 构建API请求
string url = $"{_nodeUrl}/api/assets";
using (UnityWebRequest request = UnityWebRequest.Post(url, "POST"))
{
byte[] bodyRaw = Encoding.UTF8.GetBytes(json);
request.uploadHandler = new UploadHandlerRaw(bodyRaw);
request.downloadHandler = new DownloadHandlerBuffer();
request.SetRequestHeader("Content-Type", "application/json");
request.SetRequestHeader("Authorization", $"Bearer {_privateKey}");
await request.SendWebRequest();
if (request.isNetworkError || request.isHttpError)
{
Debug.LogError($"Create asset failed: {request.error}");
return null;
}
string responseJson = request.downloadHandler.text;
return JsonConvert.DeserializeObject<BlockchainAsset>(responseJson);
}
}
// 更新资产
public async Task<bool> UpdateAssetAsync(string assetId, Dictionary<string, object> properties)
{
// 构建更新资产的数据
Dictionary<string, object> updateData = new Dictionary<string, object>
{
{ "owner", _userAddress },
{ "properties", properties }
};
// 序列化数据
string json = JsonConvert.SerializeObject(updateData);
// 构建API请求
string url = $"{_nodeUrl}/api/assets/{assetId}";
using (UnityWebRequest request = UnityWebRequest.Put(url, json))
{
byte[] bodyRaw = Encoding.UTF8.GetBytes(json);
request.uploadHandler = new UploadHandlerRaw(bodyRaw);
request.downloadHandler = new DownloadHandlerBuffer();
request.SetRequestHeader("Content-Type", "application/json");
request.SetRequestHeader("Authorization", $"Bearer {_privateKey}");
await request.SendWebRequest();
if (request.isNetworkError || request.isHttpError)
{
Debug.LogError($"Update asset failed: {request.error}");
return false;
}
return true;
}
}
// 获取用户资产
public async Task<List<BlockchainAsset>> GetUserAssetsAsync(string userAddress)
{
// 构建API请求以获取用户资产
string url = $"{_nodeUrl}/api/users/{userAddress}/assets";
using (UnityWebRequest request = UnityWebRequest.Get(url))
{
await request.SendWebRequest();
if (request.isNetworkError || request.isHttpError)
{
Debug.LogError($"Failed to fetch user assets: {request.error}");
return new List<BlockchainAsset>();
}
string json = request.downloadHandler.text;
return JsonConvert.DeserializeObject<List<BlockchainAsset>>(json);
}
}
// 获取资产历史
public async Task<List<BlockchainTransaction>> GetAssetHistoryAsync(string assetId)
{
// 构建API请求以获取资产交易历史
string url = $"{_nodeUrl}/api/assets/{assetId}/history";
using (UnityWebRequest request = UnityWebRequest.Get(url))
{
await request.SendWebRequest();
if (request.isNetworkError || request.isHttpError)
{
Debug.LogError($"Failed to fetch asset history: {request.error}");
return new List<BlockchainTransaction>();
}
string json = request.downloadHandler.text;
return JsonConvert.DeserializeObject<List<BlockchainTransaction>>(json);
}
}
}
// 持久化世界管理器
public class PersistentWorldManager : MonoBehaviour
{
[Header("Blockchain Settings")]
[SerializeField] private bool useMockBlockchain = true;
[SerializeField] private string nodeUrl = "https://api.example.org/blockchain";
[SerializeField] private string contractAddress = "0x1234567890123456789012345678901234567890";
[Header("User Settings")]
[SerializeField] private string userAddress = "0xMyUserAddress";
[SerializeField] private string privateKey = "MyPrivateKey";
[Header("World Settings")]
[SerializeField] private int worldSizeX = 100;
[SerializeField] private int worldSizeY = 100;
[SerializeField] private GameObject landPrefab;
[SerializeField] private GameObject buildingPrefab;
[SerializeField] private GameObject itemPrefab;
// 区块链服务
private IBlockchainService _blockchainService;
// 世界网格
private Dictionary<Vector2Int, GameObject> _worldGrid = new Dictionary<Vector2Int, GameObject>();
// 用户拥有的资产
private List<BlockchainAsset> _userAssets = new List<BlockchainAsset>();
// 是否已初始化
private bool _isInitialized = false;
private async void Start()
{
// 初始化区块链服务
if (useMockBlockchain)
{
_blockchainService = new MockBlockchainService();
}
else
{
_blockchainService = new EthereumBlockchainService(nodeUrl, contractAddress);
}
// 初始化服务
bool initialized = await _blockchainService.InitializeAsync(userAddress, privateKey);
if (!initialized)
{
Debug.LogError("Failed to initialize blockchain service");
return;
}
_isInitialized = true;
// 加载用户资产
await LoadUserAssetsAsync();
// 构建初始世界
BuildWorld();
}
// 加载用户资产
private async Task LoadUserAssetsAsync()
{
if (!_isInitialized)
return;
_userAssets = await _blockchainService.GetUserAssetsAsync(userAddress);
Debug.Log($"Loaded {_userAssets.Count} assets for user {userAddress}");
}
// 构建世界
private void BuildWorld()
{
// 清除现有世界
foreach (var obj in _worldGrid.Values)
{
Destroy(obj);
}
_worldGrid.Clear();
// 根据用户拥有的土地资产构建世界
foreach (var asset in _userAssets)
{
if (asset.AssetType == "VirtualLand" && asset is VirtualLandAsset landAsset)
{
// 获取土地坐标和尺寸
int x = landAsset.GetX();
int y = landAsset.GetY();
int width = landAsset.GetWidth();
int height = landAsset.GetHeight();
// 实例化土地
for (int dx = 0; dx < width; dx++)
{
for (int dy = 0; dy < height; dy++)
{
Vector2Int gridPos = new Vector2Int(x + dx, y + dy);
Vector3 worldPos = new Vector3(gridPos.x * 10, 0, gridPos.y * 10);
if (!_worldGrid.ContainsKey(gridPos))
{
GameObject landObj = Instantiate(landPrefab, worldPos, Quaternion.identity);
landObj.name = $"Land_{gridPos.x}_{gridPos.y}";
// 添加持久性组件
PersistentObject persistentObj = landObj.AddComponent<PersistentObject>();
persistentObj.Initialize(landAsset);
_worldGrid[gridPos] = landObj;
}
}
}
}
}
// 加载建筑物和物品
foreach (var asset in _userAssets)
{
if (asset.AssetType == "Building")
{
int x = asset.GetProperty<int>("x");
int y = asset.GetProperty<int>("y");
Vector2Int gridPos = new Vector2Int(x, y);
if (_worldGrid.ContainsKey(gridPos))
{
Vector3 worldPos = _worldGrid[gridPos].transform.position + Vector3.up * 0.5f;
GameObject buildingObj = Instantiate(buildingPrefab, worldPos, Quaternion.identity);
buildingObj.name = $"Building_{asset.AssetId}";
// 添加持久性组件
PersistentObject persistentObj = buildingObj.AddComponent<PersistentObject>();
persistentObj.Initialize(asset);
}
}
else if (asset.AssetType == "VirtualItem" && asset is VirtualItemAsset itemAsset)
{
// 检查物品是否放置在世界中
if (asset.Properties.ContainsKey("x") && asset.Properties.ContainsKey("y"))
{
int x = asset.GetProperty<int>("x");
int y = asset.GetProperty<int>("y");
Vector2Int gridPos = new Vector2Int(x, y);
if (_worldGrid.ContainsKey(gridPos))
{
Vector3 worldPos = _worldGrid[gridPos].transform.position + Vector3.up * 1.0f;
GameObject itemObj = Instantiate(itemPrefab, worldPos, Quaternion.identity);
itemObj.name = $"Item_{asset.AssetId}";
// 根据物品类型自定义外观
CustomizeItemAppearance(itemObj, itemAsset);
// 添加持久性组件
PersistentObject persistentObj = itemObj.AddComponent<PersistentObject>();
persistentObj.Initialize(asset);
}
}
}
}
}
// 自定义物品外观
private void CustomizeItemAppearance(GameObject itemObj, VirtualItemAsset itemAsset)
{
string itemType = itemAsset.GetItemType();
string rarity = itemAsset.GetRarity();
// 获取渲染器组件
Renderer renderer = itemObj.GetComponent<Renderer>();
if (renderer != null)
{
// 根据稀有度设置颜色
switch (rarity.ToLower())
{
case "common":
renderer.material.color = Color.white;
break;
case "uncommon":
renderer.material.color = Color.green;
break;
case "rare":
renderer.material.color = Color.blue;
break;
case "epic":
renderer.material.color = Color.magenta;
break;
case "legendary":
renderer.material.color = Color.yellow;
break;
default:
renderer.material.color = Color.gray;
break;
}
}
// 根据物品类型设置比例或其他属性
switch (itemType.ToLower())
{
case "weapon":
itemObj.transform.localScale = new Vector3(0.2f, 1.0f, 0.2f);
break;
case "armor":
itemObj.transform.localScale = new Vector3(0.5f, 0.7f, 0.5f);
break;
case "potion":
itemObj.transform.localScale = new Vector3(0.3f, 0.5f, 0.3f);
break;
default:
itemObj.transform.localScale = Vector3.one * 0.5f;
break;
}
}
// 购买新的土地
public async Task<bool> PurchaseLandAsync(int x, int y, int width, int height)
{
if (!_isInitialized)
return false;
// 创建属性字典
Dictionary<string, object> properties = new Dictionary<string, object>
{
{ "x", x },
{ "y", y },
{ "width", width },
{ "height", height }
};
// 创建新的土地资产
BlockchainAsset newLand = await _blockchainService.CreateAssetAsync("VirtualLand", properties);
if (newLand != null)
{
// 添加到用户资产列表
_userAssets.Add(newLand);
// 更新世界
BuildWorld();
return true;
}
return false;
}
// 放置建筑物
public async Task<bool> PlaceBuildingAsync(int x, int y, string buildingType)
{
if (!_isInitialized)
return false;
// 检查用户是否拥有该位置的土地
Vector2Int gridPos = new Vector2Int(x, y);
bool ownsTile = false;
foreach (var asset in _userAssets)
{
if (asset.AssetType == "VirtualLand" && asset is VirtualLandAsset landAsset)
{
int landX = landAsset.GetX();
int landY = landAsset.GetY();
int width = landAsset.GetWidth();
int height = landAsset.GetHeight();
if (x >= landX && x < landX + width && y >= landY && y < landY + height)
{
ownsTile = true;
break;
}
}
}
if (!ownsTile)
{
Debug.LogError($"User does not own the land at position ({x}, {y})");
return false;
}
// 创建属性字典
Dictionary<string, object> properties = new Dictionary<string, object>
{
{ "x", x },
{ "y", y },
{ "buildingType", buildingType }
};
// 创建新的建筑物资产
BlockchainAsset newBuilding = await _blockchainService.CreateAssetAsync("Building", properties);
if (newBuilding != null)
{
// 添加到用户资产列表
_userAssets.Add(newBuilding);
// 更新世界
BuildWorld();
return true;
}
return false;
}
// 放置物品
public async Task<bool> PlaceItemAsync(int x, int y, string itemType, string rarity)
{
if (!_isInitialized)
return false;
// 检查用户是否拥有该位置的土地
Vector2Int gridPos = new Vector2Int(x, y);
bool ownsTile = false;
foreach (var asset in _userAssets)
{
if (asset.AssetType == "VirtualLand" && asset is VirtualLandAsset landAsset)
{
int landX = landAsset.GetX();
int landY = landAsset.GetY();
int width = landAsset.GetWidth();
int height = landAsset.GetHeight();
if (x >= landX && x < landX + width && y >= landY && y < landY + height)
{
ownsTile = true;
break;
}
}
}
if (!ownsTile)
{
Debug.LogError($"User does not own the land at position ({x}, {y})");
return false;
}
// 创建属性字典
Dictionary<string, object> properties = new Dictionary<string, object>
{
{ "x", x },
{ "y", y },
{ "itemType", itemType },
{ "rarity", rarity }
};
// 创建新的物品资产
BlockchainAsset newItem = await _blockchainService.CreateAssetAsync("VirtualItem", properties);
if (newItem != null)
{
// 添加到用户资产列表
_userAssets.Add(newItem);
// 更新世界
BuildWorld();
return true;
}
return false;
}
// 转移资产
public async Task<bool> TransferAssetAsync(string assetId, string toAddress)
{
if (!_isInitialized)
return false;
bool success = await _blockchainService.TransferAssetAsync(assetId, toAddress);
if (success)
{
// 从用户资产列表中移除
_userAssets.RemoveAll(a => a.AssetId == assetId);
// 更新世界
BuildWorld();
}
return success;
}
// 更新资产属性
public async Task<bool> UpdateAssetPropertiesAsync(string assetId, Dictionary<string, object> properties)
{
if (!_isInitialized)
return false;
bool success = await _blockchainService.UpdateAssetAsync(assetId, properties);
if (success)
{
// 刷新用户资产
await LoadUserAssetsAsync();
// 更新世界
BuildWorld();
}
return success;
}
// 获取资产历史
public async Task<List<BlockchainTransaction>> GetAssetHistoryAsync(string assetId)
{
if (!_isInitialized)
return new List<BlockchainTransaction>();
return await _blockchainService.GetAssetHistoryAsync(assetId);
}
}
// 持久性对象组件
public class PersistentObject : MonoBehaviour
{
// 关联的区块链资产
private BlockchainAsset _asset;
// 初始化
public void Initialize(BlockchainAsset asset)
{
_asset = asset;
// 根据资产类型设置组件行为
SetupBehaviorByAssetType();
}
// 根据资产类型设置行为
private void SetupBehaviorByAssetType()
{
if (_asset.AssetType == "VirtualLand")
{
// 为土地添加适当的组件
gameObject.AddComponent<LandBehavior>();
}
else if (_asset.AssetType == "Building")
{
// 为建筑添加适当的组件
BuildingBehavior building = gameObject.AddComponent<BuildingBehavior>();
building.Initialize(_asset.GetProperty<string>("buildingType", "default"));
}
else if (_asset.AssetType == "VirtualItem" && _asset is VirtualItemAsset itemAsset)
{
// 为物品添加适当的组件
ItemBehavior item = gameObject.AddComponent<ItemBehavior>();
item.Initialize(itemAsset.GetItemType(), itemAsset.GetRarity());
}
}
// 获取资产ID
public string GetAssetId()
{
return _asset?.AssetId;
}
// 获取资产
public BlockchainAsset GetAsset()
{
return _asset;
}
}
// 土地行为组件
public class LandBehavior : MonoBehaviour
{
private void OnMouseDown()
{
// 处理土地点击事件
Debug.Log($"Land clicked: {gameObject.name}");
}
}
// 建筑行为组件
public class BuildingBehavior : MonoBehaviour
{
private string _buildingType;
public void Initialize(string buildingType)
{
_buildingType = buildingType;
// 根据建筑类型设置外观
SetupAppearance();
}
private void SetupAppearance()
{
// 设置建筑外观
// 在实际实现中,可能会加载不同的模型或材质
}
private void OnMouseDown()
{
// 处理建筑点击事件
Debug.Log($"Building clicked: {gameObject.name} (Type: {_buildingType})");
}
}
// 物品行为组件
public class ItemBehavior : MonoBehaviour
{
private string _itemType;
private string _rarity;
public void Initialize(string itemType, string rarity)
{
_itemType = itemType;
_rarity = rarity;
// 根据物品类型和稀有度设置行为
SetupBehavior();
}
private void SetupBehavior()
{
// 设置物品行为
// 在实际实现中,可能会根据物品类型添加不同的交互组件
}
private void OnMouseDown()
{
// 处理物品点击事件
Debug.Log($"Item clicked: {gameObject.name} (Type: {_itemType}, Rarity: {_rarity})");
}
}
25. 高级用户界面与交互系统
25.1 虚拟现实交互框架
csharp
// 虚拟现实交互框架
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR;
using UnityEngine.XR.Interaction.Toolkit;
// 手部跟踪数据结构
public struct HandTrackingData
{
public Transform wristTransform;
public Transform thumbProximal;
public Transform thumbIntermediate;
public Transform thumbDistal;
public Transform thumbTip;
public Transform indexProximal;
public Transform indexIntermediate;
public Transform indexDistal;
public Transform indexTip;
public Transform middleProximal;
public Transform middleIntermediate;
public Transform middleDistal;
public Transform middleTip;
public Transform ringProximal;
public Transform ringIntermediate;
public Transform ringDistal;
public Transform ringTip;
public Transform pinkyProximal;
public Transform pinkyIntermediate;
public Transform pinkyDistal;
public Transform pinkyTip;
// 手指弯曲度 (0-1,0为伸直,1为完全弯曲)
public float thumbCurl;
public float indexCurl;
public float middleCurl;
public float ringCurl;
public float pinkyCurl;
// 手掌朝向
public Vector3 palmNormal;
public Vector3 palmDirection;
}
// 手势识别器接口
public interface IGestureRecognizer
{
bool RecognizeGesture(HandTrackingData handData, out string gestureName, out float confidence);
void AddGesture(string name, List<HandTrackingData> samples);
void RemoveGesture(string name);
void ClearGestures();
}
// 基于手指弯曲的简单手势识别器
public class FingerCurlGestureRecognizer : IGestureRecognizer
{
// 手势定义
private class GestureDefinition
{
public string name;
public Vector5 curlPattern; // 使用Vector5存储5个手指的弯曲模式
public float threshold; // 匹配阈值
public GestureDefinition(string name, Vector5 curlPattern, float threshold = 0.2f)
{
this.name = name;
this.curlPattern = curlPattern;
this.threshold = threshold;
}
}
private List<GestureDefinition> _gestures = new List<GestureDefinition>();
// 构造函数 - 添加默认手势
public FingerCurlGestureRecognizer()
{
// 添加一些常见手势
// 握拳 - 所有手指都弯曲
_gestures.Add(new GestureDefinition("Fist", new Vector5(1, 1, 1, 1, 1)));
// 指向 - 食指伸直,其他手指弯曲
_gestures.Add(new GestureDefinition("Point", new Vector5(1, 0, 1, 1, 1)));
// 大拇指向上 - 拇指伸直,其他手指弯曲
_gestures.Add(new GestureDefinition("ThumbsUp", new Vector5(0, 1, 1, 1, 1)));
// 手掌张开 - 所有手指伸直
_gestures.Add(new GestureDefinition("OpenPalm", new Vector5(0, 0, 0, 0, 0)));
// 手枪形状 - 食指伸直,拇指伸直,其他弯曲
_gestures.Add(new GestureDefinition("Gun", new Vector5(0, 0, 1, 1, 1)));
// OK手势 - 拇指和食指接触,其他手指伸展
_gestures.Add(new GestureDefinition("OK", new Vector5(0.5f, 0.5f, 0, 0, 0)));
}
// 识别手势
public bool RecognizeGesture(HandTrackingData handData, out string gestureName, out float confidence)
{
gestureName = string.Empty;
confidence = 0f;
// 从手部数据中提取手指弯曲信息
Vector5 currentCurl = new Vector5(
handData.thumbCurl,
handData.indexCurl,
handData.middleCurl,
handData.ringCurl,
handData.pinkyCurl
);
// 检查每个已注册的手势
float bestMatch = float.MaxValue;
foreach (var gesture in _gestures)
{
// 计算当前手指弯曲与手势模式的差异
float diff = Vector5.Distance(currentCurl, gesture.curlPattern);
if (diff < bestMatch && diff < gesture.threshold)
{
bestMatch = diff;
gestureName = gesture.name;
confidence = 1f - (diff / gesture.threshold);
}
}
return !string.IsNullOrEmpty(gestureName);
}
// 添加新手势
public void AddGesture(string name, List<HandTrackingData> samples)
{
if (samples == null || samples.Count == 0)
return;
// 计算所有样本的平均弯曲值
Vector5 averageCurl = Vector5.zero;
foreach (var sample in samples)
{
averageCurl.x += sample.thumbCurl;
averageCurl.y += sample.indexCurl;
averageCurl.z += sample.middleCurl;
averageCurl.w += sample.ringCurl;
averageCurl.v += sample.pinkyCurl;
}
averageCurl.x /= samples.Count;
averageCurl.y /= samples.Count;
averageCurl.z /= samples.Count;
averageCurl.w /= samples.Count;
averageCurl.v /= samples.Count;
// 移除同名的现有手势
_gestures.RemoveAll(g => g.name == name);
// 添加新手势
_gestures.Add(new GestureDefinition(name, averageCurl));
}
// 移除手势
public void RemoveGesture(string name)
{
_gestures.RemoveAll(g => g.name == name);
}
// 清除所有手势
public void ClearGestures()
{
_gestures.Clear();
}
}
// 自定义Vector5结构
public struct Vector5
{
public float x, y, z, w, v;
public Vector5(float x, float y, float z, float w, float v)
{
this.x = x;
this.y = y;
this.z = z;
this.w = w;
this.v = v;
}
public static Vector5 zero => new Vector5(0, 0, 0, 0, 0);
// 计算两个Vector5之间的欧几里得距离
public static float Distance(Vector5 a, Vector5 b)
{
float dx = a.x - b.x;
float dy = a.y - b.y;
float dz = a.z - b.z;
float dw = a.w - b.w;
float dv = a.v - b.v;
return Mathf.Sqrt(dx * dx + dy * dy + dz * dz + dw * dw + dv * dv);
}
}
// VR交互管理器
public class VRInteractionManager : MonoBehaviour
{
[Header("References")]
[SerializeField] private XRController leftController;
[SerializeField] private XRController rightController;
[SerializeField] private GameObject leftHandModel;
[SerializeField] private GameObject rightHandModel;
[Header("Interaction Settings")]
[SerializeField] private float grabThreshold = 0.5f;
[SerializeField] private float pinchThreshold = 0.3f;
[SerializeField] private float pointThreshold = 0.1f;
[SerializeField] private float raycastDistance = 10f;
[SerializeField] private LayerMask interactableLayers;
[Header("Haptic Feedback")]
[SerializeField] private float hapticAmplitude = 0.5f;
[SerializeField] private float hapticDuration = 0.1f;
// 手部跟踪数据
private HandTrackingData _leftHandData;
private HandTrackingData _rightHandData;
// 手势识别器
private IGestureRecognizer _gestureRecognizer;
// 当前交互对象
private Dictionary<InteractionHand, IVRInteractable> _currentInteractables = new Dictionary<InteractionHand, IVRInteractable>();
// 手部枚举
public enum InteractionHand
{
Left,
Right
}
private void Awake()
{
// 初始化手势识别器
_gestureRecognizer = new FingerCurlGestureRecognizer();
// 初始化交互字典
_currentInteractables[InteractionHand.Left] = null;
_currentInteractables[InteractionHand.Right] = null;
// 确保手模型设置正确
if (leftHandModel != null)
{
InitializeHandModel(leftHandModel, ref _leftHandData);
}
if (rightHandModel != null)
{
InitializeHandModel(rightHandModel, ref _rightHandData);
}
}
private void Update()
{
// 更新手部追踪数据
if (leftHandModel != null)
{
UpdateHandData(leftHandModel, ref _leftHandData);
ProcessHandInteractions(InteractionHand.Left, _leftHandData);
}
if (rightHandModel != null)
{
UpdateHandData(rightHandModel, ref _rightHandData);
ProcessHandInteractions(InteractionHand.Right, _rightHandData);
}
}
// 初始化手部模型引用
private void InitializeHandModel(GameObject handModel, ref HandTrackingData handData)
{
// 查找所有手部骨骼
Transform wrist = FindChildRecursive(handModel.transform, "Wrist");
if (wrist != null)
{
handData.wristTransform = wrist;
// 初始化拇指骨骼
handData.thumbProximal = FindChildRecursive(wrist, "Thumb_Proximal");
handData.thumbIntermediate = FindChildRecursive(wrist, "Thumb_Intermediate");
handData.thumbDistal = FindChildRecursive(wrist, "Thumb_Distal");
handData.thumbTip = FindChildRecursive(wrist, "Thumb_Tip");
// 初始化食指骨骼
handData.indexProximal = FindChildRecursive(wrist, "Index_Proximal");
handData.indexIntermediate = FindChildRecursive(wrist, "Index_Intermediate");
handData.indexDistal = FindChildRecursive(wrist, "Index_Distal");
handData.indexTip = FindChildRecursive(wrist, "Index_Tip");
// 初始化中指骨骼
handData.middleProximal = FindChildRecursive(wrist, "Middle_Proximal");
handData.middleIntermediate = FindChildRecursive(wrist, "Middle_Intermediate");
handData.middleDistal = FindChildRecursive(wrist, "Middle_Distal");
handData.middleTip = FindChildRecursive(wrist, "Middle_Tip");
// 初始化无名指骨骼
handData.ringProximal = FindChildRecursive(wrist, "Ring_Proximal");
handData.ringIntermediate = FindChildRecursive(wrist, "Ring_Intermediate");
handData.ringDistal = FindChildRecursive(wrist, "Ring_Distal");
handData.ringTip = FindChildRecursive(wrist, "Ring_Tip");
// 初始化小指骨骼
handData.pinkyProximal = FindChildRecursive(wrist, "Pinky_Proximal");
handData.pinkyIntermediate = FindChildRecursive(wrist, "Pinky_Intermediate");
handData.pinkyDistal = FindChildRecursive(wrist, "Pinky_Distal");
handData.pinkyTip = FindChildRecursive(wrist, "Pinky_Tip");
}
}
// 递归查找子物体
private Transform FindChildRecursive(Transform parent, string name)
{
if (parent == null)
return null;
for (int i = 0; i < parent.childCount; i++)
{
Transform child = parent.GetChild(i);
if (child.name.Contains(name))
return child;
Transform found = FindChildRecursive(child, name);
if (found != null)
return found;
}
return null;
}
// 更新手部追踪数据
private void UpdateHandData(GameObject handModel, ref HandTrackingData handData)
{
if (handData.wristTransform == null)
return;
// 计算手掌朝向
if (handData.indexProximal != null && handData.pinkyProximal != null && handData.wristTransform != null)
{
Vector3 indexToWrist = handData.wristTransform.position - handData.indexProximal.position;
Vector3 pinkyToWrist = handData.wristTransform.position - handData.pinkyProximal.position;
handData.palmNormal = Vector3.Cross(indexToWrist, pinkyToWrist).normalized;
handData.palmDirection = Vector3.Cross(handData.palmNormal, Vector3.up).normalized;
}
// 计算手指弯曲度
handData.thumbCurl = CalculateFingerCurl(handData.thumbProximal, handData.thumbIntermediate, handData.thumbDistal, handData.thumbTip);
handData.indexCurl = CalculateFingerCurl(handData.indexProximal, handData.indexIntermediate, handData.indexDistal, handData.indexTip);
handData.middleCurl = CalculateFingerCurl(handData.middleProximal, handData.middleIntermediate, handData.middleDistal, handData.middleTip);
handData.ringCurl = CalculateFingerCurl(handData.ringProximal, handData.ringIntermediate, handData.ringDistal, handData.ringTip);
handData.pinkyCurl = CalculateFingerCurl(handData.pinkyProximal, handData.pinkyIntermediate, handData.pinkyDistal, handData.pinkyTip);
}
// 计算手指弯曲度 (0-1)
private float CalculateFingerCurl(Transform proximal, Transform intermediate, Transform distal, Transform tip)
{
if (proximal == null || intermediate == null || distal == null)
return 0f;
// 计算手指骨骼之间的角度
float angle1 = Vector3.Angle(intermediate.position - proximal.position, distal.position - intermediate.position);
float angle2 = tip != null ? Vector3.Angle(distal.position - intermediate.position, tip.position - distal.position) : 0f;
// 根据角度计算弯曲度
float maxAngle = 80f; // 假设最大弯曲角度为80度
return Mathf.Clamp01((angle1 + angle2) / (2f * maxAngle));
}
// 处理手部交互
private void ProcessHandInteractions(InteractionHand hand, HandTrackingData handData)
{
// 识别手势
string gestureName;
float confidence;
bool gestureRecognized = _gestureRecognizer.RecognizeGesture(handData, out gestureName, out confidence);
// 获取控制器
XRController controller = (hand == InteractionHand.Left) ? leftController : rightController;
// 处理不同的手势和交互
if (gestureRecognized)
{
switch (gestureName)
{
case "Point":
ProcessPointingInteraction(hand, handData, controller);
break;
case "Fist":
case "Grab":
ProcessGrabInteraction(hand, handData, controller);
break;
case "Pinch":
ProcessPinchInteraction(hand, handData, controller);
break;
case "OpenPalm":
ProcessReleaseInteraction(hand, handData, controller);
break;
default:
CheckControllerButtons(hand, controller);
break;
}
}
else
{
// 如果没有识别出特定手势,检查控制器按钮输入
CheckControllerButtons(hand, controller);
}
}
// 处理指向交互
private void ProcessPointingInteraction(InteractionHand hand, HandTrackingData handData, XRController controller)
{
// 创建射线,从食指尖端向前
Vector3 rayOrigin = handData.indexTip.position;
Vector3 rayDirection = handData.indexTip.forward;
Debug.DrawRay(rayOrigin, rayDirection * raycastDistance, Color.blue);
// 执行射线检测
RaycastHit hit;
if (Physics.Raycast(rayOrigin, rayDirection, out hit, raycastDistance, interactableLayers))
{
// 尝试获取可交互对象
IVRInteractable interactable = hit.collider.GetComponent<IVRInteractable>();
if (interactable != null)
{
// 调用悬停事件
interactable.OnHover(hand);
// 如果按下触发按钮,尝试激活对象
if (controller != null && controller.inputDevice.TryGetFeatureValue(CommonUsages.triggerButton, out bool triggerPressed) && triggerPressed)
{
interactable.OnActivate(hand);
// 提供触觉反馈
if (controller.inputDevice.TryGetHapticCapabilities(out HapticCapabilities capabilities) && capabilities.supportsImpulse)
{
controller.inputDevice.SendHapticImpulse(0, hapticAmplitude, hapticDuration);
}
}
// 更新当前交互对象
if (_currentInteractables[hand] != interactable)
{
if (_currentInteractables[hand] != null)
{
_currentInteractables[hand].OnHoverExit(hand);
}
_currentInteractables[hand] = interactable;
}
}
else
{
// 如果射线没有击中可交互对象,清除当前交互
if (_currentInteractables[hand] != null)
{
_currentInteractables[hand].OnHoverExit(hand);
_currentInteractables[hand] = null;
}
}
}
else
{
// 如果射线没有击中任何对象,清除当前交互
if (_currentInteractables[hand] != null)
{
_currentInteractables[hand].OnHoverExit(hand);
_currentInteractables[hand] = null;
}
}
}
// 处理抓取交互
private void ProcessGrabInteraction(InteractionHand hand, HandTrackingData handData, XRController controller)
{
// 检查手部周围的可抓取对象
Collider[] colliders = Physics.OverlapSphere(handData.wristTransform.position, 0.1f, interactableLayers);
if (colliders.Length > 0)
{
// 找到最近的可交互对象
float closestDistance = float.MaxValue;
IVRInteractable closestInteractable = null;
foreach (var collider in colliders)
{
IVRInteractable interactable = collider.GetComponent<IVRInteractable>();
if (interactable != null && interactable.CanGrab(hand))
{
float distance = Vector3.Distance(handData.wristTransform.position, collider.transform.position);
if (distance < closestDistance)
{
closestDistance = distance;
closestInteractable = interactable;
}
}
}
// 如果找到可抓取对象,进行抓取
if (closestInteractable != null)
{
closestInteractable.OnGrab(hand);
// 提供触觉反馈
if (controller != null && controller.inputDevice.TryGetHapticCapabilities(out HapticCapabilities capabilities) && capabilities.supportsImpulse)
{
controller.inputDevice.SendHapticImpulse(0, hapticAmplitude, hapticDuration);
}
// 更新当前交互对象
_currentInteractables[hand] = closestInteractable;
}
}
}
// 处理捏取交互
private void ProcessPinchInteraction(InteractionHand hand, HandTrackingData handData, XRController controller)
{
// 检查拇指和食指之间的距离
if (handData.thumbTip != null && handData.indexTip != null)
{
float distance = Vector3.Distance(handData.thumbTip.position, handData.indexTip.position);
// 如果距离足够小,执行捏取
if (distance < pinchThreshold)
{
// 检查捏取点周围的可交互对象
Vector3 pinchPosition = (handData.thumbTip.position + handData.indexTip.position) / 2f;
Collider[] colliders = Physics.OverlapSphere(pinchPosition, 0.05f, interactableLayers);
if (colliders.Length > 0)
{
// 找到最近的可交互对象
float closestDistance = float.MaxValue;
IVRInteractable closestInteractable = null;
foreach (var collider in colliders)
{
IVRInteractable interactable = collider.GetComponent<IVRInteractable>();
if (interactable != null && interactable.CanPinch(hand))
{
float objDistance = Vector3.Distance(pinchPosition, collider.transform.position);
if (objDistance < closestDistance)
{
closestDistance = objDistance;
closestInteractable = interactable;
}
}
}
// 如果找到可捏取对象,进行捏取
if (closestInteractable != null)
{
closestInteractable.OnPinch(hand);
// 提供触觉反馈
if (controller != null && controller.inputDevice.TryGetHapticCapabilities(out HapticCapabilities capabilities) && capabilities.supportsImpulse)
{
controller.inputDevice.SendHapticImpulse(0, hapticAmplitude, hapticDuration);
}
// 更新当前交互对象
_currentInteractables[hand] = closestInteractable;
}
}
}
}
}
// 处理释放交互
private void ProcessReleaseInteraction(InteractionHand hand, HandTrackingData handData, XRController controller)
{
// 如果当前有交互对象,尝试释放
if (_currentInteractables[hand] != null)
{
_currentInteractables[hand].OnRelease(hand);
_currentInteractables[hand] = null;
}
}
// 检查控制器按钮
private void CheckControllerButtons(InteractionHand hand, XRController controller)
{
if (controller == null || !controller.inputDevice.isValid)
return;
// 检查抓取按钮
if (controller.inputDevice.TryGetFeatureValue(CommonUsages.gripButton, out bool gripPressed) && gripPressed)
{
ProcessGrabInteraction(hand, hand == InteractionHand.Left ? _leftHandData : _rightHandData, controller);
}
// 检查触发按钮
if (controller.inputDevice.TryGetFeatureValue(CommonUsages.triggerButton, out bool triggerPressed) && triggerPressed)
{
// 如果当前有交互对象,尝试激活
if (_currentInteractables[hand] != null)
{
_currentInteractables[hand].OnActivate(hand);
// 提供触觉反馈
if (controller.inputDevice.TryGetHapticCapabilities(out HapticCapabilities capabilities) && capabilities.supportsImpulse)
{
controller.inputDevice.SendHapticImpulse(0, hapticAmplitude, hapticDuration);
}
}
else
{
// 如果没有当前交互对象,尝试射线交互
ProcessPointingInteraction(hand, hand == InteractionHand.Left ? _leftHandData : _rightHandData, controller);
}
}
// 检查主按钮 (通常是A/X按钮)
if (controller.inputDevice.TryGetFeatureValue(CommonUsages.primaryButton, out bool primaryPressed) && primaryPressed)
{
// 执行主按钮操作
if (_currentInteractables[hand] != null)
{
_currentInteractables[hand].OnButtonPress(hand, "Primary");
}
}
// 检查次按钮 (通常是B/Y按钮)
if (controller.inputDevice.TryGetFeatureValue(CommonUsages.secondaryButton, out bool secondaryPressed) && secondaryPressed)
{
// 执行次按钮操作
if (_currentInteractables[hand] != null)
{
_currentInteractables[hand].OnButtonPress(hand, "Secondary");
}
}
}
}
// VR可交互对象接口
public interface IVRInteractable
{
void OnHover(VRInteractionManager.InteractionHand hand);
void OnHoverExit(VRInteractionManager.InteractionHand hand);
void OnActivate(VRInteractionManager.InteractionHand hand);
void OnGrab(VRInteractionManager.InteractionHand hand);
void OnPinch(VRInteractionManager.InteractionHand hand);
void OnRelease(VRInteractionManager.InteractionHand hand);
void OnButtonPress(VRInteractionManager.InteractionHand hand, string buttonName);
bool CanGrab(VRInteractionManager.InteractionHand hand);
bool CanPinch(VRInteractionManager.InteractionHand hand);
}
// 基础VR可交互对象
public class BaseVRInteractable : MonoBehaviour, IVRInteractable
{
[Header("Interaction Settings")]
[SerializeField] protected bool canBeGrabbed = true;
[SerializeField] protected bool canBePinched = false;
[SerializeField] protected bool useHighlighting = true;
[SerializeField] protected Color highlightColor = new Color(1f, 0.6f, 0.0f, 0.5f);
[Header("Transform Tracking")]
[SerializeField] protected bool returnToOriginalPosition = false;
// 原始变换
protected Vector3 _originalPosition;
protected Quaternion _originalRotation;
protected Vector3 _originalScale;
// 当前交互状态
protected bool _isHovered = false;
protected bool _isGrabbed = false;
protected bool _isPinched = false;
protected VRInteractionManager.InteractionHand _currentHand;
// 渲染器引用
protected Renderer _renderer;
protected Material[] _originalMaterials;
protected Material[] _highlightMaterials;
// 刚体引用
protected Rigidbody _rigidbody;
protected virtual void Awake()
{
// 保存原始变换
_originalPosition = transform.position;
_originalRotation = transform.rotation;
_originalScale = transform.localScale;
// 获取渲染器
_renderer = GetComponent<Renderer>();
if (_renderer != null && useHighlighting)
{
// 创建高亮材质
_originalMaterials = _renderer.materials;
_highlightMaterials = new Material[_originalMaterials.Length];
for (int i = 0; i < _originalMaterials.Length; i++)
{
_highlightMaterials[i] = new Material(_originalMaterials[i]);
_highlightMaterials[i].color = highlightColor;
}
}
// 获取刚体
_rigidbody = GetComponent<Rigidbody>();
}
public virtual void OnHover(VRInteractionManager.InteractionHand hand)
{
if (!_isHovered)
{
_isHovered = true;
SetHighlight(true);
}
}
public virtual void OnHoverExit(VRInteractionManager.InteractionHand hand)
{
if (_isHovered && !_isGrabbed && !_isPinched)
{
_isHovered = false;
SetHighlight(false);
}
}
public virtual void OnActivate(VRInteractionManager.InteractionHand hand)
{
// 默认激活行为
Debug.Log($"Object {gameObject.name} activated by {hand} hand");
}
public virtual void OnGrab(VRInteractionManager.InteractionHand hand)
{
if (canBeGrabbed && !_isGrabbed)
{
_isGrabbed = true;
_currentHand = hand;
// 如果有刚体,禁用物理并记录状态
if (_rigidbody != null)
{
_rigidbody.isKinematic = true;
}
Debug.Log($"Object {gameObject.name} grabbed by {hand} hand");
}
}
public virtual void OnPinch(VRInteractionManager.InteractionHand hand)
{
if (canBePinched && !_isPinched)
{
_isPinched = true;
_currentHand = hand;
// 如果有刚体,禁用物理并记录状态
if (_rigidbody != null)
{
_rigidbody.isKinematic = true;
}
Debug.Log($"Object {gameObject.name} pinched by {hand} hand");
}
}
public virtual void OnRelease(VRInteractionManager.InteractionHand hand)
{
if ((_isGrabbed || _isPinched) && _currentHand == hand)
{
_isGrabbed = false;
_isPinched = false;
// 如果需要返回原位
if (returnToOriginalPosition)
{
transform.position = _originalPosition;
transform.rotation = _originalRotation;
transform.localScale = _originalScale;
}
// 如果有刚体,恢复物理
if (_rigidbody != null)
{
_rigidbody.isKinematic = false;
}
// 如果仍然在悬停,保持高亮状态
if (_isHovered)
{
SetHighlight(true);
}
else
{
SetHighlight(false);
}
Debug.Log($"Object {gameObject.name} released by {hand} hand");
}
}
public virtual void OnButtonPress(VRInteractionManager.InteractionHand hand, string buttonName)
{
Debug.Log($"Button {buttonName} pressed on object {gameObject.name} by {hand} hand");
}
public virtual bool CanGrab(VRInteractionManager.InteractionHand hand)
{
return canBeGrabbed && !_isGrabbed && !_isPinched;
}
public virtual bool CanPinch(VRInteractionManager.InteractionHand hand)
{
return canBePinched && !_isPinched && !_isGrabbed;
}
protected virtual void SetHighlight(bool highlight)
{
if (_renderer != null && useHighlighting)
{
_renderer.materials = highlight ? _highlightMaterials : _originalMaterials;
}
}
protected virtual void Update()
{
// 跟随手部移动
if (_isGrabbed || _isPinched)
{
UpdateObjectPosition();
}
}
protected virtual void UpdateObjectPosition()
{
// 具体的位置更新逻辑会在子类中实现
}
}
// 可抓取物体
public class GrabbableObject : BaseVRInteractable
{
[Header("Grabbable Settings")]
[SerializeField] private Transform attachPoint;
[SerializeField] private float followSpeed = 10f;
[SerializeField] private float rotationSpeed = 10f;
// 手部引用
private Transform _handTransform;
public override void OnGrab(VRInteractionManager.InteractionHand hand)
{
base.OnGrab(hand);
// 获取手部变换
if (hand == VRInteractionManager.InteractionHand.Left)
{
_handTransform = GameObject.FindGameObjectWithTag("LeftHand").transform;
}
else
{
_handTransform = GameObject.FindGameObjectWithTag("RightHand").transform;
}
}
protected override void UpdateObjectPosition()
{
if (_handTransform != null)
{
// 计算目标位置和旋转
Vector3 targetPosition = attachPoint != null ?
_handTransform.TransformPoint(attachPoint.localPosition) :
_handTransform.position;
Quaternion targetRotation = attachPoint != null ?
_handTransform.rotation * attachPoint.localRotation :
_handTransform.rotation;
// 平滑跟随手部
transform.position = Vector3.Lerp(transform.position, targetPosition, followSpeed * Time.deltaTime);
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, rotationSpeed * Time.deltaTime);
}
}
}
// 可缩放物体
public class ScalableObject : BaseVRInteractable
{
[Header("Scalable Settings")]
[SerializeField] private float minScale = 0.1f;
[SerializeField] private float maxScale = 5.0f;
[SerializeField] private float scaleSpeed = 1.0f;
// 初始捏取距离
private float _initialPinchDistance;
private float _initialScale;
// 手指变换
private Transform _thumbTip;
private Transform _indexTip;
public override void OnPinch(VRInteractionManager.InteractionHand hand)
{
base.OnPinch(hand);
// 获取手指变换
GameObject handObj = (hand == VRInteractionManager.InteractionHand.Left) ?
GameObject.FindGameObjectWithTag("LeftHand") :
GameObject.FindGameObjectWithTag("RightHand");
if (handObj != null)
{
_thumbTip = FindChildRecursive(handObj.transform, "Thumb_Tip");
_indexTip = FindChildRecursive(handObj.transform, "Index_Tip");
if (_thumbTip != null && _indexTip != null)
{
_initialPinchDistance = Vector3.Distance(_thumbTip.position, _indexTip.position);
_initialScale = transform.localScale.x;
}
}
}
protected override void UpdateObjectPosition()
{
if (_thumbTip != null && _indexTip != null)
{
// 计算当前捏取距离
float currentPinchDistance = Vector3.Distance(_thumbTip.position, _indexTip.position);
// 计算比例因子
float scaleFactor = currentPinchDistance / _initialPinchDistance;
// 计算新的缩放值
float newScale = Mathf.Clamp(_initialScale * scaleFactor, minScale, maxScale);
// 应用缩放
transform.localScale = Vector3.one * newScale;
// 如果物体需要跟随手部,计算中心点位置
Vector3 pinchCenter = (_thumbTip.position + _indexTip.position) / 2f;
transform.position = Vector3.Lerp(transform.position, pinchCenter, scaleSpeed * Time.deltaTime);
}
}
private Transform FindChildRecursive(Transform parent, string name)
{
if (parent == null)
return null;
for (int i = 0; i < parent.childCount; i++)
{
Transform child = parent.GetChild(i);
if (child.name.Contains(name))
return child;
Transform found = FindChildRecursive(child, name);
if (found != null)
return found;
}
return null;
}
}
// UI交互器
public class VRUIInteractor : BaseVRInteractable
{
[Header("UI Settings")]
[SerializeField] private bool isDraggable = true;
[SerializeField] private bool isScrollable = true;
[SerializeField] private float scrollSpeed = 0.1f;
[SerializeField] private float dragSpeed = 5.0f;
// UI 组件引用
private RectTransform _rectTransform;
private ScrollRect _scrollRect;
private Slider _slider;
private Button _button;
// 交互状态
private Vector3 _dragStartPosition;
private Vector2 _initialScrollPosition;
private Vector2 _initialTouchPosition;
protected override void Awake()
{
base.Awake();
// 获取UI组件
_rectTransform = GetComponent<RectTransform>();
_scrollRect = GetComponent<ScrollRect>();
_slider = GetComponent<Slider>();
_button = GetComponent<Button>();
}
public override void OnActivate(VRInteractionManager.InteractionHand hand)
{
base.OnActivate(hand);
// 按钮激活
if (_button != null)
{
_button.onClick.Invoke();
}
}
public override void OnGrab(VRInteractionManager.InteractionHand hand)
{
if (!isDraggable)
return;
base.OnGrab(hand);
// 记录拖拽开始位置
_dragStartPosition = transform.position;
}
public override void OnPinch(VRInteractionManager.InteractionHand hand)
{
base.OnPinch(hand);
// 初始化滚动或滑块位置
if (_scrollRect != null && isScrollable)
{
_initialScrollPosition = _scrollRect.normalizedPosition;
}
if (_slider != null)
{
_initialTouchPosition = new Vector2(transform.position.x, transform.position.y);
}
}
protected override void UpdateObjectPosition()
{
if (_isGrabbed && isDraggable && _rectTransform != null)
{
// 拖动UI元素
if (_handTransform != null)
{
Vector3 targetPosition = new Vector3(
_handTransform.position.x,
_handTransform.position.y,
_rectTransform.position.z
);
_rectTransform.position = Vector3.Lerp(_rectTransform.position, targetPosition, dragSpeed * Time.deltaTime);
}
}
else if (_isPinched)
{
// 滚动或调整滑块
if (_scrollRect != null && isScrollable && _thumbTip != null && _indexTip != null)
{
float pinchDelta = Vector3.Distance(_thumbTip.position, _indexTip.position) - _initialPinchDistance;
Vector2 scrollDelta = new Vector2(0, pinchDelta * scrollSpeed);
_scrollRect.normalizedPosition = _initialScrollPosition + scrollDelta;
}
if (_slider != null && _handTransform != null)
{
float handDelta = _handTransform.position.x - _initialTouchPosition.x;
_slider.value = Mathf.Clamp01(_slider.value + handDelta * 0.01f);
}
}
}
}
25.2 空间UI系统
csharp
// 空间UI系统
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using TMPro;
// 空间UI管理器
public class SpatialUIManager : MonoBehaviour
{
[Header("UI Canvas Settings")]
[SerializeField] private Canvas worldSpaceCanvas;
[SerializeField] private float defaultDistance = 1.0f;
[SerializeField] private float defaultScale = 0.001f;
[SerializeField] private float followSpeed = 5.0f;
[SerializeField] private float minDistanceFromUser = 0.5f;
[SerializeField] private LayerMask uiLayerMask;
[Header("Input Settings")]
[SerializeField] private float gazeTimeThreshold = 2.0f;
[SerializeField] private bool useGazeInput = true;
[SerializeField] private bool useControllerInput = true;
[SerializeField] private bool useHandTracking = true;
[Header("UI References")]
[SerializeField] private GameObject gazeCursor;
[SerializeField] private Image gazeProgressIndicator;
// UI状态
private bool _isCanvasVisible = true;
private bool _isCanvasLocked = false;
private Vector3 _lockedPosition;
private Quaternion _lockedRotation;
// 输入状态
private bool _isGazing = false;
private float _gazeTimer = 0.0f;
private GameObject _currentGazedObject = null;
// 相机引用
private Camera _mainCamera;
// UI元素注册表
private Dictionary<string, SpatialUIElement> _uiElements = new Dictionary<string, SpatialUIElement>();
private void Start()
{
_mainCamera = Camera.main;
// 初始化世界空间Canvas
if (worldSpaceCanvas != null)
{
worldSpaceCanvas.renderMode = RenderMode.WorldSpace;
worldSpaceCanvas.transform.localScale = Vector3.one * defaultScale;
PositionCanvasInFrontOfUser();
}
// 初始化注视光标
if (gazeCursor != null)
{
gazeCursor.SetActive(useGazeInput);
}
if (gazeProgressIndicator != null)
{
gazeProgressIndicator.fillAmount = 0;
}
}
private void Update()
{
if (worldSpaceCanvas == null)
return;
// 更新Canvas位置
if (_isCanvasVisible && !_isCanvasLocked)
{
PositionCanvasInFrontOfUser();
}
// 处理注视输入
if (useGazeInput)
{
ProcessGazeInput();
}
// 处理控制器输入
if (useControllerInput)
{
ProcessControllerInput();
}
// 处理手势输入
if (useHandTracking)
{
ProcessHandInput();
}
}
// 定位Canvas在用户面前
private void PositionCanvasInFrontOfUser()
{
if (_mainCamera == null)
return;
// 计算目标位置和旋转
Vector3 targetPosition = _mainCamera.transform.position + _mainCamera.transform.forward * defaultDistance;
Quaternion targetRotation = Quaternion.LookRotation(targetPosition - _mainCamera.transform.position);
// 平滑移动Canvas
worldSpaceCanvas.transform.position = Vector3.Lerp(worldSpaceCanvas.transform.position, targetPosition, followSpeed * Time.deltaTime);
worldSpaceCanvas.transform.rotation = Quaternion.Slerp(worldSpaceCanvas.transform.rotation, targetRotation, followSpeed * Time.deltaTime);
}
// 处理注视输入
private void ProcessGazeInput()
{
if (_mainCamera == null)
return;
// 发射注视射线
Ray gazeRay = new Ray(_mainCamera.transform.position, _mainCamera.transform.forward);
RaycastHit hit;
// 更新注视光标位置
if (gazeCursor != null)
{
if (Physics.Raycast(gazeRay, out hit, 100f, uiLayerMask))
{
gazeCursor.transform.position = hit.point;
gazeCursor.transform.rotation = Quaternion.LookRotation(-hit.normal);
gazeCursor.SetActive(true);
}
else
{
gazeCursor.SetActive(false);
}
}
// 检查注视的UI元素
if (Physics.Raycast(gazeRay, out hit, 100f, uiLayerMask))
{
GameObject hitObject = hit.collider.gameObject;
// 检查是否是UI元素
SpatialUIElement uiElement = hitObject.GetComponent<SpatialUIElement>();
if (uiElement != null)
{
// 如果注视新对象
if (_currentGazedObject != hitObject)
{
// 退出前一个注视对象
if (_currentGazedObject != null)
{
ExecuteEvents.Execute<IPointerExitHandler>(_currentGazedObject, new PointerEventData(EventSystem.current), ExecuteEvents.pointerExitHandler);
_isGazing = false;
_gazeTimer = 0f;
}
// 进入新注视对象
_currentGazedObject = hitObject;
ExecuteEvents.Execute<IPointerEnterHandler>(_currentGazedObject, new PointerEventData(EventSystem.current), ExecuteEvents.pointerEnterHandler);
_isGazing = true;
_gazeTimer = 0f;
}
// 更新注视计时器
if (_isGazing)
{
_gazeTimer += Time.deltaTime;
// 更新进度指示器
if (gazeProgressIndicator != null)
{
gazeProgressIndicator.fillAmount = _gazeTimer / gazeTimeThreshold;
}
// 如果超过阈值,触发点击
if (_gazeTimer >= gazeTimeThreshold)
{
ExecuteEvents.Execute<IPointerClickHandler>(_currentGazedObject, new PointerEventData(EventSystem.current), ExecuteEvents.pointerClickHandler);
_gazeTimer = 0f;
// 如果是按钮,触发点击事件
Button button = _currentGazedObject.GetComponent<Button>();
if (button != null)
{
button.onClick.Invoke();
}
}
}
}
else
{
// 如果不是UI元素,退出当前注视
if (_currentGazedObject != null)
{
ExecuteEvents.Execute<IPointerExitHandler>(_currentGazedObject, new PointerEventData(EventSystem.current), ExecuteEvents.pointerExitHandler);
_currentGazedObject = null;
_isGazing = false;
_gazeTimer = 0f;
if (gazeProgressIndicator != null)
{
gazeProgressIndicator.fillAmount = 0f;
}
}
}
}
else
{
// 如果没有命中任何物体,退出当前注视
if (_currentGazedObject != null)
{
ExecuteEvents.Execute<IPointerExitHandler>(_currentGazedObject, new PointerEventData(EventSystem.current), ExecuteEvents.pointerExitHandler);
_currentGazedObject = null;
_isGazing = false;
_gazeTimer = 0f;
if (gazeProgressIndicator != null)
{
gazeProgressIndicator.fillAmount = 0f;
}
}
}
}
// 处理控制器输入
private void ProcessControllerInput()
{
// 由于XR控制器的具体实现依赖于特定的XR框架,这里提供一个简化的示例
// 在实际项目中,应该根据使用的XR框架(如OpenXR, SteamVR等)进行相应的实现
// 示例:基于射线的控制器指向
Ray controllerRay = new Ray(transform.position, transform.forward);
RaycastHit hit;
if (Physics.Raycast(controllerRay, out hit, 100f, uiLayerMask))
{
GameObject hitObject = hit.collider.gameObject;
// 检查是否是UI元素
SpatialUIElement uiElement = hitObject.GetComponent<SpatialUIElement>();
if (uiElement != null)
{
// 在实际项目中,这里应检查控制器的触发按钮输入
if (Input.GetButtonDown("Fire1")) // 替换为实际的控制器输入
{
// 触发UI元素点击
ExecuteEvents.Execute<IPointerClickHandler>(hitObject, new PointerEventData(EventSystem.current), ExecuteEvents.pointerClickHandler);
// 如果是按钮,触发点击事件
Button button = hitObject.GetComponent<Button>();
if (button != null)
{
button.onClick.Invoke();
}
}
}
}
}
// 处理手势输入
private void ProcessHandInput()
{
// 手势处理同样依赖于特定的手势跟踪系统实现
// 在实际项目中,应该根据使用的手势跟踪系统进行相应的实现
// 示例:简单的手指指向检测
// 假定有一个API可以获取食指尖端位置和方向
Vector3 fingerPosition = Vector3.zero; // 替换为实际的手指位置
Vector3 fingerDirection = Vector3.forward; // 替换为实际的手指方向
Ray fingerRay = new Ray(fingerPosition, fingerDirection);
RaycastHit hit;
if (Physics.Raycast(fingerRay, out hit, 100f, uiLayerMask))
{
GameObject hitObject = hit.collider.gameObject;
// 检查是否是UI元素
SpatialUIElement uiElement = hitObject.GetComponent<SpatialUIElement>();
if (uiElement != null)
{
// 检测捏合手势
bool isPinching = false; // 替换为实际的捏合检测
if (isPinching)
{
// 触发UI元素点击
ExecuteEvents.Execute<IPointerClickHandler>(hitObject, new PointerEventData(EventSystem.current), ExecuteEvents.pointerClickHandler);
// 如果是按钮,触发点击事件
Button button = hitObject.GetComponent<Button>();
if (button != null)
{
button.onClick.Invoke();
}
}
}
}
}
// 注册UI元素
public void RegisterUIElement(SpatialUIElement element)
{
if (element != null && !string.IsNullOrEmpty(element.ElementID))
{
_uiElements[element.ElementID] = element;
}
}
// 取消注册UI元素
public void UnregisterUIElement(string elementID)
{
if (_uiElements.ContainsKey(elementID))
{
_uiElements.Remove(elementID);
}
}
// 获取UI元素
public SpatialUIElement GetUIElement(string elementID)
{
if (_uiElements.ContainsKey(elementID))
{
return _uiElements[elementID];
}
return null;
}
// 显示Canvas
public void ShowCanvas()
{
if (worldSpaceCanvas != null)
{
worldSpaceCanvas.gameObject.SetActive(true);
_isCanvasVisible = true;
}
}
// 隐藏Canvas
public void HideCanvas()
{
if (worldSpaceCanvas != null)
{
worldSpaceCanvas.gameObject.SetActive(false);
_isCanvasVisible = false;
}
}
// 锁定/解锁Canvas位置
public void ToggleCanvasLock()
{
_isCanvasLocked = !_isCanvasLocked;
if (_isCanvasLocked)
{
_lockedPosition = worldSpaceCanvas.transform.position;
_lockedRotation = worldSpaceCanvas.transform.rotation;
}
}
// 设置Canvas距离
public void SetCanvasDistance(float distance)
{
if (distance < minDistanceFromUser)
{
distance = minDistanceFromUser;
}
defaultDistance = distance;
if (!_isCanvasLocked)
{
PositionCanvasInFrontOfUser();
}
}
}
// 空间UI元素组件
public class SpatialUIElement : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler, IPointerClickHandler
{
[Header("Element Settings")]
[SerializeField] private string elementID;
[SerializeField] private bool isInteractable = true;
[SerializeField] private float hoverScaleMultiplier = 1.1f;
[SerializeField] private Color normalColor = Color.white;
[SerializeField] private Color hoverColor = Color.cyan;
[SerializeField] private Color disabledColor = Color.gray;
[SerializeField] private AudioClip hoverSound;
[SerializeField] private AudioClip clickSound;
// 元素状态
private bool _isHovering = false;
private Vector3 _originalScale;
private Image _image;
private TextMeshProUGUI _text;
private AudioSource _audioSource;
// 事件
public event Action<SpatialUIElement> OnElementHoverEnter;
public event Action<SpatialUIElement> OnElementHoverExit;
public event Action<SpatialUIElement> OnElementClick;
// 属性
public string ElementID => elementID;
public bool IsInteractable
{
get { return isInteractable; }
set { SetInteractable(value); }
}
private void Awake()
{
_originalScale = transform.localScale;
_image = GetComponent<Image>();
_text = GetComponent<TextMeshProUGUI>();
_audioSource = GetComponent<AudioSource>();
if (_audioSource == null && (hoverSound != null || clickSound != null))
{
_audioSource = gameObject.AddComponent<AudioSource>();
_audioSource.spatialBlend = 1.0f; // 完全3D音效
_audioSource.minDistance = 0.1f;
_audioSource.maxDistance = 10f;
}
}
private void Start()
{
// 注册到空间UI管理器
SpatialUIManager manager = FindObjectOfType<SpatialUIManager>();
if (manager != null)
{
manager.RegisterUIElement(this);
}
UpdateVisuals();
}
private void OnDestroy()
{
// 从空间UI管理器取消注册
SpatialUIManager manager = FindObjectOfType<SpatialUIManager>();
if (manager != null)
{
manager.UnregisterUIElement(elementID);
}
}
// 设置交互状态
public void SetInteractable(bool interactable)
{
isInteractable = interactable;
UpdateVisuals();
}
// 更新视觉效果
private void UpdateVisuals()
{
if (_image != null)
{
if (!isInteractable)
{
_image.color = disabledColor;
}
else if (_isHovering)
{
_image.color = hoverColor;
}
else
{
_image.color = normalColor;
}
}
if (_text != null)
{
_text.color = isInteractable ? normalColor : disabledColor;
}
}
// 实现接口方法
public void OnPointerEnter(PointerEventData eventData)
{
if (!isInteractable)
return;
_isHovering = true;
transform.localScale = _originalScale * hoverScaleMultiplier;
if (_audioSource != null && hoverSound != null)
{
_audioSource.PlayOneShot(hoverSound);
}
UpdateVisuals();
// 触发事件
OnElementHoverEnter?.Invoke(this);
}
public void OnPointerExit(PointerEventData eventData)
{
if (!isInteractable)
return;
_isHovering = false;
transform.localScale = _originalScale;
UpdateVisuals();
// 触发事件
OnElementHoverExit?.Invoke(this);
}
public void OnPointerClick(PointerEventData eventData)
{
if (!isInteractable)
return;
if (_audioSource != null && clickSound != null)
{
_audioSource.PlayOneShot(clickSound);
}
// 触发事件
OnElementClick?.Invoke(this);
}
}
// 空间UI面板
public class SpatialUIPanel : MonoBehaviour
{
[Header("Panel Settings")]
[SerializeField] private string panelID;
[SerializeField] private bool startVisible = true;
[SerializeField] private bool useAnimation = true;
[SerializeField] private float animationDuration = 0.5f;
[SerializeField] private AnimationCurve animationCurve = AnimationCurve.EaseInOut(0, 0, 1, 1);
[Header("Layout")]
[SerializeField] private float width = 1.0f;
[SerializeField] private float height = 0.6f;
[SerializeField] private float curvature = 0.0f; // 0为平面,正值为向内弯曲
[SerializeField] private int gridColumns = 3;
[SerializeField] private int gridRows = 2;
[SerializeField] private Vector2 spacing = new Vector2(0.1f, 0.1f);
[Header("Appearance")]
[SerializeField] private Material panelMaterial;
[SerializeField] private Color backgroundColor = new Color(0.2f, 0.2f, 0.2f, 0.8f);
[SerializeField] private float cornerRadius = 0.05f;
[SerializeField] private float borderWidth = 0.01f;
[SerializeField] private Color borderColor = Color.white;
// 面板状态
private bool _isVisible = false;
private RectTransform _rectTransform;
private CanvasGroup _canvasGroup;
private List<SpatialUIElement> _childElements = new List<SpatialUIElement>();
// 动画状态
private Coroutine _animationCoroutine;
private void Awake()
{
_rectTransform = GetComponent<RectTransform>();
_canvasGroup = GetComponent<CanvasGroup>();
if (_canvasGroup == null)
{
_canvasGroup = gameObject.AddComponent<CanvasGroup>();
}
// 初始化面板尺寸
if (_rectTransform != null)
{
_rectTransform.sizeDelta = new Vector2(width, height);
}
// 收集子UI元素
SpatialUIElement[] elements = GetComponentsInChildren<SpatialUIElement>(true);
_childElements.AddRange(elements);
// 初始可见性
_isVisible = startVisible;
_canvasGroup.alpha = startVisible ? 1.0f : 0.0f;
_canvasGroup.blocksRaycasts = startVisible;
_canvasGroup.interactable = startVisible;
}
private void Start()
{
// 应用材质和外观
ApplyAppearance();
// 如果使用网格布局,排列子元素
if (gridColumns > 0 && gridRows > 0)
{
ArrangeElementsInGrid();
}
// 根据面板曲率调整元素位置
if (curvature != 0)
{
ApplyCurvature();
}
}
// 应用面板外观
private void ApplyAppearance()
{
// 设置背景
Image backgroundImage = GetComponent<Image>();
if (backgroundImage == null)
{
backgroundImage = gameObject.AddComponent<Image>();
}
backgroundImage.color = backgroundColor;
if (panelMaterial != null)
{
backgroundImage.material = new Material(panelMaterial);
// 如果材质支持圆角和边框属性
if (backgroundImage.material.HasProperty("_CornerRadius"))
{
backgroundImage.material.SetFloat("_CornerRadius", cornerRadius);
}
if (backgroundImage.material.HasProperty("_BorderWidth"))
{
backgroundImage.material.SetFloat("_BorderWidth", borderWidth);
}
if (backgroundImage.material.HasProperty("_BorderColor"))
{
backgroundImage.material.SetColor("_BorderColor", borderColor);
}
}
}
// 将元素排列成网格
private void ArrangeElementsInGrid()
{
if (_childElements.Count == 0)
return;
// 计算单元格大小
float cellWidth = (width - spacing.x * (gridColumns - 1)) / gridColumns;
float cellHeight = (height - spacing.y * (gridRows - 1)) / gridRows;
// 计算起始位置(左上角)
float startX = -width / 2 + cellWidth / 2;
float startY = height / 2 - cellHeight / 2;
int elementIndex = 0;
for (int row = 0; row < gridRows; row++)
{
for (int col = 0; col < gridColumns; col++)
{
if (elementIndex >= _childElements.Count)
break;
RectTransform elementRect = _childElements[elementIndex].GetComponent<RectTransform>();
if (elementRect != null)
{
// 计算位置
float x = startX + col * (cellWidth + spacing.x);
float y = startY - row * (cellHeight + spacing.y);
// 设置位置和大小
elementRect.anchoredPosition = new Vector2(x, y);
elementRect.sizeDelta = new Vector2(cellWidth, cellHeight);
}
elementIndex++;
}
}
}
// 应用曲率
private void ApplyCurvature()
{
if (_childElements.Count == 0 || curvature == 0)
return;
foreach (var element in _childElements)
{
RectTransform elementRect = element.GetComponent<RectTransform>();
if (elementRect != null)
{
// 获取当前位置
Vector2 position = elementRect.anchoredPosition;
// 计算元素应该向内/外弯曲的距离
float distanceFromCenter = Mathf.Abs(position.x);
float zOffset = curvature * distanceFromCenter * distanceFromCenter;
// 应用Z轴偏移
elementRect.position = new Vector3(
elementRect.position.x,
elementRect.position.y,
elementRect.position.z - zOffset
);
// 调整朝向以面向中心
if (curvature > 0)
{
float angle = Mathf.Atan2(position.x, 1.0f / curvature) * Mathf.Rad2Deg;
elementRect.localRotation = Quaternion.Euler(0, angle, 0);
}
}
}
}
// 显示面板
public void Show()
{
if (_isVisible)
return;
_isVisible = true;
if (useAnimation)
{
if (_animationCoroutine != null)
{
StopCoroutine(_animationCoroutine);
}
_animationCoroutine = StartCoroutine(AnimatePanel(0, 1, animationDuration));
}
else
{
_canvasGroup.alpha = 1.0f;
_canvasGroup.blocksRaycasts = true;
_canvasGroup.interactable = true;
}
}
// 隐藏面板
public void Hide()
{
if (!_isVisible)
return;
_isVisible = false;
if (useAnimation)
{
if (_animationCoroutine != null)
{
StopCoroutine(_animationCoroutine);
}
_animationCoroutine = StartCoroutine(AnimatePanel(1, 0, animationDuration));
}
else
{
_canvasGroup.alpha = 0.0f;
_canvasGroup.blocksRaycasts = false;
_canvasGroup.interactable = false;
}
}
// 面板动画协程
private IEnumerator AnimatePanel(float startAlpha, float endAlpha, float duration)
{
float startTime = Time.time;
float elapsedTime = 0f;
_canvasGroup.blocksRaycasts = endAlpha > 0;
_canvasGroup.interactable = endAlpha > 0;
while (elapsedTime < duration)
{
elapsedTime = Time.time - startTime;
float normalizedTime = Mathf.Clamp01(elapsedTime / duration);
float curveValue = animationCurve.Evaluate(normalizedTime);
_canvasGroup.alpha = Mathf.Lerp(startAlpha, endAlpha, curveValue);
yield return null;
}
_canvasGroup.alpha = endAlpha;
}
// 切换可见性
public void Toggle()
{
if (_isVisible)
{
Hide();
}
else
{
Show();
}
}
// 获取子元素
public SpatialUIElement GetElement(string elementID)
{
return _childElements.Find(e => e.ElementID == elementID);
}
}
// 空间UI上下文菜单
public class SpatialContextMenu : MonoBehaviour
{
[Header("Menu Settings")]
[SerializeField] private float radius = 0.2f;
[SerializeField] private int maxItems = 6;
[SerializeField] private float itemSize = 0.05f;
[SerializeField] private float openAnimationDuration = 0.3f;
[SerializeField] private AnimationCurve openCurve = AnimationCurve.EaseInOut(0, 0, 1, 1);
[Header("Appearance")]
[SerializeField] private Sprite backgroundSprite;
[SerializeField] private Color backgroundColor = new Color(0.2f, 0.2f, 0.2f, 0.8f);
[SerializeField] private Sprite itemBackgroundSprite;
[SerializeField] private Color itemBackgroundColor = new Color(0.3f, 0.3f, 0.3f, 1.0f);
[SerializeField] private Color itemHoverColor = new Color(0.5f, 0.5f, 0.5f, 1.0f);
// 菜单项
[System.Serializable]
public class MenuItem
{
public string id;
public string label;
public Sprite icon;
public UnityEngine.Events.UnityEvent onClick;
}
[Header("Menu Items")]
[SerializeField] private List<MenuItem> menuItems = new List<MenuItem>();
// 内部状态
private bool _isOpen = false;
private List<GameObject> _itemGameObjects = new List<GameObject>();
private GameObject _background;
private Coroutine _animationCoroutine;
// 打开菜单的位置和朝向
private Vector3 _openPosition;
private Quaternion _openRotation;
private void Awake()
{
// 创建背景
_background = new GameObject("ContextMenuBackground");
_background.transform.SetParent(transform);
Image bgImage = _background.AddComponent<Image>();
bgImage.sprite = backgroundSprite;
bgImage.color = backgroundColor;
RectTransform bgRect = _background.GetComponent<RectTransform>();
bgRect.sizeDelta = Vector2.one * radius * 2;
// 初始化为关闭状态
gameObject.SetActive(false);
}
// 打开上下文菜单
public void Open(Vector3 position, Quaternion rotation)
{
if (_isOpen)
return;
_openPosition = position;
_openRotation = rotation;
transform.position = position;
transform.rotation = rotation;
gameObject.SetActive(true);
_isOpen = true;
// 创建菜单项
CreateMenuItems();
// 动画打开
if (_animationCoroutine != null)
{
StopCoroutine(_animationCoroutine);
}
_animationCoroutine = StartCoroutine(AnimateOpen());
}
// 关闭上下文菜单
public void Close()
{
if (!_isOpen)
return;
_isOpen = false;
// 清理菜单项
foreach (var item in _itemGameObjects)
{
Destroy(item);
}
_itemGameObjects.Clear();
gameObject.SetActive(false);
}
// 创建菜单项
private void CreateMenuItems()
{
// 清理现有菜单项
foreach (var item in _itemGameObjects)
{
Destroy(item);
}
_itemGameObjects.Clear();
// 限制菜单项数量
int itemCount = Mathf.Min(menuItems.Count, maxItems);
for (int i = 0; i < itemCount; i++)
{
MenuItem menuItem = menuItems[i];
// 创建菜单项GameObject
GameObject itemGO = new GameObject(menuItem.id);
itemGO.transform.SetParent(transform);
// 添加UI组件
Image itemImage = itemGO.AddComponent<Image>();
itemImage.sprite = itemBackgroundSprite;
itemImage.color = itemBackgroundColor;
RectTransform itemRect = itemGO.GetComponent<RectTransform>();
itemRect.sizeDelta = Vector2.one * itemSize;
// 如果有图标,添加图标
if (menuItem.icon != null)
{
GameObject iconGO = new GameObject("Icon");
iconGO.transform.SetParent(itemGO.transform);
Image iconImage = iconGO.AddComponent<Image>();
iconImage.sprite = menuItem.icon;
RectTransform iconRect = iconGO.GetComponent<RectTransform>();
iconRect.anchorMin = Vector2.zero;
iconRect.anchorMax = Vector2.one;
iconRect.offsetMin = Vector2.one * (itemSize * 0.2f);
iconRect.offsetMax = -Vector2.one * (itemSize * 0.2f);
}
// 如果有标签,添加文本
if (!string.IsNullOrEmpty(menuItem.label))
{
GameObject labelGO = new GameObject("Label");
labelGO.transform.SetParent(itemGO.transform);
TextMeshProUGUI labelText = labelGO.AddComponent<TextMeshProUGUI>();
labelText.text = menuItem.label;
labelText.alignment = TextAlignmentOptions.Center;
labelText.fontSize = itemSize * 0.3f;
RectTransform labelRect = labelGO.GetComponent<RectTransform>();
labelRect.anchorMin = new Vector2(0, 0);
labelRect.anchorMax = new Vector2(1, 0.3f);
labelRect.offsetMin = Vector2.zero;
labelRect.offsetMax = Vector2.zero;
}
// 添加按钮组件
Button itemButton = itemGO.AddComponent<Button>();
itemButton.transition = Selectable.Transition.ColorTint;
ColorBlock colors = itemButton.colors;
colors.normalColor = itemBackgroundColor;
colors.highlightedColor = itemHoverColor;
colors.pressedColor = itemHoverColor * 0.8f;
itemButton.colors = colors;
// 添加点击事件
int index = i; // 捕获循环变量
itemButton.onClick.AddListener(() => {
menuItems[index].onClick?.Invoke();
Close();
});
// 初始位置 (都在中心,然后通过动画展开)
itemRect.anchoredPosition = Vector2.zero;
// 添加到列表
_itemGameObjects.Add(itemGO);
}
}
// 打开动画
private IEnumerator AnimateOpen()
{
float startTime = Time.time;
float elapsedTime = 0f;
// 计算每个项目的最终位置 (围绕一个圆)
Vector2[] finalPositions = new Vector2[_itemGameObjects.Count];
for (int i = 0; i < _itemGameObjects.Count; i++)
{
float angle = 2 * Mathf.PI * i / _itemGameObjects.Count;
finalPositions[i] = new Vector2(
Mathf.Cos(angle) * radius,
Mathf.Sin(angle) * radius
);
}
// 初始缩放为0
_background.transform.localScale = Vector3.zero;
foreach (var item in _itemGameObjects)
{
item.transform.localScale = Vector3.zero;
}
// 动画
while (elapsedTime < openAnimationDuration)
{
elapsedTime = Time.time - startTime;
float t = Mathf.Clamp01(elapsedTime / openAnimationDuration);
float curveValue = openCurve.Evaluate(t);
// 缩放背景
_background.transform.localScale = Vector3.one * curveValue;
// 移动和缩放菜单项
for (int i = 0; i < _itemGameObjects.Count; i++)
{
RectTransform itemRect = _itemGameObjects[i].GetComponent<RectTransform>();
// 位置
itemRect.anchoredPosition = finalPositions[i] * curveValue;
// 缩放 (延迟一点点打开,让背景先出现)
float itemDelay = 0.2f;
float itemT = Mathf.Clamp01((t - itemDelay) / (1 - itemDelay));
_itemGameObjects[i].transform.localScale = Vector3.one * itemT;
}
yield return null;
}
// 确保最终状态正确
_background.transform.localScale = Vector3.one;
for (int i = 0; i < _itemGameObjects.Count; i++)
{
RectTransform itemRect = _itemGameObjects[i].GetComponent<RectTransform>();
itemRect.anchoredPosition = finalPositions[i];
_itemGameObjects[i].transform.localScale = Vector3.one;
}
}
// 添加菜单项
public void AddMenuItem(string id, string label, Sprite icon, UnityEngine.Events.UnityAction action)
{
MenuItem item = new MenuItem
{
id = id,
label = label,
icon = icon,
onClick = new UnityEngine.Events.UnityEvent()
};
if (action != null)
{
item.onClick.AddListener(action);
}
menuItems.Add(item);
}
// 移除菜单项
public void RemoveMenuItem(string id)
{
menuItems.RemoveAll(item => item.id == id);
}
}
25.3 自适应UI系统
csharp
// 自适应UI系统
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using TMPro;
// 自适应UI管理器
public class AdaptiveUIManager : MonoBehaviour
{
[Header("Device Detection")]
[SerializeField] private bool autoDetectDevice = true;
[SerializeField] private DeviceType manualDeviceType = DeviceType.Desktop;
[SerializeField] private OrientationType manualOrientation = OrientationType.Landscape;
[Header("Layout Settings")]
[SerializeField] private float desktopScaleFactor = 1.0f;
[SerializeField] private float tabletScaleFactor = 1.2f;
[SerializeField] private float mobileScaleFactor = 1.5f;
[SerializeField] private float vrScaleFactor = 2.0f;
[Header("Performance")]
[SerializeField] private bool dynamicResolutionScaling = true;
[SerializeField] private float targetFrameRate = 60f;
[SerializeField] private float resolutionScaleMin = 0.5f;
[SerializeField] private float resolutionScaleMax = 1.0f;
[Header("Accessibility")]
[SerializeField] private bool enableAccessibilityFeatures = true;
[SerializeField] private float textScaleFactor = 1.0f;
[SerializeField] private bool highContrastMode = false;
[SerializeField] private bool reduceMotion = false;
// 设备和方向类型枚举
public enum DeviceType
{
Desktop,
Tablet,
Mobile,
VR
}
public enum OrientationType
{
Portrait,
Landscape
}
// 当前设备信息
private DeviceType _currentDeviceType;
private OrientationType _currentOrientation;
private Vector2 _screenSize;
private float _currentDPI;
private float _currentScaleFactor;
// 注册的自适应UI元素
private List<IAdaptiveUIElement> _adaptiveElements = new List<IAdaptiveUIElement>();
// 性能监控
private float _frameTimeAverage;
private Queue<float> _frameTimeHistory = new Queue<float>();
private int _frameHistorySize = 20;
private float _currentResolutionScale = 1.0f;
// 事件
public event Action<DeviceType> OnDeviceTypeChanged;
public event Action<OrientationType> OnOrientationChanged;
public event Action<float> OnScaleFactorChanged;
// 单例实例
private static AdaptiveUIManager _instance;
public static AdaptiveUIManager Instance => _instance;
// 属性
public DeviceType CurrentDeviceType => _currentDeviceType;
public OrientationType CurrentOrientation => _currentOrientation;
public Vector2 ScreenSize => _screenSize;
public float CurrentDPI => _currentDPI;
public float CurrentScaleFactor => _currentScaleFactor;
public bool HighContrastMode => highContrastMode;
public bool ReduceMotion => reduceMotion;
public float TextScaleFactor => textScaleFactor;
private void Awake()
{
// 设置单例
if (_instance == null)
{
_instance = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
return;
}
// 初始化
_screenSize = new Vector2(Screen.width, Screen.height);
_currentD