AFPSCounter.cs 35 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390
  1. #region copyright
  2. //-------------------------------------------------------
  3. // Copyright (C) Dmitriy Yukhanov [https://codestage.net]
  4. //-------------------------------------------------------
  5. #endregion
  6. using System.Text;
  7. #pragma warning disable 169
  8. namespace CodeStage.AdvancedFPSCounter
  9. {
  10. using System.Collections.Generic;
  11. using CountersData;
  12. using Labels;
  13. using UnityEngine;
  14. using UnityEngine.UI;
  15. using Utils;
  16. using UnityEngine.SceneManagement;
  17. /// <summary>
  18. /// Allows to see frames per second counter, memory usage counter and some simple hardware information right in running app on any device.<br/>
  19. /// Just call AFPSCounter.AddToScene() to use it.
  20. /// </summary>
  21. /// You also may add it to GameObject (without any child or parent objects, with zero rotation, zero position and 1,1,1 scale) as usual or through the<br/>
  22. /// "GameObject > Create Other > Code Stage > Advanced FPS Counter" menu.
  23. [AddComponentMenu(MenuPath)]
  24. [DisallowMultipleComponent]
  25. [HelpURL("http://codestage.net/uas_files/afps/api/class_code_stage_1_1_advanced_f_p_s_counter_1_1_a_f_p_s_counter.html")]
  26. public class AFPSCounter : MonoBehaviour
  27. {
  28. // ----------------------------------------------------------------------------
  29. // constants
  30. // ----------------------------------------------------------------------------
  31. private const string MenuPath = "Code Stage/🚀 Advanced FPS Counter";
  32. private const string ComponentName = "Advanced FPS Counter";
  33. #if UNITY_EDITOR
  34. internal const string LogPrefix = "<b>[AFPSCounter]:</b> ";
  35. #else
  36. internal const string LogPrefix = "[AFPSCounter]: ";
  37. #endif
  38. internal const char NewLine = '\n';
  39. internal const char Space = ' ';
  40. // ----------------------------------------------------------------------------
  41. // public fields
  42. // ----------------------------------------------------------------------------
  43. /// <summary>
  44. /// Frames Per Second counter.
  45. /// </summary>
  46. public FPSCounterData fpsCounter = new FPSCounterData();
  47. /// <summary>
  48. /// Mono or heap memory counter.
  49. /// </summary>
  50. public MemoryCounterData memoryCounter = new MemoryCounterData();
  51. /// <summary>
  52. /// Device hardware info.<br/>
  53. /// Shows CPU name, cores (threads) count, GPU name, total VRAM, total RAM, screen DPI and screen size.
  54. /// </summary>
  55. public DeviceInfoCounterData deviceInfoCounter = new DeviceInfoCounterData();
  56. /// <summary>
  57. /// Used to enable / disable plugin at runtime. Set to KeyCode.None to disable.
  58. /// </summary>
  59. [Tooltip("Used to enable / disable plugin at runtime.\nSet to None to disable.")]
  60. public KeyCode hotKey = KeyCode.BackQuote;
  61. /// <summary>
  62. /// Used to enable / disable plugin at runtime. Make two circle gestures with your finger \ mouse to switch plugin on and off.
  63. /// </summary>
  64. [Tooltip("Used to enable / disable plugin at runtime.\nMake two circle gestures with your finger \\ mouse to switch plugin on and off.")]
  65. public bool circleGesture;
  66. /// <summary>
  67. /// Hot key modifier: any Control on Windows or any Command on Mac.
  68. /// </summary>
  69. [Tooltip("Hot key modifier: any Control on Windows or any Command on Mac.")]
  70. public bool hotKeyCtrl;
  71. /// <summary>
  72. /// Hot key modifier: any Shift.
  73. /// </summary>
  74. [Tooltip("Hot key modifier: any Shift.")]
  75. public bool hotKeyShift;
  76. /// <summary>
  77. /// Hot key modifier: any Alt.
  78. /// </summary>
  79. [Tooltip("Hot key modifier: any Alt.")]
  80. public bool hotKeyAlt;
  81. [Tooltip("Prevents current or other topmost Game Object from destroying on level (scene) load.\nApplied once, on Start phase.")]
  82. [SerializeField]
  83. private bool keepAlive = true;
  84. // ----------------------------------------------------------------------------
  85. // private fields
  86. // ----------------------------------------------------------------------------
  87. private Canvas canvas;
  88. private CanvasScaler canvasScaler;
  89. private bool externalCanvas;
  90. private DrawableLabel[] labels;
  91. private int anchorsCount;
  92. private int cachedVSync = -1;
  93. private int cachedFrameRate = -1;
  94. private bool inited;
  95. /* circle gesture variables */
  96. private readonly List<Vector2> gesturePoints = new List<Vector2>();
  97. private int gestureCount;
  98. // ----------------------------------------------------------------------------
  99. // properties
  100. // ----------------------------------------------------------------------------
  101. /// <summary>
  102. /// Read-only property allowing to figure out current keepAlive state.
  103. /// </summary>
  104. public bool KeepAlive
  105. {
  106. get { return keepAlive; }
  107. }
  108. #region OperationMode
  109. [Tooltip("Disabled: removes labels and stops all internal processes except Hot Key listener.\n\n" +
  110. "Background: removes labels keeping counters alive; use for hidden performance monitoring.\n\n" +
  111. "Normal: shows labels and runs all internal processes as usual.")]
  112. [SerializeField]
  113. private OperationMode operationMode = OperationMode.Normal;
  114. /// <summary>
  115. /// Use it to change %AFPSCounter operation mode.
  116. /// </summary>
  117. /// Disabled: removes labels and stops all internal processes except Hot Key listener.<br/>
  118. /// Background: removes labels keeping counters alive. May be useful for hidden performance monitoring and benchmarking. Hot Key has no effect in this mode.<br/>
  119. /// Normal: shows labels and runs all internal processes as usual.
  120. public OperationMode OperationMode
  121. {
  122. get { return operationMode; }
  123. set
  124. {
  125. if (operationMode == value || !Application.isPlaying) return;
  126. operationMode = value;
  127. if (operationMode != OperationMode.Disabled)
  128. {
  129. if (operationMode == OperationMode.Background)
  130. {
  131. for (var i = 0; i < anchorsCount; i++)
  132. {
  133. labels[i].Clear();
  134. }
  135. }
  136. OnEnable();
  137. fpsCounter.UpdateValue();
  138. memoryCounter.UpdateValue();
  139. deviceInfoCounter.UpdateValue();
  140. UpdateTexts();
  141. }
  142. else
  143. {
  144. OnDisable();
  145. }
  146. }
  147. }
  148. #endregion
  149. #region ForceFrameRate
  150. [Tooltip("Allows to see how your game performs on specified frame rate.\n" +
  151. "Does not guarantee selected frame rate. Set -1 to render as fast as possible in current conditions.\n" +
  152. "IMPORTANT: this option disables VSync while enabled!")]
  153. [SerializeField]
  154. private bool forceFrameRate;
  155. /// <summary>
  156. /// Allows to see how your game performs on specified frame rate.<br/>
  157. /// <strong>\htmlonly<font color="7030A0">IMPORTANT:</font>\endhtmlonly this option disables VSync while enabled!</strong>
  158. /// </summary>
  159. /// Useful to check how physics performs on slow devices for example.
  160. public bool ForceFrameRate
  161. {
  162. get { return forceFrameRate; }
  163. set
  164. {
  165. if (forceFrameRate == value || !Application.isPlaying) return;
  166. forceFrameRate = value;
  167. if (operationMode == OperationMode.Disabled) return;
  168. RefreshForcedFrameRate();
  169. }
  170. }
  171. #endregion
  172. #region ForcedFrameRate
  173. [Range(-1, 200)]
  174. [SerializeField]
  175. private int forcedFrameRate = -1;
  176. /// <summary>
  177. /// Desired frame rate for ForceFrameRate option, does not guarantee selected frame rate.
  178. /// Set to -1 to render as fast as possible in current conditions.
  179. /// </summary>
  180. public int ForcedFrameRate
  181. {
  182. get { return forcedFrameRate; }
  183. set
  184. {
  185. if (forcedFrameRate == value || !Application.isPlaying) return;
  186. forcedFrameRate = value;
  187. if (operationMode == OperationMode.Disabled) return;
  188. RefreshForcedFrameRate();
  189. }
  190. }
  191. #endregion
  192. /* look and feel settings */
  193. #region Background
  194. [Tooltip("Background for all texts. Cheapest effect. Overhead: 1 Draw Call.")]
  195. [SerializeField]
  196. private bool background = true;
  197. /// <summary>
  198. /// Background for all texts.
  199. /// </summary>
  200. public bool Background
  201. {
  202. get { return background; }
  203. set
  204. {
  205. if (background == value || !Application.isPlaying) return;
  206. background = value;
  207. if (operationMode == OperationMode.Disabled || labels == null) return;
  208. for (var i = 0; i < anchorsCount; i++)
  209. {
  210. labels[i].ChangeBackground(background);
  211. }
  212. }
  213. }
  214. #endregion
  215. #region BackgroundColor
  216. [Tooltip("Color of the background.")]
  217. [SerializeField]
  218. private Color backgroundColor = new Color32(0, 0, 0, 155);
  219. /// <summary>
  220. /// Color of the background.
  221. /// </summary>
  222. public Color BackgroundColor
  223. {
  224. get { return backgroundColor; }
  225. set
  226. {
  227. if (backgroundColor == value || !Application.isPlaying) return;
  228. backgroundColor = value;
  229. if (operationMode == OperationMode.Disabled || labels == null) return;
  230. for (var i = 0; i < anchorsCount; i++)
  231. {
  232. labels[i].ChangeBackgroundColor(backgroundColor);
  233. }
  234. }
  235. }
  236. #endregion
  237. #region BackgroundPadding
  238. [Tooltip("Padding of the background.")]
  239. [Range(0, 30)]
  240. [SerializeField]
  241. private int backgroundPadding = 5;
  242. /// <summary>
  243. /// Padding of the background. Change forces the HorizontalLayoutGroup.SetLayoutHorizontal() call.
  244. /// </summary>
  245. public int BackgroundPadding
  246. {
  247. get { return backgroundPadding; }
  248. set
  249. {
  250. if (backgroundPadding == value || !Application.isPlaying) return;
  251. backgroundPadding = value;
  252. if (operationMode == OperationMode.Disabled || labels == null) return;
  253. for (var i = 0; i < anchorsCount; i++)
  254. {
  255. labels[i].ChangeBackgroundPadding(backgroundPadding);
  256. }
  257. }
  258. }
  259. #endregion
  260. #region Shadow
  261. [Tooltip("Shadow effect for all texts. This effect uses extra resources. Overhead: medium CPU and light GPU usage.")]
  262. [SerializeField]
  263. private bool shadow;
  264. /// <summary>
  265. /// Shadow effect for all texts.
  266. /// </summary>
  267. public bool Shadow
  268. {
  269. get { return shadow; }
  270. set
  271. {
  272. if (shadow == value || !Application.isPlaying) return;
  273. shadow = value;
  274. if (operationMode == OperationMode.Disabled || labels == null) return;
  275. for (var i = 0; i < anchorsCount; i++)
  276. {
  277. labels[i].ChangeShadow(shadow);
  278. }
  279. }
  280. }
  281. #endregion
  282. #region ShadowColor
  283. [Tooltip("Color of the shadow effect.")]
  284. [SerializeField]
  285. private Color shadowColor = new Color32(0, 0, 0, 128);
  286. /// <summary>
  287. /// Color of the shadow effect.
  288. /// </summary>
  289. public Color ShadowColor
  290. {
  291. get { return shadowColor; }
  292. set
  293. {
  294. if (shadowColor == value || !Application.isPlaying) return;
  295. shadowColor = value;
  296. if (operationMode == OperationMode.Disabled || labels == null) return;
  297. for (var i = 0; i < anchorsCount; i++)
  298. {
  299. labels[i].ChangeShadowColor(shadowColor);
  300. }
  301. }
  302. }
  303. #endregion
  304. #region ShadowDistance
  305. [Tooltip("Distance of the shadow effect.")]
  306. [SerializeField]
  307. private Vector2 shadowDistance = new Vector2(1, -1);
  308. /// <summary>
  309. /// Distance of the shadow effect.
  310. /// </summary>
  311. public Vector2 ShadowDistance
  312. {
  313. get { return shadowDistance; }
  314. set
  315. {
  316. if (shadowDistance == value || !Application.isPlaying) return;
  317. shadowDistance = value;
  318. if (operationMode == OperationMode.Disabled || labels == null) return;
  319. for (var i = 0; i < anchorsCount; i++)
  320. {
  321. labels[i].ChangeShadowDistance(shadowDistance);
  322. }
  323. }
  324. }
  325. #endregion
  326. #region Outline
  327. [Tooltip("Outline effect for all texts. Resource-heaviest effect. Overhead: huge CPU and medium GPU usage. Not recommended for use unless really necessary.")]
  328. [SerializeField]
  329. private bool outline;
  330. /// <summary>
  331. /// Outline effect for all texts.
  332. /// </summary>
  333. public bool Outline
  334. {
  335. get { return outline; }
  336. set
  337. {
  338. if (outline == value || !Application.isPlaying) return;
  339. outline = value;
  340. if (operationMode == OperationMode.Disabled || labels == null) return;
  341. for (var i = 0; i < anchorsCount; i++)
  342. {
  343. labels[i].ChangeOutline(outline);
  344. }
  345. }
  346. }
  347. #endregion
  348. #region OutlineColor
  349. [Tooltip("Color of the outline effect.")]
  350. [SerializeField]
  351. private Color outlineColor = new Color32(0, 0, 0, 128);
  352. /// <summary>
  353. /// Color of the outline effect.
  354. /// </summary>
  355. public Color OutlineColor
  356. {
  357. get { return outlineColor; }
  358. set
  359. {
  360. if (outlineColor == value || !Application.isPlaying) return;
  361. outlineColor = value;
  362. if (operationMode == OperationMode.Disabled || labels == null) return;
  363. for (var i = 0; i < anchorsCount; i++)
  364. {
  365. labels[i].ChangeOutlineColor(outlineColor);
  366. }
  367. }
  368. }
  369. #endregion
  370. #region OutlineDistance
  371. [Tooltip("Distance of the outline effect.")]
  372. [SerializeField]
  373. private Vector2 outlineDistance = new Vector2(1, -1);
  374. /// <summary>
  375. /// Distance of the outline effect.
  376. /// </summary>
  377. public Vector2 OutlineDistance
  378. {
  379. get { return outlineDistance; }
  380. set
  381. {
  382. if (outlineDistance == value || !Application.isPlaying) return;
  383. outlineDistance = value;
  384. if (operationMode == OperationMode.Disabled || labels == null) return;
  385. for (var i = 0; i < anchorsCount; i++)
  386. {
  387. labels[i].ChangeOutlineDistance(outlineDistance);
  388. }
  389. }
  390. }
  391. #endregion
  392. #region AutoScale
  393. [Tooltip("Controls own Canvas Scaler scale mode. Check to use ScaleWithScreenSize. Otherwise ConstantPixelSize will be used.")]
  394. [SerializeField]
  395. private bool autoScale;
  396. /// <summary>
  397. /// Controls own Canvas Scaler scale mode. Check to use ScaleWithScreenSize. Otherwise ConstantPixelSize will be used.
  398. /// </summary>
  399. public bool AutoScale
  400. {
  401. get { return autoScale; }
  402. set
  403. {
  404. if (autoScale == value || !Application.isPlaying) return;
  405. autoScale = value;
  406. if (operationMode == OperationMode.Disabled || labels == null) return;
  407. if (canvasScaler == null) return;
  408. canvasScaler.uiScaleMode = autoScale
  409. ? CanvasScaler.ScaleMode.ScaleWithScreenSize
  410. : CanvasScaler.ScaleMode.ConstantPixelSize;
  411. }
  412. }
  413. #endregion
  414. #region ScaleFactor
  415. [Tooltip("Controls global scale of all texts.")]
  416. [Range(0f, 30f)]
  417. [SerializeField]
  418. private float scaleFactor = 1;
  419. /// <summary>
  420. /// Controls global scale of all texts.
  421. /// </summary>
  422. public float ScaleFactor
  423. {
  424. get { return scaleFactor; }
  425. set
  426. {
  427. if (System.Math.Abs(scaleFactor - value) < 0.001f || !Application.isPlaying) return;
  428. scaleFactor = value;
  429. if (operationMode == OperationMode.Disabled || canvasScaler == null) return;
  430. canvasScaler.scaleFactor = scaleFactor;
  431. }
  432. }
  433. #endregion
  434. #region LabelsFont
  435. [Tooltip("Leave blank to use default font.")]
  436. [SerializeField]
  437. private Font labelsFont;
  438. /// <summary>
  439. /// Font to render labels with.
  440. /// </summary>
  441. public Font LabelsFont
  442. {
  443. get { return labelsFont; }
  444. set
  445. {
  446. if (labelsFont == value || !Application.isPlaying) return;
  447. labelsFont = value;
  448. if (operationMode == OperationMode.Disabled || labels == null) return;
  449. for (var i = 0; i < anchorsCount; i++)
  450. {
  451. labels[i].ChangeFont(labelsFont);
  452. }
  453. }
  454. }
  455. #endregion
  456. #region FontSize
  457. [Tooltip("Set to 0 to use font size specified in the font importer.")]
  458. [Range(0, 100)]
  459. [SerializeField]
  460. private int fontSize = 14;
  461. /// <summary>
  462. /// The font size to use (for dynamic fonts).
  463. /// </summary>
  464. /// If this is set to a non-zero value, the font size specified in the font importer is overridden with a custom size. This is only supported for fonts set to use dynamic font rendering. Other fonts will always use the default font size.
  465. public int FontSize
  466. {
  467. get { return fontSize; }
  468. set
  469. {
  470. if (fontSize == value || !Application.isPlaying) return;
  471. fontSize = value;
  472. if (operationMode == OperationMode.Disabled || labels == null) return;
  473. for (var i = 0; i < anchorsCount; i++)
  474. {
  475. labels[i].ChangeFontSize(fontSize);
  476. }
  477. }
  478. }
  479. #endregion
  480. #region LineSpacing
  481. [Tooltip("Space between lines in labels.")]
  482. [Range(0f, 10f)]
  483. [SerializeField]
  484. private float lineSpacing = 1;
  485. /// <summary>
  486. /// Space between lines.
  487. /// </summary>
  488. public float LineSpacing
  489. {
  490. get { return lineSpacing; }
  491. set
  492. {
  493. if (System.Math.Abs(lineSpacing - value) < 0.001f || !Application.isPlaying) return;
  494. lineSpacing = value;
  495. if (operationMode == OperationMode.Disabled || labels == null) return;
  496. for (var i = 0; i < anchorsCount; i++)
  497. {
  498. labels[i].ChangeLineSpacing(lineSpacing);
  499. }
  500. }
  501. }
  502. #endregion
  503. #region CountersSpacing
  504. [Tooltip("Lines count between different counters in a single label.")]
  505. [Range(0, 10)]
  506. [SerializeField]
  507. private int countersSpacing;
  508. /// <summary>
  509. /// Lines count between different counters in a single label.
  510. /// </summary>
  511. public int CountersSpacing
  512. {
  513. get { return countersSpacing; }
  514. set
  515. {
  516. if (countersSpacing == value || !Application.isPlaying) return;
  517. countersSpacing = value;
  518. if (operationMode == OperationMode.Disabled || labels == null) return;
  519. UpdateTexts();
  520. for (var i = 0; i < anchorsCount; i++)
  521. {
  522. labels[i].dirty = true;
  523. }
  524. }
  525. }
  526. #endregion
  527. #region PaddingOffset
  528. [Tooltip("Pixel offset for anchored labels. Automatically applied to all labels.")]
  529. [SerializeField]
  530. private Vector2 paddingOffset = new Vector2(5, 5);
  531. /// <summary>
  532. /// Pixel offset for anchored labels. Automatically applied to all labels.
  533. /// </summary>
  534. public Vector2 PaddingOffset
  535. {
  536. get { return paddingOffset; }
  537. set
  538. {
  539. if (paddingOffset == value || !Application.isPlaying) return;
  540. paddingOffset = value;
  541. if (operationMode == OperationMode.Disabled || labels == null) return;
  542. for (var i = 0; i < anchorsCount; i++)
  543. {
  544. labels[i].ChangeOffset(paddingOffset);
  545. }
  546. }
  547. }
  548. #endregion
  549. #region PixelPerfect
  550. [Tooltip("Controls own canvas Pixel Perfect property.")]
  551. [SerializeField]
  552. private bool pixelPerfect = true;
  553. /// <summary>
  554. /// Controls own canvas Pixel Perfect property.
  555. /// </summary>
  556. public bool PixelPerfect
  557. {
  558. get { return pixelPerfect; }
  559. set
  560. {
  561. if (pixelPerfect == value || !Application.isPlaying) return;
  562. pixelPerfect = value;
  563. if (operationMode == OperationMode.Disabled || labels == null) return;
  564. canvas.pixelPerfect = pixelPerfect;
  565. }
  566. }
  567. #endregion
  568. /* advanced settings */
  569. #region SortingOrder
  570. [Tooltip("Sorting order to use for the canvas.\nSet higher value to get closer to the user.")]
  571. [SerializeField]
  572. private int sortingOrder = 10000;
  573. /// <summary>
  574. /// Sorting order to use for the canvas.
  575. /// </summary>
  576. /// Set higher value to get closer to the user.
  577. public int SortingOrder
  578. {
  579. get { return sortingOrder; }
  580. set
  581. {
  582. if (sortingOrder == value || !Application.isPlaying) return;
  583. sortingOrder = value;
  584. if (operationMode == OperationMode.Disabled || canvas == null) return;
  585. canvas.sortingOrder = sortingOrder;
  586. }
  587. }
  588. #endregion
  589. // preventing direct instantiation
  590. private AFPSCounter() { }
  591. // ----------------------------------------------------------------------------
  592. // instance
  593. // ----------------------------------------------------------------------------
  594. /// <summary>
  595. /// Allows reaching public properties from code. Can be null.
  596. /// \sa AddToScene()
  597. /// </summary>
  598. public static AFPSCounter Instance { get; private set; }
  599. private static AFPSCounter GetOrCreateInstance(bool keepAlive)
  600. {
  601. if (Instance != null) return Instance;
  602. var counter = FindObjectOfType<AFPSCounter>();
  603. if (counter != null)
  604. {
  605. Instance = counter;
  606. }
  607. else
  608. {
  609. var newInstance = CreateInScene(false);
  610. newInstance.keepAlive = keepAlive;
  611. }
  612. return Instance;
  613. }
  614. // ----------------------------------------------------------------------------
  615. // public static methods
  616. // ----------------------------------------------------------------------------
  617. /// <summary>
  618. /// Creates and adds new %AFPSCounter instance to the scene if it doesn't exists with keepAlive set to true.
  619. /// Use it to instantiate %AFPSCounter from code before using AFPSCounter.Instance.
  620. /// </summary>
  621. /// <returns>Existing or new %AFPSCounter instance.</returns>
  622. public static AFPSCounter AddToScene()
  623. {
  624. return AddToScene(true);
  625. }
  626. /// <summary>
  627. /// Creates and adds new %AFPSCounter instance to the scene if it doesn't exists.
  628. /// Use it to instantiate %AFPSCounter from code before using AFPSCounter.Instance.
  629. /// </summary>
  630. /// <param name="keepAlive">Set true to prevent AFPSCounter's Game Object from destroying on level (scene) load.
  631. /// Applies to the new Instance only.</param>
  632. /// <returns>Existing or new %AFPSCounter instance.</returns>
  633. public static AFPSCounter AddToScene(bool keepAlive)
  634. {
  635. return GetOrCreateInstance(keepAlive);
  636. }
  637. [System.Obsolete("Please use SelfDestroy() instead. This method will be removed in future updates.")]
  638. public static void Dispose()
  639. {
  640. SelfDestroy();
  641. }
  642. /// <summary>
  643. /// Use it to completely dispose current %AFPSCounter instance.
  644. /// </summary>
  645. public static void SelfDestroy()
  646. {
  647. if (Instance != null) Instance.DisposeInternal();
  648. }
  649. // ----------------------------------------------------------------------------
  650. // internal static methods
  651. // ----------------------------------------------------------------------------
  652. internal static string Color32ToHex(Color32 color)
  653. {
  654. return color.r.ToString("x2") + color.g.ToString("x2") + color.b.ToString("x2") + color.a.ToString("x2");
  655. }
  656. // ----------------------------------------------------------------------------
  657. // private static methods
  658. // ----------------------------------------------------------------------------
  659. private static AFPSCounter CreateInScene(bool lookForExistingContainer = true)
  660. {
  661. var container = lookForExistingContainer ? GameObject.Find(ComponentName) : null;
  662. if (container == null)
  663. {
  664. container = new GameObject(ComponentName)
  665. {
  666. layer = LayerMask.NameToLayer("UI")
  667. };
  668. #if UNITY_EDITOR
  669. if (!Application.isPlaying)
  670. {
  671. UnityEditor.Undo.RegisterCreatedObjectUndo(container, "Create " + ComponentName);
  672. if (UnityEditor.Selection.activeTransform != null)
  673. {
  674. container.transform.parent = UnityEditor.Selection.activeTransform;
  675. }
  676. UnityEditor.Selection.activeObject = container;
  677. }
  678. #endif
  679. }
  680. var newInstance = container.AddComponent<AFPSCounter>();
  681. return newInstance;
  682. }
  683. // ----------------------------------------------------------------------------
  684. // unity callbacks
  685. // ----------------------------------------------------------------------------
  686. #region unity callbacks
  687. private void Awake()
  688. {
  689. /* checks for duplication */
  690. if (Instance != null && Instance.keepAlive)
  691. {
  692. Destroy(this);
  693. return;
  694. }
  695. /* editor-only checks */
  696. #if UNITY_EDITOR
  697. if (!IsPlacedCorrectly())
  698. {
  699. Debug.LogWarning(LogPrefix + "incorrect placement detected! Please, use \"" + GameObjectMenuGroup + MenuPath + "\" menu to fix it!", this);
  700. }
  701. #endif
  702. /* initialization */
  703. Instance = this;
  704. fpsCounter.Init(this);
  705. memoryCounter.Init(this);
  706. deviceInfoCounter.Init(this);
  707. ConfigureCanvas();
  708. ConfigureLabels();
  709. inited = true;
  710. }
  711. private void Start()
  712. {
  713. if (keepAlive)
  714. {
  715. // will keep alive itself or other topmost game object
  716. DontDestroyOnLoad(transform.root.gameObject);
  717. SceneManager.sceneLoaded += OnLevelWasLoadedNew;
  718. }
  719. }
  720. private void Update()
  721. {
  722. if (!inited) return;
  723. ProcessHotKey();
  724. if (circleGesture && CircleGestureMade())
  725. {
  726. SwitchCounter();
  727. }
  728. }
  729. private void OnLevelWasLoadedNew(Scene scene, LoadSceneMode mode)
  730. {
  731. OnLevelLoadedCallback();
  732. }
  733. private void OnLevelLoadedCallback()
  734. {
  735. if (!inited) return;
  736. if (!fpsCounter.Enabled) return;
  737. fpsCounter.OnLevelLoadedCallback();
  738. }
  739. private void OnEnable()
  740. {
  741. if (!inited) return;
  742. if (operationMode == OperationMode.Disabled) return;
  743. ActivateCounters();
  744. Invoke("RefreshForcedFrameRate", 0.5f);
  745. }
  746. private void OnDisable()
  747. {
  748. if (!inited) return;
  749. DeactivateCounters();
  750. if (IsInvoking("RefreshForcedFrameRate")) CancelInvoke("RefreshForcedFrameRate");
  751. RefreshForcedFrameRate(true);
  752. for (var i = 0; i < anchorsCount; i++)
  753. {
  754. labels[i].Clear();
  755. }
  756. }
  757. private void OnDestroy()
  758. {
  759. if (inited)
  760. {
  761. fpsCounter.Destroy();
  762. memoryCounter.Destroy();
  763. deviceInfoCounter.Destroy();
  764. if (labels != null)
  765. {
  766. for (var i = 0; i < anchorsCount; i++)
  767. {
  768. labels[i].Destroy();
  769. }
  770. System.Array.Clear(labels, 0, anchorsCount);
  771. labels = null;
  772. }
  773. inited = false;
  774. }
  775. if (canvas != null)
  776. {
  777. Destroy(canvas.gameObject);
  778. }
  779. if (transform.childCount <= 1)
  780. {
  781. Destroy(gameObject);
  782. }
  783. if (Instance == this) Instance = null;
  784. }
  785. #endregion
  786. // ----------------------------------------------------------------------------
  787. // internal methods
  788. // ----------------------------------------------------------------------------
  789. internal void MakeDrawableLabelDirty(LabelAnchor anchor)
  790. {
  791. if (operationMode == OperationMode.Normal)
  792. {
  793. labels[(int)anchor].dirty = true;
  794. }
  795. }
  796. public System.Action<StringBuilder> OnAddLog;
  797. internal void UpdateTexts()
  798. {
  799. if (operationMode != OperationMode.Normal) return;
  800. var anyContentPresent = false;
  801. if (fpsCounter.Enabled)
  802. {
  803. var label = labels[(int)fpsCounter.Anchor];
  804. if (label.newText.Length > 0)
  805. {
  806. label.newText.Append(new string(NewLine, countersSpacing + 1));
  807. }
  808. label.newText.Append(fpsCounter.text);
  809. label.dirty |= fpsCounter.dirty;
  810. fpsCounter.dirty = false;
  811. anyContentPresent = true;
  812. }
  813. if (memoryCounter.Enabled)
  814. {
  815. var label = labels[(int)memoryCounter.Anchor];
  816. if (label.newText.Length > 0)
  817. {
  818. label.newText.Append(new string(NewLine, countersSpacing + 1));
  819. }
  820. label.newText.Append(memoryCounter.text);
  821. label.dirty |= memoryCounter.dirty;
  822. memoryCounter.dirty = false;
  823. anyContentPresent = true;
  824. }
  825. if (deviceInfoCounter.Enabled)
  826. {
  827. var label = labels[(int)deviceInfoCounter.Anchor];
  828. if (label.newText.Length > 0)
  829. {
  830. label.newText.Append(new string(NewLine, countersSpacing + 1));
  831. }
  832. label.newText.Append(deviceInfoCounter.text);
  833. label.dirty |= deviceInfoCounter.dirty;
  834. deviceInfoCounter.dirty = false;
  835. anyContentPresent = true;
  836. }
  837. if (anyContentPresent)
  838. {
  839. for (var i = 0; i < anchorsCount; i++)
  840. {
  841. labels[i].CheckAndUpdate();
  842. }
  843. }
  844. else
  845. {
  846. for (var i = 0; i < anchorsCount; i++)
  847. {
  848. labels[i].Clear();
  849. }
  850. }
  851. }
  852. // ----------------------------------------------------------------------------
  853. // private methods
  854. // ----------------------------------------------------------------------------
  855. private void ConfigureCanvas()
  856. {
  857. var parentCanvas = GetComponentInParent<Canvas>();
  858. if (parentCanvas != null)
  859. {
  860. externalCanvas = true;
  861. var ownRectTransform = gameObject.GetComponent<RectTransform>();
  862. if (ownRectTransform == null)
  863. {
  864. ownRectTransform = gameObject.AddComponent<RectTransform>();
  865. }
  866. UIUtils.ResetRectTransform(ownRectTransform);
  867. }
  868. var canvasObject = new GameObject("CountersCanvas", typeof(Canvas));
  869. canvasObject.tag = gameObject.tag;
  870. canvasObject.layer = gameObject.layer;
  871. canvasObject.transform.SetParent(transform, false);
  872. canvas = canvasObject.GetComponent<Canvas>();
  873. var canvasRectTransform = canvasObject.GetComponent<RectTransform>();
  874. UIUtils.ResetRectTransform(canvasRectTransform);
  875. if (!externalCanvas)
  876. {
  877. canvas.renderMode = RenderMode.ScreenSpaceOverlay;
  878. canvas.pixelPerfect = pixelPerfect;
  879. canvas.sortingOrder = sortingOrder;
  880. canvasScaler = canvasObject.AddComponent<CanvasScaler>();
  881. if (autoScale)
  882. {
  883. canvasScaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
  884. }
  885. else
  886. {
  887. canvasScaler.scaleFactor = scaleFactor;
  888. }
  889. }
  890. }
  891. private void ConfigureLabels()
  892. {
  893. anchorsCount = System.Enum.GetNames(typeof(LabelAnchor)).Length;
  894. labels = new DrawableLabel[anchorsCount];
  895. for (var i = 0; i < anchorsCount; i++)
  896. {
  897. labels[i] = new DrawableLabel(canvas.gameObject, (LabelAnchor)i,
  898. new LabelEffect(background, backgroundColor, backgroundPadding),
  899. new LabelEffect(shadow, shadowColor, shadowDistance),
  900. new LabelEffect(outline, outlineColor, outlineDistance),
  901. labelsFont, fontSize, lineSpacing, paddingOffset);
  902. }
  903. }
  904. private void DisposeInternal()
  905. {
  906. Destroy(this);
  907. if (Instance == this) Instance = null;
  908. }
  909. private void ProcessHotKey()
  910. {
  911. if (hotKey == KeyCode.None) return;
  912. var hotKeyDown = AFPSInputProxy.GetHotKeyDown(hotKey);
  913. if (hotKeyDown)
  914. {
  915. var modifiersPressed = true;
  916. if (hotKeyCtrl)
  917. {
  918. modifiersPressed &= AFPSInputProxy.GetControlKey();
  919. }
  920. if (hotKeyAlt)
  921. {
  922. modifiersPressed &= AFPSInputProxy.GetAltKey();
  923. }
  924. if (hotKeyShift)
  925. {
  926. modifiersPressed &= AFPSInputProxy.GetShiftKey();
  927. }
  928. if (modifiersPressed)
  929. {
  930. SwitchCounter();
  931. }
  932. }
  933. }
  934. private bool CircleGestureMade()
  935. {
  936. var pointsCount = gesturePoints.Count;
  937. if (Input.GetMouseButton(0))
  938. {
  939. Vector2 mousePosition = Input.mousePosition;
  940. if (pointsCount == 0 || (mousePosition - gesturePoints[pointsCount - 1]).magnitude > 10)
  941. {
  942. gesturePoints.Add(mousePosition);
  943. pointsCount++;
  944. }
  945. }
  946. else if (Input.GetMouseButtonUp(0))
  947. {
  948. pointsCount = 0;
  949. gestureCount = 0;
  950. gesturePoints.Clear();
  951. }
  952. if (pointsCount < 10)
  953. return false;
  954. float finalDeltaLength = 0;
  955. var finalDelta = Vector2.zero;
  956. var previousPointsDelta = Vector2.zero;
  957. for (var i = 0; i < pointsCount - 2; i++)
  958. {
  959. var pointsDelta = gesturePoints[i + 1] - gesturePoints[i];
  960. finalDelta += pointsDelta;
  961. var pointsDeltaLength = pointsDelta.magnitude;
  962. finalDeltaLength += pointsDeltaLength;
  963. var dotProduct = Vector2.Dot(pointsDelta, previousPointsDelta);
  964. if (dotProduct < 0f)
  965. {
  966. gesturePoints.Clear();
  967. gestureCount = 0;
  968. return false;
  969. }
  970. previousPointsDelta = pointsDelta;
  971. }
  972. var result = false;
  973. var gestureBase = (Screen.width + Screen.height) / 4;
  974. if (finalDeltaLength > gestureBase && finalDelta.magnitude < gestureBase / 2f)
  975. {
  976. gesturePoints.Clear();
  977. gestureCount++;
  978. if (gestureCount >= 2)
  979. {
  980. gestureCount = 0;
  981. result = true;
  982. }
  983. }
  984. return result;
  985. }
  986. private void SwitchCounter()
  987. {
  988. if (operationMode == OperationMode.Disabled)
  989. {
  990. OperationMode = OperationMode.Normal;
  991. }
  992. else if (operationMode == OperationMode.Normal)
  993. {
  994. OperationMode = OperationMode.Disabled;
  995. }
  996. }
  997. private void ActivateCounters()
  998. {
  999. fpsCounter.Activate();
  1000. memoryCounter.Activate();
  1001. deviceInfoCounter.Activate();
  1002. if (fpsCounter.Enabled || memoryCounter.Enabled || deviceInfoCounter.Enabled)
  1003. {
  1004. UpdateTexts();
  1005. }
  1006. }
  1007. private void DeactivateCounters()
  1008. {
  1009. if (Instance == null) return;
  1010. fpsCounter.Deactivate();
  1011. memoryCounter.Deactivate();
  1012. deviceInfoCounter.Deactivate();
  1013. }
  1014. private void RefreshForcedFrameRate()
  1015. {
  1016. RefreshForcedFrameRate(false);
  1017. }
  1018. private void RefreshForcedFrameRate(bool disabling)
  1019. {
  1020. if (forceFrameRate && !disabling)
  1021. {
  1022. if (cachedVSync == -1)
  1023. {
  1024. cachedVSync = QualitySettings.vSyncCount;
  1025. cachedFrameRate = Application.targetFrameRate;
  1026. QualitySettings.vSyncCount = 0;
  1027. }
  1028. Application.targetFrameRate = forcedFrameRate;
  1029. }
  1030. else
  1031. {
  1032. if (cachedVSync != -1)
  1033. {
  1034. QualitySettings.vSyncCount = cachedVSync;
  1035. Application.targetFrameRate = cachedFrameRate;
  1036. cachedVSync = -1;
  1037. }
  1038. }
  1039. }
  1040. // ----------------------------------------------------------------------------
  1041. // editor-only code
  1042. // ----------------------------------------------------------------------------
  1043. #region editor-only code
  1044. #if UNITY_EDITOR
  1045. private const string GameObjectMenuGroup = "GameObject/Create Other/";
  1046. private const string GameObjectMenuPath = GameObjectMenuGroup + MenuPath;
  1047. [HideInInspector]
  1048. [SerializeField]
  1049. private bool lookAndFeelFoldout;
  1050. [HideInInspector]
  1051. [SerializeField]
  1052. private bool advancedFoldout;
  1053. // ReSharper disable once UnusedMember.Local
  1054. [UnityEditor.MenuItem(GameObjectMenuPath, false)]
  1055. private static void AddToSceneInEditor()
  1056. {
  1057. var counter = FindObjectOfType<AFPSCounter>();
  1058. if (counter != null)
  1059. {
  1060. if (counter.IsPlacedCorrectly())
  1061. {
  1062. if (UnityEditor.EditorUtility.DisplayDialog("Remove " + ComponentName + "?",
  1063. ComponentName + " already exists in scene and placed correctly. Do you wish to remove it?", "Yes", "No"))
  1064. {
  1065. DestroyInEditorImmediate(counter);
  1066. }
  1067. }
  1068. else
  1069. {
  1070. if (counter.MayBePlacedHere())
  1071. {
  1072. var dialogResult = UnityEditor.EditorUtility.DisplayDialogComplex("Fix existing Game Object to work with " +
  1073. ComponentName + "?",
  1074. ComponentName + " already exists in scene and placed on Game Object \"" +
  1075. counter.name + "\" with minor errors.\nDo you wish to let plugin fix and use this Game Object further? " +
  1076. "Press Delete to remove plugin from scene at all.", "Fix", "Delete", "Cancel");
  1077. switch (dialogResult)
  1078. {
  1079. case 0:
  1080. counter.FixCurrentGameObject();
  1081. break;
  1082. case 1:
  1083. DestroyInEditorImmediate(counter);
  1084. break;
  1085. }
  1086. }
  1087. else
  1088. {
  1089. var dialogResult = UnityEditor.EditorUtility.DisplayDialogComplex("Move existing " + ComponentName +
  1090. " to own Game Object?",
  1091. "Looks like " + ComponentName + " already exists in scene and placed incorrectly on Game Object \"" +
  1092. counter.name + "\".\nDo you wish to let plugin move itself onto separate correct Game Object \"" +
  1093. ComponentName + "\"? Press Delete to remove plugin from scene at all.", "Move", "Delete", "Cancel");
  1094. switch (dialogResult)
  1095. {
  1096. case 0:
  1097. var newCounter = CreateInScene(false);
  1098. UnityEditor.EditorUtility.CopySerialized(counter, newCounter);
  1099. DestroyInEditorImmediate(counter);
  1100. break;
  1101. case 1:
  1102. DestroyInEditorImmediate(counter);
  1103. break;
  1104. }
  1105. }
  1106. }
  1107. }
  1108. else
  1109. {
  1110. var newCounter = CreateInScene();
  1111. var fonts = UnityEditor.AssetDatabase.FindAssets("t:Font, VeraMono");
  1112. if (fonts != null && fonts.Length != 0)
  1113. {
  1114. string veraMonoPath = null;
  1115. foreach (var font in fonts)
  1116. {
  1117. var fontPath = UnityEditor.AssetDatabase.GUIDToAssetPath(font);
  1118. var fontFileName = System.IO.Path.GetFileName(fontPath);
  1119. if (fontFileName == "VeraMono.ttf")
  1120. {
  1121. veraMonoPath = fontPath;
  1122. break;
  1123. }
  1124. }
  1125. if (!string.IsNullOrEmpty(veraMonoPath))
  1126. {
  1127. var font = (Font) UnityEditor.AssetDatabase.LoadAssetAtPath(veraMonoPath, typeof(Font));
  1128. var so = new UnityEditor.SerializedObject(newCounter);
  1129. so.FindProperty("labelsFont").objectReferenceValue = font;
  1130. so.ApplyModifiedProperties();
  1131. UnityEditor.EditorUtility.SetDirty(newCounter);
  1132. }
  1133. }
  1134. UnityEditor.EditorUtility.DisplayDialog("Advanced FPS Counter added!", "Advanced FPS Counter successfully added to the object \"" + ComponentName + "\"", "OK");
  1135. }
  1136. }
  1137. private bool MayBePlacedHere()
  1138. {
  1139. return transform.childCount == 0;
  1140. }
  1141. private bool IsPlacedCorrectly()
  1142. {
  1143. return transform.localRotation == Quaternion.identity &&
  1144. transform.localScale == Vector3.one &&
  1145. gameObject.isStatic == false &&
  1146. MayBePlacedHere();
  1147. }
  1148. private void FixCurrentGameObject()
  1149. {
  1150. gameObject.name = ComponentName;
  1151. transform.position = Vector3.zero;
  1152. transform.rotation = Quaternion.identity;
  1153. transform.localScale = Vector3.one;
  1154. tag = "Untagged";
  1155. gameObject.layer = LayerMask.NameToLayer("UI");
  1156. gameObject.isStatic = false;
  1157. }
  1158. private static void DestroyInEditorImmediate(AFPSCounter component)
  1159. {
  1160. if (component.transform.childCount == 0 && component.GetComponentsInChildren<Component>(true).Length <= 2)
  1161. {
  1162. UnityEditor.Undo.DestroyObjectImmediate(component.gameObject);
  1163. }
  1164. else
  1165. {
  1166. UnityEditor.Undo.DestroyObjectImmediate(component);
  1167. }
  1168. }
  1169. #endif
  1170. #endregion
  1171. }
  1172. }