From 888d6508729a17d7533a3bff475997c941d1468e Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Wed, 11 Apr 2018 01:00:22 +0900 Subject: [PATCH] Add 'screenshot' feature If you run your game with environment variable EBITEN_SCREENSHOT_KEY=, you can take a game screenshot by pressing the specified key. Fixes #553 --- genkeys.go | 18 +++++- keys.go | 164 +++++++++++++++++++++++++++++++++++++++++++++++++++++ run.go | 75 ++++++++++++++++++++++++ 3 files changed, 256 insertions(+), 1 deletion(-) diff --git a/genkeys.go b/genkeys.go index 9ddfa3453..bb57fc623 100644 --- a/genkeys.go +++ b/genkeys.go @@ -26,6 +26,7 @@ import ( "os" "sort" "strconv" + "strings" "text/template" "github.com/hajimehoshi/ebiten/internal" @@ -137,6 +138,8 @@ const ebitenKeysTmpl = `{{.License}} package ebiten import ( + "strings" + "github.com/hajimehoshi/ebiten/internal/input" ) @@ -150,6 +153,14 @@ const ( {{range $index, $name := .KeyNames}}Key{{$name}} Key = Key(input.Key{{$name}}) {{end}} KeyMax Key = Key{{.LastKeyName}} ) + +func keyNameToKey(name string) (Key, bool) { + switch strings.ToLower(name) { + {{range $name := .KeyNames}}case {{$name | printf "%q" | ToLower}}: + return Key{{$name}}, true + {{end}}} + return 0, false +} ` const inputKeysTmpl = `{{.License}} @@ -332,10 +343,15 @@ func main() { log.Fatal(err) } defer f.Close() - tmpl, err := template.New(path).Parse(tmpl) + + funcs := template.FuncMap{ + "ToLower": strings.ToLower, + } + tmpl, err := template.New(path).Funcs(funcs).Parse(tmpl) if err != nil { log.Fatal(err) } + // The build tag can't be included in the templates because of `go vet`. // Pass the build tag and extract this in the template to make `go vet` happy. buildTag := "" diff --git a/keys.go b/keys.go index f514c9fc4..e59b85b4f 100644 --- a/keys.go +++ b/keys.go @@ -17,6 +17,8 @@ package ebiten import ( + "strings" + "github.com/hajimehoshi/ebiten/internal/input" ) @@ -107,3 +109,165 @@ const ( KeyUp Key = Key(input.KeyUp) KeyMax Key = KeyUp ) + +func keyNameToKey(name string) (Key, bool) { + switch strings.ToLower(name) { + case "0": + return Key0, true + case "1": + return Key1, true + case "2": + return Key2, true + case "3": + return Key3, true + case "4": + return Key4, true + case "5": + return Key5, true + case "6": + return Key6, true + case "7": + return Key7, true + case "8": + return Key8, true + case "9": + return Key9, true + case "a": + return KeyA, true + case "b": + return KeyB, true + case "c": + return KeyC, true + case "d": + return KeyD, true + case "e": + return KeyE, true + case "f": + return KeyF, true + case "g": + return KeyG, true + case "h": + return KeyH, true + case "i": + return KeyI, true + case "j": + return KeyJ, true + case "k": + return KeyK, true + case "l": + return KeyL, true + case "m": + return KeyM, true + case "n": + return KeyN, true + case "o": + return KeyO, true + case "p": + return KeyP, true + case "q": + return KeyQ, true + case "r": + return KeyR, true + case "s": + return KeyS, true + case "t": + return KeyT, true + case "u": + return KeyU, true + case "v": + return KeyV, true + case "w": + return KeyW, true + case "x": + return KeyX, true + case "y": + return KeyY, true + case "z": + return KeyZ, true + case "alt": + return KeyAlt, true + case "apostrophe": + return KeyApostrophe, true + case "backslash": + return KeyBackslash, true + case "backspace": + return KeyBackspace, true + case "capslock": + return KeyCapsLock, true + case "comma": + return KeyComma, true + case "control": + return KeyControl, true + case "delete": + return KeyDelete, true + case "down": + return KeyDown, true + case "end": + return KeyEnd, true + case "enter": + return KeyEnter, true + case "equal": + return KeyEqual, true + case "escape": + return KeyEscape, true + case "f1": + return KeyF1, true + case "f2": + return KeyF2, true + case "f3": + return KeyF3, true + case "f4": + return KeyF4, true + case "f5": + return KeyF5, true + case "f6": + return KeyF6, true + case "f7": + return KeyF7, true + case "f8": + return KeyF8, true + case "f9": + return KeyF9, true + case "f10": + return KeyF10, true + case "f11": + return KeyF11, true + case "f12": + return KeyF12, true + case "graveaccent": + return KeyGraveAccent, true + case "home": + return KeyHome, true + case "insert": + return KeyInsert, true + case "left": + return KeyLeft, true + case "leftbracket": + return KeyLeftBracket, true + case "minus": + return KeyMinus, true + case "pagedown": + return KeyPageDown, true + case "pageup": + return KeyPageUp, true + case "period": + return KeyPeriod, true + case "right": + return KeyRight, true + case "rightbracket": + return KeyRightBracket, true + case "semicolon": + return KeySemicolon, true + case "shift": + return KeyShift, true + case "slash": + return KeySlash, true + case "space": + return KeySpace, true + case "tab": + return KeyTab, true + case "up": + return KeyUp, true + } + return 0, false +} diff --git a/run.go b/run.go index 338c3b10f..cb6022905 100644 --- a/run.go +++ b/run.go @@ -15,11 +15,14 @@ package ebiten import ( + "fmt" "image" + "os" "sync/atomic" "github.com/hajimehoshi/ebiten/internal/clock" "github.com/hajimehoshi/ebiten/internal/devicescale" + "github.com/hajimehoshi/ebiten/internal/png" "github.com/hajimehoshi/ebiten/internal/ui" ) @@ -88,6 +91,68 @@ func run(width, height int, scale float64, title string, g *graphicsContext, mai return nil } +var screenshot = false +var screenshotKey Key + +func init() { + keyname := os.Getenv("EBITEN_SCREENSHOT_KEY") + if keyname == "" { + return + } + key, ok := keyNameToKey(keyname) + if !ok { + return + } + screenshot = true + screenshotKey = key +} + +type screenshotTaker struct { + f func(screen *Image) error + keyState int + needToTake bool +} + +func (s *screenshotTaker) update(screen *Image) error { + if err := s.f(screen); err != nil { + return err + } + if !screenshot { + return nil + } + + if IsKeyPressed(screenshotKey) { + s.keyState++ + if s.keyState == 1 { + s.needToTake = true + } + } else { + s.keyState = 0 + } + if s.needToTake && !IsRunningSlowly() { + filename := "screenshot.png" + i := 0 + for { + if _, err := os.Stat(filename); os.IsNotExist(err) { + break + } + i++ + filename = fmt.Sprintf("screenshot%d.png", i) + } + f, err := os.Create(filename) + if err != nil { + return err + } + defer f.Close() + + if err := png.Encode(f, screen); err != nil { + return err + } + s.needToTake = false + } + return nil +} + // Run runs the game. // f is a function which is called at every frame. // The argument (*Image) is the render target that represents the screen. @@ -118,6 +183,11 @@ func run(width, height int, scale float64, title string, g *graphicsContext, mai // // Don't call Run twice or more in one process. func Run(f func(*Image) error, width, height int, scale float64, title string) error { + if screenshot { + s := &screenshotTaker{f: f} + f = s.update + } + ch := make(chan error) go func() { defer close(ch) @@ -142,6 +212,11 @@ func Run(f func(*Image) error, width, height int, scale float64, title string) e // Ebiten users should NOT call this function. // Instead, functions in github.com/hajimehoshi/ebiten/mobile package calls this. func RunWithoutMainLoop(f func(*Image) error, width, height int, scale float64, title string) <-chan error { + if screenshot { + s := &screenshotTaker{f: f} + f = s.update + } + ch := make(chan error) go func() { defer close(ch)