Нет описания
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

TileDragAndDrop.cs 24KB

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