SimpleApplication.cs 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. using System;
  2. using System.Threading;
  3. using System.Threading.Tasks;
  4. using System.Runtime.CompilerServices;
  5. using System.Runtime.InteropServices;
  6. using Urho.Actions;
  7. using Urho.Gui;
  8. using System.IO;
  9. using System.Linq;
  10. using System.Diagnostics;
  11. using Urho.Shapes;
  12. namespace Urho
  13. {
  14. public class SimpleApplication : Application
  15. {
  16. [Preserve]
  17. public SimpleApplication(ApplicationOptions options) : base(options) {}
  18. public static Task<SimpleApplication> RunAsync(int width = 600, int height = 500)
  19. {
  20. #if NET45
  21. return RunAsync(new ApplicationOptions(assetsFolder: "Data") { Width = width, Height = height, ResizableWindow = true });
  22. #endif
  23. return RunAsync(new ApplicationOptions(assetsFolder: null));
  24. }
  25. [Obsolete("RunAsync is Obsolete. Use Show() instead.")]
  26. public static Task<SimpleApplication> RunAsync(ApplicationOptions options)
  27. {
  28. #if NET45
  29. var dataDir = options.ResourcePaths?.FirstOrDefault();
  30. Environment.CurrentDirectory = Path.GetDirectoryName(typeof(SimpleApplication).Assembly.Location);
  31. if (!File.Exists("CoreData.pak")) {
  32. using (Stream input = typeof(SimpleApplication).Assembly.GetManifestResourceStream("Urho.CoreData.pak"))
  33. using (Stream output = File.Create(Path.Combine("CoreData.pak")))
  34. input.CopyTo(output);
  35. }
  36. if (!string.IsNullOrEmpty(dataDir))
  37. Directory.CreateDirectory("Data");
  38. #endif
  39. #if !__IOS__ && !WINDOWS_UWP
  40. var taskSource = new TaskCompletionSource<SimpleApplication>();
  41. Action callback = null;
  42. callback = () => {
  43. Started -= callback;
  44. taskSource.TrySetResult(Current as SimpleApplication);
  45. };
  46. Started += callback;
  47. Task.Factory.StartNew(() => new SimpleApplication(options).Run(),
  48. CancellationToken.None,
  49. TaskCreationOptions.DenyChildAttach,
  50. SynchronizationContext.Current == null ? TaskScheduler.Default : TaskScheduler.FromCurrentSynchronizationContext());
  51. return taskSource.Task;
  52. #else
  53. var app = new SimpleApplication(options);
  54. app.Run(); //for iOS and UWP it's not blocking
  55. return Task.FromResult(app);
  56. #endif
  57. }
  58. #if NET45
  59. [DllImport("user32")]
  60. static extern bool SetWindowPos(IntPtr hwnd, IntPtr hwnd2, int x, int y, int cx, int cy, int flags);
  61. #endif
  62. public static SimpleApplication Show(ApplicationOptions opts = null)
  63. {
  64. #if !NET45
  65. throw new NotSupportedException();
  66. #else
  67. if (SynchronizationContext.Current == null)
  68. throw new NotSupportedException("SynchronizationContext.Current should not be null.");
  69. //Close active UrhoSharp instances=windows if any
  70. StopCurrent().Wait();
  71. opts = opts ?? new ApplicationOptions();
  72. opts.ResizableWindow = true;
  73. opts.DelayedStart = true;
  74. if (opts.Width < 1)
  75. opts.Width = 900;
  76. if (opts.Height < 1)
  77. opts.Height = 800;
  78. var app = new SimpleApplication(opts);
  79. StartGameCycle(app);
  80. if (Platform == Platforms.Windows)
  81. {
  82. var handle = Process.GetCurrentProcess().MainWindowHandle;
  83. SetWindowPos(handle, new IntPtr(-1), 0, 0, 0, 0, 0x1 | 0x2 /*SWP_NOMOVE | SWP_NOSIZE */);
  84. }
  85. //on macOS it does [window setLevel:CGWindowLevelForKey(kCGMaximumWindowLevelKey)];
  86. return app;
  87. #endif
  88. }
  89. static async void StartGameCycle(SimpleApplication app)
  90. {
  91. app.Run();
  92. const int FpsLimit = 60;
  93. while (app.IsActive)
  94. {
  95. var elapsed = app.Engine.RunFrame();
  96. var targetMax = 1000000L / FpsLimit;
  97. if (elapsed >= targetMax)
  98. await Task.Yield();
  99. else
  100. {
  101. var ts = TimeSpan.FromMilliseconds((targetMax - elapsed) / 1000d);
  102. await Task.Delay(ts);
  103. }
  104. }
  105. }
  106. public Node CameraNode { get; private set; }
  107. public Camera Camera { get; private set; }
  108. public Scene Scene { get; private set; }
  109. public Octree Octree { get; private set; }
  110. public Zone Zone { get; private set; }
  111. public Node RootNode { get; private set; }
  112. public Node LightNode { get; private set; }
  113. public Light Light { get; private set; }
  114. public Viewport Viewport { get; private set; }
  115. public bool MoveCamera { get; set; } = true;
  116. public float Yaw { get; set; }
  117. public float Pitch { get; set; }
  118. protected override void Start()
  119. {
  120. // 3D scene with Octree
  121. Scene = new Scene(Context);
  122. Octree = Scene.CreateComponent<Octree>();
  123. Zone = Scene.CreateComponent<Zone>();
  124. Zone.AmbientColor = new Color(0.6f, 0.6f, 0.6f);
  125. RootNode = Scene.CreateChild("RootNode");
  126. RootNode.Position = new Vector3(x: 0, y: -2, z: 8);
  127. // Camera
  128. CameraNode = Scene.CreateChild(name: "Camera");
  129. CameraNode.Rotation = new Quaternion(Pitch = 0, 0, 0);
  130. Camera = CameraNode.CreateComponent<Camera>();
  131. // Light
  132. LightNode = CameraNode.CreateChild();
  133. LightNode.Position = new Vector3(-5, 10, 0);
  134. Light = LightNode.CreateComponent<Light>();
  135. Light.Range = 100;
  136. Light.Brightness = 0.5f;
  137. Light.LightType = LightType.Point;
  138. // Viewport
  139. Viewport = new Viewport(Context, Scene, Camera, null);
  140. Renderer.SetViewport(0, Viewport);
  141. Viewport.SetClearColor(new Color(0.88f, 0.88f, 0.88f));
  142. if (Platform == Platforms.Android || Platform == Platforms.iOS)
  143. {
  144. Viewport.RenderPath.Append(CoreAssets.PostProcess.FXAA2);
  145. }
  146. else if (Platform == Platforms.Windows || Platform == Platforms.MacOSX)
  147. {
  148. ResourceCache.AutoReloadResources = true;
  149. Renderer.HDRRendering = true;
  150. Viewport.RenderPath.Append(CoreAssets.PostProcess.BloomHDR);
  151. Viewport.RenderPath.Append(CoreAssets.PostProcess.FXAA3);
  152. }
  153. #if NET45
  154. Input.SubscribeToMouseWheel(args => CameraNode.Translate(-Vector3.UnitZ * 1f * args.Wheel * -1));
  155. Input.SetMouseVisible(true, true);
  156. Input.SubscribeToKeyDown(args => {
  157. if (args.Key == Key.Esc)
  158. {
  159. Exit();
  160. }
  161. });
  162. #endif
  163. }
  164. public float MoveSpeed { get; set; } = 10f;
  165. protected override void OnUpdate(float timeStep)
  166. {
  167. if (MoveCamera)
  168. {
  169. if (Input.GetMouseButtonDown(MouseButton.Left))
  170. MoveCameraMouse(timeStep);
  171. else
  172. MoveCameraTouches(timeStep);
  173. if (Input.GetKeyDown(Key.W)) CameraNode.Translate(Vector3.UnitZ * MoveSpeed * timeStep);
  174. if (Input.GetKeyDown(Key.S)) CameraNode.Translate(-Vector3.UnitZ * MoveSpeed * timeStep);
  175. if (Input.GetKeyDown(Key.A)) CameraNode.Translate(-Vector3.UnitX * MoveSpeed * timeStep);
  176. if (Input.GetKeyDown(Key.D)) CameraNode.Translate(Vector3.UnitX * MoveSpeed * timeStep);
  177. }
  178. base.OnUpdate(timeStep);
  179. }
  180. protected void MoveCameraMouse(float timeStep)
  181. {
  182. const float mouseSensitivity = .05f;
  183. if (UI.FocusElement != null)
  184. return;
  185. var mouseMove = Input.MouseMove;
  186. Yaw += mouseSensitivity * mouseMove.X;
  187. Pitch += mouseSensitivity * mouseMove.Y;
  188. Pitch = MathHelper.Clamp(Pitch, -90, 90);
  189. CameraNode.Rotation = new Quaternion(Pitch, Yaw, 0);
  190. }
  191. protected void MoveCameraTouches(float timeStep)
  192. {
  193. var input = Input;
  194. for (uint i = 0, num = input.NumTouches; i < num; ++i)
  195. {
  196. TouchState state = input.GetTouch(i);
  197. if (state.TouchedElement != null)
  198. continue;
  199. if (state.Delta.X != 0 || state.Delta.Y != 0)
  200. {
  201. var camera = CameraNode.GetComponent<Camera>();
  202. if (camera == null)
  203. return;
  204. var graphics = Graphics;
  205. Yaw += 2 * camera.Fov / graphics.Height * state.Delta.X;
  206. Pitch += 2 * camera.Fov / graphics.Height * state.Delta.Y;
  207. CameraNode.Rotation = new Quaternion(Pitch, Yaw, 0);
  208. }
  209. else
  210. {
  211. var cursor = UI.Cursor;
  212. if (cursor != null && cursor.Visible)
  213. cursor.Position = state.Position;
  214. }
  215. }
  216. }
  217. }
  218. public class Bar : Component
  219. {
  220. Node barNode;
  221. Node textNode;
  222. Color color;
  223. string name;
  224. public float Value
  225. {
  226. get { return barNode.Scale.Y; }
  227. set { barNode.RunActionsAsync(new EaseBackOut(new ScaleTo(3f, 1, value, 1))); }
  228. }
  229. public Bar(string name, Color color)
  230. {
  231. this.name = name;
  232. this.color = color;
  233. ReceiveSceneUpdates = true;
  234. }
  235. public override void OnAttachedToNode(Node node)
  236. {
  237. barNode = node.CreateChild();
  238. barNode.Scale = new Vector3(1, 0, 1);
  239. var box = barNode.CreateComponent<Box>();
  240. box.Color = color;
  241. textNode = node.CreateChild();
  242. textNode.Position = new Vector3(0, 3, 0);
  243. var text3D = textNode.CreateComponent<Text3D>();
  244. text3D.SetFont(CoreAssets.Fonts.AnonymousPro, 60);
  245. text3D.TextEffect = TextEffect.Stroke;
  246. text3D.Text = name;
  247. base.OnAttachedToNode(node);
  248. }
  249. protected override void OnUpdate(float timeStep)
  250. {
  251. var pos = barNode.Position;
  252. var scale = barNode.Scale;
  253. barNode.Position = new Vector3(pos.X, scale.Y / 2f, pos.Z);
  254. textNode.Position = new Vector3(-0.5f, scale.Y + 0.5f, 0);
  255. }
  256. }
  257. }