Aucune description
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

TileDragAndDrop.cs 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Reflection;
  6. using UnityEngine;
  7. using UnityEngine.Tilemaps;
  8. using Object = UnityEngine.Object;
  9. namespace UnityEditor.Tilemaps
  10. {
  11. internal static class TileDragAndDrop
  12. {
  13. private enum UserTileCreationMode
  14. {
  15. Overwrite,
  16. CreateUnique,
  17. Reuse,
  18. }
  19. private static readonly string k_TileExtension = "asset";
  20. private static List<Sprite> GetSpritesFromTexture(Texture2D texture)
  21. {
  22. string path = AssetDatabase.GetAssetPath(texture);
  23. Object[] assets = AssetDatabase.LoadAllAssetsAtPath(path);
  24. List<Sprite> sprites = new List<Sprite>();
  25. foreach (Object asset in assets)
  26. {
  27. if (asset is Sprite)
  28. {
  29. sprites.Add(asset as Sprite);
  30. }
  31. }
  32. return sprites;
  33. }
  34. private static bool AllSpritesAreSameSizeOrMultiples(List<Sprite> sprites)
  35. {
  36. if (sprites.Count == 0)
  37. return false;
  38. if (sprites.Count == 1)
  39. return true;
  40. var size = new Vector2(sprites[0].rect.width, sprites[0].rect.height);
  41. for (int i = 1; i < sprites.Count; i++)
  42. {
  43. var rect = sprites[i].rect;
  44. if (rect.width < size.x)
  45. size.x = rect.width;
  46. if (rect.height < size.y)
  47. size.y = rect.height;
  48. }
  49. foreach (var sprite in sprites)
  50. {
  51. var rect = sprite.rect;
  52. if (rect.width % size.x > 0)
  53. return false;
  54. if (rect.height % size.y > 0)
  55. return false;
  56. }
  57. return true;
  58. }
  59. /// <summary>
  60. /// Converts Objects that can be laid out in the Tile Palette and organises them for placement into a given CellLayout
  61. /// </summary>
  62. /// <param name="sheetTextures">Textures containing 2-N equal sized Sprites</param>
  63. /// <param name="singleSprites">All the leftover Sprites that were in same texture but different sizes or just dragged in as Sprite</param>
  64. /// <param name="tiles">Just plain tiles</param>
  65. /// <param name="cellLayout">Cell Layout to place objects on</param>
  66. /// <returns>Dictionary mapping the positions of the Objects on the Grid Layout with details of how to place the Objects</returns>
  67. public static Dictionary<Vector2Int, TileDragAndDropHoverData> CreateHoverData(List<Texture2D> sheetTextures, List<Sprite> singleSprites, List<TileBase> tiles, GridLayout.CellLayout cellLayout)
  68. {
  69. Dictionary<Vector2Int, TileDragAndDropHoverData> result = new Dictionary<Vector2Int, TileDragAndDropHoverData>();
  70. Vector2Int currentPosition = new Vector2Int(0, 0);
  71. int width = 0;
  72. if (sheetTextures != null)
  73. {
  74. foreach (Texture2D sheetTexture in sheetTextures)
  75. {
  76. Dictionary<Vector2Int, TileDragAndDropHoverData> sheet = CreateHoverData(sheetTexture, cellLayout);
  77. foreach (KeyValuePair<Vector2Int, TileDragAndDropHoverData> item in sheet)
  78. {
  79. result.Add(item.Key + currentPosition, item.Value);
  80. }
  81. Vector2Int min = GetMinMaxRect(sheet.Keys.ToList()).min;
  82. currentPosition += new Vector2Int(0, min.y - 1);
  83. }
  84. }
  85. if (currentPosition.x > 0)
  86. currentPosition = new Vector2Int(0, currentPosition.y - 1);
  87. if (singleSprites != null)
  88. {
  89. width = Mathf.RoundToInt(Mathf.Sqrt(singleSprites.Count));
  90. foreach (Sprite sprite in singleSprites)
  91. {
  92. result.Add(currentPosition, new TileDragAndDropHoverData(sprite));
  93. currentPosition += new Vector2Int(1, 0);
  94. if (currentPosition.x >= width)
  95. currentPosition = new Vector2Int(0, currentPosition.y - 1);
  96. }
  97. }
  98. if (currentPosition.x > 0)
  99. currentPosition = new Vector2Int(0, currentPosition.y - 1);
  100. if (tiles != null)
  101. {
  102. width = Math.Max(Mathf.RoundToInt(Mathf.Sqrt(tiles.Count)), width);
  103. foreach (TileBase tile in tiles)
  104. {
  105. result.Add(currentPosition, new TileDragAndDropHoverData(tile));
  106. currentPosition += new Vector2Int(1, 0);
  107. if (currentPosition.x >= width)
  108. currentPosition = new Vector2Int(0, currentPosition.y - 1);
  109. }
  110. }
  111. return result;
  112. }
  113. // Get all textures that are valid spritesheets. More than one Sprites and all equal size.
  114. public static List<Texture2D> GetValidSpritesheets(Object[] objects)
  115. {
  116. List<Texture2D> result = new List<Texture2D>();
  117. foreach (Object obj in objects)
  118. {
  119. if (obj is Texture2D)
  120. {
  121. Texture2D texture = obj as Texture2D;
  122. List<Sprite> sprites = GetSpritesFromTexture(texture);
  123. if (sprites.Count > 1 && AllSpritesAreSameSizeOrMultiples(sprites))
  124. {
  125. result.Add(texture);
  126. }
  127. }
  128. }
  129. return result;
  130. }
  131. // Get all single Sprite(s) and all Sprite(s) that are part of Texture2D that is not valid sheet (it sprites of varying sizes)
  132. public static List<Sprite> GetValidSingleSprites(Object[] objects)
  133. {
  134. List<Sprite> result = new List<Sprite>();
  135. foreach (Object obj in objects)
  136. {
  137. if (obj is Sprite)
  138. {
  139. result.Add(obj as Sprite);
  140. }
  141. else if (obj is Texture2D)
  142. {
  143. Texture2D texture = obj as Texture2D;
  144. List<Sprite> sprites = GetSpritesFromTexture(texture);
  145. if (sprites.Count == 1 || !AllSpritesAreSameSizeOrMultiples(sprites))
  146. {
  147. result.AddRange(sprites);
  148. }
  149. }
  150. }
  151. return result;
  152. }
  153. public static List<TileBase> GetValidTiles(Object[] objects)
  154. {
  155. List<TileBase> result = new List<TileBase>();
  156. foreach (Object obj in objects)
  157. {
  158. if (obj is TileBase)
  159. {
  160. result.Add(obj as TileBase);
  161. }
  162. }
  163. return result;
  164. }
  165. private static Vector2Int GetMinimum(List<Sprite> sprites, Func<Sprite, float> minX, Func<Sprite, float> minY)
  166. {
  167. Vector2 minVector = new Vector2(Int32.MaxValue, Int32.MaxValue);
  168. foreach (var sprite in sprites)
  169. {
  170. minVector.x = Mathf.Min(minVector.x, minX(sprite));
  171. minVector.y = Mathf.Min(minVector.y, minY(sprite));
  172. }
  173. return Vector2Int.FloorToInt(minVector);
  174. }
  175. public static Vector2Int EstimateGridPixelSize(List<Sprite> sprites)
  176. {
  177. if (sprites.Count == 0 || sprites.Any(sprite => sprite == null))
  178. {
  179. return Vector2Int.zero;
  180. }
  181. if (sprites.Count == 1)
  182. return Vector2Int.FloorToInt(sprites[0].rect.size);
  183. return GetMinimum(sprites, s => s.rect.width, s => s.rect.height);
  184. }
  185. public static Vector2Int EstimateGridOffsetSize(List<Sprite> sprites)
  186. {
  187. if (sprites.Count == 0 || sprites.Any(sprite => sprite == null))
  188. return Vector2Int.zero;
  189. if (sprites.Count == 1)
  190. return Vector2Int.FloorToInt(sprites[0].rect.position);
  191. return GetMinimum(sprites, s => s.rect.xMin, s => s.rect.yMin);
  192. }
  193. public static Vector2Int EstimateGridPaddingSize(List<Sprite> sprites, Vector2Int cellSize, Vector2Int offsetSize)
  194. {
  195. if (sprites.Count < 2 || sprites.Any(sprite => sprite == null))
  196. return Vector2Int.zero;
  197. var paddingSize = GetMinimum(sprites
  198. , (s =>
  199. {
  200. var xMin = s.rect.xMin - cellSize.x - offsetSize.x;
  201. return xMin >= 0 ? xMin : Int32.MaxValue;
  202. })
  203. , (s =>
  204. {
  205. var yMin = s.rect.yMin - cellSize.y - offsetSize.y;
  206. return yMin >= 0 ? yMin : Int32.MaxValue;
  207. })
  208. );
  209. // Assume there is no padding if the detected padding is greater than the cell size
  210. if (paddingSize.x >= cellSize.x)
  211. paddingSize.x = 0;
  212. if (paddingSize.y >= cellSize.y)
  213. paddingSize.y = 0;
  214. return paddingSize;
  215. }
  216. // Turn texture pixel position into integer grid position based on cell size, offset size and padding
  217. private static void GetGridPosition(Sprite sprite, Vector2Int cellPixelSize, Vector2Int offsetSize, Vector2Int paddingSize, out Vector2Int cellPosition, out Vector3 positionOffset)
  218. {
  219. var spritePosition = sprite.rect.position;
  220. var spriteCenter = sprite.rect.center;
  221. var position = new Vector2(
  222. ((spriteCenter.x - offsetSize.x) / (cellPixelSize.x + paddingSize.x)),
  223. (-(sprite.texture.height - spriteCenter.y - offsetSize.y) / (cellPixelSize.y + paddingSize.y)) + 1
  224. );
  225. cellPosition = new Vector2Int(Mathf.FloorToInt(position.x), Mathf.FloorToInt(position.y));
  226. positionOffset = (spriteCenter - spritePosition) / cellPixelSize;
  227. positionOffset.x = (float)(positionOffset.x - Math.Truncate(positionOffset.x));
  228. positionOffset.y = (float)(positionOffset.y - Math.Truncate(positionOffset.y));
  229. }
  230. // Turn texture pixel position into integer isometric grid position based on cell size and offset size
  231. private static void GetIsometricGridPosition(Sprite sprite, Vector2Int cellPixelSize, Vector2Int offsetSize, out Vector2Int cellPosition)
  232. {
  233. var offsetPosition = new Vector2(sprite.rect.center.x - offsetSize.x, sprite.rect.center.y - offsetSize.y);
  234. var cellStride = new Vector2(cellPixelSize.x, cellPixelSize.y) * 0.5f;
  235. var invCellStride = new Vector2(1.0f / cellStride.x, 1.0f / cellStride.y);
  236. var position = offsetPosition * invCellStride;
  237. position.y = (position.y - position.x) * 0.5f;
  238. position.x += position.y;
  239. cellPosition = new Vector2Int(Mathf.FloorToInt(position.x), Mathf.FloorToInt(position.y));
  240. }
  241. // Organizes all the sprites in a single texture nicely on a 2D "table" based on their original texture position
  242. // Only call this with spritesheet with all Sprites equal size
  243. public static Dictionary<Vector2Int, TileDragAndDropHoverData> CreateHoverData(Texture2D sheet, GridLayout.CellLayout cellLayout)
  244. {
  245. Dictionary<Vector2Int, TileDragAndDropHoverData> result = new Dictionary<Vector2Int, TileDragAndDropHoverData>();
  246. List<Sprite> sprites = GetSpritesFromTexture(sheet);
  247. Vector2Int cellPixelSize = EstimateGridPixelSize(sprites);
  248. // Get Offset
  249. Vector2Int offsetSize = EstimateGridOffsetSize(sprites);
  250. // Get Padding
  251. Vector2Int paddingSize = EstimateGridPaddingSize(sprites, cellPixelSize, offsetSize);
  252. if ((cellLayout == GridLayout.CellLayout.Isometric
  253. || cellLayout == GridLayout.CellLayout.IsometricZAsY)
  254. && (HasSpriteRectOverlaps(sprites)))
  255. {
  256. foreach (Sprite sprite in sprites)
  257. {
  258. GetIsometricGridPosition(sprite, cellPixelSize, offsetSize, out Vector2Int position);
  259. result[position] = new TileDragAndDropHoverData(sprite, Vector3.zero, (Vector2)cellPixelSize / sprite.pixelsPerUnit, false);
  260. }
  261. }
  262. else
  263. {
  264. foreach (Sprite sprite in sprites)
  265. {
  266. GetGridPosition(sprite, cellPixelSize, offsetSize, paddingSize, out Vector2Int position, out Vector3 offset);
  267. result[position] = new TileDragAndDropHoverData(sprite, offset, (Vector2)cellPixelSize / sprite.pixelsPerUnit);
  268. }
  269. }
  270. return result;
  271. }
  272. private static bool HasSpriteRectOverlaps(IReadOnlyList<Sprite> sprites)
  273. {
  274. var count = sprites.Count;
  275. for (int i = 0; i < count; i++)
  276. {
  277. var rect = sprites[i].rect;
  278. for (int j = i + 1; j < count; j++)
  279. {
  280. if (rect.Overlaps(sprites[j].rect))
  281. return true;
  282. }
  283. }
  284. return false;
  285. }
  286. internal static string GenerateUniqueNameForNamelessSprite(Sprite sprite, HashSet<string> uniqueNames, ref int count)
  287. {
  288. var baseName = "Nameless";
  289. if (sprite.texture != null)
  290. baseName = sprite.texture.name;
  291. string name;
  292. do
  293. {
  294. name = $"{baseName}_{count++}";
  295. }
  296. while (uniqueNames.Contains(name));
  297. return name;
  298. }
  299. public static List<TileBase> ConvertToTileSheet(Dictionary<Vector2Int, TileDragAndDropHoverData> sheet)
  300. {
  301. List<TileBase> result = new List<TileBase>();
  302. string defaultPath = TileDragAndDropManager.GetDefaultTileAssetPath();
  303. // Early out if all objects are already tiles
  304. if (sheet.Values.ToList().FindAll(data => data.hoverObject is TileBase).Count == sheet.Values.Count)
  305. {
  306. foreach (var item in sheet.Values)
  307. {
  308. result.Add(item.hoverObject as TileBase);
  309. }
  310. return result;
  311. }
  312. UserTileCreationMode userTileCreationMode = UserTileCreationMode.Overwrite;
  313. string path = "";
  314. bool multipleTiles = sheet.Count > 1;
  315. int i = 0;
  316. HashSet<String> uniqueNames = new HashSet<string>();
  317. if (multipleTiles)
  318. {
  319. bool userInterventionRequired = false;
  320. path = EditorUtility.SaveFolderPanel("Generate tiles into folder ", defaultPath, "");
  321. path = FileUtil.GetProjectRelativePath(path);
  322. // Check if this will overwrite any existing assets
  323. foreach (var item in sheet.Values)
  324. {
  325. if (item.hoverObject is Sprite sprite)
  326. {
  327. var name = sprite.name;
  328. if (String.IsNullOrEmpty(name) || uniqueNames.Contains(name))
  329. {
  330. name = GenerateUniqueNameForNamelessSprite(sprite, uniqueNames, ref i);
  331. }
  332. uniqueNames.Add(name);
  333. var tilePath = FileUtil.CombinePaths(path, String.Format("{0}.{1}", name, k_TileExtension));
  334. if (File.Exists(tilePath))
  335. {
  336. userInterventionRequired = true;
  337. break;
  338. }
  339. }
  340. }
  341. // There are existing tile assets in the folder with names matching the items to be created
  342. if (userInterventionRequired)
  343. {
  344. var option = EditorUtility.DisplayDialogComplex("Overwrite?", String.Format("Assets exist at {0}. Do you wish to overwrite existing assets?", path), "Overwrite", "Create New Copy", "Reuse");
  345. switch (option)
  346. {
  347. case 0: // Overwrite
  348. {
  349. userTileCreationMode = UserTileCreationMode.Overwrite;
  350. }
  351. break;
  352. case 1: // Create New Copy
  353. {
  354. userTileCreationMode = UserTileCreationMode.CreateUnique;
  355. }
  356. break;
  357. case 2: // Reuse
  358. {
  359. userTileCreationMode = UserTileCreationMode.Reuse;
  360. }
  361. break;
  362. }
  363. }
  364. }
  365. else
  366. {
  367. // Do not check if this will overwrite new tile as user has explicitly selected the file to save to
  368. path = EditorUtility.SaveFilePanelInProject("Generate new tile", sheet.Values.First().hoverObject.name, k_TileExtension, "Generate new tile", defaultPath);
  369. }
  370. TileDragAndDropManager.SetUserTileAssetPath(path);
  371. if (string.IsNullOrEmpty(path))
  372. return result;
  373. i = 0;
  374. uniqueNames.Clear();
  375. EditorUtility.DisplayProgressBar("Generating Tile Assets (" + i + "/" + sheet.Count + ")", "Generating tiles", 0f);
  376. try
  377. {
  378. MethodInfo createTileMethod = GridPaintActiveTargetsPreferences.GetCreateTileFromPaletteUsingPreferences();
  379. if (createTileMethod == null)
  380. return null;
  381. foreach (KeyValuePair<Vector2Int, TileDragAndDropHoverData> item in sheet)
  382. {
  383. TileBase tile;
  384. string tilePath = "";
  385. if (item.Value.hoverObject is Sprite sprite)
  386. {
  387. tile = createTileMethod.Invoke(null, new object[] {sprite}) as TileBase;
  388. if (tile == null)
  389. continue;
  390. var name = tile.name;
  391. if (String.IsNullOrEmpty(name) || uniqueNames.Contains(name))
  392. {
  393. name = GenerateUniqueNameForNamelessSprite(sprite, uniqueNames, ref i);
  394. }
  395. uniqueNames.Add(name);
  396. tilePath = multipleTiles
  397. ? FileUtil.CombinePaths(path, String.Format("{0}.{1}", name, k_TileExtension))
  398. : path;
  399. // Case 1216101: Fix path slashes for Windows
  400. tilePath = FileUtil.NiceWinPath(tilePath);
  401. switch (userTileCreationMode)
  402. {
  403. case UserTileCreationMode.CreateUnique:
  404. {
  405. if (File.Exists(tilePath))
  406. tilePath = AssetDatabase.GenerateUniqueAssetPath(tilePath);
  407. AssetDatabase.CreateAsset(tile, tilePath);
  408. }
  409. break;
  410. case UserTileCreationMode.Overwrite:
  411. {
  412. AssetDatabase.CreateAsset(tile, tilePath);
  413. }
  414. break;
  415. case UserTileCreationMode.Reuse:
  416. {
  417. if (File.Exists(tilePath))
  418. tile = AssetDatabase.LoadAssetAtPath<TileBase>(tilePath);
  419. else
  420. AssetDatabase.CreateAsset(tile, tilePath);
  421. }
  422. break;
  423. }
  424. }
  425. else
  426. {
  427. tile = item.Value.hoverObject as TileBase;
  428. }
  429. EditorUtility.DisplayProgressBar("Generating Tile Assets (" + i + "/" + sheet.Count + ")", "Generating " + tilePath, (float)i++ / sheet.Count);
  430. result.Add(tile);
  431. }
  432. }
  433. finally
  434. {
  435. EditorUtility.ClearProgressBar();
  436. }
  437. AssetDatabase.Refresh();
  438. return result;
  439. }
  440. internal static RectInt GetMinMaxRect(List<Vector2Int> positions)
  441. {
  442. if (positions == null || positions.Count == 0)
  443. return new RectInt();
  444. return GridEditorUtility.GetMarqueeRect(
  445. new Vector2Int(positions.Min(p1 => p1.x), positions.Min(p1 => p1.y)),
  446. new Vector2Int(positions.Max(p1 => p1.x), positions.Max(p1 => p1.y))
  447. );
  448. }
  449. }
  450. }