mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-25 19:28:57 +01:00
docs: Add example groups
This commit is contained in:
parent
d139b8ad6e
commit
444e5bab84
73
_docs/gen.go
73
_docs/gen.go
@ -176,29 +176,37 @@ func versions() string {
|
|||||||
return fmt.Sprintf("v%s (dev: v%s)", stableVersion, devVersion)
|
return fmt.Sprintf("v%s (dev: v%s)", stableVersion, devVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
var examples = []example{
|
var (
|
||||||
{"alphablending", 320, 240},
|
graphicsExamples = []example{
|
||||||
{"audio", 320, 240},
|
{"alphablending", 320, 240},
|
||||||
{"font", 320, 240},
|
{"font", 320, 240},
|
||||||
{"highdpi", 320, 240},
|
{"highdpi", 320, 240},
|
||||||
{"hsv", 320, 240},
|
{"hsv", 320, 240},
|
||||||
{"hue", 320, 240},
|
{"hue", 320, 240},
|
||||||
{"gamepad", 320, 240},
|
{"infinitescroll", 320, 240},
|
||||||
{"infinitescroll", 320, 240},
|
{"life", 320, 240},
|
||||||
{"keyboard", 320, 240},
|
{"masking", 320, 240},
|
||||||
{"life", 320, 240},
|
{"mosaic", 320, 240},
|
||||||
{"masking", 320, 240},
|
{"noise", 320, 240},
|
||||||
{"mosaic", 320, 240},
|
{"paint", 320, 240},
|
||||||
{"noise", 320, 240},
|
{"perspective", 320, 240},
|
||||||
{"paint", 320, 240},
|
{"rotate", 320, 240},
|
||||||
{"perspective", 320, 240},
|
{"sprites", 320, 240},
|
||||||
{"piano", 320, 240},
|
}
|
||||||
{"rotate", 320, 240},
|
inputExamples = []example{
|
||||||
{"sprites", 320, 240},
|
{"gamepad", 320, 240},
|
||||||
{"typewriter", 320, 240},
|
{"keyboard", 320, 240},
|
||||||
{"2048", 210, 300},
|
{"typewriter", 320, 240},
|
||||||
{"blocks", 256, 240},
|
}
|
||||||
}
|
audioExamples = []example{
|
||||||
|
{"audio", 320, 240},
|
||||||
|
{"piano", 320, 240},
|
||||||
|
}
|
||||||
|
gameExamples = []example{
|
||||||
|
{"2048", 210, 300},
|
||||||
|
{"blocks", 256, 240},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
func clear() error {
|
func clear() error {
|
||||||
if err := filepath.Walk("public", func(path string, info os.FileInfo, err error) error {
|
if err := filepath.Walk("public", func(path string, info os.FileInfo, err error) error {
|
||||||
@ -253,11 +261,14 @@ func outputMain() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"URL": url,
|
"URL": url,
|
||||||
"Copyright": copyright,
|
"Copyright": copyright,
|
||||||
"StableVersion": stableVersion,
|
"StableVersion": stableVersion,
|
||||||
"DevVersion": devVersion,
|
"DevVersion": devVersion,
|
||||||
"Examples": examples,
|
"GraphicsExamples": graphicsExamples,
|
||||||
|
"InputExamples": inputExamples,
|
||||||
|
"AudioExamples": audioExamples,
|
||||||
|
"GameExamples": gameExamples,
|
||||||
}
|
}
|
||||||
return t.Funcs(funcs).Execute(f, data)
|
return t.Funcs(funcs).Execute(f, data)
|
||||||
}
|
}
|
||||||
@ -355,6 +366,12 @@ func main() {
|
|||||||
if err := outputExampleResources(); err != nil {
|
if err := outputExampleResources(); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
examples := []example{}
|
||||||
|
examples = append(examples, graphicsExamples...)
|
||||||
|
examples = append(examples, inputExamples...)
|
||||||
|
examples = append(examples, audioExamples...)
|
||||||
|
examples = append(examples, gameExamples...)
|
||||||
for _, e := range examples {
|
for _, e := range examples {
|
||||||
if err := outputExampleContent(&e); err != nil {
|
if err := outputExampleContent(&e); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
@ -60,13 +60,39 @@
|
|||||||
</dl>
|
</dl>
|
||||||
|
|
||||||
<h2 id="examples">Examples</h2>
|
<h2 id="examples">Examples</h2>
|
||||||
|
<h3>Graphics</h3>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
{{range .Examples -}}
|
{{range .GraphicsExamples -}}
|
||||||
<div class="col-3">
|
<div class="col-3">
|
||||||
<a href="examples/{{.Name}}.html"><img src="images/examples/{{.Name}}.png" width="{{.ThumbWidth}}" height="{{.ThumbHeight}}" alt="Ebiten example: {{.Name}}" class="img-thumbnail"></a>
|
<a href="examples/{{.Name}}.html"><img src="images/examples/{{.Name}}.png" width="{{.ThumbWidth}}" height="{{.ThumbHeight}}" alt="Ebiten example: {{.Name}}" class="img-thumbnail"></a>
|
||||||
</div>
|
</div>
|
||||||
{{- end}}
|
{{- end}}
|
||||||
</div>
|
</div>
|
||||||
|
<h3>Input</h3>
|
||||||
|
<div class="row">
|
||||||
|
{{range .InputExamples -}}
|
||||||
|
<div class="col-3">
|
||||||
|
<a href="examples/{{.Name}}.html"><img src="images/examples/{{.Name}}.png" width="{{.ThumbWidth}}" height="{{.ThumbHeight}}" alt="Ebiten example: {{.Name}}" class="img-thumbnail"></a>
|
||||||
|
</div>
|
||||||
|
{{- end}}
|
||||||
|
</div>
|
||||||
|
<h3>Audio</h3>
|
||||||
|
<div class="row">
|
||||||
|
{{range .AudioExamples -}}
|
||||||
|
<div class="col-3">
|
||||||
|
<a href="examples/{{.Name}}.html"><img src="images/examples/{{.Name}}.png" width="{{.ThumbWidth}}" height="{{.ThumbHeight}}" alt="Ebiten example: {{.Name}}" class="img-thumbnail"></a>
|
||||||
|
</div>
|
||||||
|
{{- end}}
|
||||||
|
</div>
|
||||||
|
<h3>Game</h3>
|
||||||
|
<div class="row">
|
||||||
|
{{range .GameExamples -}}
|
||||||
|
<div class="col-3">
|
||||||
|
<a href="examples/{{.Name}}.html"><img src="images/examples/{{.Name}}.png" width="{{.ThumbWidth}}" height="{{.ThumbHeight}}" alt="Ebiten example: {{.Name}}" class="img-thumbnail"></a>
|
||||||
|
</div>
|
||||||
|
{{- end}}
|
||||||
|
</div>
|
||||||
|
|
||||||
<p><a href="https://blog.golang.org/go-programming-language-turns-two">The Gopher photographs by Chris Nokleberg</a> are licensed under <a href="https://creativecommons.org/licenses/by/3.0/">the Creative Commons 3.0 Attributions License</a>.</p>
|
<p><a href="https://blog.golang.org/go-programming-language-turns-two">The Gopher photographs by Chris Nokleberg</a> are licensed under <a href="https://creativecommons.org/licenses/by/3.0/">the Creative Commons 3.0 Attributions License</a>.</p>
|
||||||
|
|
||||||
<h3>Execute the examples</h3>
|
<h3>Execute the examples</h3>
|
||||||
|
@ -62,20 +62,23 @@ func (r *rand) next() uint32 {
|
|||||||
return r.w
|
return r.w
|
||||||
}
|
}
|
||||||
|
|
||||||
var randInstance = &rand{12345678, 4185243, 776511, 45411}
|
var theRand = &rand{12345678, 4185243, 776511, 45411}
|
||||||
|
|
||||||
func update(screen *ebiten.Image) error {
|
func update(screen *ebiten.Image) error {
|
||||||
|
// Generate the noise with random RGB values.
|
||||||
const l = screenWidth * screenHeight
|
const l = screenWidth * screenHeight
|
||||||
for i := 0; i < l; i++ {
|
for i := 0; i < l; i++ {
|
||||||
x := randInstance.next()
|
x := theRand.next()
|
||||||
noiseImage.Pix[4*i] = uint8(x >> 24)
|
noiseImage.Pix[4*i] = uint8(x >> 24)
|
||||||
noiseImage.Pix[4*i+1] = uint8(x >> 16)
|
noiseImage.Pix[4*i+1] = uint8(x >> 16)
|
||||||
noiseImage.Pix[4*i+2] = uint8(x >> 8)
|
noiseImage.Pix[4*i+2] = uint8(x >> 8)
|
||||||
noiseImage.Pix[4*i+3] = 0xff
|
noiseImage.Pix[4*i+3] = 0xff
|
||||||
}
|
}
|
||||||
|
|
||||||
if ebiten.IsRunningSlowly() {
|
if ebiten.IsRunningSlowly() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
screen.ReplacePixels(noiseImage.Pix)
|
screen.ReplacePixels(noiseImage.Pix)
|
||||||
ebitenutil.DebugPrint(screen, fmt.Sprintf("FPS: %f", ebiten.CurrentFPS()))
|
ebitenutil.DebugPrint(screen, fmt.Sprintf("FPS: %f", ebiten.CurrentFPS()))
|
||||||
return nil
|
return nil
|
||||||
|
@ -53,46 +53,12 @@ var (
|
|||||||
canvasImage *ebiten.Image
|
canvasImage *ebiten.Image
|
||||||
)
|
)
|
||||||
|
|
||||||
func paint(screen *ebiten.Image, x, y int) {
|
func init() {
|
||||||
op := &ebiten.DrawImageOptions{}
|
const (
|
||||||
op.GeoM.Translate(float64(x), float64(y))
|
a0 = 0x40
|
||||||
op.ColorM.Scale(1.0, 0.50, 0.125, 1.0)
|
a1 = 0xc0
|
||||||
theta := 2.0 * math.Pi * float64(count%ebiten.FPS) / ebiten.FPS
|
a2 = 0xff
|
||||||
op.ColorM.RotateHue(theta)
|
)
|
||||||
canvasImage.DrawImage(brushImage, op)
|
|
||||||
}
|
|
||||||
|
|
||||||
func update(screen *ebiten.Image) error {
|
|
||||||
drawn := false
|
|
||||||
mx, my := ebiten.CursorPosition()
|
|
||||||
if ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) {
|
|
||||||
paint(screen, mx, my)
|
|
||||||
drawn = true
|
|
||||||
}
|
|
||||||
for _, t := range ebiten.Touches() {
|
|
||||||
x, y := t.Position()
|
|
||||||
paint(screen, x, y)
|
|
||||||
drawn = true
|
|
||||||
}
|
|
||||||
if drawn {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
if ebiten.IsRunningSlowly() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
screen.DrawImage(canvasImage, nil)
|
|
||||||
|
|
||||||
msg := fmt.Sprintf("(%d, %d)", mx, my)
|
|
||||||
for _, t := range ebiten.Touches() {
|
|
||||||
x, y := t.Position()
|
|
||||||
msg += fmt.Sprintf("\n(%d, %d) touch %d", x, y, t.ID())
|
|
||||||
}
|
|
||||||
ebitenutil.DebugPrint(screen, msg)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
const a0, a1, a2 = 0x40, 0xc0, 0xff
|
|
||||||
pixels := []uint8{
|
pixels := []uint8{
|
||||||
a0, a1, a1, a0,
|
a0, a1, a1, a0,
|
||||||
a1, a2, a2, a1,
|
a1, a2, a2, a1,
|
||||||
@ -107,7 +73,55 @@ func main() {
|
|||||||
|
|
||||||
canvasImage, _ = ebiten.NewImage(screenWidth, screenHeight, ebiten.FilterNearest)
|
canvasImage, _ = ebiten.NewImage(screenWidth, screenHeight, ebiten.FilterNearest)
|
||||||
canvasImage.Fill(color.White)
|
canvasImage.Fill(color.White)
|
||||||
|
}
|
||||||
|
|
||||||
|
// paint draws the brush on the given canvas image at the position (x, y).
|
||||||
|
func paint(canvas *ebiten.Image, x, y int) {
|
||||||
|
op := &ebiten.DrawImageOptions{}
|
||||||
|
op.GeoM.Translate(float64(x), float64(y))
|
||||||
|
// Scale the color and rotate the hue so that colors vary on each frame.
|
||||||
|
op.ColorM.Scale(1.0, 0.50, 0.125, 1.0)
|
||||||
|
theta := 2.0 * math.Pi * float64(count%ebiten.FPS) / ebiten.FPS
|
||||||
|
op.ColorM.RotateHue(theta)
|
||||||
|
canvas.DrawImage(brushImage, op)
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(screen *ebiten.Image) error {
|
||||||
|
drawn := false
|
||||||
|
|
||||||
|
// Paint the brush by mouse dragging
|
||||||
|
mx, my := ebiten.CursorPosition()
|
||||||
|
if ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) {
|
||||||
|
paint(canvasImage, mx, my)
|
||||||
|
drawn = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Paint the brush by touches
|
||||||
|
for _, t := range ebiten.Touches() {
|
||||||
|
x, y := t.Position()
|
||||||
|
paint(canvasImage, x, y)
|
||||||
|
drawn = true
|
||||||
|
}
|
||||||
|
if drawn {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
|
||||||
|
if ebiten.IsRunningSlowly() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
screen.DrawImage(canvasImage, nil)
|
||||||
|
|
||||||
|
msg := fmt.Sprintf("(%d, %d)", mx, my)
|
||||||
|
for _, t := range ebiten.Touches() {
|
||||||
|
x, y := t.Position()
|
||||||
|
msg += fmt.Sprintf("\n(%d, %d) touch %d", x, y, t.ID())
|
||||||
|
}
|
||||||
|
ebitenutil.DebugPrint(screen, msg)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
if err := ebiten.Run(update, screenWidth, screenHeight, 2, "Paint (Ebiten Demo)"); err != nil {
|
if err := ebiten.Run(update, screenWidth, screenHeight, 2, "Paint (Ebiten Demo)"); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -53,17 +53,25 @@ func update(screen *ebiten.Image) error {
|
|||||||
if ebiten.IsRunningSlowly() {
|
if ebiten.IsRunningSlowly() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Split the image into horizontal lines and draw them with different scales.
|
||||||
op := &ebiten.DrawImageOptions{}
|
op := &ebiten.DrawImageOptions{}
|
||||||
w, h := gophersImage.Size()
|
w, h := gophersImage.Size()
|
||||||
for i := 0; i < h; i++ {
|
for i := 0; i < h; i++ {
|
||||||
op.GeoM.Reset()
|
op.GeoM.Reset()
|
||||||
width := w + i*3/4
|
|
||||||
x := ((h - i) * 3 / 4) / 2
|
// Move the image's center to the upper-left corner.
|
||||||
op.GeoM.Scale(float64(width)/float64(w), 1)
|
op.GeoM.Translate(-float64(w)/2, -float64(h)/2)
|
||||||
op.GeoM.Translate(float64(x), float64(i))
|
|
||||||
maxWidth := float64(w) + float64(h)*3/4
|
// Scale each lines and adjust the position.
|
||||||
op.GeoM.Translate(-maxWidth/2, -float64(h)/2)
|
lineW := w + i*3/4
|
||||||
|
x := -float64(lineW) / float64(w) / 2
|
||||||
|
op.GeoM.Scale(float64(lineW)/float64(w), 1)
|
||||||
|
op.GeoM.Translate(x, float64(i))
|
||||||
|
|
||||||
|
// Move the image's center to the screen's center.
|
||||||
op.GeoM.Translate(screenWidth/2, screenHeight/2)
|
op.GeoM.Translate(screenWidth/2, screenHeight/2)
|
||||||
|
|
||||||
r := image.Rect(0, i, w, i+1)
|
r := image.Rect(0, i, w, i+1)
|
||||||
op.SourceRect = &r
|
op.SourceRect = &r
|
||||||
screen.DrawImage(gophersImage, op)
|
screen.DrawImage(gophersImage, op)
|
||||||
|
@ -47,10 +47,6 @@ import (
|
|||||||
"github.com/hajimehoshi/ebiten/text"
|
"github.com/hajimehoshi/ebiten/text"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
arcadeFontSize = 8
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
arcadeFont font.Face
|
arcadeFont font.Face
|
||||||
)
|
)
|
||||||
@ -72,7 +68,10 @@ func init() {
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
const dpi = 72
|
const (
|
||||||
|
arcadeFontSize = 8
|
||||||
|
dpi = 72
|
||||||
|
)
|
||||||
arcadeFont = truetype.NewFace(tt, &truetype.Options{
|
arcadeFont = truetype.NewFace(tt, &truetype.Options{
|
||||||
Size: arcadeFontSize,
|
Size: arcadeFontSize,
|
||||||
DPI: dpi,
|
DPI: dpi,
|
||||||
@ -84,6 +83,7 @@ const (
|
|||||||
screenWidth = 320
|
screenWidth = 320
|
||||||
screenHeight = 240
|
screenHeight = 240
|
||||||
sampleRate = 44100
|
sampleRate = 44100
|
||||||
|
baseFreq = 220
|
||||||
)
|
)
|
||||||
|
|
||||||
var audioContext *audio.Context
|
var audioContext *audio.Context
|
||||||
@ -96,27 +96,21 @@ func init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var pcm = make([]float64, 4*sampleRate)
|
// pianoAt returns an i-th sample of piano with the given frequency.
|
||||||
|
func pianoAt(i int, freq float64) float64 {
|
||||||
const baseFreq = 220
|
// Create piano-like waves with multiple sin waves.
|
||||||
|
|
||||||
func init() {
|
|
||||||
amp := []float64{1.0, 0.8, 0.6, 0.4, 0.2}
|
amp := []float64{1.0, 0.8, 0.6, 0.4, 0.2}
|
||||||
x := []float64{4.0, 2.0, 1.0, 0.5, 0.25}
|
x := []float64{4.0, 2.0, 1.0, 0.5, 0.25}
|
||||||
for i := 0; i < len(pcm); i++ {
|
v := 0.0
|
||||||
v := 0.0
|
for j := 0; j < len(amp); j++ {
|
||||||
for j := 0; j < len(amp); j++ {
|
// Decay
|
||||||
a := amp[j] * math.Exp(-5*float64(i)/(x[j]*sampleRate))
|
a := amp[j] * math.Exp(-5*float64(i)*freq/baseFreq/(x[j]*sampleRate))
|
||||||
v += a * math.Sin(2.0*math.Pi*float64(i)*baseFreq*float64(j+1)/sampleRate)
|
v += a * math.Sin(2.0*math.Pi*float64(i)*freq*float64(j+1)/sampleRate)
|
||||||
}
|
|
||||||
pcm[i] = v / 5.0
|
|
||||||
}
|
}
|
||||||
|
return v / 5.0
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
// toBytes returns the 2ch little endian 16bit byte sequence with the given left/right sequence.
|
||||||
noteCache = map[int][]byte{}
|
|
||||||
)
|
|
||||||
|
|
||||||
func toBytes(l, r []int16) []byte {
|
func toBytes(l, r []int16) []byte {
|
||||||
if len(l) != len(r) {
|
if len(l) != len(r) {
|
||||||
panic("len(l) must equal to len(r)")
|
panic("len(l) must equal to len(r)")
|
||||||
@ -131,62 +125,109 @@ func toBytes(l, r []int16) []byte {
|
|||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func addNote(freq float64, vol float64) {
|
var (
|
||||||
// TODO: Call Close method of *audio.Player.
|
pianoNoteSamples = map[int][]byte{}
|
||||||
// However, this works without Close because Close is automatically called when GC
|
pianoNoteSamplesInited = false
|
||||||
// collects a *audio.Player object.
|
pianoNoteSamplesInitCh = make(chan struct{})
|
||||||
f := int(freq)
|
)
|
||||||
if n, ok := noteCache[f]; ok {
|
|
||||||
p, _ := audio.NewPlayerFromBytes(audioContext, n)
|
|
||||||
p.Play()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
length := len(pcm) * baseFreq / f
|
|
||||||
l := make([]int16, length)
|
|
||||||
r := make([]int16, length)
|
|
||||||
j := 0
|
|
||||||
jj := 0
|
|
||||||
for i := 0; i < len(l); i++ {
|
|
||||||
p := pcm[j]
|
|
||||||
l[i] = int16(p * vol * math.MaxInt16)
|
|
||||||
r[i] = l[i]
|
|
||||||
jj += f
|
|
||||||
j = jj / baseFreq
|
|
||||||
}
|
|
||||||
n := toBytes(l, r)
|
|
||||||
noteCache[f] = n
|
|
||||||
p, _ := audio.NewPlayerFromBytes(audioContext, n)
|
|
||||||
p.Play()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var keys = []ebiten.Key{
|
|
||||||
ebiten.KeyQ,
|
|
||||||
ebiten.KeyA,
|
|
||||||
ebiten.KeyW,
|
|
||||||
ebiten.KeyS,
|
|
||||||
ebiten.KeyD,
|
|
||||||
ebiten.KeyR,
|
|
||||||
ebiten.KeyF,
|
|
||||||
ebiten.KeyT,
|
|
||||||
ebiten.KeyG,
|
|
||||||
ebiten.KeyH,
|
|
||||||
ebiten.KeyU,
|
|
||||||
ebiten.KeyJ,
|
|
||||||
ebiten.KeyI,
|
|
||||||
ebiten.KeyK,
|
|
||||||
ebiten.KeyO,
|
|
||||||
ebiten.KeyL,
|
|
||||||
}
|
|
||||||
|
|
||||||
var keyStates = map[ebiten.Key]int{}
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
for _, key := range keys {
|
// Initialize piano data.
|
||||||
keyStates[key] = 0
|
// This takes a little long time (especially on browsers),
|
||||||
|
// so run this asynchronously and notice the progress.
|
||||||
|
go func() {
|
||||||
|
// Create a reference data and use this for other frequency.
|
||||||
|
const refFreq = 110
|
||||||
|
length := 4 * sampleRate * baseFreq / refFreq
|
||||||
|
refData := make([]int16, length)
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
|
refData[i] = int16(pianoAt(i, refFreq) * math.MaxInt16)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range keys {
|
||||||
|
freq := baseFreq * math.Exp2(float64(i-1)/12.0)
|
||||||
|
|
||||||
|
// Clculate the wave data for the freq.
|
||||||
|
length := 4 * sampleRate * baseFreq / int(freq)
|
||||||
|
l := make([]int16, length)
|
||||||
|
r := make([]int16, length)
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
|
idx := int(float64(i) * freq / refFreq)
|
||||||
|
if len(refData) <= idx {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
l[i] = refData[idx]
|
||||||
|
}
|
||||||
|
copy(r, l)
|
||||||
|
n := toBytes(l, r)
|
||||||
|
pianoNoteSamples[int(freq)] = n
|
||||||
|
}
|
||||||
|
close(pianoNoteSamplesInitCh)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.Image
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
pianoImage, _ = ebiten.NewImage(screenWidth, screenHeight, ebiten.FilterNearest)
|
||||||
|
|
||||||
|
const (
|
||||||
|
keyWidth = 24
|
||||||
|
y = 48
|
||||||
|
)
|
||||||
|
|
||||||
|
whiteKeys := []string{"A", "S", "D", "F", "G", "H", "J", "K", "L"}
|
||||||
|
for i, k := range whiteKeys {
|
||||||
|
x := i*keyWidth + 36
|
||||||
|
height := 112
|
||||||
|
ebitenutil.DrawRect(pianoImage, float64(x), float64(y), float64(keyWidth-1), float64(height), color.White)
|
||||||
|
text.Draw(pianoImage, k, arcadeFont, x+8, y+height-8, color.Black)
|
||||||
|
}
|
||||||
|
|
||||||
|
blackKeys := []string{"Q", "W", "", "R", "T", "", "U", "I", "O"}
|
||||||
|
for i, k := range blackKeys {
|
||||||
|
if k == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
x := i*keyWidth + 24
|
||||||
|
height := 64
|
||||||
|
ebitenutil.DrawRect(pianoImage, float64(x), float64(y), float64(keyWidth-1), float64(height), color.Black)
|
||||||
|
text.Draw(pianoImage, k, arcadeFont, x+8, y+height-8, color.White)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
keys = []ebiten.Key{
|
||||||
|
ebiten.KeyQ,
|
||||||
|
ebiten.KeyA,
|
||||||
|
ebiten.KeyW,
|
||||||
|
ebiten.KeyS,
|
||||||
|
ebiten.KeyD,
|
||||||
|
ebiten.KeyR,
|
||||||
|
ebiten.KeyF,
|
||||||
|
ebiten.KeyT,
|
||||||
|
ebiten.KeyG,
|
||||||
|
ebiten.KeyH,
|
||||||
|
ebiten.KeyU,
|
||||||
|
ebiten.KeyJ,
|
||||||
|
ebiten.KeyI,
|
||||||
|
ebiten.KeyK,
|
||||||
|
ebiten.KeyO,
|
||||||
|
ebiten.KeyL,
|
||||||
|
}
|
||||||
|
keyStates = map[ebiten.Key]int{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// updateInput updates the input state.
|
||||||
func updateInput() {
|
func updateInput() {
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
if !ebiten.IsKeyPressed(key) {
|
if !ebiten.IsKeyPressed(key) {
|
||||||
@ -197,47 +238,33 @@ func updateInput() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
imagePiano *ebiten.Image
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
imagePiano, _ = ebiten.NewImage(screenWidth, screenHeight, ebiten.FilterNearest)
|
|
||||||
whiteKeys := []string{"A", "S", "D", "F", "G", "H", "J", "K", "L"}
|
|
||||||
width := 24
|
|
||||||
y := 48
|
|
||||||
for i, k := range whiteKeys {
|
|
||||||
x := i*width + 36
|
|
||||||
height := 112
|
|
||||||
ebitenutil.DrawRect(imagePiano, float64(x), float64(y), float64(width-1), float64(height), color.White)
|
|
||||||
text.Draw(imagePiano, k, arcadeFont, x+8, y+height-8, color.Black)
|
|
||||||
}
|
|
||||||
|
|
||||||
blackKeys := []string{"Q", "W", "", "R", "T", "", "U", "I", "O"}
|
|
||||||
for i, k := range blackKeys {
|
|
||||||
if k == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
x := i*width + 24
|
|
||||||
height := 64
|
|
||||||
ebitenutil.DrawRect(imagePiano, float64(x), float64(y), float64(width-1), float64(height), color.Black)
|
|
||||||
text.Draw(imagePiano, k, arcadeFont, x+8, y+height-8, color.White)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func update(screen *ebiten.Image) error {
|
func update(screen *ebiten.Image) error {
|
||||||
updateInput()
|
// The piano data is still being initialized.
|
||||||
for i, key := range keys {
|
// Get the progress if available.
|
||||||
if keyStates[key] != 1 {
|
if !pianoNoteSamplesInited {
|
||||||
continue
|
select {
|
||||||
|
case <-pianoNoteSamplesInitCh:
|
||||||
|
pianoNoteSamplesInited = true
|
||||||
|
default:
|
||||||
}
|
}
|
||||||
addNote(220*math.Exp2(float64(i-1)/12.0), 1.0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if pianoNoteSamplesInited {
|
||||||
|
updateInput()
|
||||||
|
for i, key := range keys {
|
||||||
|
if keyStates[key] != 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
playNote(baseFreq * math.Exp2(float64(i-1)/12.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ebiten.IsRunningSlowly() {
|
if ebiten.IsRunningSlowly() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
screen.Fill(color.RGBA{0x80, 0x80, 0xc0, 0xff})
|
screen.Fill(color.RGBA{0x80, 0x80, 0xc0, 0xff})
|
||||||
screen.DrawImage(imagePiano, nil)
|
screen.DrawImage(pianoImage, nil)
|
||||||
|
|
||||||
ebitenutil.DebugPrint(screen, fmt.Sprintf("FPS: %0.2f", ebiten.CurrentFPS()))
|
ebitenutil.DebugPrint(screen, fmt.Sprintf("FPS: %0.2f", ebiten.CurrentFPS()))
|
||||||
return nil
|
return nil
|
||||||
|
@ -46,7 +46,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
count int
|
count = 0
|
||||||
gophersImage *ebiten.Image
|
gophersImage *ebiten.Image
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -57,8 +57,17 @@ func update(screen *ebiten.Image) error {
|
|||||||
}
|
}
|
||||||
w, h := gophersImage.Size()
|
w, h := gophersImage.Size()
|
||||||
op := &ebiten.DrawImageOptions{}
|
op := &ebiten.DrawImageOptions{}
|
||||||
|
|
||||||
|
// Move the image's center to the screen's upper-left corner.
|
||||||
|
// This is a prepartion for rotating. When geometry matrices are applied,
|
||||||
|
// the origin point is the upper-left corner.
|
||||||
op.GeoM.Translate(-float64(w)/2, -float64(h)/2)
|
op.GeoM.Translate(-float64(w)/2, -float64(h)/2)
|
||||||
|
|
||||||
|
// Rotate the image. As a result, the anchor point of this rotate is
|
||||||
|
// the center of the image.
|
||||||
op.GeoM.Rotate(float64(count%360) * 2 * math.Pi / 360)
|
op.GeoM.Rotate(float64(count%360) * 2 * math.Pi / 360)
|
||||||
|
|
||||||
|
// Move the image to the screen's center.
|
||||||
op.GeoM.Translate(screenWidth/2, screenHeight/2)
|
op.GeoM.Translate(screenWidth/2, screenHeight/2)
|
||||||
screen.DrawImage(gophersImage, op)
|
screen.DrawImage(gophersImage, op)
|
||||||
return nil
|
return nil
|
||||||
|
@ -104,43 +104,7 @@ var (
|
|||||||
op = &ebiten.DrawImageOptions{}
|
op = &ebiten.DrawImageOptions{}
|
||||||
)
|
)
|
||||||
|
|
||||||
func update(screen *ebiten.Image) error {
|
func init() {
|
||||||
if ebiten.IsKeyPressed(ebiten.KeyLeft) {
|
|
||||||
sprites.num -= 20
|
|
||||||
if sprites.num < MinSprites {
|
|
||||||
sprites.num = MinSprites
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ebiten.IsKeyPressed(ebiten.KeyRight) {
|
|
||||||
sprites.num += 20
|
|
||||||
if MaxSprites < sprites.num {
|
|
||||||
sprites.num = MaxSprites
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sprites.Update()
|
|
||||||
|
|
||||||
if ebiten.IsRunningSlowly() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
w, h := ebitenImage.Size()
|
|
||||||
for i := 0; i < sprites.num; i++ {
|
|
||||||
s := sprites.sprites[i]
|
|
||||||
op.GeoM.Reset()
|
|
||||||
op.GeoM.Translate(-float64(w)/2, -float64(h)/2)
|
|
||||||
op.GeoM.Rotate(2 * math.Pi * float64(s.angle) / maxAngle)
|
|
||||||
op.GeoM.Translate(float64(w)/2, float64(h)/2)
|
|
||||||
op.GeoM.Translate(float64(s.x), float64(s.y))
|
|
||||||
screen.DrawImage(ebitenImage, op)
|
|
||||||
}
|
|
||||||
msg := fmt.Sprintf(`FPS: %0.2f
|
|
||||||
Num of sprites: %d
|
|
||||||
Press <- or -> to change the number of sprites`, ebiten.CurrentFPS(), sprites.num)
|
|
||||||
ebitenutil.DebugPrint(screen, msg)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
var err error
|
|
||||||
img, _, err := ebitenutil.NewImageFromFile("_resources/images/ebiten.png", ebiten.FilterNearest)
|
img, _, err := ebitenutil.NewImageFromFile("_resources/images/ebiten.png", ebiten.FilterNearest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -165,6 +129,55 @@ func main() {
|
|||||||
angle: a,
|
angle: a,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(screen *ebiten.Image) error {
|
||||||
|
// Decrease the nubmer of the sprites.
|
||||||
|
if ebiten.IsKeyPressed(ebiten.KeyLeft) {
|
||||||
|
sprites.num -= 20
|
||||||
|
if sprites.num < MinSprites {
|
||||||
|
sprites.num = MinSprites
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increase the nubmer of the sprites.
|
||||||
|
if ebiten.IsKeyPressed(ebiten.KeyRight) {
|
||||||
|
sprites.num += 20
|
||||||
|
if MaxSprites < sprites.num {
|
||||||
|
sprites.num = MaxSprites
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sprites.Update()
|
||||||
|
|
||||||
|
if ebiten.IsRunningSlowly() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw each sprite.
|
||||||
|
// DrawImage can be called many many times, but in the implementation,
|
||||||
|
// the actual draw call to GPU is very few since these calls satisfy
|
||||||
|
// some conditions e.g. all the rendering sources and targets are same.
|
||||||
|
// For more detail, see:
|
||||||
|
// https://godoc.org/github.com/hajimehoshi/ebiten#Image.DrawImage
|
||||||
|
w, h := ebitenImage.Size()
|
||||||
|
for i := 0; i < sprites.num; i++ {
|
||||||
|
s := sprites.sprites[i]
|
||||||
|
op.GeoM.Reset()
|
||||||
|
op.GeoM.Translate(-float64(w)/2, -float64(h)/2)
|
||||||
|
op.GeoM.Rotate(2 * math.Pi * float64(s.angle) / maxAngle)
|
||||||
|
op.GeoM.Translate(float64(w)/2, float64(h)/2)
|
||||||
|
op.GeoM.Translate(float64(s.x), float64(s.y))
|
||||||
|
screen.DrawImage(ebitenImage, op)
|
||||||
|
}
|
||||||
|
msg := fmt.Sprintf(`FPS: %0.2f
|
||||||
|
Num of sprites: %d
|
||||||
|
Press <- or -> to change the number of sprites`, ebiten.CurrentFPS(), sprites.num)
|
||||||
|
ebitenutil.DebugPrint(screen, msg)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
if err := ebiten.Run(update, screenWidth, screenHeight, 2, "Sprites (Ebiten Demo)"); err != nil {
|
if err := ebiten.Run(update, screenWidth, screenHeight, 2, "Sprites (Ebiten Demo)"); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -46,14 +46,23 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func update(screen *ebiten.Image) error {
|
func update(screen *ebiten.Image) error {
|
||||||
|
// Add a string from InputChars, that returns string input by users.
|
||||||
|
// Note that InputChars result changes every frame, so you need to call this
|
||||||
|
// every frame.
|
||||||
text += string(ebiten.InputChars())
|
text += string(ebiten.InputChars())
|
||||||
|
|
||||||
|
// Adjust the string to be at most 10 lines.
|
||||||
ss := strings.Split(text, "\n")
|
ss := strings.Split(text, "\n")
|
||||||
if len(ss) > 10 {
|
if len(ss) > 10 {
|
||||||
text = strings.Join(ss[len(ss)-10:], "\n")
|
text = strings.Join(ss[len(ss)-10:], "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the enter key is pressed, add a line break.
|
||||||
if ebiten.IsKeyPressed(ebiten.KeyEnter) && !strings.HasSuffix(text, "\n") {
|
if ebiten.IsKeyPressed(ebiten.KeyEnter) && !strings.HasSuffix(text, "\n") {
|
||||||
text += "\n"
|
text += "\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the backspace key is pressed, remove one character.
|
||||||
bsPressed := ebiten.IsKeyPressed(ebiten.KeyBackspace)
|
bsPressed := ebiten.IsKeyPressed(ebiten.KeyBackspace)
|
||||||
if !bsPrevPressed && bsPressed {
|
if !bsPrevPressed && bsPressed {
|
||||||
if len(text) >= 1 {
|
if len(text) >= 1 {
|
||||||
@ -68,6 +77,7 @@ func update(screen *ebiten.Image) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Blink the cursor.
|
||||||
t := text
|
t := text
|
||||||
if counter%60 < 30 {
|
if counter%60 < 30 {
|
||||||
t += "_"
|
t += "_"
|
||||||
|
@ -60,11 +60,10 @@
|
|||||||
</dl>
|
</dl>
|
||||||
|
|
||||||
<h2 id="examples">Examples</h2>
|
<h2 id="examples">Examples</h2>
|
||||||
|
<h3>Graphics</h3>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-3">
|
<div class="col-3">
|
||||||
<a href="examples/alphablending.html"><img src="images/examples/alphablending.png" width="320" height="240" alt="Ebiten example: alphablending" class="img-thumbnail"></a>
|
<a href="examples/alphablending.html"><img src="images/examples/alphablending.png" width="320" height="240" alt="Ebiten example: alphablending" class="img-thumbnail"></a>
|
||||||
</div><div class="col-3">
|
|
||||||
<a href="examples/audio.html"><img src="images/examples/audio.png" width="320" height="240" alt="Ebiten example: audio" class="img-thumbnail"></a>
|
|
||||||
</div><div class="col-3">
|
</div><div class="col-3">
|
||||||
<a href="examples/font.html"><img src="images/examples/font.png" width="320" height="240" alt="Ebiten example: font" class="img-thumbnail"></a>
|
<a href="examples/font.html"><img src="images/examples/font.png" width="320" height="240" alt="Ebiten example: font" class="img-thumbnail"></a>
|
||||||
</div><div class="col-3">
|
</div><div class="col-3">
|
||||||
@ -73,12 +72,8 @@
|
|||||||
<a href="examples/hsv.html"><img src="images/examples/hsv.png" width="320" height="240" alt="Ebiten example: hsv" class="img-thumbnail"></a>
|
<a href="examples/hsv.html"><img src="images/examples/hsv.png" width="320" height="240" alt="Ebiten example: hsv" class="img-thumbnail"></a>
|
||||||
</div><div class="col-3">
|
</div><div class="col-3">
|
||||||
<a href="examples/hue.html"><img src="images/examples/hue.png" width="320" height="240" alt="Ebiten example: hue" class="img-thumbnail"></a>
|
<a href="examples/hue.html"><img src="images/examples/hue.png" width="320" height="240" alt="Ebiten example: hue" class="img-thumbnail"></a>
|
||||||
</div><div class="col-3">
|
|
||||||
<a href="examples/gamepad.html"><img src="images/examples/gamepad.png" width="320" height="240" alt="Ebiten example: gamepad" class="img-thumbnail"></a>
|
|
||||||
</div><div class="col-3">
|
</div><div class="col-3">
|
||||||
<a href="examples/infinitescroll.html"><img src="images/examples/infinitescroll.png" width="320" height="240" alt="Ebiten example: infinitescroll" class="img-thumbnail"></a>
|
<a href="examples/infinitescroll.html"><img src="images/examples/infinitescroll.png" width="320" height="240" alt="Ebiten example: infinitescroll" class="img-thumbnail"></a>
|
||||||
</div><div class="col-3">
|
|
||||||
<a href="examples/keyboard.html"><img src="images/examples/keyboard.png" width="320" height="240" alt="Ebiten example: keyboard" class="img-thumbnail"></a>
|
|
||||||
</div><div class="col-3">
|
</div><div class="col-3">
|
||||||
<a href="examples/life.html"><img src="images/examples/life.png" width="320" height="240" alt="Ebiten example: life" class="img-thumbnail"></a>
|
<a href="examples/life.html"><img src="images/examples/life.png" width="320" height="240" alt="Ebiten example: life" class="img-thumbnail"></a>
|
||||||
</div><div class="col-3">
|
</div><div class="col-3">
|
||||||
@ -91,20 +86,39 @@
|
|||||||
<a href="examples/paint.html"><img src="images/examples/paint.png" width="320" height="240" alt="Ebiten example: paint" class="img-thumbnail"></a>
|
<a href="examples/paint.html"><img src="images/examples/paint.png" width="320" height="240" alt="Ebiten example: paint" class="img-thumbnail"></a>
|
||||||
</div><div class="col-3">
|
</div><div class="col-3">
|
||||||
<a href="examples/perspective.html"><img src="images/examples/perspective.png" width="320" height="240" alt="Ebiten example: perspective" class="img-thumbnail"></a>
|
<a href="examples/perspective.html"><img src="images/examples/perspective.png" width="320" height="240" alt="Ebiten example: perspective" class="img-thumbnail"></a>
|
||||||
</div><div class="col-3">
|
|
||||||
<a href="examples/piano.html"><img src="images/examples/piano.png" width="320" height="240" alt="Ebiten example: piano" class="img-thumbnail"></a>
|
|
||||||
</div><div class="col-3">
|
</div><div class="col-3">
|
||||||
<a href="examples/rotate.html"><img src="images/examples/rotate.png" width="320" height="240" alt="Ebiten example: rotate" class="img-thumbnail"></a>
|
<a href="examples/rotate.html"><img src="images/examples/rotate.png" width="320" height="240" alt="Ebiten example: rotate" class="img-thumbnail"></a>
|
||||||
</div><div class="col-3">
|
</div><div class="col-3">
|
||||||
<a href="examples/sprites.html"><img src="images/examples/sprites.png" width="320" height="240" alt="Ebiten example: sprites" class="img-thumbnail"></a>
|
<a href="examples/sprites.html"><img src="images/examples/sprites.png" width="320" height="240" alt="Ebiten example: sprites" class="img-thumbnail"></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h3>Input</h3>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-3">
|
||||||
|
<a href="examples/gamepad.html"><img src="images/examples/gamepad.png" width="320" height="240" alt="Ebiten example: gamepad" class="img-thumbnail"></a>
|
||||||
|
</div><div class="col-3">
|
||||||
|
<a href="examples/keyboard.html"><img src="images/examples/keyboard.png" width="320" height="240" alt="Ebiten example: keyboard" class="img-thumbnail"></a>
|
||||||
</div><div class="col-3">
|
</div><div class="col-3">
|
||||||
<a href="examples/typewriter.html"><img src="images/examples/typewriter.png" width="320" height="240" alt="Ebiten example: typewriter" class="img-thumbnail"></a>
|
<a href="examples/typewriter.html"><img src="images/examples/typewriter.png" width="320" height="240" alt="Ebiten example: typewriter" class="img-thumbnail"></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h3>Audio</h3>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-3">
|
||||||
|
<a href="examples/audio.html"><img src="images/examples/audio.png" width="320" height="240" alt="Ebiten example: audio" class="img-thumbnail"></a>
|
||||||
</div><div class="col-3">
|
</div><div class="col-3">
|
||||||
|
<a href="examples/piano.html"><img src="images/examples/piano.png" width="320" height="240" alt="Ebiten example: piano" class="img-thumbnail"></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h3>Game</h3>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-3">
|
||||||
<a href="examples/2048.html"><img src="images/examples/2048.png" width="210" height="300" alt="Ebiten example: 2048" class="img-thumbnail"></a>
|
<a href="examples/2048.html"><img src="images/examples/2048.png" width="210" height="300" alt="Ebiten example: 2048" class="img-thumbnail"></a>
|
||||||
</div><div class="col-3">
|
</div><div class="col-3">
|
||||||
<a href="examples/blocks.html"><img src="images/examples/blocks.png" width="256" height="240" alt="Ebiten example: blocks" class="img-thumbnail"></a>
|
<a href="examples/blocks.html"><img src="images/examples/blocks.png" width="256" height="240" alt="Ebiten example: blocks" class="img-thumbnail"></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p><a href="https://blog.golang.org/go-programming-language-turns-two">The Gopher photographs by Chris Nokleberg</a> are licensed under <a href="https://creativecommons.org/licenses/by/3.0/">the Creative Commons 3.0 Attributions License</a>.</p>
|
<p><a href="https://blog.golang.org/go-programming-language-turns-two">The Gopher photographs by Chris Nokleberg</a> are licensed under <a href="https://creativecommons.org/licenses/by/3.0/">the Creative Commons 3.0 Attributions License</a>.</p>
|
||||||
|
|
||||||
<h3>Execute the examples</h3>
|
<h3>Execute the examples</h3>
|
||||||
|
Loading…
Reference in New Issue
Block a user