Add 'screenshot' feature

If you run your game with environment variable EBITEN_SCREENSHOT_KEY=<keyname>,
you can take a game screenshot by pressing the specified key.

Fixes #553
This commit is contained in:
Hajime Hoshi 2018-04-11 01:00:22 +09:00
parent 4708d4a6f9
commit 888d650872
3 changed files with 256 additions and 1 deletions

View File

@ -26,6 +26,7 @@ import (
"os" "os"
"sort" "sort"
"strconv" "strconv"
"strings"
"text/template" "text/template"
"github.com/hajimehoshi/ebiten/internal" "github.com/hajimehoshi/ebiten/internal"
@ -137,6 +138,8 @@ const ebitenKeysTmpl = `{{.License}}
package ebiten package ebiten
import ( import (
"strings"
"github.com/hajimehoshi/ebiten/internal/input" "github.com/hajimehoshi/ebiten/internal/input"
) )
@ -150,6 +153,14 @@ const (
{{range $index, $name := .KeyNames}}Key{{$name}} Key = Key(input.Key{{$name}}) {{range $index, $name := .KeyNames}}Key{{$name}} Key = Key(input.Key{{$name}})
{{end}} KeyMax Key = Key{{.LastKeyName}} {{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}} const inputKeysTmpl = `{{.License}}
@ -332,10 +343,15 @@ func main() {
log.Fatal(err) log.Fatal(err)
} }
defer f.Close() 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 { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
// The build tag can't be included in the templates because of `go vet`. // 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. // Pass the build tag and extract this in the template to make `go vet` happy.
buildTag := "" buildTag := ""

164
keys.go
View File

@ -17,6 +17,8 @@
package ebiten package ebiten
import ( import (
"strings"
"github.com/hajimehoshi/ebiten/internal/input" "github.com/hajimehoshi/ebiten/internal/input"
) )
@ -107,3 +109,165 @@ const (
KeyUp Key = Key(input.KeyUp) KeyUp Key = Key(input.KeyUp)
KeyMax Key = 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
}

75
run.go
View File

@ -15,11 +15,14 @@
package ebiten package ebiten
import ( import (
"fmt"
"image" "image"
"os"
"sync/atomic" "sync/atomic"
"github.com/hajimehoshi/ebiten/internal/clock" "github.com/hajimehoshi/ebiten/internal/clock"
"github.com/hajimehoshi/ebiten/internal/devicescale" "github.com/hajimehoshi/ebiten/internal/devicescale"
"github.com/hajimehoshi/ebiten/internal/png"
"github.com/hajimehoshi/ebiten/internal/ui" "github.com/hajimehoshi/ebiten/internal/ui"
) )
@ -88,6 +91,68 @@ func run(width, height int, scale float64, title string, g *graphicsContext, mai
return nil 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. // Run runs the game.
// f is a function which is called at every frame. // f is a function which is called at every frame.
// The argument (*Image) is the render target that represents the screen. // 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. // Don't call Run twice or more in one process.
func Run(f func(*Image) error, width, height int, scale float64, title string) error { 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) ch := make(chan error)
go func() { go func() {
defer close(ch) 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. // Ebiten users should NOT call this function.
// Instead, functions in github.com/hajimehoshi/ebiten/mobile package calls this. // 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 { 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) ch := make(chan error)
go func() { go func() {
defer close(ch) defer close(ch)