zorldo

Goofing around with Ebiten
git clone git://bsandro.tech/zorldo
Log | Files | Refs | README

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(&currentMaxTPS))
    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(&currentMaxTPS, 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 }