diff --git a/examples/splashmenu/scenes/assets/bsoft.png b/examples/splashmenu/scenes/assets/bsoft.png new file mode 100644 index 0000000..8ff61e3 Binary files /dev/null and b/examples/splashmenu/scenes/assets/bsoft.png differ diff --git a/examples/splashmenu/scenes/assets/title.png b/examples/splashmenu/scenes/assets/title.png new file mode 100644 index 0000000..4063474 Binary files /dev/null and b/examples/splashmenu/scenes/assets/title.png differ diff --git a/examples/splashmenu/scenes/bsoft.go b/examples/splashmenu/scenes/bsoft.go new file mode 100644 index 0000000..b81c80c --- /dev/null +++ b/examples/splashmenu/scenes/bsoft.go @@ -0,0 +1,173 @@ +package splashmenu + +import ( + "bytes" + "cosmos/diego/groovy" + _ "embed" + "image" + "image/color" + _ "image/jpeg" + _ "image/png" + "log" + "math" + "math/rand" + + "github.com/hajimehoshi/ebiten/v2" + "github.com/hajimehoshi/ebiten/v2/inpututil" +) + +const ( + magicRatio = 12 + maxGlitchOffset = 25 + minGlitchInterval = 60 + maxGlitchTimeOffset = 15 +) + +var ( + //go:embed assets/bsoft.png + mImage_png []byte + imgDimensions = groovy.Area{Width: 827, Height: 628} + + //go:embed assets/title.png + bImage_png []byte + titlePosition = groovy.Area{Width: 580, Height: 490} + + imgToBlur *ebiten.Image + bImage *ebiten.Image + + glitchTimer uint +) + +func init() { + // Decode an image from the image file's byte slice. + img, _, err := image.Decode(bytes.NewReader(mImage_png)) + if err != nil { + log.Fatal(err) + } + imgToBlur = ebiten.NewImageFromImage(img) + + // Decode an image from the image file's byte slice. + img, _, err = image.Decode(bytes.NewReader(bImage_png)) + if err != nil { + log.Fatal(err) + } + bImage = ebiten.NewImageFromImage(img) + + glitchTimer = 15 +} + +type Bsoft struct { + Dimensions groovy.Area + events map[groovy.SceneEvent]func() + bgcolor color.RGBA + increment int + renderTarget *ebiten.Image + mosaicRatio uint + countDown uint +} + +func NewBsoft() Bsoft { + return Bsoft{ + bgcolor: backgroundBaseColor, + events: make(map[groovy.SceneEvent]func()), + mosaicRatio: 16, + increment: 0, + } +} + +func (b *Bsoft) Draw(screen *ebiten.Image) { + b.DrawGlitchLogo(screen) + b.DrawGlitchLogoText(screen) +} + +func (b *Bsoft) DrawGlitchLogo(screen *ebiten.Image) { + //summation of sinusoids with different frequencies for 'step' response on pixel-glithcy-ness + var sum float64 = 0 + + for i := 0; i < 10; i++ { + w := math.Sin(float64(b.increment) / (math.Pi * float64(i+1))) + sum = sum + w + } + ratio := sum + magicRatio + + // Shrink the image once. + op := &ebiten.DrawImageOptions{} + op.GeoM.Scale(1/ratio, 1/ratio) + b.renderTarget.DrawImage(imgToBlur, op) + + // Enlarge the shrunk image. + // The filter is the nearest filter, so the result will be mosaic. + op = &ebiten.DrawImageOptions{} + op.GeoM.Scale(ratio, ratio) + + op.GeoM.Translate(float64(b.Dimensions.Width)/2-float64(imgDimensions.Width)/2, float64(b.Dimensions.Height)/2-float64(imgDimensions.Height)/2-35) + screen.DrawImage(b.renderTarget, op) +} + +func (b *Bsoft) DrawGlitchLogoText(screen *ebiten.Image) { + //glitchy twitch title behaviour + op := &ebiten.DrawImageOptions{} + var glitchX int = 0 + var glitchY int = 0 + X := minGlitchInterval + Y := maxGlitchTimeOffset + + //glithiness active until countdown resolved, randomize the delta offset + //creates temporary glitch effect on the logo text, this is achieved as follows: + // 1. if the glitch frame counter is active, glitch offset is in effect and computed + // 2. otherwise, we engage the glitch after X frames have passed with a random Y frame offset + if b.countDown > 0 { + glitchX = rand.Intn(maxGlitchOffset) * getRandomDirection() + glitchY = rand.Intn(maxGlitchOffset) * getRandomDirection() + b.countDown-- + + } else if b.increment%(X+rand.Intn(Y)) == 0 { + b.countDown = glitchTimer //set glitch timer, in frames + } + op.GeoM.Translate(float64(titlePosition.Width+glitchX), float64(titlePosition.Height+glitchY)) + screen.DrawImage(bImage, op) +} + +// returns +1 or -1 as an integer +func getRandomDirection() int { + if rand.Intn(2) == 1 { + return -1 + } + return 1 +} + +func (b *Bsoft) Update() error { + b.increment++ + + var keysPressed []ebiten.Key + keysPressed = inpututil.AppendPressedKeys(keysPressed[:0]) + + for _, k := range keysPressed { + switch k { + case ebiten.KeyUp: + b.mosaicRatio = b.mosaicRatio + 1 + case ebiten.KeyDown: + if b.mosaicRatio > 1 { + b.mosaicRatio = b.mosaicRatio - 1 + } + case ebiten.KeyQ: + if b.events[groovy.COMPLETED] != nil { + b.events[groovy.COMPLETED]() + } + default: + } + + } + + return nil +} + +// sets sene dimensions +func (b *Bsoft) SetDimensions(a groovy.Area) { + b.Dimensions = a + b.renderTarget = ebiten.NewImage(imgDimensions.Width, imgDimensions.Height) +} + +func (b *Bsoft) SetEventHandler(event groovy.SceneEvent, f func()) { + b.events[event] = f +}