227 lines
5.0 KiB
Go
227 lines
5.0 KiB
Go
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
|
|
//in seconds
|
|
fadeTimer = 1.0
|
|
fadeOffset = 2.0
|
|
)
|
|
|
|
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 Fader struct {
|
|
Curtain *ebiten.Image
|
|
counter int
|
|
timer float32
|
|
offset float32
|
|
alpha float32
|
|
fps float32
|
|
}
|
|
|
|
func NewFader(x, y int, t, offset float32) Fader {
|
|
f := Fader{
|
|
Curtain: ebiten.NewImage(x, y),
|
|
timer: t,
|
|
counter: 0,
|
|
alpha: 0.0,
|
|
fps: 60,
|
|
offset: offset,
|
|
}
|
|
f.Curtain.Fill(color.RGBA{0x00, 0x00, 0x00, 0xFF})
|
|
return f
|
|
}
|
|
func (f *Fader) Update() {
|
|
f.counter++
|
|
|
|
f.alpha = (float32(f.counter) - (f.offset * f.fps)) / (f.timer * f.fps)
|
|
if f.alpha < 0 {
|
|
f.alpha = 0
|
|
}
|
|
}
|
|
|
|
func (f *Fader) Draw(img *ebiten.Image) {
|
|
op := &ebiten.DrawImageOptions{}
|
|
op.ColorScale.Scale(1, 1, 1, f.alpha)
|
|
img.DrawImage(f.Curtain, op)
|
|
}
|
|
|
|
func (f *Fader) IsComplete() bool {
|
|
return f.alpha > 1.0
|
|
}
|
|
|
|
type Bsoft struct {
|
|
Dimensions groovy.Area
|
|
events map[groovy.SceneEvent]func()
|
|
bgcolor color.RGBA
|
|
increment int
|
|
renderTarget *ebiten.Image
|
|
mosaicRatio uint
|
|
countDown uint
|
|
curtain Fader
|
|
}
|
|
|
|
func NewBsoft() Bsoft {
|
|
return Bsoft{
|
|
bgcolor: backgroundBaseColor,
|
|
events: make(map[groovy.SceneEvent]func()),
|
|
mosaicRatio: 16,
|
|
increment: 0,
|
|
}
|
|
}
|
|
|
|
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:
|
|
}
|
|
|
|
}
|
|
|
|
if b.curtain.IsComplete() {
|
|
if b.events[groovy.COMPLETED] != nil {
|
|
b.events[groovy.COMPLETED]()
|
|
}
|
|
}
|
|
b.curtain.Update()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b *Bsoft) Draw(screen *ebiten.Image) {
|
|
b.DrawGlitchLogo(screen)
|
|
b.DrawGlitchLogoText(screen)
|
|
b.curtain.Draw(screen)
|
|
}
|
|
|
|
func (b *Bsoft) SetEventHandler(event groovy.SceneEvent, f func()) {
|
|
b.events[event] = f
|
|
}
|
|
|
|
// sets sene dimensions
|
|
func (b *Bsoft) SetDimensions(a groovy.Area) {
|
|
b.Dimensions = a
|
|
b.renderTarget = ebiten.NewImage(imgDimensions.Width, imgDimensions.Height)
|
|
b.curtain = NewFader(a.Width, a.Height, fadeTimer, fadeOffset)
|
|
}
|
|
|
|
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
|
|
}
|