1
0

run.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. // Copyright 2014 Hajime Hoshi
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package ebiten
  15. import (
  16. "sync/atomic"
  17. "github.com/hajimehoshi/ebiten/v2/internal/clock"
  18. "github.com/hajimehoshi/ebiten/v2/internal/driver"
  19. )
  20. // Game defines necessary functions for a game.
  21. type Game interface {
  22. // Update updates a game by one tick. The given argument represents a screen image.
  23. //
  24. // Update updates only the game logic and Draw draws the screen.
  25. //
  26. // In the first frame, it is ensured that Update is called at least once before Draw. You can use Update
  27. // to initialize the game state.
  28. //
  29. // After the first frame, Update might not be called or might be called once
  30. // or more for one frame. The frequency is determined by the current TPS (tick-per-second).
  31. Update() error
  32. // Draw draws the game screen by one frame.
  33. //
  34. // The give argument represents a screen image. The updated content is adopted as the game screen.
  35. Draw(screen *Image)
  36. // Layout accepts a native outside size in device-independent pixels and returns the game's logical screen
  37. // size.
  38. //
  39. // On desktops, the outside is a window or a monitor (fullscreen mode). On browsers, the outside is a body
  40. // element. On mobiles, the outside is the view's size.
  41. //
  42. // Even though the outside size and the screen size differ, the rendering scale is automatically adjusted to
  43. // fit with the outside.
  44. //
  45. // Layout is called almost every frame.
  46. //
  47. // It is ensured that Layout is invoked before Update is called in the first frame.
  48. //
  49. // If Layout returns non-positive numbers, the caller can panic.
  50. //
  51. // You can return a fixed screen size if you don't care, or you can also return a calculated screen size
  52. // adjusted with the given outside size.
  53. Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int)
  54. }
  55. // DefaultTPS represents a default ticks per second, that represents how many times game updating happens in a second.
  56. const DefaultTPS = 60
  57. // CurrentFPS returns the current number of FPS (frames per second), that represents
  58. // how many swapping buffer happens per second.
  59. //
  60. // On some environments, CurrentFPS doesn't return a reliable value since vsync doesn't work well there.
  61. // If you want to measure the application's speed, Use CurrentTPS.
  62. //
  63. // CurrentFPS is concurrent-safe.
  64. func CurrentFPS() float64 {
  65. return clock.CurrentFPS()
  66. }
  67. var (
  68. isScreenClearedEveryFrame = int32(1)
  69. isRunGameEnded_ = int32(0)
  70. currentMaxTPS = int32(DefaultTPS)
  71. )
  72. // SetScreenClearedEveryFrame enables or disables the clearing of the screen at the beginning of each frame.
  73. // The default value is true and the screen is cleared each frame by default.
  74. //
  75. // SetScreenClearedEveryFrame is concurrent-safe.
  76. func SetScreenClearedEveryFrame(cleared bool) {
  77. v := int32(0)
  78. if cleared {
  79. v = 1
  80. }
  81. atomic.StoreInt32(&isScreenClearedEveryFrame, v)
  82. theUIContext.setScreenClearedEveryFrame(cleared)
  83. }
  84. // IsScreenClearedEveryFrame returns true if the frame isn't cleared at the beginning.
  85. //
  86. // IsScreenClearedEveryFrame is concurrent-safe.
  87. func IsScreenClearedEveryFrame() bool {
  88. return atomic.LoadInt32(&isScreenClearedEveryFrame) != 0
  89. }
  90. type imageDumperGame struct {
  91. game Game
  92. d *imageDumper
  93. err error
  94. }
  95. func (i *imageDumperGame) Update() error {
  96. if i.err != nil {
  97. return i.err
  98. }
  99. if i.d == nil {
  100. i.d = &imageDumper{g: i.game}
  101. }
  102. return i.d.update()
  103. }
  104. func (i *imageDumperGame) Draw(screen *Image) {
  105. if i.err != nil {
  106. return
  107. }
  108. i.game.Draw(screen)
  109. i.err = i.d.dump(screen)
  110. }
  111. func (i *imageDumperGame) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) {
  112. return i.game.Layout(outsideWidth, outsideHeight)
  113. }
  114. // RunGame starts the main loop and runs the game.
  115. // game's Update function is called every tick to update the game logic.
  116. // game's Draw function is called every frame to draw the screen.
  117. // game's Layout function is called when necessary, and you can specify the logical screen size by the function.
  118. //
  119. // On browsers, it is strongly recommended to use iframe if you embed an Ebiten application in your website.
  120. //
  121. // RunGame must be called on the main thread.
  122. // Note that Ebiten bounds the main goroutine to the main OS thread by runtime.LockOSThread.
  123. //
  124. // Ebiten tries to call game's Update function 60 times a second by default. In other words,
  125. // TPS (ticks per second) is 60 by default.
  126. // This is not related to framerate (display's refresh rate).
  127. //
  128. // RunGame returns error when 1) error happens in the underlying graphics driver, 2) audio error happens or
  129. // 3) f returns error. In the case of 3), RunGame returns the same error.
  130. //
  131. // The size unit is device-independent pixel.
  132. //
  133. // Don't call RunGame twice or more in one process.
  134. func RunGame(game Game) error {
  135. defer atomic.StoreInt32(&isRunGameEnded_, 1)
  136. initializeWindowPositionIfNeeded(WindowSize())
  137. theUIContext.set(&imageDumperGame{
  138. game: game,
  139. })
  140. if err := uiDriver().Run(theUIContext); err != nil {
  141. if err == driver.RegularTermination {
  142. return nil
  143. }
  144. return err
  145. }
  146. return nil
  147. }
  148. func isRunGameEnded() bool {
  149. return atomic.LoadInt32(&isRunGameEnded_) != 0
  150. }
  151. // ScreenSizeInFullscreen returns the size in device-independent pixels when the game is fullscreen.
  152. // The adopted monitor is the 'current' monitor which the window belongs to.
  153. // The returned value can be given to Run or SetSize function if the perfectly fit fullscreen is needed.
  154. //
  155. // On browsers, ScreenSizeInFullscreen returns the 'window' (global object) size, not 'screen' size since an Ebiten
  156. // game should not know the outside of the window object.
  157. //
  158. // On mobiles, ScreenSizeInFullscreen returns (0, 0) so far.
  159. //
  160. // ScreenSizeInFullscreen's use cases are limited. If you are making a fullscreen application, you can use RunGame and
  161. // the Game interface's Layout function instead. If you are making a not-fullscreen application but the application's
  162. // behavior depends on the monitor size, ScreenSizeInFullscreen is useful.
  163. //
  164. // ScreenSizeInFullscreen must be called on the main thread before ebiten.Run, and is concurrent-safe after
  165. // ebiten.Run.
  166. func ScreenSizeInFullscreen() (int, int) {
  167. return uiDriver().ScreenSizeInFullscreen()
  168. }
  169. // CursorMode returns the current cursor mode.
  170. //
  171. // CursorMode returns CursorModeHidden on mobiles.
  172. //
  173. // CursorMode is concurrent-safe.
  174. func CursorMode() CursorModeType {
  175. return uiDriver().CursorMode()
  176. }
  177. // SetCursorMode sets the render and capture mode of the mouse cursor.
  178. // CursorModeVisible sets the cursor to always be visible.
  179. // CursorModeHidden hides the system cursor when over the window.
  180. // CursorModeCaptured hides the system cursor and locks it to the window.
  181. //
  182. // CursorModeCaptured also works on browsers.
  183. // When the user exits the captured mode not by SetCursorMode but by the UI (e.g., pressing ESC),
  184. // the previous cursor mode is set automatically.
  185. //
  186. // SetCursorMode does nothing on mobiles.
  187. //
  188. // SetCursorMode is concurrent-safe.
  189. func SetCursorMode(mode CursorModeType) {
  190. uiDriver().SetCursorMode(mode)
  191. }
  192. // CursorShape returns the current cursor shape.
  193. //
  194. // CursorShape returns CursorShapeDefault on mobiles.
  195. //
  196. // CursorShape is concurrent-safe.
  197. func CursorShape() CursorShapeType {
  198. return uiDriver().CursorShape()
  199. }
  200. // SetCursorShape sets the cursor shape.
  201. //
  202. // SetCursorShape is concurrent-safe.
  203. func SetCursorShape(shape CursorShapeType) {
  204. uiDriver().SetCursorShape(shape)
  205. }
  206. // IsFullscreen reports whether the current mode is fullscreen or not.
  207. //
  208. // IsFullscreen always returns false on mobiles.
  209. //
  210. // IsFullscreen is concurrent-safe.
  211. func IsFullscreen() bool {
  212. return uiDriver().IsFullscreen()
  213. }
  214. // SetFullscreen changes the current mode to fullscreen or not on desktops and browsers.
  215. //
  216. // In fullscreen mode, the game screen is automatically enlarged
  217. // to fit with the monitor. The current scale value is ignored.
  218. //
  219. // On desktops, Ebiten uses 'windowed' fullscreen mode, which doesn't change
  220. // your monitor's resolution.
  221. //
  222. // On browsers, triggering fullscreen requires a user gesture otherwise SetFullscreen does nothing but leave an error message in console.
  223. // This behaviour varies across browser implementations, your mileage may vary.
  224. //
  225. // SetFullscreen does nothing on mobiles.
  226. //
  227. // SetFullscreen does nothing on macOS when the window is fullscreened natively by the macOS desktop
  228. // instead of SetFullscreen(true).
  229. //
  230. // SetFullscreen is concurrent-safe.
  231. func SetFullscreen(fullscreen bool) {
  232. uiDriver().SetFullscreen(fullscreen)
  233. }
  234. // IsFocused returns a boolean value indicating whether
  235. // the game is in focus or in the foreground.
  236. //
  237. // IsFocused will only return true if IsRunnableOnUnfocused is false.
  238. //
  239. // IsFocused is concurrent-safe.
  240. func IsFocused() bool {
  241. return uiDriver().IsFocused()
  242. }
  243. // IsRunnableOnUnfocused returns a boolean value indicating whether
  244. // the game runs even in background.
  245. //
  246. // IsRunnableOnUnfocused is concurrent-safe.
  247. func IsRunnableOnUnfocused() bool {
  248. return uiDriver().IsRunnableOnUnfocused()
  249. }
  250. // SetRunnableOnUnfocused sets the state if the game runs even in background.
  251. //
  252. // If the given value is true, the game runs even in background e.g. when losing focus.
  253. // The initial state is true.
  254. //
  255. // Known issue: On browsers, even if the state is on, the game doesn't run in background tabs.
  256. // This is because browsers throttles background tabs not to often update.
  257. //
  258. // SetRunnableOnUnfocused does nothing on mobiles so far.
  259. //
  260. // SetRunnableOnUnfocused is concurrent-safe.
  261. func SetRunnableOnUnfocused(runnableOnUnfocused bool) {
  262. uiDriver().SetRunnableOnUnfocused(runnableOnUnfocused)
  263. }
  264. // DeviceScaleFactor returns a device scale factor value of the current monitor which the window belongs to.
  265. //
  266. // DeviceScaleFactor returns a meaningful value on high-DPI display environment,
  267. // otherwise DeviceScaleFactor returns 1.
  268. //
  269. // DeviceScaleFactor might panic on init function on some devices like Android.
  270. // Then, it is not recommended to call DeviceScaleFactor from init functions.
  271. //
  272. // DeviceScaleFactor must be called on the main thread before the main loop, and is concurrent-safe after the main
  273. // loop.
  274. //
  275. // DeviceScaleFactor is concurrent-safe.
  276. //
  277. // BUG: DeviceScaleFactor value is not affected by SetWindowPosition before RunGame (#1575).
  278. func DeviceScaleFactor() float64 {
  279. return uiDriver().DeviceScaleFactor()
  280. }
  281. // IsVsyncEnabled returns a boolean value indicating whether
  282. // the game uses the display's vsync.
  283. //
  284. // Deprecated: as of v2.2. Use FPSMode instead.
  285. func IsVsyncEnabled() bool {
  286. return uiDriver().FPSMode() == driver.FPSModeVsyncOn
  287. }
  288. // SetVsyncEnabled sets a boolean value indicating whether
  289. // the game uses the display's vsync.
  290. //
  291. // Deprecated: as of v2.2. Use SetFPSMode instead.
  292. func SetVsyncEnabled(enabled bool) {
  293. if enabled {
  294. uiDriver().SetFPSMode(driver.FPSModeVsyncOn)
  295. } else {
  296. uiDriver().SetFPSMode(driver.FPSModeVsyncOffMaximum)
  297. }
  298. }
  299. // FPSModeType is a type of FPS modes.
  300. type FPSModeType = driver.FPSMode
  301. const (
  302. // FPSModeVsyncOn indicates that the game tries to sync the display's refresh rate.
  303. // FPSModeVsyncOn is the default mode.
  304. FPSModeVsyncOn FPSModeType = driver.FPSModeVsyncOn
  305. // FPSModeVsyncOffMaximum indicates that the game doesn't sync with vsycn, and
  306. // the game is updated whenever possible.
  307. //
  308. // Be careful that FPSModeVsyncOffMaximum might consume a lot of battery power.
  309. //
  310. // In FPSModeVsyncOffMaximum, the game's Draw is called almost without sleeping.
  311. // The game's Update is called based on the specified TPS.
  312. FPSModeVsyncOffMaximum FPSModeType = driver.FPSModeVsyncOffMaximum
  313. // FPSModeVsyncOffMinimum indicates that the game doesn't sync with vsycn, and
  314. // the game is updated only when necessary.
  315. //
  316. // FPSModeVsyncOffMinimum is useful for relatively static applications to save battery power.
  317. //
  318. // In FPSModeVsyncOffMinimum, the game's Update and Draw are called only when
  319. // 1) new inputting is detected, or 2) ScheduleFrame is called.
  320. // In FPSModeVsyncOffMinimum, TPS is SyncWithFPS no matter what TPS is specified at SetMaxTPS.
  321. FPSModeVsyncOffMinimum FPSModeType = driver.FPSModeVsyncOffMinimum
  322. )
  323. // FPSMode returns the current FPS mode.
  324. //
  325. // FPSMode is concurrent-safe.
  326. func FPSMode() FPSModeType {
  327. return uiDriver().FPSMode()
  328. }
  329. // SetFPSMode sets the FPS mode.
  330. // The default FPS mode is FPSModeVsycnOn.
  331. //
  332. // SetFPSMode is concurrent-safe.
  333. func SetFPSMode(mode FPSModeType) {
  334. uiDriver().SetFPSMode(mode)
  335. }
  336. // ScheduleFrame schedules a next frame when the current FPS mode is FPSModeVsyncOffMinimum.
  337. //
  338. // ScheduleFrame is concurrent-safe.
  339. func ScheduleFrame() {
  340. uiDriver().ScheduleFrame()
  341. }
  342. // MaxTPS returns the current maximum TPS.
  343. //
  344. // MaxTPS is concurrent-safe.
  345. func MaxTPS() int {
  346. if FPSMode() == FPSModeVsyncOffMinimum {
  347. return SyncWithFPS
  348. }
  349. return int(atomic.LoadInt32(&currentMaxTPS))
  350. }
  351. // CurrentTPS returns the current TPS (ticks per second),
  352. // that represents how many update function is called in a second.
  353. //
  354. // CurrentTPS is concurrent-safe.
  355. func CurrentTPS() float64 {
  356. return clock.CurrentTPS()
  357. }
  358. // SyncWithFPS is a special TPS value that means TPS syncs with FPS.
  359. const SyncWithFPS = clock.SyncWithFPS
  360. // UncappedTPS is a special TPS value that means TPS syncs with FPS.
  361. //
  362. // Deprecated: as of v2.2. Use SyncWithFPS instead.
  363. const UncappedTPS = SyncWithFPS
  364. // SetMaxTPS sets the maximum TPS (ticks per second),
  365. // that represents how many updating function is called per second.
  366. // The initial value is 60.
  367. //
  368. // If tps is SyncWithFPS, TPS is uncapped and the game is updated per frame.
  369. // If tps is negative but not SyncWithFPS, SetMaxTPS panics.
  370. //
  371. // SetMaxTPS is concurrent-safe.
  372. func SetMaxTPS(tps int) {
  373. if tps < 0 && tps != SyncWithFPS {
  374. panic("ebiten: tps must be >= 0 or SyncWithFPS")
  375. }
  376. atomic.StoreInt32(&currentMaxTPS, int32(tps))
  377. }
  378. // IsScreenTransparent reports whether the window is transparent.
  379. //
  380. // IsScreenTransparent is concurrent-safe.
  381. func IsScreenTransparent() bool {
  382. return uiDriver().IsScreenTransparent()
  383. }
  384. // SetScreenTransparent sets the state if the window is transparent.
  385. //
  386. // SetScreenTransparent panics if SetScreenTransparent is called after the main loop.
  387. //
  388. // SetScreenTransparent does nothing on mobiles.
  389. //
  390. // SetScreenTransparent is concurrent-safe.
  391. func SetScreenTransparent(transparent bool) {
  392. uiDriver().SetScreenTransparent(transparent)
  393. }
  394. // SetInitFocused sets whether the application is focused on show.
  395. // The default value is true, i.e., the application is focused.
  396. // Note that the application does not proceed if this is not focused by default.
  397. // This behavior can be changed by SetRunnableInBackground.
  398. //
  399. // SetInitFocused does nothing on mobile.
  400. //
  401. // SetInitFocused panics if this is called after the main loop.
  402. //
  403. // SetInitFocused is cuncurrent-safe.
  404. func SetInitFocused(focused bool) {
  405. uiDriver().SetInitFocused(focused)
  406. }