还要很多可以优化的点地方,有兴趣的可以做
比如对象的销毁和生成可以做成对象池,走到最左边后再移动到最右边循环利用
分析过程文件,采用Blender,资源已上传,可以播放动画看效果,下面截个图:
视频效果如下:
anim
Untiy结构如下:
上面的ImageItem是我手动添加展示关系用的,默认就一个Target,PictureWall挂PictureWall脚本,ImageItem(预制体)挂ImageItemController 脚本即可
using UnityEngine;
public class ImageItemController : MonoBehaviour
{
public RectTransform target;
public float speed = 10;
[SerializeField]
private float radiusScale = 1;
public float horizontalOffset = 0;
private RectTransform rect;
private float radius = 0;
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.anchoredPosition = new Vector2(rect.anchoredPosition.x - horizontalOffset, rect.anchoredPosition.y);
radius = target.rect.width * radiusScale;// * Random.Range(0.8f, 1); 半径可以在范围内随机
}
/* 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;
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.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.UI;
using Utility;
public class PictureWall : MonoBehaviour
{
[SerializeField]
private GameObject prefab;
private const float WIDTH = 3072;
private const float HEIGHT = 1664;
private int row = 6;
private int column;
private float intervalDistance = 20;
[SerializeField]
private float offset = 200;
private float itemWidth;
private float itemHeight;
public float speed = 10;
private float time = 0;
[SerializeField]
private RectTransform target;
private string path = "/PictureWall/";
private List<string> texturePaths;
private int currentIndex = 0;
private List<Texture2D> textureList;
void Start()
{
textureList = new List<Texture2D>();
path = Application.streamingAssetsPath + path;
ReadImage();
CalculateRowColumn();
enabled = false;
}
private void ReadImage()
{
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
return;
}
texturePaths = new List<string>();
var jpgs = Directory.GetFiles(path, "*.jpg");
texturePaths.AddRange(jpgs);
texturePaths.Reverse();
if (texturePaths.Count > 100)
{
for (int i = texturePaths.Count - 1; i == 100; i--)
{
File.Delete(texturePaths[i]);
texturePaths.RemoveAt(i);
}
}
foreach (var filePath in texturePaths)
{
UtilityLoadImage.I.LoadImage(filePath, tex =>
{
textureList.Add(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);
if (i % 2 != 0)
{
//x -= offset;
}
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<ImageItemController>();
controller.speed = speed;
controller.target = target;
if (i % 2 != 0)
{
controller.horizontalOffset = offset;
}
SetTexture(rect);
}
}
target.SetAsLastSibling();
enabled = true;
}
private void CalculateRowColumn()
{
itemHeight = (HEIGHT - (row - 1) * intervalDistance) / row;
itemWidth = itemHeight * 16 / 9;
//offset = itemWidth / 2;
column = (int)(WIDTH / (itemWidth + intervalDistance)) + 3;
time = itemWidth / speed;
}
bool isSpawned = false;
private void Update()
{
if (!isSpawned && 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);
var controller = rect.GetComponent<ImageItemController>();
controller.speed = speed;
controller.target = target;
if (i % 2 != 0)
{
controller.horizontalOffset = offset;
}
SetTexture(rect);
}
}
target.SetAsLastSibling();
StartCoroutine(Delay());
}
private void SetTexture(RectTransform rect)
{
rect.GetComponent<RawImage>().texture = textureList[currentIndex];
currentIndex = (currentIndex + 1) % texturePaths.Count;
}
private IEnumerator Delay()
{
//yield return new WaitForSeconds(0.1f);
yield return null;
isSpawned = false;
}
}
工具类
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<MonoHelper>();
UnityEngine.Object.DontDestroyOnLoad(go);
I = new UtilityLoadImage();
}
private UtilityLoadImage() { }
#region inner method
private IEnumerator LoadTexture2D(string path, Action<Texture2D> callback)
{
//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<Texture2D> callback)
{
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<byte[]> callback)
{
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<AudioClip> callback)
{
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<AudioClip> callback)
{
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<AudioClip> callback)
{
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<Texture2D> callback)
{
helper.StartCoroutine(LoadTexture2D(path, callback));
}
public void LoadImageByFile(string path, Action<Texture2D> callback)
{
LoadTexture2DByFile(path, callback);
}
public void LoadImages(string[] paths, Action<List<Texture2D>> callback)
{
Debug.Log("start!!!!!");
List<Texture2D> list = new List<Texture2D>();
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<List<Texture2D>> callback)
{
List<Texture2D> list = new List<Texture2D>();
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<Texture2D> callback = null)
{
helper.StartCoroutine(LoadTexture2D(url, tex =>
{
File.WriteAllBytes(savedFolder + "/" + fileName, Texture2DToByte(url, tex));
callback?.Invoke(tex);
}));
}
public void DownloadImageAndSave_Texture2D(string url, string savePath, Action<Texture2D> callback = null)
{
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<string> callback = null)
{
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<string> callback = null)
{
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<string[]> callback = null, bool deleteFolder = false, bool recursive = false)
{
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<string[]> callback = null)
{
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<Texture2D[]> callback = null, bool deleteFolder = false, bool recursive = false)
{
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<Texture2D[]> callback = null)
{
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<string> callback = null)
{
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<string> callback = null)
{
helper.StartCoroutine(LoadByte(url, data =>
{
File.WriteAllBytes(savePath, data);
callback?.Invoke(savePath);
}));
}
public void DownloadFileAndSave_FileData(string url, string savedFolder, string fileName, Action<byte[]> callback = null)
{
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<byte[]> callback = null)
{
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<string[]> completedCallback = null, bool deleteFolder = false, bool recursive = false)
{
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<string[]> completedCallback = null)
{
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<List<byte[]>> completedCallback = null, bool deleteFolder = false, bool recursive = false)
{
if (urls.Length != fileNames.Length)
{
Debug.Log("下载数量和保存的文件数量不一致!");
return;
}
DeleteFolder(savedFolder, deleteFolder, recursive);
int completedNum = 0;
List<byte[]> allDatas = new List<byte[]>(fileNames.Length);
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<List<byte[]>> completedCallback = null)
{
if (urls.Length != savePath.Length)
{
Debug.Log("下载数量和保存的文件数量不一致!");
return;
}
int completedNum = 0;
List<byte[]> allDatas = new List<byte[]>(savePath.Length);
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<AudioClip> callback = null)
{
helper.StartCoroutine(LoadAudio(url, savedFolder, fileName, callback));
}
public void DownloadAudioAndSave_FileData(string url, string savePath, Action<AudioClip> callback = null)
{
helper.StartCoroutine(LoadAudio(url, savePath, callback));
}
public void LoadAudioClip(string url, Action<AudioClip> callback = null)
{
helper.StartCoroutine(LoadAudio(url, callback));
}
#endregion
}
}