diff --git a/docs/examples/audio.html b/docs/examples/audio.html index 1c3b9d478..5c957d860 100644 --- a/docs/examples/audio.html +++ b/docs/examples/audio.html @@ -46,6 +46,10 @@ import ( const ( screenWidth = 320 screenHeight = 240 + + // This sample rate doesn't match with wav/ogg's sample rate, + // but decoders adjust them. + sampleRate = 48000 ) var ( @@ -54,39 +58,59 @@ var ( ) func init() { - var err error - playerBarImage, err = ebiten.NewImage(300, 4, ebiten.FilterNearest) - if err != nil { - log.Fatal(err) - } - if err := playerBarImage.Fill(&color.RGBA{0x80, 0x80, 0x80, 0xff}); err != nil { - log.Fatal(err) - } + playerBarImage, _ = ebiten.NewImage(300, 4, ebiten.FilterNearest) + playerBarImage.Fill(&color.RGBA{0x80, 0x80, 0x80, 0xff}) - playerCurrentImage, err = ebiten.NewImage(4, 10, ebiten.FilterNearest) - if err != nil { - log.Fatal(err) + playerCurrentImage, _ = ebiten.NewImage(4, 10, ebiten.FilterNearest) + playerCurrentImage.Fill(&color.RGBA{0xff, 0xff, 0xff, 0xff}) +} + +type Input struct { + mouseButtonStates map[ebiten.MouseButton]int + keyStates map[ebiten.Key]int +} + +func (i *Input) update() { + for _, key := range []ebiten.Key{ebiten.KeyP, ebiten.KeyS, ebiten.KeyX, ebiten.KeyZ} { + if !ebiten.IsKeyPressed(key) { + i.keyStates[key] = 0 + } else { + i.keyStates[key]++ + } } - if err := playerCurrentImage.Fill(&color.RGBA{0xff, 0xff, 0xff, 0xff}); err != nil { - log.Fatal(err) + if !ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) { + i.mouseButtonStates[ebiten.MouseButtonLeft] = 0 + } else { + i.mouseButtonStates[ebiten.MouseButtonLeft]++ } } +func (i *Input) isKeyTriggered(key ebiten.Key) bool { + return i.keyStates[key] == 1 +} + +func (i *Input) isKeyPressed(key ebiten.Key) bool { + return i.keyStates[key] > 0 +} + +func (i *Input) isMouseButtonTriggered(mouseButton ebiten.MouseButton) bool { + return i.mouseButtonStates[mouseButton] == 1 +} + type Player struct { - audioPlayer *audio.Player - total time.Duration - seekedCh chan error + input *Input + audioContext *audio.Context + audioPlayer *audio.Player + total time.Duration + seekedCh chan error + seBytes []uint8 + seCh chan []uint8 + volume128 int + previousPos time.Duration } var ( - audioContext *audio.Context - musicPlayer *Player - seBytes []byte - musicCh = make(chan *Player) - seCh = make(chan []byte) - mouseButtonState = map[ebiten.MouseButton]int{} - keyState = map[ebiten.Key]int{} - volume128 = 128 + musicPlayer *Player ) func playerBarRect() (x, y, w, h int) { @@ -96,77 +120,122 @@ func playerBarRect() (x, y, w, h int) { return } -func (p *Player) updateSE() error { - if seBytes == nil { - return nil - } - if !ebiten.IsKeyPressed(ebiten.KeyP) { - keyState[ebiten.KeyP] = 0 - return nil - } - keyState[ebiten.KeyP]++ - if keyState[ebiten.KeyP] != 1 { - return nil - } - sePlayer, err := audio.NewPlayerFromBytes(audioContext, seBytes) +func NewPlayer(audioContext *audio.Context) (*Player, error) { + const bytesPerSample = 4 // TODO: This should be defined in audio package + wavF, err := ebitenutil.OpenFile("_resources/audio/jab.wav") if err != nil { + return nil, err + } + oggF, err := ebitenutil.OpenFile("_resources/audio/ragtime.ogg") + if err != nil { + return nil, err + } + s, err := vorbis.Decode(audioContext, oggF) + if err != nil { + return nil, err + } + p, err := audio.NewPlayer(audioContext, s) + if err != nil { + return nil, err + } + player := &Player{ + input: &Input{ + mouseButtonStates: map[ebiten.MouseButton]int{}, + keyStates: map[ebiten.Key]int{}, + }, + audioContext: audioContext, + audioPlayer: p, + total: time.Second * time.Duration(s.Size()) / bytesPerSample / sampleRate, + volume128: 128, + seCh: make(chan []uint8), + } + player.audioPlayer.Play() + go func() { + s, err := wav.Decode(audioContext, wavF) + if err != nil { + log.Fatal(err) + return + } + b, err := ioutil.ReadAll(s) + if err != nil { + log.Fatal(err) + return + } + player.seCh <- b + }() + return player, nil +} + +func (p *Player) update() error { + p.input.update() + select { + case p.seBytes = <-p.seCh: + close(p.seCh) + p.seCh = nil + case err := <-p.seekedCh: + if err != nil { + return err + } + close(p.seekedCh) + p.seekedCh = nil + default: + } + p.updateBar() + p.updatePlayPause() + p.updateSE() + p.updateVolume() + if err := p.audioContext.Update(); err != nil { return err } - return sePlayer.Play() + return nil +} + +func (p *Player) updateSE() { + if p.seBytes == nil { + return + } + if !p.input.isKeyTriggered(ebiten.KeyP) { + return + } + sePlayer, _ := audio.NewPlayerFromBytes(p.audioContext, p.seBytes) + sePlayer.Play() } func (p *Player) updateVolume() { - if p.audioPlayer == nil { - return + if p.input.isKeyPressed(ebiten.KeyZ) { + p.volume128-- } - if ebiten.IsKeyPressed(ebiten.KeyZ) { - volume128-- + if p.input.isKeyPressed(ebiten.KeyX) { + p.volume128++ } - if ebiten.IsKeyPressed(ebiten.KeyX) { - volume128++ + if p.volume128 < 0 { + p.volume128 = 0 } - if volume128 < 0 { - volume128 = 0 + if 128 < p.volume128 { + p.volume128 = 128 } - if 128 < volume128 { - volume128 = 128 - } - p.audioPlayer.SetVolume(float64(volume128) / 128) + p.audioPlayer.SetVolume(float64(p.volume128) / 128) } -func (p *Player) updatePlayPause() error { - if p.audioPlayer == nil { - return nil - } - if !ebiten.IsKeyPressed(ebiten.KeyS) { - keyState[ebiten.KeyS] = 0 - return nil - } - keyState[ebiten.KeyS]++ - if keyState[ebiten.KeyS] != 1 { - return nil +func (p *Player) updatePlayPause() { + if !p.input.isKeyTriggered(ebiten.KeyS) { + return } if p.audioPlayer.IsPlaying() { - return p.audioPlayer.Pause() + p.audioPlayer.Pause() + return } - return p.audioPlayer.Play() + p.audioPlayer.Play() } func (p *Player) updateBar() { - if p.audioPlayer == nil { - return - } if p.seekedCh != nil { return } - if !ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) { - mouseButtonState[ebiten.MouseButtonLeft] = 0 - return - } - mouseButtonState[ebiten.MouseButtonLeft]++ - if mouseButtonState[ebiten.MouseButtonLeft] != 1 { + if !p.input.isMouseButtonTriggered(ebiten.MouseButtonLeft) { return } + // Start seeking. x, y := ebiten.CursorPosition() bx, by, bw, bh := playerBarRect() const padding = 4 @@ -179,7 +248,6 @@ func (p *Player) updateBar() { pos := time.Duration(x-bx) * p.total / time.Duration(bw) p.seekedCh = make(chan error, 1) go func() { - // This can't be done parallely! !?!? p.seekedCh <- p.audioPlayer.Seek(pos) }() } @@ -188,133 +256,60 @@ func (p *Player) close() error { return p.audioPlayer.Close() } -func update(screen *ebiten.Image) error { - if musicPlayer == nil { - select { - case musicPlayer = <-musicCh: - default: - } - } - if seBytes == nil { - select { - case seBytes = <-seCh: - default: - } - } - if musicPlayer != nil { - musicPlayer.updateBar() - if err := musicPlayer.updatePlayPause(); err != nil { - return err - } - if err := musicPlayer.updateSE(); err != nil { - return err - } - musicPlayer.updateVolume() - } - if ebiten.IsRunningSlowly() { - return nil - } +func (p *Player) draw(screen *ebiten.Image) { op := &ebiten.DrawImageOptions{} x, y, w, h := playerBarRect() op.GeoM.Translate(float64(x), float64(y)) screen.DrawImage(playerBarImage, op) currentTimeStr := "00:00" - if musicPlayer != nil { - c := musicPlayer.audioPlayer.Current() + c := p.audioPlayer.Current() + prev := p.previousPos + p.previousPos = c - // Current Time - m := (c / time.Minute) % 100 - s := (c / time.Second) % 60 - currentTimeStr = fmt.Sprintf("%02d:%02d", m, s) + // Current Time + m := (c / time.Minute) % 100 + s := (c / time.Second) % 60 + currentTimeStr = fmt.Sprintf("%02d:%02d", m, s) - // Bar - cw, ch := playerCurrentImage.Size() - cx := int(time.Duration(w)*c/musicPlayer.total) + x - cw/2 - cy := y - (ch-h)/2 - op := &ebiten.DrawImageOptions{} - op.GeoM.Translate(float64(cx), float64(cy)) - screen.DrawImage(playerCurrentImage, op) - } + // Bar + cw, ch := playerCurrentImage.Size() + cx := int(time.Duration(w)*c/p.total) + x - cw/2 + cy := y - (ch-h)/2 + op = &ebiten.DrawImageOptions{} + op.GeoM.Translate(float64(cx), float64(cy)) + screen.DrawImage(playerCurrentImage, op) msg := fmt.Sprintf(`FPS: %0.2f Press S to toggle Play/Pause Press P to play SE Press Z or X to change volume of the music %s`, ebiten.CurrentFPS(), currentTimeStr) - if musicPlayer == nil { - msg += "\nNow Loading..." - } else if musicPlayer.seekedCh != nil { - select { - case err := <-musicPlayer.seekedCh: - if err != nil { - return err - } - close(musicPlayer.seekedCh) - musicPlayer.seekedCh = nil - default: - msg += "\nSeeking..." - } + if p.audioPlayer.IsPlaying() && prev == c { + msg += "\nLoading..." } ebitenutil.DebugPrint(screen, msg) - if err := audioContext.Update(); err != nil { +} + +func update(screen *ebiten.Image) error { + if err := musicPlayer.update(); err != nil { return err } + if ebiten.IsRunningSlowly() { + return nil + } + musicPlayer.draw(screen) return nil } func main() { - wavF, err := ebitenutil.OpenFile("_resources/audio/jab.wav") + audioContext, err := audio.NewContext(sampleRate) if err != nil { log.Fatal(err) } - oggF, err := ebitenutil.OpenFile("_resources/audio/game.ogg") + musicPlayer, err = NewPlayer(audioContext) if err != nil { log.Fatal(err) } - // This sample rate doesn't match with wav/ogg's sample rate, - // but decoders adjust them. - const sampleRate = 48000 - const bytesPerSample = 4 // TODO: This should be defined in audio package - audioContext, err = audio.NewContext(sampleRate) - if err != nil { - log.Fatal(err) - } - go func() { - s, err := wav.Decode(audioContext, wavF) - if err != nil { - log.Fatal(err) - return - } - b, err := ioutil.ReadAll(s) - if err != nil { - log.Fatal(err) - return - } - seCh <- b - close(seCh) - }() - go func() { - s, err := vorbis.Decode(audioContext, oggF) - if err != nil { - log.Fatal(err) - return - } - p, err := audio.NewPlayer(audioContext, s) - if err != nil { - log.Fatal(err) - return - } - musicCh <- &Player{ - audioPlayer: p, - total: time.Second * time.Duration(s.Size()) / bytesPerSample / sampleRate, - } - close(musicCh) - // TODO: Is this goroutine-safe? - if err := p.Play(); err != nil { - log.Fatal(err) - return - } - }() if err := ebiten.Run(update, screenWidth, screenHeight, 2, "Audio (Ebiten Demo)"); err != nil { log.Fatal(err) } diff --git a/docs/examples/infinitescroll.html b/docs/examples/infinitescroll.html index b1ac9db45..a4b0f80bc 100644 --- a/docs/examples/infinitescroll.html +++ b/docs/examples/infinitescroll.html @@ -130,7 +130,7 @@ func main() { } groundImage, _ = ebiten.NewImage(screenWidth, screenHeight, ebiten.FilterNearest) - if err := ebiten.Run(update, screenWidth, screenHeight, 2, "infinite scroll"); err != nil { + if err := ebiten.Run(update, screenWidth, screenHeight, 2, "Infinite Scroll (Ebiten Demo)"); err != nil { log.Fatal(err) } } diff --git a/docs/examples/life.html b/docs/examples/life.html index 8d3ce25df..ebef0e103 100644 --- a/docs/examples/life.html +++ b/docs/examples/life.html @@ -30,7 +30,6 @@ package main import ( - "image" "log" "math/rand" "time" @@ -38,20 +37,18 @@ import ( "github.com/hajimehoshi/ebiten" ) -var ( - randSource = rand.NewSource(time.Now().UnixNano()) - rnd = rand.New(randSource) -) - // World represents the game state type World struct { area [][]bool + rnd *rand.Rand } // NewWorld creates a new world func NewWorld(width, height int) *World { - world := World{} - world.area = makeArea(width, height) + world := World{ + area: makeArea(width, height), + rnd: rand.New(rand.NewSource(time.Now().UnixNano())), + } return &world } @@ -59,10 +56,9 @@ func NewWorld(width, height int) *World { func (w *World) RandomSeed(limit int) { height := len(w.area) width := len(w.area[0]) - for i := 0; i < limit; i++ { - x := rnd.Intn(width) - y := rnd.Intn(height) + x := w.rnd.Intn(width) + y := w.rnd.Intn(height) w.area[y][x] = true } } @@ -71,9 +67,7 @@ func (w *World) RandomSeed(limit int) { func (w *World) Progress() { height := len(w.area) width := len(w.area[0]) - next := makeArea(width, height) - for y := 0; y < height; y++ { for x := 0; x < width; x++ { @@ -105,23 +99,22 @@ func (w *World) Progress() { } // DrawImage paints current game state -func (w *World) DrawImage(img *image.RGBA) { +func (w *World) DrawImage(pix []uint8) { height := len(w.area) width := len(w.area[0]) - for y := 0; y < height; y++ { for x := 0; x < width; x++ { pos := 4*y*width + 4*x if w.area[y][x] { - img.Pix[pos] = 0xff - img.Pix[pos+1] = 0xff - img.Pix[pos+2] = 0xff - img.Pix[pos+3] = 0xff + pix[pos] = 0xff + pix[pos+1] = 0xff + pix[pos+2] = 0xff + pix[pos+3] = 0xff } else { - img.Pix[pos] = 0 - img.Pix[pos+1] = 0 - img.Pix[pos+2] = 0 - img.Pix[pos+3] = 0 + pix[pos] = 0 + pix[pos+1] = 0 + pix[pos+2] = 0 + pix[pos+3] = 0 } } } @@ -131,27 +124,22 @@ func (w *World) DrawImage(img *image.RGBA) { func neighbourCount(a [][]bool, x, y int) int { height := len(a) width := len(a[0]) - lowX := 0 if x > 0 { lowX = x - 1 } - lowY := 0 if y > 0 { lowY = y - 1 } - highX := width - 1 if x < width-1 { highX = x + 1 } - highY := height - 1 if y < height-1 { highY = y + 1 } - near := 0 for pY := lowY; pY <= highY; pY++ { for pX := lowX; pX <= highX; pX++ { @@ -178,8 +166,8 @@ const ( ) var ( - world *World - noiseImage *image.RGBA + world = NewWorld(screenWidth, screenHeight) + pixels = make([]uint8, screenWidth*screenHeight*4) ) func update(screen *ebiten.Image) error { @@ -187,20 +175,14 @@ func update(screen *ebiten.Image) error { if ebiten.IsRunningSlowly() { return nil } - world.DrawImage(noiseImage) - screen.ReplacePixels(noiseImage.Pix) + world.DrawImage(pixels) + screen.ReplacePixels(pixels) return nil } func main() { - population := int((screenWidth * screenHeight) / 10) - scale := 2.0 - - world = NewWorld(screenWidth, screenHeight) - world.RandomSeed(population) - - noiseImage = image.NewRGBA(image.Rect(0, 0, screenWidth, screenHeight)) - if err := ebiten.Run(update, screenWidth, screenHeight, scale, "Game of Life (Ebiten Demo)"); err != nil { + world.RandomSeed(int((screenWidth * screenHeight) / 10)) + if err := ebiten.Run(update, screenWidth, screenHeight, 2.0, "Game of Life (Ebiten Demo)"); err != nil { log.Fatal(err) } } diff --git a/docs/examples/paint.html b/docs/examples/paint.html index 80cdd62d7..336a8ad68 100644 --- a/docs/examples/paint.html +++ b/docs/examples/paint.html @@ -55,7 +55,7 @@ func paint(screen *ebiten.Image, x, y int) { op := &ebiten.DrawImageOptions{} op.GeoM.Translate(float64(x), float64(y)) op.ColorM.Scale(1.0, 0.50, 0.125, 1.0) - theta := 2.0 * math.Pi * float64(count%60) / ebiten.FPS + theta := 2.0 * math.Pi * float64(count%ebiten.FPS) / ebiten.FPS op.ColorM.RotateHue(theta) canvasImage.DrawImage(brushImage, op) } diff --git a/docs/examples/piano.html b/docs/examples/piano.html index c5be21dff..9ebcb8a80 100644 --- a/docs/examples/piano.html +++ b/docs/examples/piano.html @@ -94,20 +94,15 @@ func toBytes(l, r []int16) []byte { return b } -func addNote(freq float64, vol float64) error { +func addNote(freq float64, vol float64) { // TODO: Call Close method of *audio.Player. // However, this works without Close because Close is automatically called when GC // collects a *audio.Player object. f := int(freq) if n, ok := noteCache[f]; ok { - p, err := audio.NewPlayerFromBytes(audioContext, n) - if err != nil { - return err - } - if err := p.Play(); err != nil { - return err - } - return nil + p, _ := audio.NewPlayerFromBytes(audioContext, n) + p.Play() + return } length := len(pcm) * baseFreq / f l := make([]int16, length) @@ -123,14 +118,9 @@ func addNote(freq float64, vol float64) error { } n := toBytes(l, r) noteCache[f] = n - p, err := audio.NewPlayerFromBytes(audioContext, n) - if err != nil { - return err - } - if err := p.Play(); err != nil { - return err - } - return nil + p, _ := audio.NewPlayerFromBytes(audioContext, n) + p.Play() + return } var keys = []ebiten.Key{ @@ -216,9 +206,10 @@ func update(screen *ebiten.Image) error { if keyStates[key] != 1 { continue } - if err := addNote(220*math.Exp2(float64(i-1)/12.0), 1.0); err != nil { - return err - } + addNote(220*math.Exp2(float64(i-1)/12.0), 1.0) + } + if err := audioContext.Update(); err != nil { + return err } if ebiten.IsRunningSlowly() { return nil @@ -227,10 +218,6 @@ func update(screen *ebiten.Image) error { screen.DrawImage(imagePiano, nil) ebitenutil.DebugPrint(screen, fmt.Sprintf("FPS: %0.2f", ebiten.CurrentFPS())) - - if err := audioContext.Update(); err != nil { - return err - } return nil } diff --git a/docs/index.html b/docs/index.html index ff6e433e7..6b6c79a1c 100644 --- a/docs/index.html +++ b/docs/index.html @@ -31,7 +31,7 @@

Ebiten

A simple 2D game library in Go

-

Stable version: v1.4.0-rc1 / Development version: v1.5.0-alpha

+

Stable version: v1.5.0 / Development version: v1.6.0-alpha

Platforms