这里的所有都是siki学院的课程我本人自己的练习。
1.处理3d物体和ui之间交互
(1)ui响应带点击事件,3d物体不响应:让他们的脚本都继承 UnityEngine.EventSystems 命名空间下的IPointerClickHandler 接口然后在接口中实现changercolor方法即可(要在相机上添加PhysicsRaycast组件这样3d物体才能相应点击事件)
(2)ui和3d物体都响应点击事件在ui相应的脚本中添加一个方法,通过获取所有的点击物体,来实现。
2.通过vertexHelper实现圆形图标
1.其实可以通过mask遮罩实现,但是这样(1)多消耗一个drawcall来创建mask做,像素剔除(2)不利于层级的合并,原本同一图集里的ui可以合并层级,仅需一个Drawcall渲染,如果加入Mask,就会将一个ui整体分割成了Mask下的子ui与其他ui,两者只能各自进行层级合并,至少要两个Drawcall。Mask用得多了,一个ui整体会被分割得四分五裂,就会严重影响层次合并的效率了
3.不能精确点击。
using System.Collections;
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEditor.Sprites;
using UnityEngine;
using UnityEngine.UI;
[AddComponentMenu("UI/Effects/Circle2", 16)]
//[RequireComponent(typeof(Image))]
public class TestCircleScripts : BaseMeshEffect
{
[SerializeField]
private int segementCount = 100;
[SerializeField]
private float percent = 1;
private float fill = 1;
private int circleVertextCount;
private RectTransform rectTransform = null;
private Image image = null;
private bool isInstall;
public List<Vector3> vertexList;
void Start()
{
image = GetComponent<Image>();
rectTransform = GetComponent<RectTransform>();
Debug.Log("start");
}
public override void ModifyMesh(VertexHelper vh)
{
if (!isActiveAndEnabled) return;
if (rectTransform == null)
{
rectTransform = GetComponent<RectTransform>();
}
if (image == null)
{
image = GetComponent<Image>();
}
if (image.type != Image.Type.Simple)
{
return;
}
Sprite sprite = image.overrideSprite;
if (sprite == null || image.overrideSprite == null)
{
return;
}
vertexList = new List<Vector3>();
float width = rectTransform.rect.width;
float height = rectTransform.rect.height;
float maxSide = Mathf.Max(width, height);
/* if(maxSide>512)
segementCount = 60;
else if(maxSide>256)
segementCount = 50;
else if(maxSide>128)
segementCount = 30;
else if(maxSide>64)
segementCount = 25;
else if(maxSide>32)
segementCount = 20;
else
segementCount = 15;*/
circleVertextCount = segementCount + 1;
fill = (int)(segementCount * percent);
vh.Clear();
Vector2[] uvs = image.overrideSprite.uv;
float minX = 10000, minY = 10000, maxX = -10000, maxY = -10000;
for (int i = 0; i < uvs.Length; ++i)
{
var uv = uvs[i];
minX = Mathf.Min(minX, uv.x);
maxX = Mathf.Max(maxX, uv.x);
minY = Mathf.Min(minY, uv.y);
maxY = Mathf.Max(maxY, uv.y);
}
// Debug.Log("MyTest:" +"minX" + minX + "minY" + minY + "maxX " + maxX + " maxY" + maxY);
float uvCenterX = (minX + maxX) * 0.5f;
float uvCenterY = (minY + maxY) * 0.5f;
// Debug.Log("MyTest:" + uvCenterX + " " + uvCenterY);
float uvScaleX = (maxX - minX) / width;
float uvScaleY = (maxY - minY) / height;
float radian = 2 * Mathf.PI / segementCount;
float radius = 0.5f * width;
UIVertex centerVertex = new UIVertex();
centerVertex.position = Vector2.zero;
centerVertex.color = image.color;
centerVertex.uv0 = new Vector2(uvCenterX, uvCenterY);
vh.AddVert(centerVertex);
// Debug.Log("MyTest:" + centerVertex.uv0);
float curRadian = 0;
for (int i = 1; i < circleVertextCount; i++)
{
UIVertex vertex = new UIVertex();
float x = Mathf.Cos(curRadian) * radius;
float y = Mathf.Sin(curRadian) * radius;
vertex.position = new Vector2(centerVertex.position.x + x, centerVertex.position.y + y);
if (i <= fill)
{
vertex.color = image.color;
}
else
{
vertex.color = new Color32(60, 60, 60, 255);
}
var uv_x = x * uvScaleX + uvCenterX;
var uv_y = y * uvScaleY + uvCenterY;
vertex.uv0 = new Vector2(uv_x, uv_y);
vh.AddVert(vertex);
vertexList.Add(vertex.position);
curRadian += radian;
}
for (int i = 1; i < circleVertextCount; i++)
{
if (i == circleVertextCount - 1)
{
vh.AddTriangle(i, 0, 1);
}
else
{
vh.AddTriangle(i, 0, i + 1);
}
}
}
}
实现的效果如下
这个代码并且实现了技能冷却
2.1在以上基础上进行图片的精确点击
因为第二个只是进行了图片的mesh顶点的重构,但是这个响应的区域是在这个框内,所以编写脚本实现精确点击的算法。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class ship : Image
{
TestCircleScripts testCircleScripts;
List<Vector3> vertexList = new List<Vector3>();
public override bool IsRaycastLocationValid(Vector2 screenPoint, Camera eventCamera)
{
Vector2 localPoint;
RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, screenPoint, null, out localPoint);
Debug.Log("localPoint:" + localPoint);
return IsValid(localPoint);
}
private void Start()
{
testCircleScripts = GetComponent<TestCircleScripts>();
vertexList = testCircleScripts.vertexList;
Debug.Log(vertexList[1]);
Debug.Log("vertexlistcount:" + vertexList.Count);
}
private bool IsValid(Vector2 localPosition)
{
bool isvalidbool;
isvalidbool = GetCrossPointNum(localPosition) % 2 == 1;
Debug.Log("isvalidbool:" + isvalidbool);
return isvalidbool;
}
private int GetCrossPointNum(Vector2 localPosition)
{
int count = 0;
Vector3 vert1 = Vector3.zero;
Vector3 vert2 = Vector3.zero;
int vertexCount = vertexList.Count;
for (int i = 0; i < vertexCount; i++)
{
vert1 = vertexList[i];
vert2 = vertexList[(i + 1) % vertexCount];
if (IsYInRange(localPosition, vert1, vert2))
{
float k = (vert2.y - vert1.y) / (vert2.x - vert1.x);
float b = vert2.y - (vert2.x * k);
float x = (localPosition.y - b) / k;
if (localPosition.x <= x)
{
count++;
}
}
}
Debug.Log("count:" + count);
return count;
}
private bool IsYInRange(Vector2 localPosition, Vector3 vert1, Vector3 vert2)
{
bool isyinrangeBool;
if (vert1.y < vert2.y)
{
isyinrangeBool = vert1.y < localPosition.y && vert2.y > localPosition.y;
}
else
{
isyinrangeBool = vert1.y > localPosition.y && vert2.y < localPosition.y;
}
Debug.Log("isyinrang:" + isyinrangeBool);
return isyinrangeBool;
}
}
2.2实现精确点击的方法二
给图片开启read write enable 模式
然后在写一个脚本 获得image组件中的alphaHitTestMinimumThreshold并且将它的值设置为0.1f即可,这种方法不推荐使用因为这样的图片无法打包成图集,并且图片开启这个模式会造成资源的浪费。
2.3实现精确点击的方法三
(1)给图片添加 Polygon Collider 2D 组件,在图片上编辑碰撞体的大小
(2)给物体添加脚本实现碰撞体的检测
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class CustomImage : Image
{
private PolygonCollider2D _PolygonCollider2D;
public PolygonCollider2D PolygonCollider2D
{
get
{
if (_PolygonCollider2D == null)
{
_PolygonCollider2D = GetComponent<PolygonCollider2D>();
}
return _PolygonCollider2D;
}
}
public override bool IsRaycastLocationValid(Vector2 screenPoint, Camera eventCamera)
{
Vector3 point;
RectTransformUtility.ScreenPointToWorldPointInRectangle(rectTransform, screenPoint, eventCamera, out point);
return PolygonCollider2D.OverlapPoint(point);
}
}
4.1用2D imag实现3D的轮转
(1)首先在canvas下创建一个空物体
(2)通过代码创建子物体并未它添加脚本
/// <summary>
/// 创建一个旋转图的游戏物体,并为它们添加组件
/// </summary>
/// <returns></returns>
private GameObject CreatTeamMate()
{
GameObject item = new GameObject("TempMate");
item.AddComponent<RectTransform>().sizeDelta = ItemSize;
item.AddComponent<Image>();
item.AddComponent<RotationDiagramItem>();
return item;
}
/// <summary>
/// 为创建的游戏物体的相关组件赋值
/// </summary>
private void CreatItem()
{
GameObject item = CreatTeamMate();
RotationDiagramItem itemTemp = null;
foreach (Sprite sprite in sprites)
{
itemTemp = Instantiate(item).GetComponent<RotationDiagramItem>();
itemTemp.SetParent(transform);
itemTemp.SetSprite(sprite);
itemTemp.AddMoveListener(Change);
items.Add(itemTemp);
}
}
(3)因为这是用2D Image 模仿3D轮转图 所以说是通过控制它的位置x和缩放
这里是它控制缩放和位置的算法
private float GetX(float radio , float length)
{
if(radio<0||radio>1)
{
return 0;
}
else if(radio>=0&&radio<=0.25)
{
return radio * length;
}
else if(radio>0.25&&radio<0.75)
{
return (0.5f - radio) * length;
}
else
{
return (radio - 1) * length;
}
}
/// <summary>
///
/// </summary>
/// <param name="min"></param>
/// <param name="max"></param>
/// <param name="radio">相当于在这个3D轮转中这个物体的x值,这个圆环是从零到一的一个轮转</param>
/// <returns></returns>
private float GetScaleTimes(float min,float max,float radio)
{
float scalePercent = (max - min) / 0.5f;
if(radio<0&&radio>1)
{
return 0;
}
else if(radio==0||radio==1)
{
return scalePercent;
}
else if(radio>0&&radio<=0.5)
{
return scalePercent * (1 - radio);
}
else
{
return scalePercent * radio;
}
}
(4)最后就是实现位置的替换,和拖拽详细代码我放在下边
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.Linq;
public class RotationDiagram2D : MonoBehaviour
{
public Sprite[] sprites;
public Vector2 ItemSize;
/// <summary>
/// 是一个类存的子物体的位置信息
/// </summary>
public List<itemPosData> ItemPose;
/// <summary>
/// 是一个结构存的子物体的id信息
/// </summary>
public List<itemID> itemIDs;
public float offest;
public float scaleMax;
public float scaleMin;
/// <summary>
/// 是子物体上的组件
/// </summary>
private List<RotationDiagramItem> items;
void Start()
{
items = new List<RotationDiagramItem>();
ItemPose = new List<itemPosData>();
itemIDs = new List<itemID>();
CreatItem();
CalulaetData();
SetItemData();
}
// Update is called once per frame
void Update()
{
}
/// <summary>
/// 创建一个旋转图的游戏物体,并为它们添加组件
/// </summary>
/// <returns></returns>
private GameObject CreatTeamMate()
{
GameObject item = new GameObject("TempMate");
item.AddComponent<RectTransform>().sizeDelta = ItemSize;
item.AddComponent<Image>();
item.AddComponent<RotationDiagramItem>();
return item;
}
/// <summary>
/// 为创建的游戏物体的相关组件赋值
/// </summary>
private void CreatItem()
{
GameObject item = CreatTeamMate();
RotationDiagramItem itemTemp = null;
foreach (Sprite sprite in sprites)
{
itemTemp = Instantiate(item).GetComponent<RotationDiagramItem>();
itemTemp.SetParent(transform);
itemTemp.SetSprite(sprite);
itemTemp.AddMoveListener(Change);
items.Add(itemTemp);
}
}
private void Change(float offsetX)
{
int symbol = offsetX > 0 ? 1 : -1;
Debug.Log(symbol);
foreach (RotationDiagramItem item in items)
{
item.ChangeId(symbol, items.Count);
}
for(int i=0;i<ItemPose.Count;i++)
{
items[i].SetData(ItemPose[items[i].ID]);
}
}
private void CalulaetData()
{
float length = (ItemSize.x + offest) * items.Count;
float radioOffset = 1.0f / items.Count;
float radio = 0;
for(int i=0;i<items.Count;i++)
{
itemID itemID = new itemID();
itemID.id = i;
itemIDs.Add(itemID);
items[i].ID = i;
itemPosData data = new itemPosData();
data.x = GetX(radio, length);
data.ScaleTimes = GetScaleTimes(scaleMin, scaleMax, radio);
radio += radioOffset;
ItemPose.Add(data);
}
itemIDs = itemIDs.OrderBy(u => ItemPose[u.id].ScaleTimes).ToList();
for(int i=0;i<itemIDs.Count;i++)
{
ItemPose[itemIDs[i].id].order = i;
}
}
private void SetItemData()
{
for(int i=0; i<ItemPose.Count;i++)
{
items[i].SetData(ItemPose[i]);
}
}
private float GetX(float radio , float length)
{
if(radio<0||radio>1)
{
return 0;
}
else if(radio>=0&&radio<=0.25)
{
return radio * length;
}
else if(radio>0.25&&radio<0.75)
{
return (0.5f - radio) * length;
}
else
{
return (radio - 1) * length;
}
}
/// <summary>
///
/// </summary>
/// <param name="min"></param>
/// <param name="max"></param>
/// <param name="radio">相当于在这个3D轮转中这个物体的x值,这个圆环是从零到一的一个轮转</param>
/// <returns></returns>
private float GetScaleTimes(float min,float max,float radio)
{
float scalePercent = (max - min) / 0.5f;
if(radio<0&&radio>1)
{
return 0;
}
else if(radio==0||radio==1)
{
return scalePercent;
}
else if(radio>0&&radio<=0.5)
{
return scalePercent * (1 - radio);
}
else
{
return scalePercent * radio;
}
}
public class itemPosData
{
public float x;
public float ScaleTimes;
public int order;
}
public struct itemID
{
public int id;
}
}
然后是子物体上的代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using static RotationDiagram2D;
using UnityEngine.EventSystems;
using System;
using DG.Tweening;
public class RotationDiagramItem : MonoBehaviour,IDragHandler,IEndDragHandler
{
public int ID;
public float offset;
private Action<float> moveAction;
private Image _Image;
private Image Image
{
get
{
if (_Image == null)
{
_Image = GetComponent<Image>();
}
return _Image;
}
}
private RectTransform _rect;
private RectTransform rect
{
get
{
if(_rect ==null)
{
_rect = GetComponent<RectTransform>();
}
return _rect;
}
}
public void SetParent(Transform parent)
{
transform.parent = parent;
}
public void SetSprite(Sprite sprite)
{
Image.sprite = sprite;
}
/// <summary>
/// 设置子物体的位置信息
/// </summary>
/// <param name="data"></param>
public void SetData(itemPosData data)
{
rect.DOAnchorPos(Vector2.right * data.x, 0.5f);
rect.DOScale(Vector3.one * data.ScaleTimes, 0.5f);
// rect.anchoredPosition = data.x * Vector2.right;
// rect.localScale = Vector3.one * data.ScaleTimes;
StartCoroutine(Wait(data.order));
}
private IEnumerator Wait(int order)
{
yield return new WaitForSeconds(0.5f);
transform.SetSiblingIndex(order);
}
public void ChangeId(int symbol,int totalNum)
{
int id = ID;
id += symbol;
if(id<0)
{
id += totalNum;
}
ID = id % totalNum;
}
public void OnDrag(PointerEventData eventData)
{
offset += eventData.delta.x;
}
public void OnEndDrag(PointerEventData eventData)
{
moveAction(offset);
offset = 0;
}
public void AddMoveListener(Action<float> onMove)
{
moveAction = onMove;
}
}
5实现雷达图
因为在游戏中一个雷达图能够直观的反映出你的能力,所以本次针对这个写了一个雷达图
(1)创建一个image附上背景图,在创建一个子物体
(2)将代码添加到这个物体上即可
添加到子物体上的脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class RadarChart : Image
{
[SerializeField]
private int _pointCount;
[SerializeField]
private List<RectTransform> _points;
[SerializeField]
private float[] _handlerRadio;
[SerializeField]
private List<RadarChartHandler> _handlers;
[SerializeField]
private Color _pointColor = Color.white;
[SerializeField]
private Sprite _pointSprite;
[SerializeField]
private Vector2 _pointSize = new Vector2(10,10);
/// <summary>
///填充Mesh
/// </summary>
/// <param name="vh"></param>
protected override void OnPopulateMesh(VertexHelper vh)
{
vh.Clear();
AddVertexs(vh);
AddTriangle(vh);
}
/// <summary>
/// 添加顶点
/// </summary>
/// <param name="vh"></param>
private void AddVertexs(VertexHelper vh)
{
vh.AddVert(rectTransform.pivot, color, new Vector2(0.5f, 0.5f));
/* foreach (RadarChartHandler handler in _handlers)
{
vh.AddVert(handler.transform.localPosition, color, Vector2.zero);
}*/
AddVertexUV(vh);
}
/// <summary>
/// 添加uv
/// </summary>
/// <param name="vh"></param>
private void AddVertexUV(VertexHelper vh)
{
vh.AddVert(_handlers[0].transform.localPosition, color,new Vector2(0.5f, 1));
vh.AddVert(_handlers[1].transform.localPosition, color, new Vector2(0, 1));
vh.AddVert(_handlers[2].transform.localPosition, color, new Vector2(0, 0));
vh.AddVert(_handlers[3].transform.localPosition, color, new Vector2(1, 0));
vh.AddVert(_handlers[4].transform.localPosition, color, new Vector2(1, 1));
}
/// <summary>
/// 设置三角序列
/// </summary>
/// <param name="vh"></param>
private void AddTriangle(VertexHelper vh)
{
for(int i=1;i<_pointCount;i++)
{
vh.AddTriangle(i, 0, i + 1);
}
vh.AddTriangle(_pointCount, 0, 1);
}
/// <summary>
/// 初始化顶点
/// </summary>
public void InitPoint()
{
ClearPoints();
_points = new List<RectTransform>();
SpawnPoint();
SetPointPos();
}
/// <summary>
/// 清空顶点
/// </summary>
private void ClearPoints()
{
if(_points==null)
{
return;
}
foreach (RectTransform point in _points)
{
if(point!=null)
{
DestroyImmediate(point.gameObject);
}
}
}
/// <summary>
/// 初始化可拖动顶点
/// </summary>
public void InitHandlers()
{
ClearHandlers();
_handlers = new List<RadarChartHandler>();
SpawnHandlers();
SetHandlerPos();
}
/// <summary>
/// 清空可拖动顶点
/// </summary>
private void ClearHandlers()
{
if (_handlers == null)
return;
foreach (RadarChartHandler handler in _handlers)
{
if (handler != null)
DestroyImmediate(handler.gameObject);
}
}
/// <summary>
/// 生成背景顶点
/// </summary>
private void SpawnPoint()
{
for(int i=0;i<_pointCount;i++)
{
GameObject point = new GameObject("point" + i);
point.transform.SetParent(transform);
_points.Add(point.AddComponent<RectTransform>());
}
}
/// <summary>
/// 设置顶点位置
/// </summary>
private void SetPointPos()
{
float radian = 2 * Mathf.PI / _pointCount;
float radius = 100;
float curRadian = 2 * Mathf.PI / 4.0f;
for(int i=0;i<_pointCount;i++)
{
float x = Mathf.Cos(radian) * radius;
float y = Mathf.Sin(radian) * radius;
curRadian += radian;
_points[i].anchoredPosition = new Vector2(x, y);
}
}
/// <summary>
/// 生成可拖动顶点
/// </summary>
private void SpawnHandlers()
{
RadarChartHandler handler = null;
for(int i=0;i<_pointCount;i++)
{
GameObject Handler = new GameObject("Handler" + i);
Handler.AddComponent<Image>();
Handler.AddComponent<RectTransform>();
handler = Handler.AddComponent<RadarChartHandler>();
handler.SetParents(transform);
handler.ChangColor(_pointColor);
handler.ChangeSprite(_pointSprite);
handler.SetSize(_pointSize);
handler.SetScale(Vector3.one);
_handlers.Add(handler);
}
}
/// <summary>
/// 设置可拖动顶点位置
/// </summary>
private void SetHandlerPos()
{
if(_handlerRadio==null||_handlerRadio.Length!=_pointCount)
{
for(int i=0;i<_pointCount;i++)
{
_handlers[i].SetPos(_points[i].anchoredPosition);
}
}
else
{
for(int i=0;i<_pointCount;i++)
{
_handlers[i].SetPos(_points[i].anchoredPosition * _handlerRadio[i]);
}
}
}
void Start()
{
}
// Update is called once per frame
void Update()
{
//可以实时更新顶点
SetVerticesDirty();
}
}
handler上的脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
public class RadarChartHandler : MonoBehaviour,IDragHandler
{
private Image _image;
private Image Image
{
get
{
if(_image==null)
{
_image = GetComponent<Image>();
}
return _image;
}
}
private RectTransform _rect;
private RectTransform Rect
{
get
{
if(_rect==null)
{
_rect=GetComponent<RectTransform>();
}
return _rect;
}
}
public void SetParents(Transform _transform)
{
transform.SetParent(_transform);
}
public void ChangColor(Color color)
{
Image.color = color;
}
public void ChangeSprite(Sprite sprite)
{
Image.sprite = sprite;
}
public void SetPos(Vector2 pos)
{
Rect.anchoredPosition = pos;
}
public void SetSize(Vector2 size)
{
Rect.sizeDelta = size;
}
public void SetScale(Vector3 scale)
{
Rect.localScale = scale;
}
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
public void OnDrag(PointerEventData eventData)
{
Rect.anchoredPosition += eventData.delta/GetScale();
}
private float GetScale()
{
return Rect.lossyScale.x;
}
}
编辑器脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
[CustomEditor(typeof(RadarChart), true)]
[CanEditMultipleObjects]
public class RadarChartEditor : UnityEditor.UI.ImageEditor
{
SerializedProperty _pointCount;
SerializedProperty _pointSprite;
SerializedProperty _pointColor;
SerializedProperty _pointSize;
SerializedProperty _handlerRadio;
protected override void OnEnable()
{
base.OnEnable();
_pointCount = serializedObject.FindProperty("_pointCount");
_pointSprite = serializedObject.FindProperty("_pointSprite");
_pointColor = serializedObject.FindProperty("_pointColor");
_pointSize = serializedObject.FindProperty("_pointSize");
_handlerRadio = serializedObject.FindProperty("_handlerRadio");
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
serializedObject.Update();
EditorGUILayout.PropertyField(_pointCount);
EditorGUILayout.PropertyField(_pointSprite);
EditorGUILayout.PropertyField(_pointColor);
EditorGUILayout.PropertyField(_pointSize);
EditorGUILayout.PropertyField(_handlerRadio, true);
RadarChart radar = target as RadarChart;
if (radar != null)
{
if (GUILayout.Button("生成雷达图顶点"))
{
radar.InitPoint();
}
if (GUILayout.Button("生成内部可操作顶点"))
{
radar.InitHandlers();
}
}
serializedObject.ApplyModifiedProperties();
if (GUI.changed)
{
EditorUtility.SetDirty(target);
}
}
}
6.scroll 实现图片的加载
1.首先获得显示item的数量,并且生成item
2.然后根据图片的数量设置滚动视图的大小
3.设置id
4.当发生滑动时调用事件changid
挂在父物体上的脚本
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class LoopList : MonoBehaviour
{
// Start is called before the first frame update
public float offsetY;
private float _itemHeight;
private int num;
private RectTransform _content;
private List<LoopListItem> _items;
private List<LoopListItemModel> _models;
void Start()
{
_items = new List<LoopListItem>();
_models = new List<LoopListItemModel>();
GetModel();
_content = transform.Find("Viewport/Content").GetComponent<RectTransform>();
GameObject itemPerfab = Resources.Load<GameObject>("LoopListItem");
_itemHeight = itemPerfab.GetComponent<RectTransform>().rect.height;
num = GetShowItemNum(_itemHeight);
SpawnItem(itemPerfab);
SetContentSize();
transform.GetComponent<ScrollRect>().onValueChanged.AddListener(ValueChange);
}
private void ValueChange(Vector2 data)
{
foreach (LoopListItem item in _items)
{
item.OnValueChange();
}
}
private int GetShowItemNum(float itemHeight)
{
float Height = GetComponent<RectTransform>().rect.height;
return Mathf.CeilToInt(Height / (itemHeight+offsetY)) + 1;
}
/// <summary>
/// 生成每个物体,并为他们添加组件
/// </summary>
private void SpawnItem(GameObject perfab)
{
GameObject tempObject = null;
LoopListItem item = null;
for(int i=0;i<num;i++)
{
tempObject = Instantiate(perfab, _content);
item = tempObject.AddComponent<LoopListItem>();
item.AddGetDataListener(GetData);
item.Init(i,offsetY, num);
item._id = i;
_items.Add(item);
}
}
private LoopListItemModel GetData(int index)
{
if(index<0||index>=_models.Count)
{
return new LoopListItemModel();
}
return _models[index];
}
private void GetModel()
{
foreach (var sprite in Resources.LoadAll<Sprite>("Icon"))
{
_models.Add(new LoopListItemModel(sprite, sprite.name));
}
}
private void SetContentSize()
{
float height = _models.Count * (_itemHeight + offsetY) - offsetY;
_content.sizeDelta = new Vector2(_content.sizeDelta.x, height);
}
// Update is called once per frame
void Update()
{
}
}
子物体脚本
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class LoopListItem : MonoBehaviour
{
public int _id;
private RectTransform _rect;
private RectTransform Rect
{
get
{
if(_rect ==null)
{
_rect = GetComponent<RectTransform>();
}
return _rect;
}
}
private Image _icon;
private Image Icon
{
get
{
if(_icon == null)
{
_icon = transform.Find("Image").GetComponent<Image>();
//GetComponent<Image>();
}
return _icon;
}
}
private Text _des;
public Text Des
{
get
{
if (_des == null)
_des = transform.Find("Text").GetComponent<Text>();
return _des;
}
}
private RectTransform _connet;
private Func<int, LoopListItemModel> _getData;
private float _offfset;
private int _showNum;
private LoopListItemModel _modle;
public void AddGetDataListener(Func<int ,LoopListItemModel> getData)
{
_getData = getData;
}
public void Init(int id,float offset,int showNum)
{
_id = -1;
_connet = transform.parent.GetComponent<RectTransform>();
_offfset = offset;
_showNum = showNum;
ChangeId(id);
}
public void OnValueChange()
{
int startId, endId = 0;
UpdataIdRange(out startId,out endId);
JudgeSelfId(startId, endId);
}
private void UpdataIdRange(out int startId, out int endId)
{
startId = Mathf.FloorToInt(_connet.anchoredPosition.y / (Rect.rect.height + _offfset));
endId = startId + _showNum - 1;
// JudgeSelfId(startId,endId)
}
private void JudgeSelfId(int startId,int endId)
{
int offset = 0;
if(_id<startId)
{
// offset = startId - _id - 1;
ChangeId(endId-offset);
}
else if(_id>endId)
{
// offset = _id - endId - 1;
ChangeId(startId+offset);
}
}
private void ChangeId(int id)
{
if(_id !=id&&JudgeIdValid(id))
{
_id = id;
_modle = _getData(id);
Icon.sprite = _modle.Icon;
Des.text = _modle.Describe;
SetPos();
}
}
private void SetPos()
{
Rect.anchoredPosition = new Vector2(0, -_id * (Rect.rect.height + _offfset));
}
private bool JudgeIdValid(int id)
{
return !_getData(id).Equals(new LoopListItemModel());
}
}
模型用来储存图片
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public struct LoopListItemModel
{
public Sprite Icon;
public string Describe;
public LoopListItemModel(Sprite icon, string describe)
{
Icon = icon;
Describe = describe;
}
}
效果
7.实现血条
0.设置血条的中心点和自适应(左边)
1.把血条加载出来并为它添加脚本(C: SpawnLifeBar)
2.设置血条的位置,为模型的上方。(Bar:GetOffset Update)
3.血条的数值设置(血条的最大值以及血条里的数据存在struct里)(LifeBarData)
4.获得血条每单位的长度( _unitLifeScale)
5.设置子项数据(Bar Item:SetBarData SetData)
6.子项数据初始化
7.扣血逻辑,超限逻辑,跟换血条逻辑。(ChangLife,GetOutOfRange,ChangeIndex)
控制模型脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Controller : MonoBehaviour
{
private LifeBar _bar;
// Start is called before the first frame update
void Start()
{
Canvas canvas = FindObjectOfType<Canvas>();
if(canvas == null)
{
return;
}
SpawnLifeBar(canvas);
}
private void SpawnLifeBar(Canvas canvas)
{
GameObject perfab = Resources.Load<GameObject>("LifeBar");
_bar = Instantiate(perfab, canvas.transform).AddComponent<LifeBar>();
List<LifeBarData> datas = new List<LifeBarData>();
datas.Add(new LifeBarData(null, Color.green));
datas.Add(new LifeBarData(null, Color.red));
datas.Add(new LifeBarData(null, Color.yellow));
_bar.Init(transform,datas,350);
}
// Update is called once per frame
void Update()
{
if(Input.GetKey(KeyCode.A))
{
Move(Vector3.left);
}
if (Input.GetKey(KeyCode.W))
{
Move(Vector3.forward);
}
if (Input.GetKey(KeyCode.S))
{
Move(Vector3.back);
}
if (Input.GetKey(KeyCode.D))
{
Move(Vector3.right);
}
if(Input.GetMouseButtonDown(0))
{
_bar.ChangLife(-8);
}
if(Input.GetMouseButtonDown(1))
{
_bar.ChangLife(8);
}
}
private void Move(Vector3 direction)
{
transform.Translate(direction * Time.deltaTime * 3);
}
}
父物体
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class LifeBar : MonoBehaviour
{
/// <summary>
/// target就是这个飞机模型
/// </summary>
private Transform _target;
private Vector3 _offset;
/// <summary>
/// 每个血条的颜色和图片
/// </summary>
private List<LifeBarData> _data;
private LifeBarItem _currentBar;
private LifeBarItem _nextBar;
private float _unitLifeScale;
private int _lifeMax;
public int _currentIndex;
// Start is called before the first frame update
void Start()
{
}
public void Init(Transform parent,List<LifeBarData> data,int lifeMax)
{
_currentIndex = 0;
_lifeMax = lifeMax;
_target = parent;
_offset = GetOffset(_target);
_data = data;
_currentBar = GameObject.Find("CurrentBar").AddComponent<LifeBarItem>();
_nextBar = GameObject.Find("NextBar").AddComponent<LifeBarItem>();
_currentBar.Init();
_nextBar.Init();
RectTransform rect = GetComponent<RectTransform>();
_unitLifeScale = rect.rect.width * _data.Count / _lifeMax;
SetBarData(_currentIndex, data);
}
public void ChangLife(float value)
{
float width = _currentBar.ChangLife(value * _unitLifeScale);
if(width<0&&ChangeIndex(1))
{
Exchange();
_currentBar.transform.SetAsLastSibling();
_nextBar.RestDefault();
SetBarData(_currentIndex, _data);
ChangLife(width / _unitLifeScale);
}
else if(width>0&&ChangeIndex(-1))
{
Debug.Log("width<0");
Exchange();
_currentBar.transform.SetAsLastSibling();
_currentBar.RestToZero();
SetBarData(_currentIndex, _data);
ChangLife(width / _unitLifeScale);
}
}
private bool ChangeIndex(int symbol)
{
int index = _currentIndex + symbol;
// Debug.Log(index);
if(index>=0&&index<_data.Count)
{
_currentIndex = index;
return true;
}
return false;
}
/// <summary>
/// 血条交换位置
/// </summary>
private void Exchange()
{
var temp = _nextBar;
_nextBar = _currentBar;
_currentBar = temp;
}
private Vector3 GetOffset(Transform target)
{
Renderer renderer = target.GetComponent<Renderer>();
if(renderer==null)
{
return Vector3.zero;
}
return renderer.bounds.max.y*Vector3.up;
}
private void SetBarData(int index,List<LifeBarData> data)
{
if(index<0||index>data.Count)
{
return;
}
_currentBar.SetData(data[index]);
if(index+1>=data.Count)
{
_nextBar.SetData(new LifeBarData(null, Color.white));
}
else
{
_nextBar.SetData(data[index + 1]);
}
}
public void Update()
{
if (_target == null)
{
return;
}
transform.position = Camera.main.WorldToScreenPoint(_target.position + _offset);
}
}
public struct LifeBarData
{
public Sprite BarSprite;
public Color BarMainColor;
public LifeBarData(Sprite sprite, Color color)
{
BarSprite = sprite;
BarMainColor = color;
}
}
子物体
using DG.Tweening;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using static LifeBar;
public class LifeBarItem : MonoBehaviour
{
private RectTransform _rect;
private RectTransform Rect
{
get
{
if(_rect==null)
{
_rect=GetComponent<RectTransform>();
}
return _rect;
}
}
private Image _image;
private Image Image
{
get
{
if(_image==null)
{
_image=GetComponent<Image>();
}
return _image;
}
}
private LifeBarItem _child;
private float defaultWidth;
public void SetData(LifeBarData data)
{
if(data.BarSprite!=null)
{
Image.sprite = data.BarSprite;
}
Image.color = data.BarMainColor;
if (_child != null)
_child.SetData(data);
}
public void Init()
{
defaultWidth = Rect.rect.width;
if(transform.Find("AdditionBar")!=null)
_child = transform.Find("AdditionBar").gameObject.AddComponent<LifeBarItem>();
}
public float ChangLife(float value)
{
Rect.sizeDelta += value * Vector2.right;
if (_child!=null)
{
_child.DOKill();
_child.Image.color = Image.color;
_child.Rect.sizeDelta = Rect.sizeDelta;
_child.Image.DOFade(0, 0.5f).OnComplete(()=>_child.ChangLife(value));
}
return GetOutOfRange();
}
private float GetOutOfRange()
{
float offset = 0;
if(Rect.rect.width<0)
{
offset = Rect.rect.width;
RestToZero();
}
else if(Rect.rect.width>defaultWidth)
{
offset = Rect.rect.width-defaultWidth;
RestDefault();
}
return offset;
}
public void RestToZero()
{
Rect.sizeDelta = Vector2.zero;
}
public void RestDefault()
{
Rect.sizeDelta = Vector2.right * defaultWidth;
}
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}