docs: Update

This commit is contained in:
Hajime Hoshi 2018-01-29 23:39:30 +09:00
parent 80e4ee0998
commit 4f67af726b
14 changed files with 267 additions and 210 deletions

View File

@ -82,7 +82,7 @@ func update(screen *ebiten.Image) error {
func main() { func main() {
var err error var err error
ebitenImage, _, err = ebitenutil.NewImageFromFile(ebitenutil.JoinStringsIntoFilePath("_resources", "images", "ebiten.png"), ebiten.FilterNearest) ebitenImage, _, err = ebitenutil.NewImageFromFile("_resources/images/ebiten.png", ebiten.FilterNearest)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@ -120,11 +120,11 @@ func playerBarRect() (x, y, w, h int) {
func NewPlayer(audioContext *audio.Context) (*Player, error) { func NewPlayer(audioContext *audio.Context) (*Player, error) {
const bytesPerSample = 4 // TODO: This should be defined in audio package const bytesPerSample = 4 // TODO: This should be defined in audio package
wavF, err := ebitenutil.OpenFile(ebitenutil.JoinStringsIntoFilePath("_resources", "audio", "jab.wav")) wavF, err := ebitenutil.OpenFile("_resources/audio/jab.wav")
if err != nil { if err != nil {
return nil, err return nil, err
} }
mp3F, err := ebitenutil.OpenFile(ebitenutil.JoinStringsIntoFilePath("_resources", "audio", "classic.mp3")) mp3F, err := ebitenutil.OpenFile("_resources/audio/classic.mp3")
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -45,13 +45,16 @@ import (
var ( var (
count int count int
highDPIImage *ebiten.Image highDPIImage *ebiten.Image
highDPIImageCh = make(chan *ebiten.Image)
) )
func init() { func init() {
// licensed under Public Domain // Licensed under Public Domain
// https://commons.wikimedia.org/wiki/File:As08-16-2593.jpg // https://commons.wikimedia.org/wiki/File:As08-16-2593.jpg
const url = "https://upload.wikimedia.org/wikipedia/commons/1/1f/As08-16-2593.jpg" const url = "https://upload.wikimedia.org/wikipedia/commons/1/1f/As08-16-2593.jpg"
// Load the image asynchronously.
go func() {
res, err := http.Get(url) res, err := http.Get(url)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
@ -63,17 +66,35 @@ func init() {
log.Fatal(err) log.Fatal(err)
} }
highDPIImage, err = ebiten.NewImageFromImage(img, ebiten.FilterLinear) eimg, err := ebiten.NewImageFromImage(img, ebiten.FilterLinear)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
highDPIImageCh <- eimg
close(highDPIImageCh)
}()
} }
func update(screen *ebiten.Image) error { func update(screen *ebiten.Image) error {
if highDPIImage == nil {
// Use select and 'default' clause for non-blocking receiving.
select {
case img := <-highDPIImageCh:
highDPIImage = img
default:
}
}
if ebiten.IsRunningSlowly() { if ebiten.IsRunningSlowly() {
return nil return nil
} }
if highDPIImage == nil {
ebitenutil.DebugPrint(screen, "Loading the image...")
return nil
}
scale := ebiten.DeviceScaleFactor() scale := ebiten.DeviceScaleFactor()
sw, sh := screen.Size() sw, sh := screen.Size()
@ -85,6 +106,7 @@ func update(screen *ebiten.Image) error {
// The image is just too big. Adjust the scale. // The image is just too big. Adjust the scale.
op.GeoM.Scale(0.25, 0.25) op.GeoM.Scale(0.25, 0.25)
// Scale the image by the device ratio so that the rendering result can be same // Scale the image by the device ratio so that the rendering result can be same
// on various (diffrent-DPI) environments. // on various (diffrent-DPI) environments.
op.GeoM.Scale(scale, scale) op.GeoM.Scale(scale, scale)

View File

@ -47,15 +47,17 @@ const (
) )
var ( var (
hueInt = 0 hue128 = 0
saturationInt = 128 saturation128 = 128
valueInt = 128 value128 = 128
inverted = false inverted = false
prevPressedI = false prevPressedI = false
gophersImage *ebiten.Image gophersImage *ebiten.Image
) )
// clamp clamps v to the range [min, max].
func clamp(v, min, max int) int { func clamp(v, min, max int) int {
if min > max { if min > max {
panic("min must <= max") panic("min must <= max")
@ -70,25 +72,30 @@ func clamp(v, min, max int) int {
} }
func update(screen *ebiten.Image) error { func update(screen *ebiten.Image) error {
// Adjust HSV values along with the user's input.
if ebiten.IsKeyPressed(ebiten.KeyQ) { if ebiten.IsKeyPressed(ebiten.KeyQ) {
hueInt-- hue128--
} }
if ebiten.IsKeyPressed(ebiten.KeyW) { if ebiten.IsKeyPressed(ebiten.KeyW) {
hueInt++ hue128++
} }
if ebiten.IsKeyPressed(ebiten.KeyA) { if ebiten.IsKeyPressed(ebiten.KeyA) {
saturationInt-- saturation128--
} }
if ebiten.IsKeyPressed(ebiten.KeyS) { if ebiten.IsKeyPressed(ebiten.KeyS) {
saturationInt++ saturation128++
} }
if ebiten.IsKeyPressed(ebiten.KeyZ) { if ebiten.IsKeyPressed(ebiten.KeyZ) {
valueInt-- value128--
} }
if ebiten.IsKeyPressed(ebiten.KeyX) { if ebiten.IsKeyPressed(ebiten.KeyX) {
valueInt++ value128++
} }
hue128 = clamp(hue128, -256, 256)
saturation128 = clamp(saturation128, 0, 256)
value128 = clamp(value128, 0, 256)
pressedI := ebiten.IsKeyPressed(ebiten.KeyI) pressedI := ebiten.IsKeyPressed(ebiten.KeyI)
if pressedI && !prevPressedI { if pressedI && !prevPressedI {
inverted = !inverted inverted = !inverted
@ -98,18 +105,19 @@ func update(screen *ebiten.Image) error {
if ebiten.IsRunningSlowly() { if ebiten.IsRunningSlowly() {
return nil return nil
} }
hueInt = clamp(hueInt, -256, 256)
saturationInt = clamp(saturationInt, 0, 256)
valueInt = clamp(valueInt, 0, 256)
// Center the image on the screen.
w, h := gophersImage.Size() w, h := gophersImage.Size()
op := &ebiten.DrawImageOptions{} op := &ebiten.DrawImageOptions{}
op.GeoM.Translate(float64(screenWidth-w)/2, float64(screenHeight-h)/2) op.GeoM.Translate(float64(screenWidth-w)/2, float64(screenHeight-h)/2)
hue := float64(hueInt) * 2 * math.Pi / 128 // Change HSV.
saturation := float64(saturationInt) / 128 hue := float64(hue128) * 2 * math.Pi / 128
value := float64(valueInt) / 128 saturation := float64(saturation128) / 128
value := float64(value128) / 128
op.ColorM.ChangeHSV(hue, saturation, value) op.ColorM.ChangeHSV(hue, saturation, value)
// Invert the color.
if inverted { if inverted {
op.ColorM.Scale(-1, -1, -1, 1) op.ColorM.Scale(-1, -1, -1, 1)
op.ColorM.Translate(1, 1, 1, 0) op.ColorM.Translate(1, 1, 1, 0)
@ -117,6 +125,7 @@ func update(screen *ebiten.Image) error {
screen.DrawImage(gophersImage, op) screen.DrawImage(gophersImage, op)
// Draw the text of the current status.
msgInverted := "false" msgInverted := "false"
if inverted { if inverted {
msgInverted = "true" msgInverted = "true"
@ -131,7 +140,7 @@ Inverted: %s [I]`, hue, saturation, value, msgInverted)
func main() { func main() {
var err error var err error
gophersImage, _, err = ebitenutil.NewImageFromFile(ebitenutil.JoinStringsIntoFilePath("_resources", "images", "gophers.jpg"), ebiten.FilterNearest) gophersImage, _, err = ebitenutil.NewImageFromFile("_resources/images/gophers.jpg", ebiten.FilterNearest)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@ -52,20 +52,26 @@ var (
func update(screen *ebiten.Image) error { func update(screen *ebiten.Image) error {
count++ count++
if ebiten.IsRunningSlowly() { if ebiten.IsRunningSlowly() {
return nil return nil
} }
// Center the image on the screen.
w, h := gophersImage.Size() w, h := gophersImage.Size()
op := &ebiten.DrawImageOptions{} op := &ebiten.DrawImageOptions{}
op.GeoM.Translate(float64(screenWidth-w)/2, float64(screenHeight-h)/2) op.GeoM.Translate(float64(screenWidth-w)/2, float64(screenHeight-h)/2)
// Rotate the hue.
op.ColorM.RotateHue(float64(count%360) * 2 * math.Pi / 360) op.ColorM.RotateHue(float64(count%360) * 2 * math.Pi / 360)
screen.DrawImage(gophersImage, op) screen.DrawImage(gophersImage, op)
return nil return nil
} }
func main() { func main() {
var err error var err error
gophersImage, _, err = ebitenutil.NewImageFromFile(ebitenutil.JoinStringsIntoFilePath("_resources", "images", "gophers.jpg"), ebiten.FilterNearest) gophersImage, _, err = ebitenutil.NewImageFromFile("_resources/images/gophers.jpg", ebiten.FilterNearest)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@ -35,7 +35,6 @@ import (
"fmt" "fmt"
_ "image/png" _ "image/png"
"log" "log"
"math"
"github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten"
"github.com/hajimehoshi/ebiten/ebitenutil" "github.com/hajimehoshi/ebiten/ebitenutil"
@ -47,10 +46,19 @@ const (
) )
var ( var (
theViewport = &viewport{}
bgImage *ebiten.Image bgImage *ebiten.Image
repeatedBgImage *ebiten.Image )
groundImage *ebiten.Image
func init() {
var err error
bgImage, _, err = ebitenutil.NewImageFromFile("_resources/images/tile.png", ebiten.FilterNearest)
if err != nil {
log.Fatal(err)
}
}
var (
theViewport = &viewport{}
) )
type viewport struct { type viewport struct {
@ -58,80 +66,48 @@ type viewport struct {
y16 int y16 int
} }
func round(x float64) float64 {
return math.Floor(x + 0.5)
}
func (p *viewport) Move() { func (p *viewport) Move() {
w, h := bgImage.Size() w, h := bgImage.Size()
mx := w * 16 maxX16 := w * 16
my := h * 16 maxY16 := h * 16
p.x16 += w / 32 p.x16 += w / 32
p.y16 += h / 32 p.y16 += h / 32
p.x16 %= maxX16
for mx <= p.x16 { p.y16 %= maxY16
p.x16 -= mx
}
for my <= p.y16 {
p.y16 -= my
}
for p.x16 < 0 {
p.x16 += mx
}
for p.y16 < 0 {
p.y16 += my
}
} }
func (p *viewport) Position() (int, int) { func (p *viewport) Position() (int, int) {
return p.x16, p.y16 return p.x16, p.y16
} }
func updateGroundImage(ground *ebiten.Image) {
ground.Clear()
x16, y16 := theViewport.Position()
op := &ebiten.DrawImageOptions{}
op.GeoM.Translate(float64(-x16)/16, float64(-y16)/16)
ground.DrawImage(repeatedBgImage, op)
}
func drawGroundImage(screen *ebiten.Image, ground *ebiten.Image) {
op := &ebiten.DrawImageOptions{}
screen.DrawImage(ground, op)
}
func update(screen *ebiten.Image) error { func update(screen *ebiten.Image) error {
theViewport.Move() theViewport.Move()
if ebiten.IsRunningSlowly() { if ebiten.IsRunningSlowly() {
return nil return nil
} }
updateGroundImage(groundImage)
drawGroundImage(screen, groundImage)
msg := fmt.Sprintf("FPS: %0.2f", ebiten.CurrentFPS()) x16, y16 := theViewport.Position()
ebitenutil.DebugPrint(screen, msg) offsetX, offsetY := float64(-x16)/16, float64(-y16)/16
return nil
}
func main() { // Draw bgImage on the screen repeatedly.
var err error const repeat = 3
bgImage, _, err = ebitenutil.NewImageFromFile(ebitenutil.JoinStringsIntoFilePath("_resources", "images", "tile.png"), ebiten.FilterNearest)
if err != nil {
log.Fatal(err)
}
w, h := bgImage.Size() w, h := bgImage.Size()
const repeat = 5
repeatedBgImage, _ = ebiten.NewImage(w*repeat, h*repeat, ebiten.FilterNearest)
for j := 0; j < repeat; j++ { for j := 0; j < repeat; j++ {
for i := 0; i < repeat; i++ { for i := 0; i < repeat; i++ {
op := &ebiten.DrawImageOptions{} op := &ebiten.DrawImageOptions{}
op.GeoM.Translate(float64(w*i), float64(h*j)) op.GeoM.Translate(float64(w*i), float64(h*j))
repeatedBgImage.DrawImage(bgImage, op) op.GeoM.Translate(offsetX, offsetY)
screen.DrawImage(bgImage, op)
} }
} }
groundImage, _ = ebiten.NewImage(screenWidth, screenHeight, ebiten.FilterNearest)
ebitenutil.DebugPrint(screen, fmt.Sprintf("FPS: %0.2f", ebiten.CurrentFPS()))
return nil
}
func main() {
if err := ebiten.Run(update, screenWidth, screenHeight, 2, "Infinite Scroll (Ebiten Demo)"); err != nil { if err := ebiten.Run(update, screenWidth, screenHeight, 2, "Infinite Scroll (Ebiten Demo)"); err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@ -49,7 +49,7 @@ var keyboardImage *ebiten.Image
func init() { func init() {
var err error var err error
keyboardImage, _, err = ebitenutil.NewImageFromFile(ebitenutil.JoinStringsIntoFilePath("_resources", "images", "keyboard", "keyboard.png"), ebiten.FilterNearest) keyboardImage, _, err = ebitenutil.NewImageFromFile("_resources/images/keyboard/keyboard.png", ebiten.FilterNearest)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -86,15 +86,7 @@ var keyNames = map[ebiten.Key]string{
} }
func update(screen *ebiten.Image) error { func update(screen *ebiten.Image) error {
if ebiten.IsRunningSlowly() { // Collect pressed keys' names.
return nil
}
const offsetX, offsetY = 24, 40
op := &ebiten.DrawImageOptions{}
op.GeoM.Translate(offsetX, offsetY)
op.ColorM.Scale(0.5, 0.5, 0.5, 1)
screen.DrawImage(keyboardImage, op)
pressed := []string{} pressed := []string{}
for i := 0; i <= 9; i++ { for i := 0; i <= 9; i++ {
if ebiten.IsKeyPressed(ebiten.Key(i) + ebiten.Key0) { if ebiten.IsKeyPressed(ebiten.Key(i) + ebiten.Key0) {
@ -102,12 +94,12 @@ func update(screen *ebiten.Image) error {
} }
} }
for c := 'A'; c <= 'Z'; c++ { for c := 'A'; c <= 'Z'; c++ {
if ebiten.IsKeyPressed(ebiten.Key(c) - 'A' + ebiten.KeyA) { if ebiten.IsKeyPressed(ebiten.KeyA + ebiten.Key(c-'A')) {
pressed = append(pressed, string(c)) pressed = append(pressed, string(c))
} }
} }
for i := 1; i <= 12; i++ { for i := 1; i <= 12; i++ {
if ebiten.IsKeyPressed(ebiten.Key(i) + ebiten.KeyF1 - 1) { if ebiten.IsKeyPressed(ebiten.KeyF1 + ebiten.Key(i-1)) {
pressed = append(pressed, "F"+strconv.Itoa(i)) pressed = append(pressed, "F"+strconv.Itoa(i))
} }
} }
@ -117,6 +109,22 @@ func update(screen *ebiten.Image) error {
} }
} }
if ebiten.IsRunningSlowly() {
return nil
}
const (
offsetX = 24
offsetY = 40
)
// Draw the base (grayed) keyboard image.
op := &ebiten.DrawImageOptions{}
op.GeoM.Translate(offsetX, offsetY)
op.ColorM.Scale(0.5, 0.5, 0.5, 1)
screen.DrawImage(keyboardImage, op)
// Draw the highlighted keys.
op = &ebiten.DrawImageOptions{} op = &ebiten.DrawImageOptions{}
for _, p := range pressed { for _, p := range pressed {
op.GeoM.Reset() op.GeoM.Reset()

View File

@ -39,40 +39,50 @@ import (
"github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten"
) )
// 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 func newArea(width, height int) [][]bool {
func NewWorld(width, height int) *World { a := make([][]bool, height)
world := World{ for i := 0; i < height; i++ {
area: makeArea(width, height), a[i] = make([]bool, width)
rnd: rand.New(rand.NewSource(time.Now().UnixNano())),
} }
return &world return a
} }
// RandomSeed inits world with a random state // NewWorld creates a new world.
func (w *World) RandomSeed(limit int) { func NewWorld(width, height int, maxInitLiveCells int) *World {
w := &World{
area: newArea(width, height),
}
w.init(maxInitLiveCells)
return w
}
func init() {
rand.Seed(time.Now().UnixNano())
}
// init inits world with a random state.
func (w *World) init(maxLiveCells 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 < maxLiveCells; i++ {
x := w.rnd.Intn(width) x := rand.Intn(width)
y := w.rnd.Intn(height) y := rand.Intn(height)
w.area[y][x] = true w.area[y][x] = true
} }
} }
// Progress game state by one tick // Update game state by one tick.
func (w *World) Progress() { func (w *World) Update() {
height := len(w.area) height := len(w.area)
width := len(w.area[0]) width := len(w.area[0])
next := makeArea(width, height) next := newArea(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++ {
pop := neighbourCount(w.area, x, y) pop := neighbourCount(w.area, x, y)
switch { switch {
case pop < 2: case pop < 2:
@ -100,66 +110,63 @@ func (w *World) Progress() {
w.area = next w.area = next
} }
// DrawImage paints current game state // Draw paints current game state.
func (w *World) DrawImage(pix []uint8) { func (w *World) Draw(pix []byte) {
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 idx := 4*y*width + 4*x
if w.area[y][x] { if w.area[y][x] {
pix[pos] = 0xff pix[idx] = 0xff
pix[pos+1] = 0xff pix[idx+1] = 0xff
pix[pos+2] = 0xff pix[idx+2] = 0xff
pix[pos+3] = 0xff pix[idx+3] = 0xff
} else { } else {
pix[pos] = 0 pix[idx] = 0
pix[pos+1] = 0 pix[idx+1] = 0
pix[pos+2] = 0 pix[idx+2] = 0
pix[pos+3] = 0 pix[idx+3] = 0
} }
} }
} }
} }
// neighbourCount calculates the Moore neighborhood of x, y func max(a, b int) int {
if a < b {
return b
}
return a
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
// neighbourCount calculates the Moore neighborhood of (x, y).
func neighbourCount(a [][]bool, x, y int) int { func neighbourCount(a [][]bool, x, y int) int {
height := len(a) w := len(a[0])
width := len(a[0]) h := len(a)
lowX := 0 minI := max(x-1, 0)
if x > 0 { minJ := max(y-1, 0)
lowX = x - 1 maxI := min(x+1, w-1)
} maxJ := min(y+1, h-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++ {
if !(pX == x && pY == y) && a[pY][pX] {
near++
}
}
}
return near c := 0
for j := minJ; j <= maxJ; j++ {
for i := minI; i <= maxI; i++ {
if i == x && j == y {
continue
} }
if a[j][i] {
func makeArea(width, height int) [][]bool { c++
area := make([][]bool, height)
for i := 0; i < height; i++ {
area[i] = make([]bool, width)
} }
return area }
}
return c
} }
const ( const (
@ -168,23 +175,24 @@ const (
) )
var ( var (
world = NewWorld(screenWidth, screenHeight) world = NewWorld(screenWidth, screenHeight, int((screenWidth*screenHeight)/10))
pixels = make([]uint8, screenWidth*screenHeight*4) pixels = make([]byte, screenWidth*screenHeight*4)
) )
func update(screen *ebiten.Image) error { func update(screen *ebiten.Image) error {
world.Progress() world.Update()
if ebiten.IsRunningSlowly() { if ebiten.IsRunningSlowly() {
return nil return nil
} }
world.DrawImage(pixels)
world.Draw(pixels)
screen.ReplacePixels(pixels) screen.ReplacePixels(pixels)
return nil return nil
} }
func main() { func main() {
world.RandomSeed(int((screenWidth * screenHeight) / 10)) if err := ebiten.Run(update, screenWidth, screenHeight, 2, "Game of Life (Ebiten Demo)"); err != nil {
if err := ebiten.Run(update, screenWidth, screenHeight, 2.0, "Game of Life (Ebiten Demo)"); err != nil {
log.Fatal(err) log.Fatal(err)
} }
} }

View File

@ -48,9 +48,9 @@ const (
) )
var ( var (
gophersImage *ebiten.Image bgImage *ebiten.Image
fiveyearsImage *ebiten.Image fgImage *ebiten.Image
maskImage *ebiten.Image maskedFgImage *ebiten.Image
spotLightImage *ebiten.Image spotLightImage *ebiten.Image
spotLightX = 0 spotLightX = 0
spotLightY = 0 spotLightY = 0
@ -58,6 +58,36 @@ var (
spotLightVY = 1 spotLightVY = 1
) )
func init() {
var err error
bgImage, _, err = ebitenutil.NewImageFromFile("_resources/images/gophers.jpg", ebiten.FilterNearest)
if err != nil {
log.Fatal(err)
}
fgImage, _, err = ebitenutil.NewImageFromFile("_resources/images/fiveyears.jpg", ebiten.FilterNearest)
if err != nil {
log.Fatal(err)
}
maskedFgImage, _ = ebiten.NewImage(screenWidth, screenHeight, ebiten.FilterNearest)
// Initialize the spot light image.
const r = 64
alphas := image.Point{r * 2, r * 2}
a := image.NewAlpha(image.Rectangle{image.ZP, alphas})
for j := 0; j < alphas.Y; j++ {
for i := 0; i < alphas.X; i++ {
// d is the distance between (i, j) and the (circle) center.
d := math.Sqrt(float64((i-r)*(i-r) + (j-r)*(j-r)))
// Alphas around the center are 0 and values outside of the circle are 0xff.
b := uint8(max(0, min(0xff, int(3*d*0xff/r)-2*0xff)))
a.SetAlpha(i, j, color.Alpha{b})
}
}
spotLightImage, _ = ebiten.NewImageFromImage(a, ebiten.FilterNearest)
}
func update(screen *ebiten.Image) error { func update(screen *ebiten.Image) error {
spotLightX += spotLightVX spotLightX += spotLightVX
spotLightY += spotLightVY spotLightY += spotLightVY
@ -79,22 +109,34 @@ func update(screen *ebiten.Image) error {
spotLightY = -spotLightY + 2*maxY spotLightY = -spotLightY + 2*maxY
spotLightVY = -spotLightVY spotLightVY = -spotLightVY
} }
if ebiten.IsRunningSlowly() { if ebiten.IsRunningSlowly() {
return nil return nil
} }
maskImage.Clear()
// Reset the maskedFgImage.
maskedFgImage.Fill(color.White)
op := &ebiten.DrawImageOptions{} op := &ebiten.DrawImageOptions{}
op.CompositeMode = ebiten.CompositeModeCopy
op.GeoM.Translate(float64(spotLightX), float64(spotLightY)) op.GeoM.Translate(float64(spotLightX), float64(spotLightY))
maskImage.DrawImage(spotLightImage, op) maskedFgImage.DrawImage(spotLightImage, op)
// Use 'source-in' composite mode so that the source image (fgImage) is used but the alpha
// is determined by the destination image (maskedFgImage).
//
// The result image is the source image with the destination alpha. In maskedFgImage, alpha
// values in the hole is 0 and alpha values in other places are 0xff. As a result, the
// maskedFgImage draws the source image with a hole that shape is spotLightImage. Note that
// RGB values in the destination image are ignored.
//
// See also https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_srcin.
op = &ebiten.DrawImageOptions{} op = &ebiten.DrawImageOptions{}
op.CompositeMode = ebiten.CompositeModeSourceOut op.CompositeMode = ebiten.CompositeModeSourceIn
maskImage.DrawImage(fiveyearsImage, op) maskedFgImage.DrawImage(fgImage, op)
screen.Fill(color.RGBA{0x00, 0x00, 0x80, 0xff}) screen.Fill(color.RGBA{0x00, 0x00, 0x80, 0xff})
screen.DrawImage(gophersImage, &ebiten.DrawImageOptions{}) screen.DrawImage(bgImage, &ebiten.DrawImageOptions{})
screen.DrawImage(maskImage, &ebiten.DrawImageOptions{}) screen.DrawImage(maskedFgImage, &ebiten.DrawImageOptions{})
return nil return nil
} }
@ -114,28 +156,6 @@ func min(a, b int) int {
} }
func main() { func main() {
var err error
gophersImage, _, err = ebitenutil.NewImageFromFile(ebitenutil.JoinStringsIntoFilePath("_resources", "images", "gophers.jpg"), ebiten.FilterNearest)
if err != nil {
log.Fatal(err)
}
fiveyearsImage, _, err = ebitenutil.NewImageFromFile(ebitenutil.JoinStringsIntoFilePath("_resources", "images", "fiveyears.jpg"), ebiten.FilterNearest)
if err != nil {
log.Fatal(err)
}
maskImage, _ = ebiten.NewImage(screenWidth, screenHeight, ebiten.FilterNearest)
as := image.Point{128, 128}
a := image.NewAlpha(image.Rectangle{image.ZP, as})
for j := 0; j < as.Y; j++ {
for i := 0; i < as.X; i++ {
r := as.X / 2
d := math.Sqrt(float64((i-r)*(i-r) + (j-r)*(j-r)))
b := uint8(max(0, min(0xff, 3*0xff-int(d*3*0xff)/r)))
a.SetAlpha(i, j, color.Alpha{b})
}
}
spotLightImage, _ = ebiten.NewImageFromImage(a, ebiten.FilterNearest)
if err := ebiten.Run(update, screenWidth, screenHeight, 2, "Masking (Ebiten Demo)"); err != nil { if err := ebiten.Run(update, screenWidth, screenHeight, 2, "Masking (Ebiten Demo)"); err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@ -51,13 +51,26 @@ var (
gophersRenderTarget *ebiten.Image gophersRenderTarget *ebiten.Image
) )
func init() {
var err error
gophersImage, _, err = ebitenutil.NewImageFromFile("_resources/images/gophers.jpg", ebiten.FilterNearest)
if err != nil {
log.Fatal(err)
}
}
func update(screen *ebiten.Image) error { func update(screen *ebiten.Image) error {
if ebiten.IsRunningSlowly() { if ebiten.IsRunningSlowly() {
return nil return nil
} }
// Shrink the image once.
op := &ebiten.DrawImageOptions{} op := &ebiten.DrawImageOptions{}
op.GeoM.Scale(1.0/mosaicRatio, 1.0/mosaicRatio) op.GeoM.Scale(1.0/mosaicRatio, 1.0/mosaicRatio)
gophersRenderTarget.DrawImage(gophersImage, op) gophersRenderTarget.DrawImage(gophersImage, op)
// Enlarge the shrunk image.
// The filter is the nearest filter, so the result will be mosaic.
op = &ebiten.DrawImageOptions{} op = &ebiten.DrawImageOptions{}
op.GeoM.Scale(mosaicRatio, mosaicRatio) op.GeoM.Scale(mosaicRatio, mosaicRatio)
screen.DrawImage(gophersRenderTarget, op) screen.DrawImage(gophersRenderTarget, op)
@ -65,11 +78,6 @@ func update(screen *ebiten.Image) error {
} }
func main() { func main() {
var err error
gophersImage, _, err = ebitenutil.NewImageFromFile(ebitenutil.JoinStringsIntoFilePath("_resources", "images", "gophers.jpg"), ebiten.FilterNearest)
if err != nil {
log.Fatal(err)
}
w, h := gophersImage.Size() w, h := gophersImage.Size()
gophersRenderTarget, _ = ebiten.NewImage(w/mosaicRatio, h/mosaicRatio, ebiten.FilterNearest) gophersRenderTarget, _ = ebiten.NewImage(w/mosaicRatio, h/mosaicRatio, ebiten.FilterNearest)
if err := ebiten.Run(update, screenWidth, screenHeight, 2, "Mosaic (Ebiten Demo)"); err != nil { if err := ebiten.Run(update, screenWidth, screenHeight, 2, "Mosaic (Ebiten Demo)"); err != nil {

View File

@ -73,7 +73,7 @@ func update(screen *ebiten.Image) error {
func main() { func main() {
var err error var err error
gophersImage, _, err = ebitenutil.NewImageFromFile(ebitenutil.JoinStringsIntoFilePath("_resources", "images", "gophers.jpg"), ebiten.FilterNearest) gophersImage, _, err = ebitenutil.NewImageFromFile("_resources/images/gophers.jpg", ebiten.FilterNearest)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@ -56,7 +56,7 @@ var (
) )
func init() { func init() {
f, err := ebitenutil.OpenFile(ebitenutil.JoinStringsIntoFilePath("_resources", "fonts", "arcade_n.ttf")) f, err := ebitenutil.OpenFile("_resources/fonts/arcade_n.ttf")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@ -66,7 +66,7 @@ func update(screen *ebiten.Image) error {
func main() { func main() {
var err error var err error
gophersImage, _, err = ebitenutil.NewImageFromFile(ebitenutil.JoinStringsIntoFilePath("_resources", "images", "gophers.jpg"), ebiten.FilterNearest) gophersImage, _, err = ebitenutil.NewImageFromFile("_resources/images/gophers.jpg", ebiten.FilterNearest)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@ -141,7 +141,7 @@ Press <- or -> to change the number of sprites`, ebiten.CurrentFPS(), spri
func main() { func main() {
var err error var err error
img, _, err := ebitenutil.NewImageFromFile(ebitenutil.JoinStringsIntoFilePath("_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)
} }