run.go (13911B)
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 // If Layout returns non-positive numbers, the caller can panic. 54 // 55 // You can return a fixed screen size if you don't care, or you can also return a calculated screen size 56 // adjusted with the given outside size. 57 Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) 58 } 59 60 // DefaultTPS represents a default ticks per second, that represents how many times game updating happens in a second. 61 const DefaultTPS = 60 62 63 // CurrentFPS returns the current number of FPS (frames per second), that represents 64 // how many swapping buffer happens per second. 65 // 66 // On some environments, CurrentFPS doesn't return a reliable value since vsync doesn't work well there. 67 // If you want to measure the application's speed, Use CurrentTPS. 68 // 69 // CurrentFPS is concurrent-safe. 70 func CurrentFPS() float64 { 71 return clock.CurrentFPS() 72 } 73 74 var ( 75 isScreenClearedEveryFrame = int32(1) 76 currentMaxTPS = int32(DefaultTPS) 77 ) 78 79 // SetScreenClearedEveryFrame enables or disables the clearing of the screen at the beginning of each frame. 80 // The default value is false and the screen is cleared each frame by default. 81 // 82 // SetScreenClearedEveryFrame is concurrent-safe. 83 func SetScreenClearedEveryFrame(cleared bool) { 84 v := int32(0) 85 if cleared { 86 v = 1 87 } 88 atomic.StoreInt32(&isScreenClearedEveryFrame, v) 89 theUIContext.setScreenClearedEveryFrame(cleared) 90 } 91 92 // IsScreenClearedEveryFrame returns true if the frame isn't cleared at the beginning. 93 // 94 // IsScreenClearedEveryFrame is concurrent-safe. 95 func IsScreenClearedEveryFrame() bool { 96 return atomic.LoadInt32(&isScreenClearedEveryFrame) != 0 97 } 98 99 type imageDumperGameWithDraw struct { 100 game Game 101 d *imageDumper 102 err error 103 } 104 105 func (i *imageDumperGameWithDraw) Update() error { 106 if i.err != nil { 107 return i.err 108 } 109 if i.d == nil { 110 i.d = &imageDumper{g: i.game} 111 } 112 return i.d.update() 113 } 114 115 func (i *imageDumperGameWithDraw) Draw(screen *Image) { 116 if i.err != nil { 117 return 118 } 119 120 i.game.Draw(screen) 121 i.err = i.d.dump(screen) 122 } 123 124 func (i *imageDumperGameWithDraw) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) { 125 return i.game.Layout(outsideWidth, outsideHeight) 126 } 127 128 // RunGame starts the main loop and runs the game. 129 // game's Update function is called every tick to update the game logic. 130 // game's Draw function is, if it exists, called every frame to draw the screen. 131 // game's Layout function is called when necessary, and you can specify the logical screen size by the function. 132 // 133 // game must implement Game interface. 134 // Game's Draw function is optional, but it is recommended to implement Draw to seperate updating the logic and 135 // rendering. 136 // 137 // RunGame is a more flexibile form of Run due to game's Layout function. 138 // You can make a resizable window if you use RunGame, while you cannot if you use Run. 139 // RunGame is more sophisticated way than Run and hides the notion of 'scale'. 140 // 141 // While Run specifies the window size, RunGame does not. 142 // You need to call SetWindowSize before RunGame if you want. 143 // Otherwise, a default window size is adopted. 144 // 145 // Some functions (ScreenScale, SetScreenScale, SetScreenSize) are not available with RunGame. 146 // 147 // On browsers, it is strongly recommended to use iframe if you embed an Ebiten application in your website. 148 // 149 // RunGame must be called on the main thread. 150 // Note that Ebiten bounds the main goroutine to the main OS thread by runtime.LockOSThread. 151 // 152 // Ebiten tries to call game's Update function 60 times a second by default. In other words, 153 // TPS (ticks per second) is 60 by default. 154 // This is not related to framerate (display's refresh rate). 155 // 156 // RunGame returns error when 1) OpenGL error happens, 2) audio error happens or 157 // 3) f returns error. In the case of 3), RunGame returns the same error. 158 // 159 // The size unit is device-independent pixel. 160 // 161 // Don't call RunGame twice or more in one process. 162 func RunGame(game Game) error { 163 fixWindowPosition(WindowSize()) 164 return runGame(&imageDumperGameWithDraw{ 165 game: game, 166 }, 0) 167 } 168 169 func runGame(game Game, scale float64) error { 170 theUIContext.set(game, scale) 171 if err := uiDriver().Run(theUIContext); err != nil { 172 if err == driver.RegularTermination { 173 return nil 174 } 175 return err 176 } 177 return nil 178 } 179 180 // RunGameWithoutMainLoop runs the game, but don't call the loop on the main (UI) thread. 181 // Different from Run, RunGameWithoutMainLoop returns immediately. 182 // 183 // Ebiten users should NOT call RunGameWithoutMainLoop. 184 // Instead, functions in github.com/hajimehoshi/ebiten/v2/mobile package calls this. 185 func RunGameWithoutMainLoop(game Game) { 186 fixWindowPosition(WindowSize()) 187 game = &imageDumperGameWithDraw{ 188 game: game, 189 } 190 theUIContext.set(game, 0) 191 uiDriver().RunWithoutMainLoop(theUIContext) 192 } 193 194 // ScreenSizeInFullscreen returns the size in device-independent pixels when the game is fullscreen. 195 // The adopted monitor is the 'current' monitor which the window belongs to. 196 // The returned value can be given to Run or SetSize function if the perfectly fit fullscreen is needed. 197 // 198 // On browsers, ScreenSizeInFullscreen returns the 'window' (global object) size, not 'screen' size since an Ebiten 199 // game should not know the outside of the window object. For more details, see SetFullscreen API comment. 200 // 201 // On mobiles, ScreenSizeInFullscreen returns (0, 0) so far. 202 // 203 // ScreenSizeInFullscreen's use cases are limited. If you are making a fullscreen application, you can use RunGame and 204 // the Game interface's Layout function instead. If you are making a not-fullscreen application but the application's 205 // behavior depends on the monitor size, ScreenSizeInFullscreen is useful. 206 // 207 // ScreenSizeInFullscreen must be called on the main thread before ebiten.Run, and is concurrent-safe after 208 // ebiten.Run. 209 func ScreenSizeInFullscreen() (int, int) { 210 return uiDriver().ScreenSizeInFullscreen() 211 } 212 213 // CursorMode returns the current cursor mode. 214 // 215 // On browsers, only CursorModeVisible and CursorModeHidden are supported. 216 // 217 // CursorMode returns CursorModeHidden on mobiles. 218 // 219 // CursorMode is concurrent-safe. 220 func CursorMode() CursorModeType { 221 return CursorModeType(uiDriver().CursorMode()) 222 } 223 224 // SetCursorMode sets the render and capture mode of the mouse cursor. 225 // CursorModeVisible sets the cursor to always be visible. 226 // CursorModeHidden hides the system cursor when over the window. 227 // CursorModeCaptured hides the system cursor and locks it to the window. 228 // 229 // On browsers, only CursorModeVisible and CursorModeHidden are supported. 230 // 231 // SetCursorMode does nothing on mobiles. 232 // 233 // SetCursorMode is concurrent-safe. 234 func SetCursorMode(mode CursorModeType) { 235 uiDriver().SetCursorMode(driver.CursorMode(mode)) 236 } 237 238 // IsFullscreen reports whether the current mode is fullscreen or not. 239 // 240 // IsFullscreen always returns false on browsers or mobiles. 241 // 242 // IsFullscreen is concurrent-safe. 243 func IsFullscreen() bool { 244 return uiDriver().IsFullscreen() 245 } 246 247 // SetFullscreen changes the current mode to fullscreen or not on desktops. 248 // 249 // On fullscreen mode, the game screen is automatically enlarged 250 // to fit with the monitor. The current scale value is ignored. 251 // 252 // On desktops, Ebiten uses 'windowed' fullscreen mode, which doesn't change 253 // your monitor's resolution. 254 // 255 // SetFullscreen does nothing on browsers or mobiles. 256 // 257 // SetFullscreen is concurrent-safe. 258 func SetFullscreen(fullscreen bool) { 259 uiDriver().SetFullscreen(fullscreen) 260 } 261 262 // IsFocused returns a boolean value indicating whether 263 // the game is in focus or in the foreground. 264 // 265 // IsFocused will only return true if IsRunnableOnUnfocused is false. 266 // 267 // IsFocused is concurrent-safe. 268 func IsFocused() bool { 269 return uiDriver().IsFocused() 270 } 271 272 // IsRunnableOnUnfocused returns a boolean value indicating whether 273 // the game runs even in background. 274 // 275 // IsRunnableOnUnfocused is concurrent-safe. 276 func IsRunnableOnUnfocused() bool { 277 return uiDriver().IsRunnableOnUnfocused() 278 } 279 280 // SetRunnableOnUnfocused sets the state if the game runs even in background. 281 // 282 // If the given value is true, the game runs even in background e.g. when losing focus. 283 // The initial state is true. 284 // 285 // Known issue: On browsers, even if the state is on, the game doesn't run in background tabs. 286 // This is because browsers throttles background tabs not to often update. 287 // 288 // SetRunnableOnUnfocused does nothing on mobiles so far. 289 // 290 // SetRunnableOnUnfocused is concurrent-safe. 291 func SetRunnableOnUnfocused(runnableOnUnfocused bool) { 292 uiDriver().SetRunnableOnUnfocused(runnableOnUnfocused) 293 } 294 295 // DeviceScaleFactor returns a device scale factor value of the current monitor which the window belongs to. 296 // 297 // DeviceScaleFactor returns a meaningful value on high-DPI display environment, 298 // otherwise DeviceScaleFactor returns 1. 299 // 300 // DeviceScaleFactor might panic on init function on some devices like Android. 301 // Then, it is not recommended to call DeviceScaleFactor from init functions. 302 // 303 // DeviceScaleFactor must be called on the main thread before the main loop, and is concurrent-safe after the main loop. 304 func DeviceScaleFactor() float64 { 305 return uiDriver().DeviceScaleFactor() 306 } 307 308 // IsVsyncEnabled returns a boolean value indicating whether 309 // the game uses the display's vsync. 310 // 311 // IsVsyncEnabled is concurrent-safe. 312 func IsVsyncEnabled() bool { 313 return uiDriver().IsVsyncEnabled() 314 } 315 316 // SetVsyncEnabled sets a boolean value indicating whether 317 // the game uses the display's vsync. 318 // 319 // If the given value is true, the game tries to sync the display's refresh rate. 320 // If false, the game ignores the display's refresh rate. 321 // The initial value is true. 322 // By disabling vsync, the game works more efficiently but consumes more CPU. 323 // 324 // Note that the state doesn't affect TPS (ticks per second, i.e. how many the run function is 325 // updated per second). 326 // 327 // SetVsyncEnabled does nothing on mobiles so far. 328 // 329 // SetVsyncEnabled is concurrent-safe. 330 func SetVsyncEnabled(enabled bool) { 331 uiDriver().SetVsyncEnabled(enabled) 332 } 333 334 // MaxTPS returns the current maximum TPS. 335 // 336 // MaxTPS is concurrent-safe. 337 func MaxTPS() int { 338 return int(atomic.LoadInt32(¤tMaxTPS)) 339 } 340 341 // CurrentTPS returns the current TPS (ticks per second), 342 // that represents how many update function is called in a second. 343 // 344 // CurrentTPS is concurrent-safe. 345 func CurrentTPS() float64 { 346 return clock.CurrentTPS() 347 } 348 349 // UncappedTPS is a special TPS value that means the game doesn't have limitation on TPS. 350 const UncappedTPS = clock.UncappedTPS 351 352 // SetMaxTPS sets the maximum TPS (ticks per second), 353 // that represents how many updating function is called per second. 354 // The initial value is 60. 355 // 356 // If tps is UncappedTPS, TPS is uncapped and the game is updated per frame. 357 // If tps is negative but not UncappedTPS, SetMaxTPS panics. 358 // 359 // SetMaxTPS is concurrent-safe. 360 func SetMaxTPS(tps int) { 361 if tps < 0 && tps != UncappedTPS { 362 panic("ebiten: tps must be >= 0 or UncappedTPS") 363 } 364 atomic.StoreInt32(¤tMaxTPS, int32(tps)) 365 } 366 367 // IsScreenTransparent reports whether the window is transparent. 368 // 369 // IsScreenTransparent is concurrent-safe. 370 func IsScreenTransparent() bool { 371 return uiDriver().IsScreenTransparent() 372 } 373 374 // SetScreenTransparent sets the state if the window is transparent. 375 // 376 // SetScreenTransparent panics if SetScreenTransparent is called after the main loop. 377 // 378 // SetScreenTransparent does nothing on mobiles. 379 // 380 // SetScreenTransparent is concurrent-safe. 381 func SetScreenTransparent(transparent bool) { 382 uiDriver().SetScreenTransparent(transparent) 383 } 384 385 // SetInitFocused sets whether the application is focused on show. 386 // The default value is true, i.e., the application is focused. 387 // Note that the application does not proceed if this is not focused by default. 388 // This behavior can be changed by SetRunnableInBackground. 389 // 390 // SetInitFocused does nothing on mobile. 391 // 392 // SetInitFocused panics if this is called after the main loop. 393 // 394 // SetInitFocused is cuncurrent-safe. 395 func SetInitFocused(focused bool) { 396 uiDriver().SetInitFocused(focused) 397 }