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 (
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)
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -31,7 +31,7 @@
<main><div class="container">
<h1>Ebiten</h1>
<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>
<dl class="dl-horizontal">