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