CharacterEditor.cs 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using Assets.HeroEditor4D.Common.Scripts.CharacterScripts;
  6. using Assets.HeroEditor4D.Common.Scripts.Collections;
  7. using Assets.HeroEditor4D.Common.Scripts.Common;
  8. using Assets.HeroEditor4D.Common.Scripts.Data;
  9. using Assets.HeroEditor4D.Common.Scripts.Enums;
  10. using Assets.HeroEditor4D.InventorySystem.Scripts.Data;
  11. using Assets.HeroEditor4D.InventorySystem.Scripts.Elements;
  12. using Assets.HeroEditor4D.Common.SimpleColorPicker.Scripts;
  13. using Assets.HeroEditor4D.InventorySystem.Scripts;
  14. using Assets.HeroEditor4D.InventorySystem.Scripts.Enums;
  15. using Newtonsoft.Json;
  16. using UnityEngine;
  17. using UnityEngine.UI;
  18. namespace Assets.HeroEditor4D.Common.Scripts.EditorScripts
  19. {
  20. /// <summary>
  21. /// Character editor UI and behaviour.
  22. /// </summary>
  23. public class CharacterEditor : MonoBehaviour
  24. {
  25. [Header("Main")]
  26. public SpriteCollection SpriteCollection;
  27. public IconCollection IconCollection;
  28. public Character4D Character;
  29. public Transform Tabs;
  30. public ScrollInventory Inventory;
  31. public Text ItemName;
  32. [Header("Materials")]
  33. public Material DefaultMaterial;
  34. public Material EyesPaintMaterial;
  35. public Material EquipmentPaintMaterial;
  36. public Material HuePaintMaterial;
  37. [Header("Other")]
  38. public List<string> PaintParts;
  39. public Button PaintButton;
  40. public ColorPicker ColorPicker;
  41. public ColorSetup ColorSetup;
  42. public List<string> CollectionSorting;
  43. public List<CollectionBackground> CollectionBackgrounds;
  44. public List<Button> EditorOnlyButtons;
  45. public string AssetUrl;
  46. [Serializable]
  47. public class CollectionBackground
  48. {
  49. public string Name;
  50. public Sprite Sprite;
  51. }
  52. public Action<Item> EquipCallback;
  53. private Toggle ActiveTab => Tabs.GetComponentsInChildren<Toggle>().Single(i => i.isOn);
  54. public void OnValidate()
  55. {
  56. if (Character == null)
  57. {
  58. Character = FindObjectOfType<Character4D>();
  59. }
  60. }
  61. /// <summary>
  62. /// Called automatically on app start.
  63. /// </summary>
  64. public void Awake()
  65. {
  66. ItemCollection.Active = ScriptableObject.CreateInstance<ItemCollection>();
  67. ItemCollection.Active.SpriteCollections = new List<SpriteCollection> { SpriteCollection };
  68. ItemCollection.Active.IconCollections = new List<IconCollection> { IconCollection };
  69. ItemCollection.Active.BackgroundBrown = CollectionBackgrounds[0].Sprite;
  70. ItemCollection.Active.GetBackgroundCustom = item => CollectionBackgrounds.SingleOrDefault(i => i.Name == item.Icon?.Collection)?.Sprite;
  71. }
  72. /// <summary>
  73. /// Called automatically on app start.
  74. /// </summary>
  75. public void Start()
  76. {
  77. Character.Initialize();
  78. OnSelectTab(true);
  79. EditorOnlyButtons.ForEach(i => i.interactable = Application.isEditor);
  80. RequestFeedback();
  81. }
  82. public void Load(Character4D character)
  83. {
  84. Character.CopyFrom(character);
  85. }
  86. /// <summary>
  87. /// This can be used as an example for building your own inventory UI.
  88. /// </summary>
  89. public void OnSelectTab(bool value)
  90. {
  91. if (!value) return;
  92. Action<Item> equipAction;
  93. int equippedIndex;
  94. var tab = Tabs.GetComponentsInChildren<Toggle>().Single(i => i.isOn);
  95. ItemCollection.Active.Reset();
  96. List<ItemSprite> SortCollection(List<ItemSprite> collection)
  97. {
  98. return collection.OrderBy(i => CollectionSorting.Contains(i.Collection) ? CollectionSorting.IndexOf(i.Collection) : 999).ThenBy(i => i.Id).ToList();
  99. }
  100. switch (tab.name)
  101. {
  102. case "Armor":
  103. {
  104. var sprites = SortCollection(SpriteCollection.Armor);
  105. ItemCollection.Active.Items = sprites.Select(i => CreateFakeItemParams(new Item(i.Id), i)).ToList();
  106. equipAction = item => Character.Equip(item.Sprite, EquipmentPart.Armor);
  107. equippedIndex = Character.Front.Armor == null ? -1 : sprites.FindIndex(i => i.Sprites.Contains(Character.Front.Armor.SingleOrDefault(j => j.name == "FrontBody")));
  108. break;
  109. }
  110. case "Helmet":
  111. {
  112. var sprites = SortCollection(SpriteCollection.Armor);
  113. ItemCollection.Active.Items = sprites.Select(i => CreateFakeItemParams(new Item(i.Id), i, ".Armor.", ".Helmet.")).ToList();
  114. equipAction = item => Character.Equip(item.Sprite, EquipmentPart.Helmet);
  115. equippedIndex = sprites.FindIndex(i => i.Sprites.Contains(Character.Front.Helmet));
  116. break;
  117. }
  118. case "Vest":
  119. case "Bracers":
  120. case "Leggings":
  121. {
  122. string part;
  123. switch (tab.name)
  124. {
  125. case "Vest": part = "FrontBody"; break;
  126. case "Bracers": part = "FrontArmL"; break;
  127. case "Leggings": part = "FrontLegL"; break;
  128. default: throw new NotSupportedException(tab.name);
  129. }
  130. var sprites = SortCollection(SpriteCollection.Armor);
  131. ItemCollection.Active.Items = sprites.Select(i => CreateFakeItemParams(new Item(i.Id), i, ".Armor.", $".{tab.name}.")).ToList();
  132. equipAction = item => Character.Equip(item.Sprite, tab.name.ToEnum<EquipmentPart>());
  133. equippedIndex = Character.Front.Armor == null ? -1 : sprites.FindIndex(i => i.Sprites.Contains(Character.Front.Armor.SingleOrDefault(j => j.name == part)));
  134. break;
  135. }
  136. case "Shield":
  137. {
  138. var sprites = SortCollection(SpriteCollection.Shield);
  139. ItemCollection.Active.Items = sprites.Select(i => CreateFakeItemParams(new Item(i.Id), i)).ToList();
  140. equipAction = item => Character.Equip(item.Sprite, EquipmentPart.Shield);
  141. equippedIndex = Character.Front.Shield == null ? -1 : sprites.FindIndex(i => i.Sprites.SequenceEqual(Character.Front.Shield));
  142. break;
  143. }
  144. case "Melee1H":
  145. {
  146. var sprites = SortCollection(SpriteCollection.MeleeWeapon1H);
  147. ItemCollection.Active.Items = sprites.Select(i => CreateFakeItemParams(new Item(i.Id), i)).ToList();
  148. equipAction = item => Character.Equip(item.Sprite, EquipmentPart.MeleeWeapon1H);
  149. equippedIndex = sprites.FindIndex(i => i.Sprites.Contains(Character.Front.PrimaryWeapon));
  150. break;
  151. }
  152. case "Melee2H":
  153. {
  154. var sprites = SortCollection(SpriteCollection.MeleeWeapon2H);
  155. ItemCollection.Active.Items = sprites.Select(i => CreateFakeItemParams(new Item(i.Id), i)).ToList();
  156. equipAction = item => Character.Equip(item.Sprite, EquipmentPart.MeleeWeapon2H);
  157. equippedIndex = sprites.FindIndex(i => i.Sprites.Contains(Character.Front.PrimaryWeapon));
  158. break;
  159. }
  160. case "MeleePaired":
  161. {
  162. var sprites = SortCollection(SpriteCollection.MeleeWeapon1H);
  163. ItemCollection.Active.Items = sprites.Select(i => CreateFakeItemParams(new Item(i.Id), i)).ToList();
  164. equipAction = item => Character.Equip(item.Sprite, EquipmentPart.SecondaryMelee1H);
  165. equippedIndex = sprites.FindIndex(i => i.Sprites.Contains(Character.Front.SecondaryWeapon));
  166. break;
  167. }
  168. case "Bow":
  169. {
  170. var sprites = SortCollection(SpriteCollection.Bow);
  171. ItemCollection.Active.Items = sprites.Select(i => CreateFakeItemParams(new Item(i.Id), i)).ToList();
  172. equipAction = item => Character.Equip(item.Sprite, EquipmentPart.Bow);
  173. equippedIndex = Character.Front.CompositeWeapon == null ? -1 : sprites.FindIndex(i => i.Sprites.SequenceEqual(Character.Front.CompositeWeapon));
  174. break;
  175. }
  176. case "Crossbow":
  177. {
  178. var sprites = SortCollection(SpriteCollection.Crossbow);
  179. ItemCollection.Active.Items = sprites.Select(i => CreateFakeItemParams(new Item(i.Id), i)).ToList();
  180. equipAction = item => Character.Equip(item.Sprite, EquipmentPart.Crossbow);
  181. equippedIndex = Character.Front.CompositeWeapon == null ? -1 : sprites.FindIndex(i => i.Sprites.SequenceEqual(Character.Front.CompositeWeapon));
  182. break;
  183. }
  184. case "Firearm1H":
  185. {
  186. var sprites = SortCollection(SpriteCollection.Firearm1H);
  187. ItemCollection.Active.Items = sprites.Select(i => CreateFakeItemParams(new Item(i.Id), i)).ToList();
  188. equipAction = item => Character.Equip(item.Sprite, EquipmentPart.Firearm1H);
  189. equippedIndex = Character.Front.SecondaryWeapon == null ? -1 : sprites.FindIndex(i => i.Sprites.Contains(Character.Front.PrimaryWeapon));
  190. break;
  191. }
  192. case "Firearm2H":
  193. {
  194. var sprites = SortCollection(SpriteCollection.Firearm2H);
  195. ItemCollection.Active.Items = sprites.Select(i => CreateFakeItemParams(new Item(i.Id), i)).ToList();
  196. equipAction = item => Character.Equip(item.Sprite, EquipmentPart.Firearm2H);
  197. equippedIndex = Character.Front.PrimaryWeapon == null ? -1 : sprites.FindIndex(i => i.Sprites.Contains(Character.Front.PrimaryWeapon));
  198. break;
  199. }
  200. case "SecondaryFirearm1H":
  201. {
  202. var sprites = SortCollection(SpriteCollection.Firearm1H);
  203. ItemCollection.Active.Items = sprites.Select(i => CreateFakeItemParams(new Item(i.Id), i)).ToList();
  204. equipAction = item => Character.Equip(item.Sprite, EquipmentPart.SecondaryFirearm1H);
  205. equippedIndex = Character.Front.SecondaryWeapon == null ? -1 : sprites.FindIndex(i => i.Sprites.Contains(Character.Front.SecondaryWeapon));
  206. break;
  207. }
  208. case "Body":
  209. {
  210. var sprites = SortCollection(SpriteCollection.Body);
  211. ItemCollection.Active.Items = SpriteCollection.Body.Select(i => CreateFakeItemParams(new Item(i.Id), i)).ToList();
  212. equipAction = item => Character.SetBody(item.Sprite, BodyPart.Body);
  213. equippedIndex = Character.Front.Body == null ? -1 : sprites.FindIndex(i => i.Sprites.SequenceEqual(Character.Front.Body));
  214. break;
  215. }
  216. case "Head":
  217. {
  218. var sprites = SortCollection(SpriteCollection.Body);
  219. ItemCollection.Active.Items = sprites.Select(i => CreateFakeItemParams(new Item(i.Id), i)).ToList();
  220. equipAction = item => Character.SetBody(item.Sprite, BodyPart.Head);
  221. equippedIndex = Character.Front.Head == null ? -1 : sprites.FindIndex(i => i.Sprites.Contains(Character.Front.Head));
  222. break;
  223. }
  224. case "Ears":
  225. {
  226. var sprites = SortCollection(SpriteCollection.Ears);
  227. ItemCollection.Active.Items = sprites.Select(i => CreateFakeItemParams(new Item(i.Id), i)).ToList();
  228. equipAction = item => Character.SetBody(item.Sprite, BodyPart.Ears);
  229. equippedIndex = Character.Front.Ears == null ? -1 : sprites.FindIndex(i => i.Sprites.SequenceEqual(Character.Front.Ears));
  230. break;
  231. }
  232. case "Eyebrows":
  233. {
  234. var sprites = SortCollection(SpriteCollection.Eyebrows);
  235. ItemCollection.Active.Items = sprites.Select(i => CreateFakeItemParams(new Item(i.Id), i)).ToList();
  236. equipAction = item => Character.SetBody(item.Sprite, BodyPart.Eyebrows);
  237. equippedIndex = sprites.FindIndex(i => i.Sprites.Contains(Character.Front.Expressions[0].Eyebrows));
  238. break;
  239. }
  240. case "Eyes":
  241. {
  242. var sprites = SortCollection(SpriteCollection.Eyes);
  243. ItemCollection.Active.Items = sprites.Select(i => CreateFakeItemParams(new Item(i.Id), i)).ToList();
  244. equipAction = item => Character.SetBody(item.Sprite, BodyPart.Eyes);
  245. equippedIndex = sprites.FindIndex(i => i.Sprites.Contains(Character.Front.Expressions[0].Eyes));
  246. break;
  247. }
  248. case "Hair":
  249. {
  250. var sprites = SortCollection(SpriteCollection.Hair);
  251. ItemCollection.Active.Items = sprites.Select(i => CreateFakeItemParams(new Item(i.Id), i)).ToList();
  252. equipAction = item => Character.SetBody(item.Sprite, BodyPart.Hair);
  253. equippedIndex = sprites.FindIndex(i => i.Sprites.Contains(Character.Front.Hair));
  254. break;
  255. }
  256. case "Beard":
  257. {
  258. var sprites = SortCollection(SpriteCollection.Beard);
  259. ItemCollection.Active.Items = sprites.Select(i => CreateFakeItemParams(new Item(i.Id), i)).ToList();
  260. equipAction = item => Character.SetBody(item.Sprite, BodyPart.Beard);
  261. equippedIndex = sprites.FindIndex(i => i.Sprites.Contains(Character.Front.Beard));
  262. break;
  263. }
  264. case "Mouth":
  265. {
  266. var sprites = SortCollection(SpriteCollection.Mouth);
  267. ItemCollection.Active.Items = sprites.Select(i => CreateFakeItemParams(new Item(i.Id), i)).ToList();
  268. equipAction = item => Character.SetBody(item.Sprite, BodyPart.Mouth);
  269. equippedIndex = sprites.FindIndex(i => i.Sprites.Contains(Character.Front.Expressions[0].Mouth));
  270. break;
  271. }
  272. case "Makeup":
  273. {
  274. var sprites = SortCollection(SpriteCollection.Makeup);
  275. ItemCollection.Active.Items = sprites.Select(i => CreateFakeItemParams(new Item(i.Id), i)).ToList();
  276. equipAction = item => Character.SetBody(item.Sprite, BodyPart.Makeup);
  277. equippedIndex = sprites.FindIndex(i => i.Sprites.Contains(Character.Front.Makeup));
  278. break;
  279. }
  280. case "Mask":
  281. {
  282. var sprites = SortCollection(SpriteCollection.Mask);
  283. ItemCollection.Active.Items = sprites.Select(i => CreateFakeItemParams(new Item(i.Id), i)).ToList();
  284. equipAction = item => Character.Equip(item.Sprite, EquipmentPart.Mask, item.Sprite != null && item.Sprite.Tags.Contains("Paint") ? null : Color.white);
  285. equippedIndex = sprites.FindIndex(i => i.Sprites.Contains(Character.Front.Mask));
  286. break;
  287. }
  288. case "Earrings":
  289. {
  290. var sprites = SortCollection(SpriteCollection.Earrings);
  291. ItemCollection.Active.Items = sprites.Select(i => CreateFakeItemParams(new Item(i.Id), i)).ToList();
  292. equipAction = item => Character.Equip(item.Sprite, EquipmentPart.Earrings);
  293. equippedIndex = Character.Front.Earrings == null ? -1 : sprites.FindIndex(i => i.Sprites.SequenceEqual(Character.Front.Earrings));
  294. break;
  295. }
  296. case "Supplies":
  297. {
  298. var sprites = SortCollection(SpriteCollection.Supplies);
  299. ItemCollection.Active.Items = sprites.Select(i => CreateFakeItemParams(new Item(i.Id), i)).ToList();
  300. equipAction = item => { if (item.Id != null) Debug.LogWarning("Supplies are present as icons only and are not displayed on a character. Can be used for inventory."); };
  301. equippedIndex = -1;
  302. break;
  303. }
  304. default:
  305. throw new NotImplementedException(tab.name);
  306. }
  307. var items = ItemCollection.Active.Items.Select(i => new Item(i.Id)).ToList();
  308. var emptyItem = new Item(null);
  309. ItemCollection.Active.Items.Add(CreateFakeItemParams(emptyItem, null));
  310. items.Insert(0, emptyItem);
  311. // Remove items with no icons.
  312. for (var i = 0; i < items.Count; i++)
  313. {
  314. if (items[i].Id != null && IconCollection.Icons.All(j => j.Id != items[i].Params.IconId))
  315. {
  316. items.RemoveAt(i);
  317. if (equippedIndex == i) equippedIndex = -1;
  318. else if (equippedIndex > i) equippedIndex--;
  319. i--;
  320. }
  321. }
  322. InventoryItem.OnLeftClick = item =>
  323. {
  324. equipAction?.Invoke(item);
  325. EquipCallback?.Invoke(item);
  326. ItemName.text = item == emptyItem ? emptyItem.Id : item.Params.SpriteId;
  327. SetPaintButton(tab.name, item);
  328. };
  329. Inventory.Initialize(ref items, items[equippedIndex + 1], reset: true);
  330. Inventory.ScrollRect.verticalNormalizedPosition = 1;
  331. var equipped = items.Count > equippedIndex + 1 ? items[equippedIndex + 1] : null;
  332. SetPaintButton(tab.name, equipped);
  333. }
  334. private ItemParams CreateFakeItemParams(Item item, ItemSprite itemSprite, string replaceable = null, string replacement = null)
  335. {
  336. var spriteId = itemSprite?.Id;
  337. var iconId = itemSprite?.Id;
  338. var rarity = ItemRarity.Common;
  339. if (itemSprite != null)
  340. {
  341. switch (itemSprite.Collection)
  342. {
  343. case "Basic":
  344. case "Undead":
  345. break;
  346. default:
  347. rarity = ItemRarity.Epic;
  348. break;
  349. }
  350. }
  351. if (iconId != null && item.Id != null && replaceable != null && replacement != null)
  352. {
  353. iconId = iconId.Replace(replaceable, replacement);
  354. }
  355. return new ItemParams { Id = item.Id, IconId = iconId, SpriteId = spriteId, Rarity = rarity, Meta = itemSprite == null ? null : JsonConvert.SerializeObject(itemSprite.Tags) };
  356. }
  357. private void SetPaintButton(string tab, Item item)
  358. {
  359. var tags = item?.Params.MetaToList() ?? new List<string>();
  360. if (PaintParts.Contains(tab) && !tags.Contains("NoPaint") || tags.Contains("Paint"))
  361. {
  362. PaintButton.interactable = true;
  363. PaintButton.onClick.AddListener(OpenColorPicker);
  364. }
  365. else
  366. {
  367. PaintButton.interactable = false;
  368. //PaintButton.onClick.AddListener(OpenColorSetup);
  369. }
  370. }
  371. /// <summary>
  372. /// Remove all equipment and reset appearance.
  373. /// </summary>
  374. public void Reset()
  375. {
  376. Character.Parts.ForEach(i => i.ResetEquipment());
  377. new CharacterAppearance().Setup(Character.GetComponent<Character4D>());
  378. }
  379. /// <summary>
  380. /// Randomize character.
  381. /// </summary>
  382. public void Randomize()
  383. {
  384. Character.Randomize();
  385. OnSelectTab(true);
  386. }
  387. /// <summary>
  388. /// Save character to json.
  389. /// </summary>
  390. public void SaveToJson()
  391. {
  392. StartCoroutine(StandaloneFilePicker.SaveFile("Save as JSON", "", "New character", ".json", Encoding.Default.GetBytes(Character.ToJson()), (success, path) => { Debug.Log(success ? $"Saved as {path}" : "Error saving."); }));
  393. }
  394. /// <summary>
  395. /// Load character from json.
  396. /// </summary>
  397. public void LoadFromJson()
  398. {
  399. StartCoroutine(StandaloneFilePicker.OpenFile("Open as JSON", "", ".json", (success, path, bytes) =>
  400. {
  401. if (success)
  402. {
  403. var json = System.IO.File.ReadAllText(path);
  404. Character.FromJson(json, silent: false);
  405. }
  406. }));
  407. }
  408. #if UNITY_EDITOR
  409. private string _path;
  410. /// <summary>
  411. /// Save character to prefab.
  412. /// </summary>
  413. public void Save()
  414. {
  415. var path = UnityEditor.EditorUtility.SaveFilePanel("Save character prefab (should be inside Assets folder)", _path, "New character", "prefab");
  416. if (path.Length > 0)
  417. {
  418. if (!path.Contains("/Assets/")) throw new Exception("Unity can save prefabs only inside Assets folder!");
  419. Save("Assets" + path.Replace(Application.dataPath, null));
  420. _path = path;
  421. }
  422. }
  423. /// <summary>
  424. /// Load character from prefab.
  425. /// </summary>
  426. public void Load()
  427. {
  428. var path = UnityEditor.EditorUtility.OpenFilePanel("Load character prefab", _path, "prefab");
  429. if (path.Length > 0)
  430. {
  431. Load("Assets" + path.Replace(Application.dataPath, null));
  432. _path = path;
  433. }
  434. }
  435. public void Save(string path)
  436. {
  437. Character.transform.localScale = Vector3.one;
  438. #if UNITY_2018_3_OR_NEWER
  439. UnityEditor.PrefabUtility.SaveAsPrefabAsset(Character.gameObject, path);
  440. #else
  441. UnityEditor.PrefabUtility.CreatePrefab(path, Character.gameObject);
  442. #endif
  443. Debug.LogFormat("Prefab saved as {0}", path);
  444. }
  445. public void Load(string path)
  446. {
  447. var character = UnityEditor.AssetDatabase.LoadAssetAtPath<Character4D>(path);
  448. Load(character);
  449. }
  450. #else
  451. public void Save(string path)
  452. {
  453. throw new System.NotSupportedException();
  454. }
  455. public void Load(string path)
  456. {
  457. throw new System.NotSupportedException();
  458. }
  459. #endif
  460. private Color _color;
  461. public void OpenColorPicker()
  462. {
  463. var currentColor = ResolveParts(ActiveTab.name).FirstOrDefault()?.color ?? Color.white;
  464. ColorPicker.Color = _color = currentColor;
  465. ColorPicker.OnColorChanged = Paint;
  466. ColorPicker.SetActive(true);
  467. }
  468. public void CloseColorPicker(bool apply)
  469. {
  470. if (!apply) Paint(_color);
  471. ColorPicker.SetActive(false);
  472. }
  473. public void OpenColorSetup()
  474. {
  475. var currentColor = ResolveParts(ActiveTab.name).FirstOrDefault()?.color ?? Color.white;
  476. ColorSetup.Color = _color = currentColor;
  477. ColorSetup.OnColorChanged = SetupColor;
  478. ColorSetup.SetActive(true);
  479. }
  480. public void CloseColorSetup(bool apply)
  481. {
  482. Color.RGBToHSV(_color, out _, out var s, out var v);
  483. if (!apply) SetupColor(0, s, v);
  484. ColorSetup.SetActive(false);
  485. }
  486. /// <summary>
  487. /// Pick color and apply to sprite.
  488. /// </summary>
  489. public void Paint(Color color)
  490. {
  491. foreach (var part in ResolveParts(ActiveTab.name))
  492. {
  493. part.color = color;
  494. part.sharedMaterial = color == Color.white ? DefaultMaterial : ActiveTab.name == "Eyes" ? EyesPaintMaterial : EquipmentPaintMaterial;
  495. }
  496. if (ActiveTab.name == "Eyes")
  497. {
  498. foreach (var expression in Character.Parts.SelectMany(i => i.Expressions))
  499. {
  500. if (expression.Name != "Dead") expression.EyesColor = color;
  501. }
  502. }
  503. }
  504. /// <summary>
  505. /// Pick HSB and apply to sprite.
  506. /// </summary>
  507. public void SetupColor(float h, float s, float v)
  508. {
  509. var color = Color.HSVToRGB(0, s, v);
  510. foreach (var part in ResolveParts(ActiveTab.name))
  511. {
  512. part.color = color;
  513. part.sharedMaterial = h <= 0 ? DefaultMaterial : HuePaintMaterial;
  514. if (h > 0)
  515. {
  516. var huePaint = part.GetComponent<HuePaint>() ?? part.gameObject.AddComponent<HuePaint>();
  517. huePaint.Hue = h;
  518. huePaint.ShiftHue();
  519. }
  520. else
  521. {
  522. var huePaint = part.GetComponent<HuePaint>();
  523. if (huePaint)
  524. {
  525. Destroy(huePaint);
  526. }
  527. }
  528. }
  529. }
  530. protected List<SpriteRenderer> ResolveParts(string target)
  531. {
  532. switch (target)
  533. {
  534. case "Helmet": return Character.Parts.Select(i => i.HelmetRenderer).ToList();
  535. case "Ears": return Character.Parts.SelectMany(i => i.EarsRenderers).ToList();
  536. case "Body": return Character.Parts.SelectMany(i => i.BodyRenderers.Union(new List<SpriteRenderer> { i.HeadRenderer }).Union(i.EarsRenderers)).ToList();
  537. case "Head": return Character.Parts.Select(i => i.HeadRenderer).ToList();
  538. case "Hair": return Character.Parts.Select(i => i.HairRenderer).ToList();
  539. case "Beard": return Character.Parts.Select(i => i.BeardRenderer).Where(i => i != null).ToList();
  540. case "Eyes": return Character.Parts.Select(i => i.EyesRenderer).Where(i => i != null).ToList();
  541. case "Eyebrows": return Character.Parts.Select(i => i.EyebrowsRenderer).Where(i => i != null).ToList();
  542. case "Mouth": return Character.Parts.Select(i => i.MouthRenderer).Where(i => i != null).ToList();
  543. case "Mask": return Character.Parts.Select(i => i.MaskRenderer).Where(i => i != null).ToList();
  544. case "Makeup": return Character.Parts.Select(i => i.MakeupRenderer).Where(i => i != null).ToList();
  545. default: throw new NotImplementedException(target);
  546. }
  547. }
  548. protected void FeedbackTip()
  549. {
  550. #if UNITY_EDITOR
  551. var success = UnityEditor.EditorUtility.DisplayDialog("HeroView Editor", "Hi! Thank you for using my asset! I hope you enjoy making your game with it. The only thing I would ask you to do is to leave a review on the Asset Store. It would be awesome support for my asset, thanks!", "Review", "Later");
  552. RequestFeedbackResult(success);
  553. #endif
  554. }
  555. /// <summary>
  556. /// Navigate to URL.
  557. /// </summary>
  558. public void Navigate(string url)
  559. {
  560. Application.OpenURL(url);
  561. }
  562. private const string FeedbackRequestTimeKey = "CE:FeedbackRequestTime";
  563. private const string FeedbackTimeKey = "CE:FeedbackTime";
  564. protected void RequestFeedback()
  565. {
  566. if (PlayerPrefs.HasKey(FeedbackTimeKey) && (DateTime.UtcNow - new DateTime(long.Parse(PlayerPrefs.GetString(FeedbackTimeKey)))).TotalDays < 14)
  567. {
  568. return;
  569. }
  570. if (!PlayerPrefs.HasKey(FeedbackRequestTimeKey))
  571. {
  572. PlayerPrefs.SetString(FeedbackRequestTimeKey, DateTime.UtcNow.AddHours(-23).Ticks.ToString());
  573. }
  574. else if ((DateTime.UtcNow - new DateTime(long.Parse(PlayerPrefs.GetString(FeedbackRequestTimeKey)))).TotalDays > 1)
  575. {
  576. FeedbackTip();
  577. }
  578. }
  579. protected void RequestFeedbackResult(bool success)
  580. {
  581. if (success)
  582. {
  583. PlayerPrefs.SetString(FeedbackTimeKey, DateTime.UtcNow.Ticks.ToString());
  584. Application.OpenURL(AssetUrl);
  585. }
  586. else if (PlayerPrefs.HasKey(FeedbackTimeKey))
  587. {
  588. PlayerPrefs.SetString(FeedbackRequestTimeKey, DateTime.UtcNow.AddDays(7).Ticks.ToString());
  589. }
  590. else
  591. {
  592. PlayerPrefs.SetString(FeedbackRequestTimeKey, DateTime.UtcNow.Ticks.ToString());
  593. }
  594. }
  595. }
  596. }