doc: Update

This commit is contained in:
Hajime Hoshi 2017-06-09 11:43:50 +09:00
parent db74b4c34c
commit 989aef8e2b
6 changed files with 204 additions and 240 deletions

View File

@ -46,6 +46,10 @@ import (
const ( const (
screenWidth = 320 screenWidth = 320
screenHeight = 240 screenHeight = 240
// This sample rate doesn't match with wav/ogg's sample rate,
// but decoders adjust them.
sampleRate = 48000
) )
var ( var (
@ -54,39 +58,59 @@ var (
) )
func init() { func init() {
var err error playerBarImage, _ = ebiten.NewImage(300, 4, ebiten.FilterNearest)
playerBarImage, err = ebiten.NewImage(300, 4, ebiten.FilterNearest) playerBarImage.Fill(&color.RGBA{0x80, 0x80, 0x80, 0xff})
if err != nil {
log.Fatal(err)
}
if err := playerBarImage.Fill(&color.RGBA{0x80, 0x80, 0x80, 0xff}); err != nil {
log.Fatal(err)
}
playerCurrentImage, err = ebiten.NewImage(4, 10, ebiten.FilterNearest) playerCurrentImage, _ = ebiten.NewImage(4, 10, ebiten.FilterNearest)
if err != nil { playerCurrentImage.Fill(&color.RGBA{0xff, 0xff, 0xff, 0xff})
log.Fatal(err) }
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 { if !ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) {
log.Fatal(err) 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 { type Player struct {
audioPlayer *audio.Player input *Input
total time.Duration audioContext *audio.Context
seekedCh chan error audioPlayer *audio.Player
total time.Duration
seekedCh chan error
seBytes []uint8
seCh chan []uint8
volume128 int
previousPos time.Duration
} }
var ( var (
audioContext *audio.Context musicPlayer *Player
musicPlayer *Player
seBytes []byte
musicCh = make(chan *Player)
seCh = make(chan []byte)
mouseButtonState = map[ebiten.MouseButton]int{}
keyState = map[ebiten.Key]int{}
volume128 = 128
) )
func playerBarRect() (x, y, w, h int) { func playerBarRect() (x, y, w, h int) {
@ -96,77 +120,122 @@ func playerBarRect() (x, y, w, h int) {
return return
} }
func (p *Player) updateSE() error { func NewPlayer(audioContext *audio.Context) (*Player, error) {
if seBytes == nil { const bytesPerSample = 4 // TODO: This should be defined in audio package
return nil wavF, err := ebitenutil.OpenFile("_resources/audio/jab.wav")
}
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)
if err != nil { 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 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() { func (p *Player) updateVolume() {
if p.audioPlayer == nil { if p.input.isKeyPressed(ebiten.KeyZ) {
return p.volume128--
} }
if ebiten.IsKeyPressed(ebiten.KeyZ) { if p.input.isKeyPressed(ebiten.KeyX) {
volume128-- p.volume128++
} }
if ebiten.IsKeyPressed(ebiten.KeyX) { if p.volume128 < 0 {
volume128++ p.volume128 = 0
} }
if volume128 < 0 { if 128 < p.volume128 {
volume128 = 0 p.volume128 = 128
} }
if 128 < volume128 { p.audioPlayer.SetVolume(float64(p.volume128) / 128)
volume128 = 128
}
p.audioPlayer.SetVolume(float64(volume128) / 128)
} }
func (p *Player) updatePlayPause() error { func (p *Player) updatePlayPause() {
if p.audioPlayer == nil { if !p.input.isKeyTriggered(ebiten.KeyS) {
return nil return
}
if !ebiten.IsKeyPressed(ebiten.KeyS) {
keyState[ebiten.KeyS] = 0
return nil
}
keyState[ebiten.KeyS]++
if keyState[ebiten.KeyS] != 1 {
return nil
} }
if p.audioPlayer.IsPlaying() { if p.audioPlayer.IsPlaying() {
return p.audioPlayer.Pause() p.audioPlayer.Pause()
return
} }
return p.audioPlayer.Play() p.audioPlayer.Play()
} }
func (p *Player) updateBar() { func (p *Player) updateBar() {
if p.audioPlayer == nil {
return
}
if p.seekedCh != nil { if p.seekedCh != nil {
return return
} }
if !ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) { if !p.input.isMouseButtonTriggered(ebiten.MouseButtonLeft) {
mouseButtonState[ebiten.MouseButtonLeft] = 0
return
}
mouseButtonState[ebiten.MouseButtonLeft]++
if mouseButtonState[ebiten.MouseButtonLeft] != 1 {
return return
} }
// Start seeking.
x, y := ebiten.CursorPosition() x, y := ebiten.CursorPosition()
bx, by, bw, bh := playerBarRect() bx, by, bw, bh := playerBarRect()
const padding = 4 const padding = 4
@ -179,7 +248,6 @@ func (p *Player) updateBar() {
pos := time.Duration(x-bx) * p.total / time.Duration(bw) pos := time.Duration(x-bx) * p.total / time.Duration(bw)
p.seekedCh = make(chan error, 1) p.seekedCh = make(chan error, 1)
go func() { go func() {
// This can't be done parallely! !?!?
p.seekedCh <- p.audioPlayer.Seek(pos) p.seekedCh <- p.audioPlayer.Seek(pos)
}() }()
} }
@ -188,133 +256,60 @@ func (p *Player) close() error {
return p.audioPlayer.Close() return p.audioPlayer.Close()
} }
func update(screen *ebiten.Image) error { func (p *Player) draw(screen *ebiten.Image) {
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
}
op := &ebiten.DrawImageOptions{} op := &ebiten.DrawImageOptions{}
x, y, w, h := playerBarRect() x, y, w, h := playerBarRect()
op.GeoM.Translate(float64(x), float64(y)) op.GeoM.Translate(float64(x), float64(y))
screen.DrawImage(playerBarImage, op) screen.DrawImage(playerBarImage, op)
currentTimeStr := "00:00" currentTimeStr := "00:00"
if musicPlayer != nil { c := p.audioPlayer.Current()
c := musicPlayer.audioPlayer.Current() prev := p.previousPos
p.previousPos = c
// Current Time // Current Time
m := (c / time.Minute) % 100 m := (c / time.Minute) % 100
s := (c / time.Second) % 60 s := (c / time.Second) % 60
currentTimeStr = fmt.Sprintf("%02d:%02d", m, s) currentTimeStr = fmt.Sprintf("%02d:%02d", m, s)
// Bar // Bar
cw, ch := playerCurrentImage.Size() cw, ch := playerCurrentImage.Size()
cx := int(time.Duration(w)*c/musicPlayer.total) + x - cw/2 cx := int(time.Duration(w)*c/p.total) + x - cw/2
cy := y - (ch-h)/2 cy := y - (ch-h)/2
op := &ebiten.DrawImageOptions{} op = &ebiten.DrawImageOptions{}
op.GeoM.Translate(float64(cx), float64(cy)) op.GeoM.Translate(float64(cx), float64(cy))
screen.DrawImage(playerCurrentImage, op) screen.DrawImage(playerCurrentImage, op)
}
msg := fmt.Sprintf(`FPS: %0.2f msg := fmt.Sprintf(`FPS: %0.2f
Press S to toggle Play/Pause Press S to toggle Play/Pause
Press P to play SE Press P to play SE
Press Z or X to change volume of the music Press Z or X to change volume of the music
%s`, ebiten.CurrentFPS(), currentTimeStr) %s`, ebiten.CurrentFPS(), currentTimeStr)
if musicPlayer == nil { if p.audioPlayer.IsPlaying() && prev == c {
msg += "\nNow Loading..." msg += "\nLoading..."
} else if musicPlayer.seekedCh != nil {
select {
case err := <-musicPlayer.seekedCh:
if err != nil {
return err
}
close(musicPlayer.seekedCh)
musicPlayer.seekedCh = nil
default:
msg += "\nSeeking..."
}
} }
ebitenutil.DebugPrint(screen, msg) ebitenutil.DebugPrint(screen, msg)
if err := audioContext.Update(); err != nil { }
func update(screen *ebiten.Image) error {
if err := musicPlayer.update(); err != nil {
return err return err
} }
if ebiten.IsRunningSlowly() {
return nil
}
musicPlayer.draw(screen)
return nil return nil
} }
func main() { func main() {
wavF, err := ebitenutil.OpenFile("_resources/audio/jab.wav") audioContext, err := audio.NewContext(sampleRate)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
oggF, err := ebitenutil.OpenFile("_resources/audio/game.ogg") musicPlayer, err = NewPlayer(audioContext)
if err != nil { if err != nil {
log.Fatal(err) 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 { if err := ebiten.Run(update, screenWidth, screenHeight, 2, "Audio (Ebiten Demo)"); err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@ -130,7 +130,7 @@ func main() {
} }
groundImage, _ = ebiten.NewImage(screenWidth, screenHeight, ebiten.FilterNearest) 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) log.Fatal(err)
} }
} }

View File

@ -30,7 +30,6 @@
package main package main
import ( import (
"image"
"log" "log"
"math/rand" "math/rand"
"time" "time"
@ -38,20 +37,18 @@ import (
"github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten"
) )
var (
randSource = rand.NewSource(time.Now().UnixNano())
rnd = rand.New(randSource)
)
// World represents the game state // World represents the game state
type World struct { type World struct {
area [][]bool area [][]bool
rnd *rand.Rand
} }
// NewWorld creates a new world // NewWorld creates a new world
func NewWorld(width, height int) *World { func NewWorld(width, height int) *World {
world := World{} world := World{
world.area = makeArea(width, height) area: makeArea(width, height),
rnd: rand.New(rand.NewSource(time.Now().UnixNano())),
}
return &world return &world
} }
@ -59,10 +56,9 @@ func NewWorld(width, height int) *World {
func (w *World) RandomSeed(limit int) { func (w *World) RandomSeed(limit int) {
height := len(w.area) height := len(w.area)
width := len(w.area[0]) width := len(w.area[0])
for i := 0; i < limit; i++ { for i := 0; i < limit; i++ {
x := rnd.Intn(width) x := w.rnd.Intn(width)
y := rnd.Intn(height) y := w.rnd.Intn(height)
w.area[y][x] = true w.area[y][x] = true
} }
} }
@ -71,9 +67,7 @@ func (w *World) RandomSeed(limit int) {
func (w *World) Progress() { func (w *World) Progress() {
height := len(w.area) height := len(w.area)
width := len(w.area[0]) width := len(w.area[0])
next := makeArea(width, height) next := makeArea(width, height)
for y := 0; y < height; y++ { for y := 0; y < height; y++ {
for x := 0; x < width; x++ { for x := 0; x < width; x++ {
@ -105,23 +99,22 @@ func (w *World) Progress() {
} }
// DrawImage paints current game state // DrawImage paints current game state
func (w *World) DrawImage(img *image.RGBA) { func (w *World) DrawImage(pix []uint8) {
height := len(w.area) height := len(w.area)
width := len(w.area[0]) width := len(w.area[0])
for y := 0; y < height; y++ { for y := 0; y < height; y++ {
for x := 0; x < width; x++ { for x := 0; x < width; x++ {
pos := 4*y*width + 4*x pos := 4*y*width + 4*x
if w.area[y][x] { if w.area[y][x] {
img.Pix[pos] = 0xff pix[pos] = 0xff
img.Pix[pos+1] = 0xff pix[pos+1] = 0xff
img.Pix[pos+2] = 0xff pix[pos+2] = 0xff
img.Pix[pos+3] = 0xff pix[pos+3] = 0xff
} else { } else {
img.Pix[pos] = 0 pix[pos] = 0
img.Pix[pos+1] = 0 pix[pos+1] = 0
img.Pix[pos+2] = 0 pix[pos+2] = 0
img.Pix[pos+3] = 0 pix[pos+3] = 0
} }
} }
} }
@ -131,27 +124,22 @@ func (w *World) DrawImage(img *image.RGBA) {
func neighbourCount(a [][]bool, x, y int) int { func neighbourCount(a [][]bool, x, y int) int {
height := len(a) height := len(a)
width := len(a[0]) width := len(a[0])
lowX := 0 lowX := 0
if x > 0 { if x > 0 {
lowX = x - 1 lowX = x - 1
} }
lowY := 0 lowY := 0
if y > 0 { if y > 0 {
lowY = y - 1 lowY = y - 1
} }
highX := width - 1 highX := width - 1
if x < width-1 { if x < width-1 {
highX = x + 1 highX = x + 1
} }
highY := height - 1 highY := height - 1
if y < height-1 { if y < height-1 {
highY = y + 1 highY = y + 1
} }
near := 0 near := 0
for pY := lowY; pY <= highY; pY++ { for pY := lowY; pY <= highY; pY++ {
for pX := lowX; pX <= highX; pX++ { for pX := lowX; pX <= highX; pX++ {
@ -178,8 +166,8 @@ const (
) )
var ( var (
world *World world = NewWorld(screenWidth, screenHeight)
noiseImage *image.RGBA pixels = make([]uint8, screenWidth*screenHeight*4)
) )
func update(screen *ebiten.Image) error { func update(screen *ebiten.Image) error {
@ -187,20 +175,14 @@ func update(screen *ebiten.Image) error {
if ebiten.IsRunningSlowly() { if ebiten.IsRunningSlowly() {
return nil return nil
} }
world.DrawImage(noiseImage) world.DrawImage(pixels)
screen.ReplacePixels(noiseImage.Pix) screen.ReplacePixels(pixels)
return nil return nil
} }
func main() { func main() {
population := int((screenWidth * screenHeight) / 10) world.RandomSeed(int((screenWidth * screenHeight) / 10))
scale := 2.0 if err := ebiten.Run(update, screenWidth, screenHeight, 2.0, "Game of Life (Ebiten Demo)"); err != nil {
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 {
log.Fatal(err) log.Fatal(err)
} }
} }

View File

@ -55,7 +55,7 @@ func paint(screen *ebiten.Image, x, y int) {
op := &ebiten.DrawImageOptions{} op := &ebiten.DrawImageOptions{}
op.GeoM.Translate(float64(x), float64(y)) op.GeoM.Translate(float64(x), float64(y))
op.ColorM.Scale(1.0, 0.50, 0.125, 1.0) 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) op.ColorM.RotateHue(theta)
canvasImage.DrawImage(brushImage, op) canvasImage.DrawImage(brushImage, op)
} }

View File

@ -94,20 +94,15 @@ func toBytes(l, r []int16) []byte {
return b return b
} }
func addNote(freq float64, vol float64) error { func addNote(freq float64, vol float64) {
// TODO: Call Close method of *audio.Player. // TODO: Call Close method of *audio.Player.
// However, this works without Close because Close is automatically called when GC // However, this works without Close because Close is automatically called when GC
// collects a *audio.Player object. // collects a *audio.Player object.
f := int(freq) f := int(freq)
if n, ok := noteCache[f]; ok { if n, ok := noteCache[f]; ok {
p, err := audio.NewPlayerFromBytes(audioContext, n) p, _ := audio.NewPlayerFromBytes(audioContext, n)
if err != nil { p.Play()
return err return
}
if err := p.Play(); err != nil {
return err
}
return nil
} }
length := len(pcm) * baseFreq / f length := len(pcm) * baseFreq / f
l := make([]int16, length) l := make([]int16, length)
@ -123,14 +118,9 @@ func addNote(freq float64, vol float64) error {
} }
n := toBytes(l, r) n := toBytes(l, r)
noteCache[f] = n noteCache[f] = n
p, err := audio.NewPlayerFromBytes(audioContext, n) p, _ := audio.NewPlayerFromBytes(audioContext, n)
if err != nil { p.Play()
return err return
}
if err := p.Play(); err != nil {
return err
}
return nil
} }
var keys = []ebiten.Key{ var keys = []ebiten.Key{
@ -216,9 +206,10 @@ func update(screen *ebiten.Image) error {
if keyStates[key] != 1 { if keyStates[key] != 1 {
continue continue
} }
if err := addNote(220*math.Exp2(float64(i-1)/12.0), 1.0); err != nil { addNote(220*math.Exp2(float64(i-1)/12.0), 1.0)
return err }
} if err := audioContext.Update(); err != nil {
return err
} }
if ebiten.IsRunningSlowly() { if ebiten.IsRunningSlowly() {
return nil return nil
@ -227,10 +218,6 @@ func update(screen *ebiten.Image) error {
screen.DrawImage(imagePiano, nil) screen.DrawImage(imagePiano, nil)
ebitenutil.DebugPrint(screen, fmt.Sprintf("FPS: %0.2f", ebiten.CurrentFPS())) ebitenutil.DebugPrint(screen, fmt.Sprintf("FPS: %0.2f", ebiten.CurrentFPS()))
if err := audioContext.Update(); err != nil {
return err
}
return nil return nil
} }

View File

@ -31,7 +31,7 @@
<main><div class="container"> <main><div class="container">
<h1>Ebiten</h1> <h1>Ebiten</h1>
<p class="lead">A simple 2D game library in Go</p> <p class="lead">A simple 2D game library in Go</p>
<p>Stable version: v1.4.0-rc1 / Development version: v1.5.0-alpha</p> <p>Stable version: v1.5.0 / Development version: v1.6.0-alpha</p>
<h2 id="platforms">Platforms</h2> <h2 id="platforms">Platforms</h2>
<dl class="dl-horizontal"> <dl class="dl-horizontal">