examples: Reduce global variables

Closes #1669
This commit is contained in:
Hajime Hoshi 2021-07-22 00:15:30 +09:00
parent c2a5f4ab94
commit 1dc8002689
19 changed files with 302 additions and 243 deletions

View File

@ -43,9 +43,6 @@ var (
gophersImage *ebiten.Image
repeatedGophersImage *ebiten.Image
groundImage = ebiten.NewImage(screenWidth*3, screenHeight*2/3+200)
perspectiveGroundImage = ebiten.NewImage(screenWidth*3, screenHeight)
fogImage *ebiten.Image
)
func init() {
@ -77,23 +74,6 @@ func init() {
repeatedGophersImage.DrawImage(gophersImage, op)
}
}
const fogHeight = 16
w, _ = perspectiveGroundImage.Size()
fogRGBA := image.NewRGBA(image.Rect(0, 0, w, fogHeight))
for j := 0; j < fogHeight; j++ {
a := uint32(float64(fogHeight-1-j) * 0xff / (fogHeight - 1))
clr := skyColor
r, g, b, oa := uint32(clr.R), uint32(clr.G), uint32(clr.B), uint32(clr.A)
clr.R = uint8(r * a / oa)
clr.G = uint8(g * a / oa)
clr.B = uint8(b * a / oa)
clr.A = uint8(a)
for i := 0; i < w; i++ {
fogRGBA.SetRGBA(i, j, clr)
}
}
fogImage = ebiten.NewImageFromImage(fogRGBA)
}
// player represents the current airship's position.
@ -200,9 +180,9 @@ func (g *Game) updateGroundImage(ground *ebiten.Image) {
// drawGroundImage draws the ground image to the given screen image.
func (g *Game) drawGroundImage(screen *ebiten.Image, ground *ebiten.Image) {
perspectiveGroundImage.Clear()
g.perspectiveGroundImage.Clear()
gw, _ := ground.Size()
pw, ph := perspectiveGroundImage.Size()
pw, ph := g.perspectiveGroundImage.Size()
for j := 0; j < ph; j++ {
// z is in [2, -1]
rate := float64(j) / float64(ph)
@ -216,30 +196,55 @@ func (g *Game) drawGroundImage(screen *ebiten.Image, ground *ebiten.Image) {
op.GeoM.Scale(1/z, 8) // 8 is an arbitrary number not to make empty lines.
op.GeoM.Translate(float64(pw)/2, float64(j)/z)
perspectiveGroundImage.DrawImage(ground.SubImage(image.Rect(0, j, gw, j+1)).(*ebiten.Image), op)
g.perspectiveGroundImage.DrawImage(ground.SubImage(image.Rect(0, j, gw, j+1)).(*ebiten.Image), op)
}
perspectiveGroundImage.DrawImage(fogImage, nil)
g.perspectiveGroundImage.DrawImage(g.fogImage, nil)
op := &ebiten.DrawImageOptions{}
op.GeoM.Translate(-float64(pw)/2, 0)
op.GeoM.Rotate(-1 * float64(g.player.lean) / maxLean * math.Pi / 8)
op.GeoM.Translate(float64(screenWidth)/2, screenHeight/3)
screen.DrawImage(perspectiveGroundImage, op)
screen.DrawImage(g.perspectiveGroundImage, op)
}
type Game struct {
player *player
groundImage *ebiten.Image
perspectiveGroundImage *ebiten.Image
fogImage *ebiten.Image
}
func NewGame() *Game {
return &Game{
g := &Game{
player: &player{
x16: 16 * 100,
y16: 16 * 200,
angle: maxAngle * 3 / 4,
},
groundImage: ebiten.NewImage(screenWidth*3, screenHeight*2/3+200),
perspectiveGroundImage: ebiten.NewImage(screenWidth*3, screenHeight),
}
const fogHeight = 16
w, _ := g.perspectiveGroundImage.Size()
fogRGBA := image.NewRGBA(image.Rect(0, 0, w, fogHeight))
for j := 0; j < fogHeight; j++ {
a := uint32(float64(fogHeight-1-j) * 0xff / (fogHeight - 1))
clr := skyColor
r, g, b, oa := uint32(clr.R), uint32(clr.G), uint32(clr.B), uint32(clr.A)
clr.R = uint8(r * a / oa)
clr.G = uint8(g * a / oa)
clr.B = uint8(b * a / oa)
clr.A = uint8(a)
for i := 0; i < w; i++ {
fogRGBA.SetRGBA(i, j, clr)
}
}
g.fogImage = ebiten.NewImageFromImage(fogRGBA)
return g
}
func (g *Game) Update() error {
@ -265,8 +270,8 @@ func (g *Game) Update() error {
func (g *Game) Draw(screen *ebiten.Image) {
// Draw the ground image.
screen.Fill(skyColor)
g.updateGroundImage(groundImage)
g.drawGroundImage(screen, groundImage)
g.updateGroundImage(g.groundImage)
g.drawGroundImage(screen, g.groundImage)
// Draw the message.
tutrial := "Space: Move forward\nLeft/Right: Rotate"

View File

@ -55,9 +55,6 @@ var (
)
var (
playButtonPosition image.Point
alertButtonPosition image.Point
playButtonImage *ebiten.Image
pauseButtonImage *ebiten.Image
alertButtonImage *ebiten.Image
@ -81,15 +78,6 @@ func init() {
panic(err)
}
alertButtonImage = ebiten.NewImageFromImage(img)
const buttonPadding = 16
w, _ := playButtonImage.Size()
playButtonPosition.X = (screenWidth - w*2 + buttonPadding*1) / 2
playButtonPosition.Y = screenHeight - 160
alertButtonPosition.X = playButtonPosition.X + w + buttonPadding
alertButtonPosition.Y = playButtonPosition.Y
}
type musicType int
@ -121,6 +109,9 @@ type Player struct {
seCh chan []byte
volume128 int
musicType musicType
playButtonPosition image.Point
alertButtonPosition image.Point
}
func playerBarRect() (x, y, w, h int) {
@ -172,6 +163,15 @@ func NewPlayer(game *Game, audioContext *audio.Context, musicType musicType) (*P
if player.total == 0 {
player.total = 1
}
const buttonPadding = 16
w, _ := playButtonImage.Size()
player.playButtonPosition.X = (screenWidth - w*2 + buttonPadding*1) / 2
player.playButtonPosition.Y = screenHeight - 160
player.alertButtonPosition.X = player.playButtonPosition.X + w + buttonPadding
player.alertButtonPosition.Y = player.playButtonPosition.Y
player.audioPlayer.Play()
go func() {
s, err := wav.Decode(audioContext, bytes.NewReader(raudio.Jab_wav))
@ -226,8 +226,8 @@ func (p *Player) shouldPlaySE() bool {
return true
}
r := image.Rectangle{
Min: alertButtonPosition,
Max: alertButtonPosition.Add(image.Pt(alertButtonImage.Size())),
Min: p.alertButtonPosition,
Max: p.alertButtonPosition.Add(image.Pt(alertButtonImage.Size())),
}
if image.Pt(ebiten.CursorPosition()).In(r) {
if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
@ -271,8 +271,8 @@ func (p *Player) shouldSwitchPlayStateIfNeeded() bool {
return true
}
r := image.Rectangle{
Min: playButtonPosition,
Max: playButtonPosition.Add(image.Pt(playButtonImage.Size())),
Min: p.playButtonPosition,
Max: p.playButtonPosition.Add(image.Pt(playButtonImage.Size())),
}
if image.Pt(ebiten.CursorPosition()).In(r) {
if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
@ -350,14 +350,14 @@ func (p *Player) draw(screen *ebiten.Image) {
// Draw buttons
op := &ebiten.DrawImageOptions{}
op.GeoM.Translate(float64(playButtonPosition.X), float64(playButtonPosition.Y))
op.GeoM.Translate(float64(p.playButtonPosition.X), float64(p.playButtonPosition.Y))
if p.audioPlayer.IsPlaying() {
screen.DrawImage(pauseButtonImage, op)
} else {
screen.DrawImage(playButtonImage, op)
}
op.GeoM.Reset()
op.GeoM.Translate(float64(alertButtonPosition.X), float64(alertButtonPosition.Y))
op.GeoM.Translate(float64(p.alertButtonPosition.X), float64(p.alertButtonPosition.Y))
screen.DrawImage(alertButtonImage, op)
// Draw the debug message.

View File

@ -39,10 +39,9 @@ const (
loopLengthInSecond = 4
)
var audioContext = audio.NewContext(sampleRate)
type Game struct {
player *audio.Player
audioContext *audio.Context
}
func (g *Game) Update() error {
@ -50,9 +49,13 @@ func (g *Game) Update() error {
return nil
}
if g.audioContext == nil {
g.audioContext = audio.NewContext(sampleRate)
}
// Decode an Ogg file.
// oggS is a decoded io.ReadCloser and io.Seeker.
oggS, err := vorbis.Decode(audioContext, bytes.NewReader(raudio.Ragtime_ogg))
oggS, err := vorbis.Decode(g.audioContext, bytes.NewReader(raudio.Ragtime_ogg))
if err != nil {
return err
}
@ -61,7 +64,7 @@ func (g *Game) Update() error {
// s is still an io.ReadCloser and io.Seeker.
s := audio.NewInfiniteLoopWithIntro(oggS, introLengthInSecond*4*sampleRate, loopLengthInSecond*4*sampleRate)
g.player, err = audio.NewPlayer(audioContext, s)
g.player, err = audio.NewPlayer(g.audioContext, s)
if err != nil {
return err
}

View File

@ -41,9 +41,7 @@ const (
sampleRate = 22050
)
var img *ebiten.Image
var audioContext = audio.NewContext(sampleRate)
var ebitenImage *ebiten.Image
type Game struct {
player *audio.Player
@ -57,6 +55,8 @@ type Game struct {
count int
xpos float64
audioContext *audio.Context
}
func (g *Game) initAudio() {
@ -64,9 +64,13 @@ func (g *Game) initAudio() {
return
}
if g.audioContext == nil {
g.audioContext = audio.NewContext(sampleRate)
}
// Decode an Ogg file.
// oggS is a decoded io.ReadCloser and io.Seeker.
oggS, err := vorbis.Decode(audioContext, bytes.NewReader(raudio.Ragtime_ogg))
oggS, err := vorbis.Decode(g.audioContext, bytes.NewReader(raudio.Ragtime_ogg))
if err != nil {
log.Fatal(err)
}
@ -74,7 +78,7 @@ func (g *Game) initAudio() {
// Wrap the raw audio with the StereoPanStream
g.panstream = NewStereoPanStreamFromReader(audio.NewInfiniteLoop(oggS, oggS.Length()))
g.player, err = audio.NewPlayer(audioContext, g.panstream)
g.player, err = audio.NewPlayer(g.audioContext, g.panstream)
if err != nil {
log.Fatal(err)
}
@ -109,8 +113,8 @@ Panning: %.2f`, ebiten.CurrentTPS(), float64(pos)/float64(time.Second), g.pannin
// draw image to show where the sound is at related to the screen
op := &ebiten.DrawImageOptions{}
op.GeoM.Translate(g.xpos-float64(img.Bounds().Dx()/2), screenHeight/2)
screen.DrawImage(img, op)
op.GeoM.Translate(g.xpos-float64(ebitenImage.Bounds().Dx()/2), screenHeight/2)
screen.DrawImage(ebitenImage, op)
}
func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
@ -127,11 +131,11 @@ func main() {
// This works even on browsers.
// 3) Use ebitenutil.NewImageFromFile to create an ebiten.Image directly from a file.
// This also works on browsers.
rawimg, _, err := image.Decode(bytes.NewReader(images.Ebiten_png))
img, _, err := image.Decode(bytes.NewReader(images.Ebiten_png))
if err != nil {
log.Fatal(err)
}
img = ebiten.NewImageFromImage(rawimg)
ebitenImage = ebiten.NewImageFromImage(img)
ebiten.SetWindowSize(screenWidth, screenHeight)
ebiten.SetWindowTitle("Audio Panning Loop (Ebiten Demo)")

View File

@ -33,7 +33,6 @@ const (
)
var (
pixels = make([]byte, screenSize*4)
firePalette = []color.RGBA{
{R: 7, G: 7, B: 7, A: 255}, // 0
{R: 31, G: 7, B: 7, A: 255}, // 1
@ -76,16 +75,18 @@ var (
)
type Game struct {
firePixels []byte
pixels []byte
indices []byte
}
func NewGame() *Game {
firePixels := make([]byte, screenSize)
indices := make([]byte, screenSize)
for i := screenSize - screenWidth; i < screenSize; i++ {
firePixels[i] = 36
indices[i] = 36
}
return &Game{
firePixels: firePixels,
pixels: make([]byte, screenSize*4),
indices: indices,
}
}
@ -105,7 +106,7 @@ func (g *Game) updateFireIntensityPerPixel(currentPixelIndex int) {
}
d := rand.Intn(3)
newI := int(g.firePixels[below]) - d
newI := int(g.indices[below]) - d
if newI < 0 {
newI = 0
}
@ -113,16 +114,16 @@ func (g *Game) updateFireIntensityPerPixel(currentPixelIndex int) {
if currentPixelIndex-d < 0 {
return
}
g.firePixels[currentPixelIndex-d] = byte(newI)
g.indices[currentPixelIndex-d] = byte(newI)
}
func (g *Game) renderFire() {
for i, v := range g.firePixels {
for i, v := range g.indices {
p := firePalette[v]
pixels[i*4] = p.R
pixels[i*4+1] = p.G
pixels[i*4+2] = p.B
pixels[i*4+3] = p.A
g.pixels[i*4] = p.R
g.pixels[i*4+1] = p.G
g.pixels[i*4+2] = p.B
g.pixels[i*4+3] = p.A
}
}
@ -133,7 +134,7 @@ func (g *Game) Update() error {
func (g *Game) Draw(screen *ebiten.Image) {
g.renderFire()
screen.ReplacePixels(pixels)
screen.ReplacePixels(g.pixels)
}
func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {

View File

@ -126,32 +126,6 @@ func init() {
}
}
var (
audioContext = audio.NewContext(48000)
jumpPlayer *audio.Player
hitPlayer *audio.Player
)
func init() {
jumpD, err := vorbis.Decode(audioContext, bytes.NewReader(raudio.Jump_ogg))
if err != nil {
log.Fatal(err)
}
jumpPlayer, err = audio.NewPlayer(audioContext, jumpD)
if err != nil {
log.Fatal(err)
}
jabD, err := wav.Decode(audioContext, bytes.NewReader(raudio.Jab_wav))
if err != nil {
log.Fatal(err)
}
hitPlayer, err = audio.NewPlayer(audioContext, jabD)
if err != nil {
log.Fatal(err)
}
}
type Mode int
const (
@ -179,6 +153,10 @@ type Game struct {
touchIDs []ebiten.TouchID
gamepadIDs []ebiten.GamepadID
audioContext *audio.Context
jumpPlayer *audio.Player
hitPlayer *audio.Player
}
func NewGame() *Game {
@ -196,6 +174,26 @@ func (g *Game) init() {
for i := range g.pipeTileYs {
g.pipeTileYs[i] = rand.Intn(6) + 2
}
g.audioContext = audio.NewContext(48000)
jumpD, err := vorbis.Decode(g.audioContext, bytes.NewReader(raudio.Jump_ogg))
if err != nil {
log.Fatal(err)
}
g.jumpPlayer, err = audio.NewPlayer(g.audioContext, jumpD)
if err != nil {
log.Fatal(err)
}
jabD, err := wav.Decode(g.audioContext, bytes.NewReader(raudio.Jab_wav))
if err != nil {
log.Fatal(err)
}
g.hitPlayer, err = audio.NewPlayer(g.audioContext, jabD)
if err != nil {
log.Fatal(err)
}
}
func (g *Game) isKeyJustPressed() bool {
@ -246,8 +244,8 @@ func (g *Game) Update() error {
g.cameraX += 2
if g.isKeyJustPressed() {
g.vy16 = -96
jumpPlayer.Rewind()
jumpPlayer.Play()
g.jumpPlayer.Rewind()
g.jumpPlayer.Play()
}
g.y16 += g.vy16
@ -258,8 +256,8 @@ func (g *Game) Update() error {
}
if g.hit() {
hitPlayer.Rewind()
hitPlayer.Play()
g.hitPlayer.Rewind()
g.hitPlayer.Play()
g.mode = ModeGameOver
g.gameoverCount = 30
}

View File

@ -26,11 +26,16 @@ import (
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
)
var (
highDPIImageCh = make(chan *ebiten.Image)
)
type Game struct {
highDPIImageCh chan *ebiten.Image
highDPIImage *ebiten.Image
}
func NewGame() *Game {
g := &Game{
highDPIImageCh: make(chan *ebiten.Image),
}
func init() {
// Licensed under Public Domain
// https://commons.wikimedia.org/wiki/File:As08-16-2593.jpg
const url = "https://upload.wikimedia.org/wikipedia/commons/1/1f/As08-16-2593.jpg"
@ -41,13 +46,11 @@ func init() {
if err != nil {
log.Fatal(err)
}
highDPIImageCh <- img
close(highDPIImageCh)
g.highDPIImageCh <- img
close(g.highDPIImageCh)
}()
}
type Game struct {
highDPIImage *ebiten.Image
return g
}
func (g *Game) Update() error {
@ -61,7 +64,7 @@ func (g *Game) Update() error {
// Use select and 'default' clause for non-blocking receiving.
select {
case img := <-highDPIImageCh:
case img := <-g.highDPIImageCh:
g.highDPIImage = img
default:
}
@ -110,7 +113,7 @@ func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
func main() {
ebiten.SetWindowSize(640, 480)
ebiten.SetWindowTitle("High DPI (Ebiten Demo)")
if err := ebiten.RunGame(&Game{}); err != nil {
if err := ebiten.RunGame(NewGame()); err != nil {
log.Fatal(err)
}
}

View File

@ -31,11 +31,15 @@ const (
)
var (
offscreen = ebiten.NewImage(screenWidth, screenHeight)
offscreenPix []byte
palette [maxIt]byte
)
func init() {
for i := range palette {
palette[i] = byte(math.Sqrt(float64(i)/float64(len(palette))) * 0x80)
}
}
func color(it int) (r, g, b byte) {
if it == maxIt {
return 0xff, 0xff, 0xff
@ -44,7 +48,22 @@ func color(it int) (r, g, b byte) {
return c, c, c
}
func updateOffscreen(centerX, centerY, size float64) {
type Game struct {
offscreen *ebiten.Image
offscreenPix []byte
}
func NewGame() *Game {
g := &Game{
offscreen: ebiten.NewImage(screenWidth, screenHeight),
offscreenPix: make([]byte, screenWidth*screenHeight*4),
}
// Now it is not feasible to call updateOffscreen every frame due to performance.
g.updateOffscreen(-0.75, 0.25, 2)
return g
}
func (gm *Game) updateOffscreen(centerX, centerY, size float64) {
for j := 0; j < screenHeight; j++ {
for i := 0; i < screenHeight; i++ {
x := float64(i)*size/screenWidth - size/2 + centerX
@ -60,25 +79,13 @@ func updateOffscreen(centerX, centerY, size float64) {
}
r, g, b := color(it)
p := 4 * (i + j*screenWidth)
offscreenPix[p] = r
offscreenPix[p+1] = g
offscreenPix[p+2] = b
offscreenPix[p+3] = 0xff
gm.offscreenPix[p] = r
gm.offscreenPix[p+1] = g
gm.offscreenPix[p+2] = b
gm.offscreenPix[p+3] = 0xff
}
}
offscreen.ReplacePixels(offscreenPix)
}
func init() {
offscreenPix = make([]byte, screenWidth*screenHeight*4)
for i := range palette {
palette[i] = byte(math.Sqrt(float64(i)/float64(len(palette))) * 0x80)
}
// Now it is not feasible to call updateOffscreen every frame due to performance.
updateOffscreen(-0.75, 0.25, 2)
}
type Game struct {
gm.offscreen.ReplacePixels(gm.offscreenPix)
}
func (g *Game) Update() error {
@ -86,7 +93,7 @@ func (g *Game) Update() error {
}
func (g *Game) Draw(screen *ebiten.Image) {
screen.DrawImage(offscreen, nil)
screen.DrawImage(g.offscreen, nil)
}
func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
@ -96,7 +103,7 @@ func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
func main() {
ebiten.SetWindowSize(screenWidth, screenHeight)
ebiten.SetWindowTitle("Mandelbrot (Ebiten Demo)")
if err := ebiten.RunGame(&Game{}); err != nil {
if err := ebiten.RunGame(NewGame()); err != nil {
log.Fatal(err)
}
}

View File

@ -36,7 +36,6 @@ const mosaicRatio = 16
var (
gophersImage *ebiten.Image
gophersRenderTarget *ebiten.Image
)
func init() {
@ -57,6 +56,7 @@ func init() {
}
type Game struct {
gophersRenderTarget *ebiten.Image
}
func (g *Game) Update() error {
@ -67,13 +67,13 @@ func (g *Game) Draw(screen *ebiten.Image) {
// Shrink the image once.
op := &ebiten.DrawImageOptions{}
op.GeoM.Scale(1.0/mosaicRatio, 1.0/mosaicRatio)
gophersRenderTarget.DrawImage(gophersImage, op)
g.gophersRenderTarget.DrawImage(gophersImage, op)
// Enlarge the shrunk image.
// The filter is the nearest filter, so the result will be mosaic.
op = &ebiten.DrawImageOptions{}
op.GeoM.Scale(mosaicRatio, mosaicRatio)
screen.DrawImage(gophersRenderTarget, op)
screen.DrawImage(g.gophersRenderTarget, op)
}
func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
@ -82,10 +82,12 @@ func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
func main() {
w, h := gophersImage.Size()
gophersRenderTarget = ebiten.NewImage(w/mosaicRatio, h/mosaicRatio)
g := &Game{
gophersRenderTarget: ebiten.NewImage(w/mosaicRatio, h/mosaicRatio),
}
ebiten.SetWindowSize(screenWidth*2, screenHeight*2)
ebiten.SetWindowTitle("Mosaic (Ebiten Demo)")
if err := ebiten.RunGame(&Game{}); err != nil {
if err := ebiten.RunGame(g); err != nil {
log.Fatal(err)
}
}

View File

@ -63,8 +63,6 @@ func (g *Game) Update() error {
return nil
}
var offscreen = ebiten.NewImage(320, 240)
func (g *Game) Draw(screen *ebiten.Image) {
screen.ReplacePixels(g.noiseImage.Pix)
ebitenutil.DebugPrint(screen, fmt.Sprintf("TPS: %0.2f\nFPS: %0.2f", ebiten.CurrentTPS(), ebiten.CurrentFPS()))

View File

@ -35,7 +35,6 @@ const (
var (
brushImage *ebiten.Image
canvasImage = ebiten.NewImage(screenWidth, screenHeight)
)
func init() {
@ -55,13 +54,21 @@ func init() {
Stride: 4,
Rect: image.Rect(0, 0, 4, 4),
})
canvasImage.Fill(color.White)
}
type Game struct {
touches []ebiten.TouchID
count int
canvasImage *ebiten.Image
}
func NewGame() *Game {
g := &Game{
canvasImage: ebiten.NewImage(screenWidth, screenHeight),
}
g.canvasImage.Fill(color.White)
return g
}
func (g *Game) Update() error {
@ -70,7 +77,7 @@ func (g *Game) Update() error {
// Paint the brush by mouse dragging
mx, my := ebiten.CursorPosition()
if ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) {
g.paint(canvasImage, mx, my)
g.paint(g.canvasImage, mx, my)
drawn = true
}
@ -78,7 +85,7 @@ func (g *Game) Update() error {
g.touches = ebiten.AppendTouchIDs(g.touches[:0])
for _, t := range g.touches {
x, y := ebiten.TouchPosition(t)
g.paint(canvasImage, x, y)
g.paint(g.canvasImage, x, y)
drawn = true
}
if drawn {
@ -100,7 +107,7 @@ func (g *Game) paint(canvas *ebiten.Image, x, y int) {
}
func (g *Game) Draw(screen *ebiten.Image) {
screen.DrawImage(canvasImage, nil)
screen.DrawImage(g.canvasImage, nil)
mx, my := ebiten.CursorPosition()
msg := fmt.Sprintf("(%d, %d)", mx, my)
@ -118,7 +125,7 @@ func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
func main() {
ebiten.SetWindowSize(screenWidth, screenHeight)
ebiten.SetWindowTitle("Paint (Ebiten Demo)")
if err := ebiten.RunGame(&Game{}); err != nil {
if err := ebiten.RunGame(NewGame()); err != nil {
log.Fatal(err)
}
}

View File

@ -33,8 +33,6 @@ const (
sampleRate = 44100
)
var audioContext = audio.NewContext(sampleRate)
const (
freqA = 440.0
freqAS = 466.2
@ -91,8 +89,22 @@ func toBytes(l, r []int16) []byte {
return b
}
type Game struct {
scoreIndex int
frames int
currentNote rune
audioContext *audio.Context
}
func NewGame() *Game {
return &Game{
audioContext: audio.NewContext(sampleRate),
}
}
// playNote plays the note at scoreIndex of the score.
func playNote(scoreIndex int) rune {
func (g *Game) playNote(scoreIndex int) rune {
note := score[scoreIndex]
// If the note is 'rest', play nothing.
@ -118,22 +130,16 @@ func playNote(scoreIndex int) rune {
square(l, vol, freq, 0.25)
square(r, vol, freq, 0.25)
p := audio.NewPlayerFromBytes(audioContext, toBytes(l, r))
p := audio.NewPlayerFromBytes(g.audioContext, toBytes(l, r))
p.Play()
return rune(note)
}
type Game struct {
scoreIndex int
frames int
currentNote rune
}
func (g *Game) Update() error {
// Play notes for each half second.
if g.frames%30 == 0 && audioContext.IsReady() {
g.currentNote = playNote(g.scoreIndex)
if g.frames%30 == 0 && g.audioContext.IsReady() {
g.currentNote = g.playNote(g.scoreIndex)
g.scoreIndex++
g.scoreIndex %= len(score)
}
@ -148,7 +154,7 @@ func (g *Game) Draw(screen *ebiten.Image) {
} else {
msg += string(g.currentNote)
}
if !audioContext.IsReady() {
if !g.audioContext.IsReady() {
msg += "\n\n(If the audio doesn't start,\n click the screen or press keys)"
}
ebitenutil.DebugPrint(screen, msg)
@ -161,7 +167,7 @@ func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
func main() {
ebiten.SetWindowSize(screenWidth, screenHeight)
ebiten.SetWindowTitle("PCM (Ebiten Demo)")
if err := ebiten.RunGame(&Game{}); err != nil {
if err := ebiten.RunGame(NewGame()); err != nil {
log.Fatal(err)
}
}

View File

@ -65,8 +65,6 @@ const (
baseFreq = 220
)
var audioContext = audio.NewContext(sampleRate)
// pianoAt returns an i-th sample of piano with the given frequency.
func pianoAt(i int, freq float64) float64 {
// Create piano-like waves with multiple sin waves.
@ -137,13 +135,6 @@ func init() {
}()
}
// playNote plays piano sound with the given frequency.
func playNote(freq float64) {
f := int(freq)
p := audio.NewPlayerFromBytes(audioContext, pianoNoteSamples[f])
p.Play()
}
var (
pianoImage = ebiten.NewImage(screenWidth, screenHeight)
)
@ -196,6 +187,13 @@ var (
)
type Game struct {
audioContext *audio.Context
}
func NewGame() *Game {
return &Game{
audioContext: audio.NewContext(sampleRate),
}
}
func (g *Game) Update() error {
@ -214,12 +212,19 @@ func (g *Game) Update() error {
if !inpututil.IsKeyJustPressed(key) {
continue
}
playNote(baseFreq * math.Exp2(float64(i-1)/12.0))
g.playNote(baseFreq * math.Exp2(float64(i-1)/12.0))
}
}
return nil
}
// playNote plays piano sound with the given frequency.
func (g *Game) playNote(freq float64) {
f := int(freq)
p := audio.NewPlayerFromBytes(g.audioContext, pianoNoteSamples[f])
p.Play()
}
func (g *Game) Draw(screen *ebiten.Image) {
screen.Fill(color.RGBA{0x80, 0x80, 0xc0, 0xff})
screen.DrawImage(pianoImage, nil)
@ -234,7 +239,7 @@ func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
func main() {
ebiten.SetWindowSize(screenWidth*2, screenHeight*2)
ebiten.SetWindowTitle("Piano (Ebiten Demo)")
if err := ebiten.RunGame(&Game{}); err != nil {
if err := ebiten.RunGame(NewGame()); err != nil {
log.Fatal(err)
}
}

View File

@ -37,13 +37,18 @@ func init() {
rand.Seed(time.Now().UnixNano())
}
var offscreen = ebiten.NewImage(screenWidth, screenHeight)
type Game struct {
offscreen *ebiten.Image
}
func NewGame() *Game {
return &Game{
offscreen: ebiten.NewImage(screenWidth, screenHeight),
}
}
func (g *Game) Update() error {
w, h := offscreen.Size()
w, h := g.offscreen.Size()
x := rand.Intn(w)
y := rand.Intn(h)
c := color.RGBA{
@ -52,12 +57,12 @@ func (g *Game) Update() error {
byte(rand.Intn(256)),
byte(0xff),
}
offscreen.Set(x, y, c)
g.offscreen.Set(x, y, c)
return nil
}
func (g *Game) Draw(screen *ebiten.Image) {
screen.DrawImage(offscreen, nil)
screen.DrawImage(g.offscreen, nil)
ebitenutil.DebugPrint(screen, fmt.Sprintf("TPS: %0.2f\nFPS: %0.2f", ebiten.CurrentTPS(), ebiten.CurrentFPS()))
}
@ -68,7 +73,7 @@ func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
func main() {
ebiten.SetWindowSize(screenWidth*2, screenHeight*2)
ebiten.SetWindowTitle("Set (Ebiten Demo)")
if err := ebiten.RunGame(&Game{}); err != nil {
if err := ebiten.RunGame(NewGame()); err != nil {
log.Fatal(err)
}
}

View File

@ -34,8 +34,6 @@ const (
frequency = 440
)
var audioContext = audio.NewContext(sampleRate)
// stream is an infinite stream of 440 Hz sine wave.
type stream struct {
position int64
@ -87,15 +85,19 @@ func (s *stream) Close() error {
}
type Game struct {
audioContext *audio.Context
player *audio.Player
}
func (g *Game) Update() error {
if g.audioContext == nil {
g.audioContext = audio.NewContext(sampleRate)
}
if g.player == nil {
// Pass the (infinite) stream to audio.NewPlayer.
// After calling Play, the stream never ends as long as the player object lives.
var err error
g.player, err = audio.NewPlayer(audioContext, &stream{})
g.player, err = audio.NewPlayer(g.audioContext, &stream{})
if err != nil {
return err
}

View File

@ -78,10 +78,7 @@ var (
},
},
}
selectedPalette = 0
colorCycle = 0
canvas = ebiten.NewImage(width, height)
auto *automaton
// blocker is an arbitrary color used to prevent the
// squirals from leaving the canvas.
blocker = color.RGBA{0, 0, 0, 254}
@ -130,7 +127,7 @@ type squiral struct {
dead bool
}
func (s *squiral) spawn() {
func (s *squiral) spawn(game *Game) {
s.dead = false
rx := rand.Intn(width-4) + 2
@ -139,7 +136,7 @@ func (s *squiral) spawn() {
for dx := -2; dx <= 2; dx++ {
for dy := -2; dy <= 2; dy++ {
tx, ty := rx+dx, ry+dy
if auto.colorMap[tx][ty] != background {
if game.auto.colorMap[tx][ty] != background {
s.dead = true
return
}
@ -151,13 +148,13 @@ func (s *squiral) spawn() {
s.pos.y = ry
s.dir = rand.Intn(4)
colorCycle = (colorCycle + 1) % len(palettes[selectedPalette].colors)
s.col = palettes[selectedPalette].colors[colorCycle]
game.colorCycle = (game.colorCycle + 1) % len(palettes[game.selectedPalette].colors)
s.col = palettes[game.selectedPalette].colors[game.colorCycle]
s.rot = rand.Intn(2)
}
func (s *squiral) step(debug int) {
func (s *squiral) step(game *Game) {
if s.dead {
return
}
@ -179,7 +176,7 @@ func (s *squiral) step(debug int) {
x: x + off.x,
y: y + off.y,
}
if auto.colorMap[target.x][target.y] == background {
if game.auto.colorMap[target.x][target.y] == background {
// If the target is free we need to also check the
// surrounding cells.
@ -189,7 +186,7 @@ func (s *squiral) step(debug int) {
x: target.x + off.x,
y: target.y + off.y,
}
if auto.colorMap[ntarg.x][ntarg.y] == s.col {
if game.auto.colorMap[ntarg.x][ntarg.y] == s.col {
// If this has the same color, we cannot go into this direction,
// to avoid ugly blocks of equal color.
continue // try next direction
@ -206,7 +203,7 @@ func (s *squiral) step(debug int) {
// If one of the outer targets equals the squiral's
// color, again continue with next direction.
if auto.colorMap[xtarg.x][xtarg.y] == s.col {
if game.auto.colorMap[xtarg.x][xtarg.y] == s.col {
// If this is not free we cannot go into this direction.
set = false
break // try next direction
@ -217,7 +214,7 @@ func (s *squiral) step(debug int) {
// If one of the outer targets equals the squiral's
// color, again continue with next direction.
if auto.colorMap[xtarg.x][xtarg.y] == s.col {
if game.auto.colorMap[xtarg.x][xtarg.y] == s.col {
// If this is not free we cannot go into this direction.
set = false
break // try next direction
@ -229,7 +226,7 @@ func (s *squiral) step(debug int) {
s.dir = dir
// 2. set the color of this squiral to its
// current position.
setpix(s.pos, s.col)
game.setpix(s.pos, s.col)
return
}
}
@ -243,50 +240,60 @@ type automaton struct {
colorMap [width][height]color.Color
}
func (au *automaton) init() {
func (au *automaton) init(game *Game) {
// Init the test grid with color (0,0,0,0) and the borders of
// it with color(0,0,0,254) as a blocker color, so the squirals
// cannot escape the scene.
for x := 0; x < width; x++ {
for y := 0; y < height; y++ {
if x == 0 || x == width-1 || y == 0 || y == height-1 {
auto.colorMap[x][y] = blocker
au.colorMap[x][y] = blocker
} else {
auto.colorMap[x][y] = background
au.colorMap[x][y] = background
}
}
}
for i := 0; i < numOfSquirals; i++ {
auto.squirals[i].spawn()
au.squirals[i].spawn(game)
}
}
func (au *automaton) step() {
func (a *automaton) step(game *Game) {
for i := 0; i < numOfSquirals; i++ {
for s := 0; s < au.squirals[i].speed; s++ {
au.squirals[i].step(i)
if au.squirals[i].dead {
au.squirals[i].spawn()
for s := 0; s < a.squirals[i].speed; s++ {
a.squirals[i].step(game)
if a.squirals[i].dead {
a.squirals[i].spawn(game)
}
}
}
}
func setpix(xy vec2, col color.Color) {
canvas.Set(xy.x, xy.y, col)
auto.colorMap[xy.x][xy.y] = col
}
func init() {
rand.Seed(time.Now().UnixNano())
canvas.Fill(background)
auto = &automaton{}
auto.init()
}
type Game struct{}
type Game struct {
selectedPalette int
colorCycle int
canvas *ebiten.Image
auto automaton
}
func NewGame() *Game {
g := &Game{
canvas: ebiten.NewImage(width, height),
}
g.canvas.Fill(background)
g.auto.init(g)
return g
}
func (g *Game) setpix(xy vec2, col color.Color) {
g.canvas.Set(xy.x, xy.y, col)
g.auto.colorMap[xy.x][xy.y] = col
}
func (g *Game) Update() error {
reset := false
@ -299,24 +306,24 @@ func (g *Game) Update() error {
}
reset = true
} else if inpututil.IsKeyJustPressed(ebiten.KeyT) {
selectedPalette = (selectedPalette + 1) % len(palettes)
g.selectedPalette = (g.selectedPalette + 1) % len(palettes)
reset = true
} else if inpututil.IsKeyJustPressed(ebiten.KeyR) {
reset = true
}
if reset {
canvas.Fill(background)
auto.init()
g.canvas.Fill(background)
g.auto.init(g)
}
auto.step()
g.auto.step(g)
return nil
}
func (g *Game) Draw(screen *ebiten.Image) {
screen.DrawImage(canvas, nil)
screen.DrawImage(g.canvas, nil)
ebitenutil.DebugPrintAt(
screen,
fmt.Sprintf("TPS: %0.2f, FPS: %0.2f", ebiten.CurrentTPS(), ebiten.CurrentFPS()),
@ -334,7 +341,7 @@ func (g *Game) Draw(screen *ebiten.Image) {
)
ebitenutil.DebugPrintAt(
screen,
fmt.Sprintf("[t]: cycle theme (current: %s)", palettes[selectedPalette].name),
fmt.Sprintf("[t]: cycle theme (current: %s)", palettes[g.selectedPalette].name),
1, 48,
)
}
@ -347,7 +354,7 @@ func main() {
ebiten.SetMaxTPS(250)
ebiten.SetWindowSize(width*scale, height*scale)
ebiten.SetWindowTitle("Squirals (Ebiten Demo)")
if err := ebiten.RunGame(&Game{}); err != nil {
if err := ebiten.RunGame(NewGame()); err != nil {
log.Fatal(err)
}
}

View File

@ -454,9 +454,8 @@ type Game struct {
textBoxLog *TextBox
}
var g Game
func init() {
func NewGame() *Game {
g := &Game{}
g.button1 = &Button{
Rect: image.Rect(16, 16, 144, 48),
Text: "Button 1",
@ -489,6 +488,7 @@ func init() {
}
g.textBoxLog.AppendLine(msg)
})
return g
}
func (g *Game) Update() error {
@ -514,7 +514,7 @@ func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
func main() {
ebiten.SetWindowSize(screenWidth, screenHeight)
ebiten.SetWindowTitle("UI (Ebiten Demo)")
if err := ebiten.RunGame(&g); err != nil {
if err := ebiten.RunGame(NewGame()); err != nil {
log.Fatal(err)
}
}

View File

@ -40,9 +40,9 @@ type Game struct {
audioPlayer *audio.Player
}
var g Game
func NewGame() (*Game, error) {
g := &Game{}
func init() {
var err error
// Initialize audio context.
g.audioContext = audio.NewContext(sampleRate)
@ -64,14 +64,16 @@ func init() {
// Decode wav-formatted data and retrieve decoded PCM stream.
d, err := wav.Decode(g.audioContext, bytes.NewReader(raudio.Jab_wav))
if err != nil {
log.Fatal(err)
return nil, err
}
// Create an audio.Player that has one stream.
g.audioPlayer, err = audio.NewPlayer(g.audioContext, d)
if err != nil {
log.Fatal(err)
return nil, err
}
return g, nil
}
func (g *Game) Update() error {
@ -97,9 +99,13 @@ func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
}
func main() {
g, err := NewGame()
if err != nil {
log.Fatal(err)
}
ebiten.SetWindowSize(screenWidth, screenHeight)
ebiten.SetWindowTitle("WAV (Ebiten Demo)")
if err := ebiten.RunGame(&g); err != nil {
if err := ebiten.RunGame(g); err != nil {
log.Fatal(err)
}
}

View File

@ -64,7 +64,6 @@ const (
var (
gophersImage *ebiten.Image
count = 0
)
func createRandomIconImage() image.Image {
@ -92,6 +91,7 @@ func createRandomIconImage() image.Image {
}
type game struct {
count int
width int
height int
transparent bool
@ -276,7 +276,7 @@ func (g *game) Update() error {
ebiten.SetWindowIcon([]image.Image{createRandomIconImage()})
}
count++
g.count++
return nil
}
@ -285,8 +285,8 @@ func (g *game) Draw(screen *ebiten.Image) {
w2, h2 := screen.Size()
op := &ebiten.DrawImageOptions{}
op.GeoM.Translate(float64(-w+w2)/2, float64(-h+h2)/2)
dx := math.Cos(2*math.Pi*float64(count)/360) * 20
dy := math.Sin(2*math.Pi*float64(count)/360) * 20
dx := math.Cos(2*math.Pi*float64(g.count)/360) * 20
dy := math.Sin(2*math.Pi*float64(g.count)/360) * 20
op.GeoM.Translate(dx, dy)
screen.DrawImage(gophersImage, op)