RenderTexture通俗解释
RenderTexture就像是Unity中的"虚拟相机胶片",它可以:
- 捕获3D内容:将3D场景或对象"拍照"记录下来
- 实时更新:不是静态图片,而是动态视频,角色可以动起来
- 用作纹理:可以显示在UI界面上
在角色预览系统中的作用
从你的截图和代码中可以看出:
- 分离渲染:CharacterPreviewRT(512×512分辨率)专门用来渲染角色模型
- 桥梁作用:连接了3D世界和2D界面
- 3D世界:CharacterPreviewCamera(相机)拍摄放在CharacterPosition(位置)的角色模型
- 2D界面:通过RawImage显示这个"拍摄"结果
- 性能优化:
- 只渲染需要的角色,不渲染整个场景
- 可以控制渲染质量(如你设置的2×抗锯齿)
- 交互实现:
- 你的代码中的HandleCharacterRotation()方法让用户可以旋转预览角色
实现思路(我的界面结构参考)
创建一个显示的面板(CharacterInfoPanel),并且添加组件(重要)
界面参考布局
相机配置注意配置Culling Mask为对应的层级
代码参考:
using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
using Game.Managers;namespace Game.UI
{/// <summary>/// 角色选择面板,处理角色浏览、选择和预览功能/// </summary>public class CharacterSelectPanel : BasePanel{[Header("角色列表")][SerializeField] private Transform characterListContainer; // 角色列表容器[SerializeField] private GameObject characterItemPrefab; // 角色选项预制体[SerializeField] private ScrollRect characterScrollRect; // 角色列表滚动视图[Header("角色预览")][SerializeField] private RawImage characterPreviewImage; // 角色预览图像[SerializeField] private Transform characterPosition; // 角色位置[Header("按钮")][SerializeField] private Button confirmButton; // 确认按钮[Header("旋转设置")][SerializeField] private float rotationSpeed = 30f; // 旋转速度// 角色数据private List<CharacterData> characterDataList = new List<CharacterData>();// 预览相关private GameObject currentPreviewCharacter;private bool isDragging = false;private float lastMouseX;private int selectedCharacterIndex = -1;private List<CharacterItemUI> characterItemUIs = new List<CharacterItemUI>();protected override void Initialize(){base.Initialize();Debug.Log("[CharacterSelectPanel] 初始化面板");// 验证组件引用if(characterPosition == null)Debug.LogError("[CharacterSelectPanel] characterPosition未设置,请将预览位置拖拽到Inspector面板");if(characterPreviewImage == null)Debug.LogError("[CharacterSelectPanel] characterPreviewImage未设置,请将RawImage拖拽到Inspector面板");if(characterItemPrefab == null)Debug.LogError("[CharacterSelectPanel] characterItemPrefab未设置,请将角色项预制体拖拽到Inspector面板");// 检查角色项预制体是否包含必要组件CharacterItemUI testItemUI = characterItemPrefab.GetComponent<CharacterItemUI>();if(testItemUI == null)Debug.LogError("[CharacterSelectPanel] 角色项预制体必须包含CharacterItemUI组件!请在预制体上添加此组件");// 初始化组件引用if (confirmButton != null){confirmButton.onClick.AddListener(OnConfirmButtonClicked);confirmButton.interactable = false; // 初始状态下禁用确认按钮}// 加载角色数据LoadCharacterData();// 初始化角色列表PopulateCharacterList();}protected override void SetupButtons(){base.SetupButtons();// 可以在这里添加其他按钮的监听器}void Update(){// 处理角色旋转HandleCharacterRotation();}/// <summary>/// 处理角色旋转/// </summary>private void HandleCharacterRotation(){if (characterPreviewImage == null || currentPreviewCharacter == null) return;// 检查预览图片上的鼠标事件if (RectTransformUtility.RectangleContainsScreenPoint(characterPreviewImage.rectTransform, Input.mousePosition)){// 鼠标按下开始拖动if (Input.GetMouseButtonDown(0)){isDragging = true;lastMouseX = Input.mousePosition.x;}}// 松开鼠标停止拖动if (Input.GetMouseButtonUp(0)){isDragging = false;}// 拖动时旋转角色if (isDragging && currentPreviewCharacter != null){float deltaX = Input.mousePosition.x - lastMouseX;currentPreviewCharacter.transform.Rotate(0, -deltaX * rotationSpeed * Time.deltaTime, 0);lastMouseX = Input.mousePosition.x;}}/// <summary>/// 加载角色数据/// </summary>private void LoadCharacterData(){Debug.Log("[CharacterSelectPanel] 加载角色数据");// 理想情况下,这些数据应该从DataManager或资源文件加载// 这里为演示创建一些测试数据characterDataList.Clear();// 简化测试数据,只包含必要信息characterDataList.Add(new CharacterData(0, "战士", "", "Characters/Warrior"));characterDataList.Add(new CharacterData(1, "忍者", "", "Characters/Ninja"));characterDataList.Add(new CharacterData(2, "魔法师", "", "Characters/Mage"));characterDataList.Add(new CharacterData(3, "武僧", "", "Characters/Monk"));Debug.Log($"[CharacterSelectPanel] 已加载{characterDataList.Count}个角色数据");}/// <summary>/// 填充角色列表/// </summary>private void PopulateCharacterList(){if (characterListContainer == null){Debug.LogError("[CharacterSelectPanel] characterListContainer未设置,无法填充角色列表");return;}if (characterItemPrefab == null){Debug.LogError("[CharacterSelectPanel] characterItemPrefab未设置,无法填充角色列表");return;}Debug.Log("[CharacterSelectPanel] 填充角色列表");// 清空现有列表和记录foreach (Transform child in characterListContainer){Destroy(child.gameObject);}characterItemUIs.Clear();// 为每个角色创建列表项for (int i = 0; i < characterDataList.Count; i++){CharacterData data = characterDataList[i];GameObject item = Instantiate(characterItemPrefab, characterListContainer);item.name = $"CharacterItem_{data.name}";// 配置角色项CharacterItemUI itemUI = item.GetComponent<CharacterItemUI>();if (itemUI != null){int index = i; // 创建局部变量以便在闭包中使用itemUI.Setup(data);itemUI.OnItemSelected += () => OnCharacterItemSelected(index);characterItemUIs.Add(itemUI);Debug.Log($"[CharacterSelectPanel] 创建角色列表项: {data.name}");}else{Debug.LogError($"[CharacterSelectPanel] 角色列表项预制体上没有CharacterItemUI组件!");}}}/// <summary>/// 角色项被选中/// </summary>private void OnCharacterItemSelected(int index){if (index < 0 || index >= characterDataList.Count) return;Debug.Log($"[CharacterSelectPanel] 选择角色: {characterDataList[index].name}");// 更新选中的角色索引selectedCharacterIndex = index;CharacterData data = characterDataList[index];// 更新角色项UI状态for (int i = 0; i < characterItemUIs.Count; i++){if (characterItemUIs[i] != null){characterItemUIs[i].SetSelected(i == index);}}// 加载角色预览LoadCharacterPreview(data.prefabPath);// 启用确认按钮if (confirmButton != null)confirmButton.interactable = true;}/// <summary>/// 加载角色预览/// </summary>private void LoadCharacterPreview(string prefabPath){Debug.Log($"[CharacterSelectPanel] 开始加载角色预览: {prefabPath}");if (characterPosition == null){Debug.LogError("[CharacterSelectPanel] characterPosition未设置,无法加载角色预览");return;}// 清除当前预览角色if (currentPreviewCharacter != null){Destroy(currentPreviewCharacter);currentPreviewCharacter = null;}// 加载角色预制体GameObject characterPrefab = Resources.Load<GameObject>(prefabPath);if (characterPrefab == null){Debug.LogError($"[CharacterSelectPanel] 无法加载角色预制体: {prefabPath},请检查路径是否正确并且确保预制体存在于Resources目录");return;}Debug.Log($"[CharacterSelectPanel] 成功加载角色预制体: {prefabPath}");// 实例化新角色currentPreviewCharacter = Instantiate(characterPrefab, characterPosition);// 设置层和位置int previewLayer = LayerMask.NameToLayer("CharacterPreview");if (previewLayer < 0){Debug.LogError("[CharacterSelectPanel] 未找到'CharacterPreview'层,请在项目设置中添加此层");previewLayer = 0; // 使用默认层}currentPreviewCharacter.layer = previewLayer;SetLayerRecursively(currentPreviewCharacter, previewLayer);// 重置位置和旋转currentPreviewCharacter.transform.localPosition = Vector3.zero;currentPreviewCharacter.transform.localRotation = Quaternion.identity;// 播放待机动画(如果有)Animator animator = currentPreviewCharacter.GetComponent<Animator>();if (animator != null){animator.Play("Idle");Debug.Log("[CharacterSelectPanel] 已播放Idle动画");}else{Debug.Log("[CharacterSelectPanel] 角色没有Animator组件,跳过动画播放");}Debug.Log($"[CharacterSelectPanel] 角色预览加载完成: {prefabPath}");}/// <summary>/// 递归设置物体及其子物体的层/// </summary>private void SetLayerRecursively(GameObject obj, int layer){obj.layer = layer;foreach (Transform child in obj.transform){SetLayerRecursively(child.gameObject, layer);}}/// <summary>/// 确认按钮点击事件/// </summary>private void OnConfirmButtonClicked(){if (selectedCharacterIndex < 0){Debug.LogWarning("请先选择一个角色");return;}// 保存选择的角色if (DataManager.Instance != null){DataManager.Instance.SetSelectedCharacter(selectedCharacterIndex);}Debug.Log($"确认选择角色: {characterDataList[selectedCharacterIndex].name}");// 隐藏角色选择面板Hide();// 进入战斗场景if (GameManager.Instance != null){GameManager.Instance.ChangeState(GameState.Battle);}}protected override void OnPanelShow(){base.OnPanelShow();Debug.Log("[CharacterSelectPanel] 显示面板");// 重置选择状态selectedCharacterIndex = -1;if (confirmButton != null)confirmButton.interactable = false;// 清除当前预览if (currentPreviewCharacter != null){Destroy(currentPreviewCharacter);currentPreviewCharacter = null;}}protected override void OnBackButtonClicked(){Debug.Log("[CharacterSelectPanel] 返回按钮点击");Hide();// 返回难度选择界面UIManager.Instance.ShowDifficultySelectPanel();}}/// <summary>/// 角色数据类/// </summary>[System.Serializable]public class CharacterData{public int id;public string name;public string description;public string prefabPath;public CharacterData(int id, string name, string description, string prefabPath){this.id = id;this.name = name;this.description = description;this.prefabPath = prefabPath;}}
}