有积分的可以直接下载上面的资源(竟然要积分头一次知道,还以为自己设置的那,果然垃圾CSDN) 下面是百度网盘资源: 链接:https://pan.baidu.com/s/1KDfrda3L64T0NmUdquztuQ 提取码:36cl
场景分辨率设置为:1920*1080
还要很多可以优化的点地方,有兴趣的可以做 比如对象的销毁和生成可以做成对象池,走到最左边后再移动到最右边循环利用
分析过程文件,采用Blender,资源已上传,可以播放动画看效果,下面截个图:
视频效果如下:
anim
Untiy结构如下: 上面的ImageItem是我手动添加展示关系用的,默认就一个Target,PictureWall挂PictureWall脚本,ImageItem(预制体)挂ImageItemController 脚本即可
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.UI;
using Utility;
public class PictureWall : MonoBehaviour
{
public class PictureInfo
{
public string name;
public Texture2D texture;
}
[SerializeField]
private GameObject prefab;
[SerializeField]
private float width = 1920;
[SerializeField]
private float height = 1080;
[SerializeField, Tooltip("行数")]
private int row = 6;
private int column;
[SerializeField, Tooltip("间隔距离")]
private float intervalDistance = 20;
[SerializeField, Tooltip("奇数行的偏移")]
private float offset = 200;
[SerializeField, Tooltip("圆半径大小")]
private float radius = 300;
private float itemWidth;
private float itemHeight;
public float speed = 10;
[SerializeField]
private RectTransform target;
private string path = "/照片墙/";
private List
private int currentIndex = 0;
private List
void Start()
{
#region 设置Target锚点和位置,觉得麻烦,手动设置请注释这段代码
var rect = transform as RectTransform;
var originalPos = target.anchoredPosition;
target.anchorMin = new Vector2(0, 1);
target.anchorMax = new Vector2(0, 1);
//一般情况新建一个Image或RawImage,锚点是居中的,需要设置到左上,但位置会变化,如有问题请看锚点是否正确
target.anchoredPosition = new Vector2(rect.rect.width / 2 + originalPos.x, originalPos.y - rect.rect.height / 2);
#endregion
textureList = new List
path = Application.streamingAssetsPath + path;
ReadImage();
CalculateRowColumn();
}
private void ReadImage()
{
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
return;
}
texturePaths = new List
var jpgs = Directory.GetFiles(path, "*.jpg");
texturePaths.AddRange(jpgs);
foreach (var filePath in texturePaths)
{
UtilityLoadImage.I.LoadImage(filePath, tex =>
{
textureList.Add(new PictureInfo { name = Path.GetFileNameWithoutExtension(filePath), texture = tex });
addNum++;
if (addNum == texturePaths.Count)
{
Spawn();
}
});
}
}
float addNum = 0;
private void Spawn()
{
float x = 0;
float y = 0;
for (int i = 0; i < row; i++)
{
y = i * (itemHeight + intervalDistance);
for (int j = 0; j < column; j++)
{
x = j * (itemWidth + intervalDistance);
RectTransform rect = Instantiate(prefab, transform).transform as RectTransform;
rect.pivot = new Vector2(0.5f, 0.5f);
rect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, itemWidth);
rect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, itemHeight);
rect.anchoredPosition = new Vector2(x, -y) + new Vector2(rect.rect.width / 2, -rect.rect.height / 2);
var controller = rect.GetComponent
controller.speed = speed;
controller.target = target;
controller.radius = radius;
if (i % 2 != 0)
{
controller.horizontalOffset = offset;
}
SetTexture(rect);
}
}
target.SetAsLastSibling();
}
private void CalculateRowColumn()
{
itemHeight = (height - (row - 1) * intervalDistance) / row;
itemWidth = itemHeight * 16 / 9;
column = (int)(width / (itemWidth + intervalDistance)) + 3;
}
bool isSpawned = false;
private void Update()
{
if (!isSpawned && transform.childCount != 1 && transform.childCount <= (column - 1) * row + 1)
{
isSpawned = true;
SpawnColumn();
}
}
private void SpawnColumn()
{
float x = 0;
float y = 0;
for (int i = 0; i < row; i++)
{
y = i * (itemHeight + intervalDistance);
for (int j = 0; j < 1; j++)
{
x = (column - 1) * (itemWidth + intervalDistance);
RectTransform rect = Instantiate(prefab, transform).transform as RectTransform;
rect.pivot = new Vector2(0.5f, 0.5f);
rect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, itemWidth);
rect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, itemHeight);
rect.anchoredPosition = new Vector2(x, -y) + new Vector2(intervalDistance - Time.deltaTime * speed, -rect.rect.height / 2);
//重新计算位置:运行时间长导致误差间隔变大,可能是浮点误差导致的
rect.anchoredPosition = GetPos(rect.anchoredPosition);
var controller = rect.GetComponent
controller.speed = speed;
controller.target = target;
controller.radius = radius;
SetTexture(rect);
}
}
target.SetAsLastSibling();
StartCoroutine(Delay());
}
private Vector2 GetPos(Vector2 originPos)
{
Vector2 nearPos = new Vector2((transform as RectTransform).rect.width, originPos.y);
foreach (RectTransform t in transform)
{
if (t.anchoredPosition.y == originPos.y)
{
if (t.anchoredPosition.x > nearPos.x && t.anchoredPosition.x < originPos.x)
{
nearPos.x = t.anchoredPosition.x + (itemWidth + intervalDistance);
}
}
}
return nearPos;
}
private void SetTexture(RectTransform rect)
{
if (textureList.Count == 0) return;
if (currentIndex >= textureList.Count)
{
currentIndex %= textureList.Count;
}
rect.GetComponent
currentIndex = (currentIndex + 1) % texturePaths.Count;
}
private IEnumerator Delay()
{
yield return null;
isSpawned = false;
}
}
using UnityEngine;
public class ImageItemController : MonoBehaviour
{
[HideInInspector]
public RectTransform target;
[HideInInspector]
public float speed = 10;
[HideInInspector]
public float horizontalOffset = 0;
private RectTransform rect;
[HideInInspector]
public float radius = 300;
private Vector2 originalPos = Vector2.zero;
private bool isCheck = false;
private bool isStartRotate = false;
private Vector2 circleCenter;
private float xDelta = 0;
private float offset = 0;
void Start()
{
rect = transform as RectTransform;
rect.anchorMin = new Vector2(0, 1);
rect.anchorMax = new Vector2(0, 1);
rect.anchoredPosition = new Vector2(rect.anchoredPosition.x - horizontalOffset, rect.anchoredPosition.y);
}
/* 1.现根据接触点计算出圆的路径:目标移动的位移,计算在圆的的位置,只需修改x即可,y保持不变
* 2.计算出的位置x加上移动的距离,得出最总x的位置
* 3.设置位置即可
* 4.走远时的接触点:开始接触时的关于x对称位置
* 5.添加移动:平移原点和圆点即可
*/
//移动
void Update()
{
if (!isCheck)
{
var dis = Vector2.Distance(target.anchoredPosition, rect.anchoredPosition);
if (dis <= radius)
{
isCheck = true;
originalPos = rect.anchoredPosition;
float y = Mathf.Abs(originalPos.y - target.anchoredPosition.y);
float xToCircleCenter = Mathf.Sqrt(radius * radius - y * y);
float x = originalPos.x - xToCircleCenter;
circleCenter = new Vector2(x, target.anchoredPosition.y);
isStartRotate = true;
rect.SetSiblingIndex(transform.parent.childCount - 2);
}
}
xDelta = Time.deltaTime * speed;
rect.anchoredPosition = new Vector2(rect.anchoredPosition.x - xDelta, rect.anchoredPosition.y);
if (isStartRotate)
{
circleCenter.x -= xDelta;
originalPos.x -= xDelta;
float moveXDistance = target.anchoredPosition.x - circleCenter.x;
float x = originalPos.x - circleCenter.x - moveXDistance;
float y = Mathf.Sqrt(radius * radius - x * x);
float maxY = radius;
if (originalPos.y < circleCenter.y)
{
y = -y;
maxY = -radius;
}
Vector2 circlePoint = new Vector2(x, y);
if (rect.anchoredPosition.x >= target.anchoredPosition.x)
{
var v1 = circlePoint - (originalPos - circleCenter);
var v2 = (originalPos - circleCenter) + new Vector2(0, maxY);
v2.Normalize();
offset = Vector2.Dot(v1, v2);
}
else
{
float tempX = originalPos.x - circleCenter.x;
Vector2 originalPos2 = originalPos + 2 * new Vector2(-tempX, 0);
var v1 = circlePoint - new Vector2(0, maxY);
var v2 = originalPos2 - circleCenter + new Vector2(0, maxY);
v2.Normalize();
offset = -Vector2.Dot(v1, v2);
}
if (float.IsNaN(offset))
{
offset = 0;
}
x += moveXDistance + offset;
Vector2 pos = circleCenter + new Vector2(x, y);
rect.anchoredPosition = pos;
rect.SetSiblingIndex(transform.parent.childCount - 2);
if (target.anchoredPosition.x >= originalPos.x + originalPos.x - circleCenter.x)
{
rect.anchoredPosition = originalPos;
rect.SetAsFirstSibling();
}
else if (target.anchoredPosition.x <= circleCenter.x)
{
rect.anchoredPosition = originalPos;
rect.SetAsFirstSibling();
}
}
if (rect.anchoredPosition.x <= -rect.rect.width)
{
Destroy(gameObject);
}
}
}
工具类
using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
namespace Utility
{
public class UtilityLoadImage
{
public class MonoHelper : MonoBehaviour { }
public static UtilityLoadImage I;
private static MonoHelper helper;
static UtilityLoadImage()
{
var go = new GameObject("UtilityLoadImage");
helper = go.AddComponent
UnityEngine.Object.DontDestroyOnLoad(go);
I = new UtilityLoadImage();
}
private UtilityLoadImage() { }
#region inner method
private IEnumerator LoadTexture2D(string path, Action
{
//Debug.Log("path:" + path);
UnityWebRequest uwr = UnityWebRequestTexture.GetTexture(path);
yield return uwr.SendWebRequest();
if (uwr.downloadHandler.isDone)
{
var tex = DownloadHandlerTexture.GetContent(uwr);
callback?.Invoke(tex);
}
}
private void LoadTexture2DByFile(string path, Action
{
if (path.StartsWith("file://"))
{
var bytes = File.ReadAllBytes(path);
Texture2D tex = new Texture2D(1, 1);
if (tex.LoadImage(bytes))
callback?.Invoke(tex);
}
}
private IEnumerator LoadByte(string path, Action
{
UnityWebRequest uwr = UnityWebRequest.Get(path);
yield return uwr.SendWebRequest();
if (uwr.downloadHandler.isDone)
{
var data = uwr.downloadHandler.data;
callback?.Invoke(data);
}
}
private void DeleteFolder(string savedFolder, bool clearSavedPath, bool isRecursive = false)
{
if (!Directory.Exists(savedFolder))
{
Debug.LogError("要删除的文件夹不存在!");
return;
}
if (clearSavedPath)
{
Directory.Delete(savedFolder, isRecursive);
Directory.CreateDirectory(savedFolder);
}
}
private byte[] Texture2DToByte(string path, Texture2D tex)
{
byte[] data = null;
int index = path.LastIndexOf('.');
if (index != -1)
{
string expandedName = path.Substring(index + 1);
switch (expandedName)
{
case "jpeg":
case "jpg":
data = tex.EncodeToJPG();
break;
case "png":
data = tex.EncodeToPNG();
break;
default:
Debug.Log("");
break;
}
}
else
{
Debug.Log("path is not correct!!!");
}
return data;
}
private IEnumerator LoadAudio(string path, string savedFolder, string fileName, Action
{
UnityWebRequest request = UnityWebRequestMultimedia.GetAudioClip(path, AudioType.MPEG);
yield return request.SendWebRequest();
if (request.downloadHandler.isDone)
{
File.WriteAllBytes(savedFolder + "/" + fileName, request.downloadHandler.data);
AudioClip clip = DownloadHandlerAudioClip.GetContent(request);
callback?.Invoke(clip);
}
}
private IEnumerator LoadAudio(string path, string savePath, Action
{
UnityWebRequest request = UnityWebRequestMultimedia.GetAudioClip(path, AudioType.MPEG);
yield return request.SendWebRequest();
if (request.downloadHandler.isDone)
{
File.WriteAllBytes(savePath, request.downloadHandler.data);
AudioClip clip = DownloadHandlerAudioClip.GetContent(request);
callback?.Invoke(clip);
}
}
private IEnumerator LoadAudio(string path, Action
{
UnityWebRequest request = UnityWebRequestMultimedia.GetAudioClip(path, AudioType.MPEG);
yield return request.SendWebRequest();
if (request.downloadHandler.isDone)
{
AudioClip clip = DownloadHandlerAudioClip.GetContent(request);
if (callback != null)
callback(clip);
else
Debug.Log("加载音频回调为null");
}
}
#endregion
#region load and download image
public void LoadImage(string path, Action
{
helper.StartCoroutine(LoadTexture2D(path, callback));
}
public void LoadImageByFile(string path, Action
{
LoadTexture2DByFile(path, callback);
}
public void LoadImages(string[] paths, Action> callback)
{
Debug.Log("start!!!!!");
List
for (int i = 0; i < paths.Length; i++)
{
LoadImage(paths[i], tex => list.Add(tex));
}
callback?.Invoke(list);
Debug.Log("end!!!!!" + list.Count);
}
public void LoadImagesByFile(string[] paths, Action> callback)
{
List
for (int i = 0; i < paths.Length; i++)
{
var data = File.ReadAllBytes(paths[i]);
Texture2D tex = new Texture2D(1, 1);
if (tex.LoadImage(data))
list.Add(tex);
}
callback?.Invoke(list);
}
public void DownloadImageAndSave(string url, string savedFolder, string fileName, Action callback = null)
{
helper.StartCoroutine(LoadTexture2D(url, tex =>
{
File.WriteAllBytes(savedFolder + "/" + fileName, Texture2DToByte(url, tex));
callback?.Invoke();
}));
}
public void DownloadImageAndSave(string url, string savePath, Action callback = null)
{
helper.StartCoroutine(LoadTexture2D(url, tex =>
{
File.WriteAllBytes(savePath, Texture2DToByte(url, tex));
callback?.Invoke();
}));
}
public void DownloadImageAndSave_Texture2D(string url, string savedFolder, string fileName, Action
{
helper.StartCoroutine(LoadTexture2D(url, tex =>
{
File.WriteAllBytes(savedFolder + "/" + fileName, Texture2DToByte(url, tex));
callback?.Invoke(tex);
}));
}
public void DownloadImageAndSave_Texture2D(string url, string savePath, Action
{
helper.StartCoroutine(LoadTexture2D(url, tex =>
{
File.WriteAllBytes(savePath, Texture2DToByte(url, tex));
callback?.Invoke(tex);
}));
}
public void DownloadImageAndSave_FilePath(string url, string savedFolder, string fileName, Action
{
helper.StartCoroutine(LoadTexture2D(url, tex =>
{
string path = savedFolder + "/" + fileName;
File.WriteAllBytes(path, Texture2DToByte(url, tex));
callback?.Invoke(path);
}));
}
public void DownloadImageAndSave_FilePath(string url, string savePath, Action
{
helper.StartCoroutine(LoadTexture2D(url, tex =>
{
File.WriteAllBytes(savePath, Texture2DToByte(url, tex));
callback?.Invoke(savePath);
}));
}
public void DownloadImagesAndSave(string[] urls, string savedFolder, string[] fileNames, Action completedCallback = null, bool deleteFolder = false, bool recursive = false)
{
if (urls.Length != fileNames.Length)
{
Debug.Log("下载数量和保存的文件数量不一致!");
return;
}
DeleteFolder(savedFolder, deleteFolder, recursive);
int completedNum = 0;
for (int i = 0; i < urls.Length; i++)
{
DownloadImageAndSave(urls[i], savedFolder, fileNames[i], () =>
{
++completedNum;
if (completedNum == urls.Length)
{
completedCallback?.Invoke();
Debug.Log("所以文件下载完成!");
}
});
}
}
public void DownloadImagesAndSave_Texture2DPaths(string[] urls, string savedFolder, string[] fileNames, Action
{
if (urls.Length != fileNames.Length)
{
Debug.Log("下载数量和保存的文件数量不一致!");
return;
}
DeleteFolder(savedFolder, deleteFolder, recursive);
int completedNum = 0;
string[] filePaths = new string[fileNames.Length];
for (int i = 0; i < urls.Length; i++)
{
DownloadImageAndSave_FilePath(urls[i], savedFolder, fileNames[i], path =>
{
filePaths[completedNum] = path;
++completedNum;
if (completedNum == urls.Length)
{
callback?.Invoke(filePaths);
Debug.Log("所以图片下载完成!");
}
});
}
}
public void DownloadImagesAndSave_Texture2DPaths(string[] urls, string[] savePaths, Action
{
if (urls.Length != savePaths.Length)
{
Debug.Log("下载数量和保存的文件数量不一致!");
return;
}
int completedNum = 0;
string[] filePaths = new string[savePaths.Length];
for (int i = 0; i < urls.Length; i++)
{
DownloadImageAndSave_FilePath(urls[i], savePaths[i], path =>
{
filePaths[completedNum] = path;
++completedNum;
if (completedNum == urls.Length)
{
callback?.Invoke(filePaths);
Debug.Log("所以图片下载完成!");
}
});
}
}
public void DownloadImagesAndSave_Texture2Ds(string[] urls, string savedFolder, string[] fileNames, Action
{
if (urls.Length != fileNames.Length)
{
Debug.Log("下载数量和保存的文件数量不一致!");
return;
}
DeleteFolder(savedFolder, deleteFolder, recursive);
int completedNum = 0;
Texture2D[] textures = new Texture2D[fileNames.Length];
for (int i = 0; i < urls.Length; i++)
{
DownloadImageAndSave_Texture2D(urls[i], savedFolder, fileNames[i], tex =>
{
textures[completedNum] = tex;
++completedNum;
if (completedNum == urls.Length)
{
callback?.Invoke(textures);
Debug.Log("所以图片下载完成!");
}
});
}
}
public void DownloadImagesAndSave_Texture2Ds(string[] urls, string[] savePaths, Action
{
if (urls.Length != savePaths.Length)
{
Debug.Log("下载数量和保存的文件数量不一致!");
return;
}
int completedNum = 0;
Texture2D[] textures = new Texture2D[savePaths.Length];
for (int i = 0; i < urls.Length; i++)
{
DownloadImageAndSave_Texture2D(urls[i], savePaths[i], tex =>
{
textures[completedNum] = tex;
++completedNum;
if (completedNum == urls.Length)
{
callback?.Invoke(textures);
Debug.Log("所以图片下载完成!");
}
});
}
}
#endregion
#region download file
public void DownloadFileAndSave(string url, string savedFolder, string fileName, Action callback = null)
{
helper.StartCoroutine(LoadByte(url, data =>
{
File.WriteAllBytes(savedFolder + "/" + fileName, data);
callback?.Invoke();
}));
}
public void DownloadFileAndSave(string url, string savePath, Action callback = null)
{
helper.StartCoroutine(LoadByte(url, data =>
{
File.WriteAllBytes(savePath, data);
callback?.Invoke();
}));
}
public void DownloadFileAndSave_FilePath(string url, string savedFolder, string fileName, Action
{
helper.StartCoroutine(LoadByte(url, data =>
{
string path = savedFolder + "/" + fileName;
File.WriteAllBytes(path, data);
callback?.Invoke(path);
}));
}
public void DownloadFileAndSave_FilePath(string url, string savePath, Action
{
helper.StartCoroutine(LoadByte(url, data =>
{
File.WriteAllBytes(savePath, data);
callback?.Invoke(savePath);
}));
}
public void DownloadFileAndSave_FileData(string url, string savedFolder, string fileName, Action
{
helper.StartCoroutine(LoadByte(url, data =>
{
string path = savedFolder + "/" + fileName;
File.WriteAllBytes(path, data);
callback?.Invoke(data);
}));
}
public void DownloadFileAndSave_FileData(string url, string savePath, Action
{
helper.StartCoroutine(LoadByte(url, data =>
{
File.WriteAllBytes(savePath, data);
callback?.Invoke(data);
}));
}
public void DownloadFilesAndSave(string[] urls, string savedFolder, string[] fileNames, Action completedCallback = null, bool deleteFolder = false, bool recursive = false)
{
if (urls.Length != fileNames.Length)
{
Debug.Log("下载数量和保存的文件数量不一致!");
return;
}
DeleteFolder(savedFolder, deleteFolder, recursive);
int completedNum = 0;
for (int i = 0; i < urls.Length; i++)
{
DownloadFileAndSave(urls[i], savedFolder, fileNames[i], () =>
{
++completedNum;
if (completedNum == fileNames.Length)
{
completedCallback?.Invoke();
}
});
}
}
public void DownloadFilesAndSave(string[] urls, string[] savePath, Action callback = null)
{
if (urls.Length != savePath.Length)
{
Debug.Log("下载数量和保存的文件数量不一致!");
return;
}
int completedNum = 0;
for (int i = 0; i < urls.Length; i++)
{
DownloadFileAndSave(urls[i], savePath[i], () =>
{
++completedNum;
if (completedNum == savePath.Length)
{
callback?.Invoke();
}
});
}
}
public void DownloadFilesAndSave_FilePaths(string[] urls, string savedFolder, string[] fileNames, Action
{
if (urls.Length != fileNames.Length)
{
Debug.Log("下载数量和保存的文件数量不一致!");
return;
}
DeleteFolder(savedFolder, deleteFolder, recursive);
int completedNum = 0;
string[] filePaths = new string[fileNames.Length];
for (int i = 0; i < urls.Length; i++)
{
DownloadFileAndSave_FilePath(urls[i], savedFolder, fileNames[i], path =>
{
filePaths[completedNum] = path;
++completedNum;
if (completedNum == fileNames.Length)
{
completedCallback?.Invoke(filePaths);
}
});
}
}
public void DownloadFilesAndSave_FilePaths(string[] urls, string[] savePath, Action
{
if (urls.Length != savePath.Length)
{
Debug.Log("下载数量和保存的文件数量不一致!");
return;
}
int completedNum = 0;
string[] filePaths = new string[savePath.Length];
for (int i = 0; i < urls.Length; i++)
{
DownloadFileAndSave_FilePath(urls[i], savePath[i], path =>
{
filePaths[completedNum] = path;
++completedNum;
if (completedNum == savePath.Length)
{
completedCallback?.Invoke(filePaths);
}
});
}
}
public void DownloadFilesAndSave_FileDatas(string[] urls, string savedFolder, string[] fileNames, Action> completedCallback = null, bool deleteFolder = false, bool recursive = false)
{
if (urls.Length != fileNames.Length)
{
Debug.Log("下载数量和保存的文件数量不一致!");
return;
}
DeleteFolder(savedFolder, deleteFolder, recursive);
int completedNum = 0;
List
for (int i = 0; i < urls.Length; i++)
{
DownloadFileAndSave_FileData(urls[i], savedFolder, fileNames[i], data =>
{
allDatas.Add(data);
++completedNum;
if (completedNum == fileNames.Length)
{
completedCallback?.Invoke(allDatas);
}
});
}
}
public void DownloadFilesAndSave_FileDatas(string[] urls, string[] savePath, Action> completedCallback = null)
{
if (urls.Length != savePath.Length)
{
Debug.Log("下载数量和保存的文件数量不一致!");
return;
}
int completedNum = 0;
List
for (int i = 0; i < urls.Length; i++)
{
DownloadFileAndSave_FileData(urls[i], savePath[i], data =>
{
allDatas.Add(data);
++completedNum;
if (completedNum == savePath.Length)
{
completedCallback?.Invoke(allDatas);
}
});
}
}
#endregion
#region download audio
public void DownloadAudioAndSave_FileData(string url, string savedFolder, string fileName, Action
{
helper.StartCoroutine(LoadAudio(url, savedFolder, fileName, callback));
}
public void DownloadAudioAndSave_FileData(string url, string savePath, Action
{
helper.StartCoroutine(LoadAudio(url, savePath, callback));
}
public void LoadAudioClip(string url, Action
{
helper.StartCoroutine(LoadAudio(url, callback));
}
#endregion
}
}
参考阅读
发表评论