Compare commits

..

7 Commits

Author SHA1 Message Date
4c19ce7f87 Added assets. 2023-08-31 17:05:08 -04:00
8138fb6329 Updated for new scenes. 2023-08-31 17:04:40 -04:00
5075196e72 Updated for new scenes. 2023-08-31 17:04:17 -04:00
b6ea6abad6 Updated exit condition. Added interactive mosaic effect. 2023-08-31 17:03:20 -04:00
ffb02b2169 Refactored for consistency. Removed font dependency. 2023-08-31 17:02:21 -04:00
d5094c699b New scene examples added. 2023-08-31 17:01:21 -04:00
4b701e3ff3 Area computation added. 2023-08-31 17:00:30 -04:00
23 changed files with 1147 additions and 154 deletions

View File

@@ -32,9 +32,12 @@ func main() {
// Example loading of two scenes
func loadScenes(m *groovy.Manager) {
//call the loaders for each scene
loadSplash(m)
loadBsoft(m)
loadMenu(m)
//loadSplash(m)
//loadBsoft(m)
//loadMenu(m)
//loadNoise(m)
//loadSplashpad(m)
loadParallax(m)
//reset the manager to start scene 1
m.ResetScenes()
@@ -55,8 +58,10 @@ func loadMenu(m *groovy.Manager) {
1: {Description: "splash", SelectionEvent: groovy.RESET, Mapping: ebiten.Key1},
2: {Description: "bsoft", SelectionEvent: groovy.COMPLETED, Mapping: ebiten.Key2},
3: {Description: "menu"},
4: {Description: "swing"},
5: {Description: "exit", SelectionEvent: groovy.ENDGAME, Mapping: ebiten.Key5},
4: {Description: "noise", SelectionEvent: groovy.COMPLETED, Mapping: ebiten.Key4},
5: {Description: "guy", SelectionEvent: groovy.COMPLETED, Mapping: ebiten.Key5},
6: {Description: "parallax", SelectionEvent: groovy.COMPLETED, Mapping: ebiten.Key6},
7: {Description: "exit", SelectionEvent: groovy.ENDGAME, Mapping: ebiten.Key7},
})
m.AddScene(&sceneMenu)
@@ -74,3 +79,19 @@ func loadBsoft(m *groovy.Manager) {
sceneBsoft := splashmenu.NewBsoft()
m.AddScene(&sceneBsoft)
}
func loadNoise(m *groovy.Manager) {
sceneNoisy := splashmenu.NewNoisy()
m.AddScene(&sceneNoisy)
}
func loadSplashpad(m *groovy.Manager) {
sceneSplashpad := splashmenu.NewSplashPad()
m.AddScene(&sceneSplashpad)
}
func loadParallax(m *groovy.Manager) {
sceneParallax := splashmenu.NewParallax()
m.AddScene(&sceneParallax)
sceneParallax.InitializeParallax()
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 540 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -21,6 +21,9 @@ const (
maxGlitchOffset = 25
minGlitchInterval = 60
maxGlitchTimeOffset = 15
//in seconds
fadeTimer = 1.0
fadeOffset = 2.0
)
var (
@@ -56,6 +59,46 @@ func init() {
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()
@@ -64,6 +107,7 @@ type Bsoft struct {
renderTarget *ebiten.Image
mosaicRatio uint
countDown uint
curtain Fader
}
func NewBsoft() Bsoft {
@@ -75,9 +119,54 @@ func NewBsoft() Bsoft {
}
}
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) {
@@ -135,39 +224,3 @@ func getRandomDirection() int {
}
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
}

View File

@@ -0,0 +1,563 @@
package splashmenu
import (
"bytes"
"cosmos/diego/groovy"
splashmenu "cosmos/diego/groovy/examples/splashmenu/fonts"
"fmt"
"image"
"image/color"
"log"
"math"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/inpututil"
"github.com/hajimehoshi/ebiten/v2/text"
"github.com/hajimehoshi/ebiten/v2/vector"
_ "embed"
)
const (
FORCE_GRAVITY = 0
FORCE_ATTACK = 1
FORCE_ENVIRONMENT = 2
MAX_FLOOR_POS = 720 - 50
HERO_WIDTH = 20
HERO_HEIGHT = HERO_WIDTH
//initial thrust
MAX_JUMP_HEIGHT = -150 //pixels
TIME_TO_APEX = 20.0 // in frame counts (60fps means this value is 1/60 seconds)
JUMP_DURATION = TIME_TO_APEX * 2.0
GRAVITY_VECTOR = -2.0 * MAX_JUMP_HEIGHT / (TIME_TO_APEX * TIME_TO_APEX)
INITIAL_JUMP_VELOCITY = -GRAVITY_VECTOR * TIME_TO_APEX
//weighted drop
TIME_TO_APEX2 = 17.0 // in frame counts (60fps means this value is 1/60 seconds)
GRAVITY_VECTOR2 = -2.0 * MAX_JUMP_HEIGHT / (TIME_TO_APEX2 * TIME_TO_APEX2)
INITIAL_DROP_VELOCITY = -GRAVITY_VECTOR2 * TIME_TO_APEX2
//run animation ramp up
TOP_SPEED = 20
TIME_TO_TOP_SPEED = 6 //in frames
VELOCITY_VECTOR = 2 * TOP_SPEED / (TIME_TO_TOP_SPEED * TIME_TO_TOP_SPEED)
//decel animation ramp down
TIME_TO_STOP = 5 //in frames
//attacking stats
MAX_HERO_ATTACK_DURATION = 22 //in frames
TOTAL_ATTACK_ANIMATION_IMAGES = 8
FRAMES_PER_ATTACK_SLIDE = MAX_HERO_ATTACK_DURATION / TOTAL_ATTACK_ANIMATION_IMAGES
SENSITIVITY = 0.15
)
var (
whiteImage = ebiten.NewImage(3, 3)
whiteSubImage = whiteImage.SubImage(image.Rect(1, 1, 2, 2)).(*ebiten.Image)
//go:embed assets/slash.png
slashImg_png []byte
//go:embed assets/slashright.png
slashRightImg_png []byte
//go:embed assets/forest.png
forestImg_png []byte
slashImgHeight = 60
slashImgCellsWidth = 72
//slashImgCells = 8
//slashImgDimensions = groovy.Area{Width: 576, Height: slashImgHeight}
slashImage = ebiten.NewImage(576, slashImgHeight)
slashRightImage = ebiten.NewImage(576, slashImgHeight)
forestImage = ebiten.NewImage(1280, 720)
//slashRenderTarget = ebiten.NewImage(slashImgCellsWidth, slashImgHeight)
slowmo = false
)
func init() {
// Decode an image from the image file's byte slice.
img, _, err := image.Decode(bytes.NewReader(slashImg_png))
if err != nil {
log.Fatal(err)
}
slashImage = ebiten.NewImageFromImage(img)
img, _, err = image.Decode(bytes.NewReader(slashRightImg_png))
if err != nil {
log.Fatal(err)
}
slashRightImage = ebiten.NewImageFromImage(img)
img, _, err = image.Decode(bytes.NewReader(forestImg_png))
if err != nil {
log.Fatal(err)
}
forestImage = ebiten.NewImageFromImage(img)
}
type vec struct {
x float32
y float32
}
type impulse struct {
f vec
timeleft int
}
type forceType uint32
type MyChar struct {
posX int16
posY int16
mass float32 //g
//acceleration
aX float32
aY float32
jumptime int
jumping bool
leftRamptime int
runningLeft bool
rightRamptime int
runningRight bool
runningStopFrame int
attacking bool
attacktime int
facingLeft bool
}
type force struct {
vector vec
ftype forceType
}
func (v *vec) Size() float32 {
return float32(math.Sqrt(float64(v.x*v.x + v.y*v.y)))
}
type SplashPad struct {
increment int
Dimensions groovy.Area
events map[groovy.SceneEvent]func()
gamepadIDs map[ebiten.GamepadID]struct{}
globalForces []force
hero MyChar
heroImpulse impulse
jumping bool
jumptimer int
}
func NewSplashPad() SplashPad {
h := MyChar{
posX: 640,
posY: MAX_FLOOR_POS,
mass: 80000,
aX: 0,
aY: 0,
jumptime: 0,
jumping: false,
leftRamptime: 0,
runningLeft: false,
rightRamptime: 0,
runningRight: false,
runningStopFrame: 0,
attacking: false,
attacktime: 0,
facingLeft: false,
}
s := SplashPad{
increment: 0,
events: make(map[groovy.SceneEvent]func()),
gamepadIDs: make(map[ebiten.GamepadID]struct{}),
hero: h,
jumping: false,
jumptimer: 0,
}
gravity := &force{}
gravity.vector = vec{x: 0, y: 9.8}
gravity.ftype = FORCE_GRAVITY
s.globalForces = append(s.globalForces, *gravity)
s.heroImpulse = impulse{f: vec{x: 0, y: 0}, timeleft: 0}
whiteImage.Fill(color.White)
log.Println("hero.PosY, prev.impulse.Y, impulse.Y, inc, netFy, timer")
return s
}
func (s *SplashPad) Draw(screen *ebiten.Image) {
s.DrawBackground(screen)
s.DrawController(screen)
s.DrawHero(screen)
if slowmo && s.increment/60%2 == 0 {
text.Draw(screen, " slow mo ", splashmenu.SplashFont.BigTitle, s.Dimensions.Width/2-140, 80, color.White)
}
}
func (s *SplashPad) Update() error {
var keys []ebiten.Key
keys = inpututil.AppendJustPressedKeys(keys[:0])
for _, k := range keys {
if k == ebiten.Key9 {
slowmo = !slowmo
}
}
s.increment++
keys = inpututil.AppendJustPressedKeys(keys[:0])
for _, k := range keys {
if k == ebiten.KeyB {
s.hero.posX = int16(s.Dimensions.Width) / 2
s.hero.posY = MAX_FLOOR_POS
}
}
s.doThePhysics()
if s.hero.jumping && s.hero.jumptime < JUMP_DURATION {
s.hero.jumptime++
} else if s.hero.posY >= MAX_FLOOR_POS {
s.hero.posY = MAX_FLOOR_POS
s.heroImpulse.f.y = 0.0
s.hero.jumptime = 0
s.hero.jumping = false
}
return nil
}
func (s *SplashPad) SetEventHandler(event groovy.SceneEvent, f func()) {
s.events[event] = f
}
func (s *SplashPad) SetDimensions(a groovy.Area) {
s.Dimensions = a
}
func (s *SplashPad) DrawController(screen *ebiten.Image) {
const radInner = 20
const radOuter = 25
const maxOffset = radOuter - radInner
const padding = 20
//leftOriginX := float32(s.Dimensions.Width/2.0) - 1.75*radOuter
//leftOriginY := float32(s.Dimensions.Width / 2.0)
leftOriginX := float32(s.Dimensions.Width - 3*(2*radOuter) - padding)
leftOriginY := float32((2 * radOuter) - padding)
leftXOffset := float32(ebiten.StandardGamepadAxisValue(0, ebiten.StandardGamepadAxisLeftStickHorizontal) * maxOffset)
leftYOffset := float32(ebiten.StandardGamepadAxisValue(0, ebiten.StandardGamepadAxisLeftStickVertical) * maxOffset)
vector.DrawFilledCircle(screen, leftOriginX, leftOriginY, radOuter, color.White, true)
vector.DrawFilledCircle(screen, leftOriginX+leftXOffset, leftOriginY+leftYOffset, radInner, color.Black, true)
vector.DrawFilledCircle(screen, leftOriginX+leftXOffset, leftOriginY+leftYOffset, radInner-5, color.White, true)
vector.DrawFilledCircle(screen, leftOriginX+leftXOffset, leftOriginY+leftYOffset, radInner-8, color.RGBA{0xff, 0x5c, 0x5c, 0xff}, true)
//rightOriginX := float32(s.Dimensions.Width/2.0) + 1.75*radOuter
//rightOriginY := float32(s.Dimensions.Width / 2.0)
rightOriginX := float32(s.Dimensions.Width - 2*(2*radOuter))
rightOriginY := float32((2 * radOuter) - padding)
rightXOffset := float32(ebiten.StandardGamepadAxisValue(0, ebiten.StandardGamepadAxisRightStickHorizontal) * maxOffset)
rightYOffset := float32(ebiten.StandardGamepadAxisValue(0, ebiten.StandardGamepadAxisRightStickVertical) * maxOffset)
vector.DrawFilledCircle(screen, rightOriginX, rightOriginY, radOuter, color.White, true)
vector.DrawFilledCircle(screen, rightOriginX+rightXOffset, rightOriginY+rightYOffset, radInner, color.Black, true)
vector.DrawFilledCircle(screen, rightOriginX+rightXOffset, rightOriginY+rightYOffset, radInner-5, color.White, true)
vector.DrawFilledCircle(screen, rightOriginX+rightXOffset, rightOriginY+rightYOffset, radInner-8, color.RGBA{0xff, 0x5c, 0x5c, 0xff}, true)
gpInput := ebiten.GamepadAxisValue(0, 0)
text.Draw(screen, fmt.Sprintf("%1.02f", gpInput), splashmenu.SplashFont.Menu, 20, 20, color.White)
}
func (s *SplashPad) DrawBackground(screen *ebiten.Image) {
screen.DrawImage(forestImage, nil)
}
func (s *SplashPad) DrawHero(screen *ebiten.Image) {
var path vector.Path
//set up origin and boundaries
x0 := float32(s.hero.posX)
y0 := float32(s.hero.posY)
xd := float32(HERO_WIDTH / 2.0)
yd := float32(HERO_WIDTH / 2.0)
//connect the dots
path.MoveTo(x0-xd, y0-yd)
path.LineTo(x0+xd, y0-yd)
path.LineTo(x0+xd, y0+yd)
path.LineTo(x0-xd, y0+yd)
path.LineTo(x0-xd, y0+yd)
path.Close()
//fill time
var vs []ebiten.Vertex
var is []uint16
vs, is = path.AppendVerticesAndIndicesForFilling(nil, nil)
for i := range vs {
vs[i].SrcX = 1
vs[i].SrcY = 1
vs[i].ColorR = float32(0xff) / 0xFF
vs[i].ColorG = float32(0x00) / 0xFF
vs[i].ColorB = float32(0x00) / 0xFF
vs[i].ColorA = 1
}
//op := &ebiten.DrawTrianglesOptions{}
screen.DrawTriangles(vs, is, whiteSubImage, nil)
op := &ebiten.DrawImageOptions{}
//handle attacking animation
if s.hero.attacking {
i := (s.hero.attacktime / FRAMES_PER_ATTACK_SLIDE)
if s.hero.facingLeft {
op.GeoM.Translate(float64(s.hero.posX-int16(slashImgCellsWidth)/2-13), float64(s.hero.posY-HERO_HEIGHT/2-5))
dx := i * slashImgCellsWidth
screen.DrawImage(slashImage.SubImage(image.Rect(dx, 0, dx+slashImgCellsWidth, slashImgHeight)).(*ebiten.Image), op)
} else {
op.GeoM.Translate(float64(s.hero.posX-24), float64(s.hero.posY-HERO_HEIGHT/2-5))
dx := slashImgCellsWidth*TOTAL_ATTACK_ANIMATION_IMAGES - i*slashImgCellsWidth
screen.DrawImage(slashRightImage.SubImage(image.Rect(dx, 0, dx+slashImgCellsWidth, slashImgHeight)).(*ebiten.Image), op)
}
}
if s.hero.jumping {
text.Draw(screen, "hup!", splashmenu.SplashFont.Splash, int(s.hero.posX), int(s.hero.posY)-HERO_HEIGHT, color.White)
}
}
func (s *SplashPad) doThePhysics() {
var netFx float32 = 0
var netFy float32 = 0
for _, i := range s.globalForces {
netFx = netFx + i.vector.x
netFy = netFy + i.vector.y
}
//HANDLE JUMPING
s.handleJumping()
//HANDLE LEFT/RIGHT MOVEMENT
s.handleLeftMovement()
s.handleRightMovement()
//HANDLE ATTACK
s.handleAttack()
//if s.hero.ramptime < TIME_TO_STOP {
// s.hero.posX = s.hero.posX - (VELOCITY_VECTOR * s.hero.ramptime)
//} else if s.hero.ramptime >= TIME_TO_TOP_SPEED {
// s.hero.posX = s.hero.posX - TOP_SPEED
//}
//
//HANDLE FLOOR COLLISION CHECK
if s.hero.posY > MAX_FLOOR_POS {
s.hero.posY = MAX_FLOOR_POS
}
}
func (s *SplashPad) handleAttack() {
maxButton := ebiten.GamepadButton(ebiten.GamepadButtonCount(0))
for b := ebiten.GamepadButton(0); b < maxButton; b++ {
if ebiten.IsGamepadButtonPressed(0, ebiten.GamepadButton0) {
s.hero.attacking = true
}
}
if ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) {
s.hero.attacking = true
}
if s.hero.attacking {
s.hero.attacktime++
if s.hero.attacktime < MAX_HERO_ATTACK_DURATION {
} else {
s.hero.attacking = false
s.hero.attacktime = 0
}
}
}
func (s *SplashPad) handleJumping() {
maxButton := ebiten.GamepadButton(ebiten.GamepadButtonCount(0))
for b := ebiten.GamepadButton(0); b < maxButton; b++ {
if ebiten.IsGamepadButtonPressed(0, ebiten.GamepadButton1) {
if !s.hero.jumping {
s.hero.jumptime = 0
s.hero.jumping = true
}
}
}
if inpututil.IsKeyJustPressed(ebiten.KeySpace) {
if !s.hero.jumping {
s.hero.jumptime = 0
s.hero.jumping = true
}
}
if s.hero.jumping {
//asymmetric jump to simulate additional character weight
if s.hero.jumptime < TIME_TO_APEX {
t2 := float32((s.hero.jumptime * s.hero.jumptime))
s.hero.posY = int16(0.5*GRAVITY_VECTOR*t2 + INITIAL_JUMP_VELOCITY*float32(s.hero.jumptime) + MAX_FLOOR_POS)
} else {
t2 := float32((s.hero.jumptime * s.hero.jumptime))
s.hero.posY = int16(0.5*GRAVITY_VECTOR2*t2 + INITIAL_DROP_VELOCITY*float32(s.hero.jumptime) + MAX_FLOOR_POS)
}
}
}
func (s *SplashPad) handleLeftMovement() {
var keys []ebiten.Key
keys = inpututil.AppendPressedKeys(keys[:0])
for _, pressed := range keys {
if pressed == ebiten.KeyA {
s.hero.leftRamptime++
s.hero.runningLeft = true
log.Println(" a engaged")
s.hero.facingLeft = true
}
}
keys = inpututil.AppendJustReleasedKeys(keys[:0])
for _, pressed := range keys {
if pressed == ebiten.KeyA {
s.hero.runningLeft = false
s.hero.runningStopFrame = s.increment
log.Println(" a released")
}
}
if ebiten.GamepadAxisValue(0, 0) <= -SENSITIVITY {
s.hero.leftRamptime++
s.hero.runningLeft = true
log.Println(" left gamepad engaged")
s.hero.facingLeft = true
}
if -SENSITIVITY < ebiten.GamepadAxisValue(0, 0) && ebiten.GamepadAxisValue(0, 0) < SENSITIVITY {
s.hero.runningLeft = false
if s.hero.runningStopFrame > 0 {
s.hero.runningStopFrame = s.increment
log.Println(" left gamepad released")
}
}
//if running, need to update their position
var offset = 0
if s.hero.runningLeft {
if s.hero.leftRamptime < TIME_TO_TOP_SPEED {
offset = s.hero.leftRamptime * s.hero.leftRamptime
} else {
offset = TOP_SPEED
}
}
////we've let go, time to decel
//if !s.hero.runningLeft && s.hero.leftRamptime < {
// s.hero.posX = s.hero.posX
//}
if s.hero.runningStopFrame > 0 {
if s.increment-s.hero.runningStopFrame < TIME_TO_STOP {
scaler := float32(TOP_SPEED) / float32(TIME_TO_STOP*TIME_TO_STOP)
deltaT := float32(s.increment - s.hero.runningStopFrame)
offset = TOP_SPEED - int(scaler*deltaT)
offset = max(0, offset) //should not be less than zero, otherwise player would move wrong way ??
log.Printf(" stopping (%d,%d)...", s.hero.posX, s.hero.posY)
} else {
s.hero.runningStopFrame = 0
s.hero.leftRamptime = 0
}
}
s.hero.posX = s.hero.posX - int16(offset)
}
func (s *SplashPad) handleRightMovement() {
var keys []ebiten.Key
keys = inpututil.AppendPressedKeys(keys[:0])
for _, pressed := range keys {
if pressed == ebiten.KeyD {
s.hero.rightRamptime++
s.hero.runningRight = true
log.Println(" d engaged")
s.hero.facingLeft = false
}
}
keys = inpututil.AppendJustReleasedKeys(keys[:0])
for _, pressed := range keys {
if pressed == ebiten.KeyD {
s.hero.runningRight = false
log.Println(" d released")
}
}
if ebiten.GamepadAxisValue(0, 0) >= SENSITIVITY {
s.hero.rightRamptime++
s.hero.runningRight = true
log.Println(" right gamepad engaged")
s.hero.facingLeft = false
}
if -SENSITIVITY < ebiten.GamepadAxisValue(0, 0) && ebiten.GamepadAxisValue(0, 0) < SENSITIVITY {
s.hero.runningRight = false
if s.hero.runningStopFrame > 0 {
s.hero.runningStopFrame = s.increment
log.Println(" right gamepad released")
}
}
//if running, need to update their position
var offset = 0
if s.hero.runningRight {
if s.hero.rightRamptime < TIME_TO_TOP_SPEED {
offset = s.hero.rightRamptime * s.hero.rightRamptime
} else {
offset = TOP_SPEED
}
s.hero.posX = s.hero.posX + int16(offset)
}
}

View File

@@ -2,52 +2,22 @@ package splashmenu
import (
"cosmos/diego/groovy"
splashmenu "cosmos/diego/groovy/examples/splashmenu/fonts"
"image/color"
"log"
"math"
"strconv"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/examples/resources/fonts"
"github.com/hajimehoshi/ebiten/v2/inpututil"
"github.com/hajimehoshi/ebiten/v2/text"
"golang.org/x/image/font"
"golang.org/x/image/font/opentype"
)
var (
titleFont font.Face
menuFont font.Face
backgroundBaseColor color.RGBA
)
func init() {
backgroundBaseColor = color.RGBA{0x33, 0x33, 0x99, 0xFF}
tt, err := opentype.Parse(fonts.PressStart2P_ttf)
if err != nil {
log.Fatal(err)
}
const dpi = 72
titleFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
Size: 16,
DPI: dpi,
Hinting: font.HintingVertical,
})
if err != nil {
log.Fatal(err)
}
menuFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
Size: 10,
DPI: dpi,
Hinting: font.HintingVertical,
})
if err != nil {
log.Fatal(err)
}
}
type Menu struct {
@@ -66,49 +36,6 @@ func NewMenu() Menu {
}
}
func (m *Menu) Draw(screen *ebiten.Image) {
screen.Fill(m.bgcolor)
text.Draw(screen, "menu", titleFont, 40, 40, color.White)
m.RenderMetadata(screen)
m.RenderMenu(screen)
}
func (m *Menu) RenderMetadata(screen *ebiten.Image) {
bgcolor := " 0x" + strconv.FormatUint(uint64(m.bgcolor.R), 16) + " 0x" + strconv.FormatUint(uint64(m.bgcolor.G), 16) + " 0x" + strconv.FormatUint(uint64(m.bgcolor.B), 16)
text.Draw(screen, bgcolor, menuFont, 40, 700, color.White)
}
func (m Menu) SetEventHandler(event groovy.SceneEvent, f func()) {
m.events[event] = f
}
func (m *Menu) SetOptions(options map[int]MenuOption) {
m.options = make(map[int]MenuOption)
for k, v := range options {
m.options[k] = v
}
}
// sets sene dimensions
func (m *Menu) SetDimensions(a groovy.Area) {
m.Dimensions = a
}
func (m *Menu) RenderMenu(screen *ebiten.Image) {
var offset int = 20
screen.Set(m.Dimensions.Width/2, m.Dimensions.Height/2, color.RGBA{0xFF, 0x00, 0x00, 0xFF})
for k, v := range m.options {
m.options[k] = v
text.Draw(screen, strconv.Itoa(k)+": "+v.Description, menuFont, 40, 60+offset*k, color.White)
}
}
func (m *Menu) Update() error {
m.increment++
@@ -136,6 +63,49 @@ func (m *Menu) Update() error {
return nil
}
func (m *Menu) Draw(screen *ebiten.Image) {
screen.Fill(m.bgcolor)
text.Draw(screen, "menu", splashmenu.SplashFont.Title, 40, 40, color.White)
m.RenderMetadata(screen)
m.RenderMenu(screen)
}
func (m *Menu) SetEventHandler(event groovy.SceneEvent, f func()) {
m.events[event] = f
}
// sets sene dimensions
func (m *Menu) SetDimensions(a groovy.Area) {
m.Dimensions = a
}
func (m *Menu) RenderMetadata(screen *ebiten.Image) {
bgcolor := " 0x" + strconv.FormatUint(uint64(m.bgcolor.R), 16) + " 0x" + strconv.FormatUint(uint64(m.bgcolor.G), 16) + " 0x" + strconv.FormatUint(uint64(m.bgcolor.B), 16)
text.Draw(screen, bgcolor, splashmenu.SplashFont.Menu, 40, 700, color.White)
}
func (m *Menu) SetOptions(options map[int]MenuOption) {
m.options = make(map[int]MenuOption)
for k, v := range options {
m.options[k] = v
}
}
func (m *Menu) RenderMenu(screen *ebiten.Image) {
var offset int = 20
screen.Set(m.Dimensions.Width/2, m.Dimensions.Height/2, color.RGBA{0xFF, 0x00, 0x00, 0xFF})
for k, v := range m.options {
m.options[k] = v
text.Draw(screen, strconv.Itoa(k)+": "+v.Description, splashmenu.SplashFont.Menu, 40, 60+offset*k, color.White)
}
}
func (m *Menu) GetMenuSelection() uint {
return m.menuSelection
}

View File

@@ -0,0 +1,157 @@
package splashmenu
import (
"cosmos/diego/groovy"
splashmenu "cosmos/diego/groovy/examples/splashmenu/fonts"
"fmt"
"image"
"image/color"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/inpututil"
"github.com/hajimehoshi/ebiten/v2/text"
)
var (
// theRand = &randy{12345678, 4185243, 776511, 45411}
)
const (
scaleRatio = 12.0
maxMagWidth = 100
)
//type randy struct {
// x, y, z, w uint32
//}
type Noisy struct {
increment int
Dimensions groovy.Area
magnifier groovy.Area
sX int
sY int
events map[groovy.SceneEvent]func()
pixImage *image.RGBA
pixSubImage *image.RGBA
subTarget *ebiten.Image
renderTarget *ebiten.Image
fader *ebiten.Image
//alpha float32
}
func NewNoisy() Noisy {
return Noisy{
increment: 0,
events: make(map[groovy.SceneEvent]func()),
magnifier: groovy.Area{Width: maxMagWidth, Height: maxMagWidth},
}
}
func (n *Noisy) Update() error {
n.increment++
//n.alpha = float32(n.increment) / (60.0 * 5.0) //60fps * 5 seconds = 300frames
//fmt.Printf("%f, ", n.alpha)
n.UpdateNoise()
return nil
}
func (n *Noisy) Draw(screen *ebiten.Image) {
n.subTarget.WritePixels(n.pixSubImage.Pix)
screen.WritePixels(n.pixImage.Pix)
//screen.WritePixels(n.pixSubImage.Pix)
op := &ebiten.DrawImageOptions{}
op.GeoM.Scale(1/scaleRatio, 1/scaleRatio)
n.renderTarget.DrawImage(n.subTarget, op)
op = &ebiten.DrawImageOptions{}
op.GeoM.Scale(scaleRatio, scaleRatio)
op.GeoM.Translate(float64(n.sX)-maxMagWidth/2, float64(n.sY)-maxMagWidth/2)
screen.DrawImage(n.renderTarget, op)
//screen.DrawImage(n.subTarget, nil)
text.Draw(screen, fmt.Sprintf("%04d, ", n.sX), splashmenu.SplashFont.Menu, n.Dimensions.Width/2-58, n.Dimensions.Height/2+18, color.White)
text.Draw(screen, fmt.Sprintf("%04d ", n.sY), splashmenu.SplashFont.Menu, n.Dimensions.Width/2, n.Dimensions.Height/2+18, color.White)
//op = &ebiten.DrawImageOptions{}
//op.ColorScale.Scale(1, 1, 1, n.alpha)
//screen.DrawImage(n.fader, op)
text.Draw(screen, "Q TO EXIT", splashmenu.SplashFont.Title, n.Dimensions.Width/2-77, n.Dimensions.Height/2, color.RGBA{0xff, 0xff, 0xff, uint8(n.increment / (60 * 2) % 0xff)})
}
// register scenevent handler
func (n *Noisy) SetEventHandler(event groovy.SceneEvent, f func()) {
n.events[event] = f
}
// set the current scene dimensions
func (n *Noisy) SetDimensions(a groovy.Area) {
n.Dimensions = a
n.pixImage = image.NewRGBA(image.Rect(0, 0, a.Width, a.Height))
n.fader = ebiten.NewImage(a.Width, a.Height)
n.fader.Fill(color.RGBA{0x00, 0x00, 0x00, 0xff})
n.pixSubImage = image.NewRGBA(image.Rect(0, 0, n.magnifier.Width, n.magnifier.Height))
n.subTarget = ebiten.NewImage(n.magnifier.Width, n.magnifier.Height)
n.renderTarget = ebiten.NewImage(n.magnifier.Width, n.magnifier.Height)
}
func (n *Noisy) UpdateNoise() {
//update cursor position and lock it into the game region bounds
n.sX, n.sY = ebiten.CursorPosition()
n.sX = min(n.sX, n.Dimensions.Width-1)
n.sX = max(n.sX, 0)
n.sY = min(n.sY, n.Dimensions.Height-1)
n.sY = max(n.sY, 0)
l := n.Dimensions.Width * n.Dimensions.Height
for i := 0; i < l; i++ {
//x := theRand.next()
//n.pixImage.Pix[4*i] = uint8(x >> 24)
//n.pixImage.Pix[4*i+1] = uint8(x >> 16)
//n.pixImage.Pix[4*i+2] = uint8(x >> 8)
//n.pixImage.Pix[4*i+3] = 0xff
ballsMode := int(float32(i) * (float32(n.increment) / 60.0))
n.pixImage.Pix[4*i] = uint8(n.increment)
n.pixImage.Pix[4*i] = uint8(i % 0xff)
n.pixImage.Pix[4*i+1] = uint8(i >> 16 % 0xff)
n.pixImage.Pix[4*i+2] = uint8(i >> 24 % 0xff)
n.pixImage.Pix[4*i+3] = uint8(ballsMode) % 0xFF
}
startIdx := n.sX * n.sY * 4
endIdx := startIdx + n.magnifier.Width*n.magnifier.Height*4
if endIdx >= l*4 {
delta := endIdx - l*4
startIdx = startIdx - delta
endIdx = endIdx - delta
}
copy(n.pixSubImage.Pix[:], n.pixImage.Pix[startIdx:endIdx])
//check for user input to transition scene
if inpututil.IsKeyJustPressed(ebiten.KeyQ) {
if n.events[groovy.COMPLETED] != nil {
n.events[groovy.COMPLETED]()
}
}
}
//func (r *randy) next() uint32 {
// // math/rand is too slow to keep 60 FPS on web browsers.
// // Use Xorshift instead: http://en.wikipedia.org/wiki/Xorshift
// t := r.x ^ (r.x << 11)
// r.x, r.y, r.z = r.y, r.z, r.w
// r.w = (r.w ^ (r.w >> 19)) ^ (t ^ (t >> 8))
// return r.w
//}

View File

@@ -0,0 +1,254 @@
package splashmenu
import (
"bytes"
"cosmos/diego/groovy"
"image"
"image/color"
"log"
"math"
"math/rand"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/vector"
_ "embed"
)
const (
GALAXY_SCALE_DIST = 50
DEBRIS_SCALE_DIST = 10
FLOAT_SCALE = 8
ASTEROID_COUNT = 14
MAX_ROTATION_SPEED = 3
MIN_ROTATION_SPEED = 1
OFF_SCREEN_SCALE = 2 // number of parallax dimension screens for the logical game display area
)
var (
//go:embed assets/galaxy4.jpg
galaxyImg_jpg []byte
//go:embed assets/debris_00.png
debrisImg_png []byte
//go:embed assets/roid1.png
roid1Img_png []byte
//go:embed assets/roid2.png
roid2Img_png []byte
//go:embed assets/blend.png
blendImg_png []byte
galaxyBackground *ebiten.Image
debrisImage *ebiten.Image
blendImage *ebiten.Image
blendTmp *ebiten.Image
roids []*ebiten.Image
)
type Asteroid struct {
cX int
cY int
rotationSpeed float64
}
func init() {
// Load up images
img, _, err := image.Decode(bytes.NewReader(galaxyImg_jpg))
if err != nil {
log.Fatal(err)
}
galaxyBackground = ebiten.NewImageFromImage(img)
img, _, err = image.Decode(bytes.NewReader(debrisImg_png))
if err != nil {
log.Fatal(err)
}
debrisImage = ebiten.NewImageFromImage(img)
//asteroids!
img, _, err = image.Decode(bytes.NewReader(roid1Img_png))
if err != nil {
log.Fatal(err)
}
roids = append(roids, ebiten.NewImageFromImage(img))
img, _, err = image.Decode(bytes.NewReader(roid2Img_png))
if err != nil {
log.Fatal(err)
}
roids = append(roids, ebiten.NewImageFromImage(img))
img, _, err = image.Decode(bytes.NewReader(blendImg_png))
if err != nil {
log.Fatal(err)
}
blendImage = ebiten.NewImageFromImage(img)
blendTmp = ebiten.NewImageFromImage(blendImage)
}
type Parallax struct {
events map[groovy.SceneEvent]func()
Dimensions groovy.Area
increment int
asteroids []Asteroid
}
func NewParallax() Parallax {
return Parallax{
events: make(map[groovy.SceneEvent]func()),
}
}
func (p *Parallax) Update() error {
p.increment++
return nil
}
func (p *Parallax) Draw(screen *ebiten.Image) {
p.DrawDynamicBackground(screen)
p.DrawObstacles(screen)
//p.DrawObstaclesWithShamLighting(screen)
p.RepositionTest(screen)
}
func (p *Parallax) SetDimensions(a groovy.Area) {
p.Dimensions = a
}
func (p *Parallax) SetEventHandler(event groovy.SceneEvent, f func()) {
p.events[event] = f
}
func (p *Parallax) InitializeParallax() {
if p.Dimensions.Area() <= 0 {
return
}
for i := 0; i < ASTEROID_COUNT; i++ {
newX := rand.Intn(p.Dimensions.Width) * OFF_SCREEN_SCALE
newY := rand.Intn(p.Dimensions.Height) * OFF_SCREEN_SCALE
rS := rand.Float64()*MAX_ROTATION_SPEED + MIN_ROTATION_SPEED
p.asteroids = append(p.asteroids, Asteroid{cX: newX, cY: newY, rotationSpeed: rS})
}
whiteImage.Fill(color.White)
}
func (p *Parallax) DrawDynamicBackground(screen *ebiten.Image) {
x, y := ebiten.CursorPosition()
op := &ebiten.DrawImageOptions{}
op.GeoM.Translate(float64(-x-p.Dimensions.Width)/GALAXY_SCALE_DIST, float64(-y-p.Dimensions.Height)/GALAXY_SCALE_DIST)
screen.DrawImage(galaxyBackground, op)
//debris layers
const DEBRIS_QTY = 3
const DEBRIS_WIDTH = 640 / 3 * 2
const DEBRIS_HEIGHT = 480 / 3 * 2
const TOTAL_LAYERS = 2
for k := 0; k < TOTAL_LAYERS; k++ {
for i := 0; i < DEBRIS_QTY; i++ {
for j := 0; j < DEBRIS_QTY; j++ {
//compute object origins (pre-shifts)
x0 := float64(j*DEBRIS_WIDTH) + float64(p.increment/640)
y0 := float64(i*DEBRIS_HEIGHT) + float64(p.increment/480)
//now compute delta with parallax effect
xd := (x0-float64(x))/DEBRIS_SCALE_DIST*float64(k+1) + x0 - float64(p.increment*k)/FLOAT_SCALE
yd := (y0-float64(y))/DEBRIS_SCALE_DIST*float64(k+1) + y0 - float64(p.increment*k)/FLOAT_SCALE
op = &ebiten.DrawImageOptions{}
op.GeoM.Translate(xd, yd)
//op.GeoM.Rotate(math.Pi / 180 * float64(p.increment))
op.Blend = ebiten.BlendSourceOver
screen.DrawImage(debrisImage, op)
//log.Printf("%d, %d", xoffset, yoffset)
}
}
}
}
func (p *Parallax) DrawObstacles(screen *ebiten.Image) {
x, y := ebiten.CursorPosition()
for _, a := range p.asteroids {
cx := float64(a.cX)
cy := float64(a.cY)
op := &ebiten.DrawImageOptions{}
op.GeoM.Translate(-100, -66)
op.GeoM.Rotate(-float64(p.increment) * a.rotationSpeed / (math.Pi * 120))
op.GeoM.Translate(cx-float64(x), cy-float64(y))
screen.DrawImage(roids[0], op)
}
}
func (p *Parallax) DrawObstaclesWithShamLighting(screen *ebiten.Image) {
x, y := ebiten.CursorPosition()
for _, a := range p.asteroids {
cx := float64(a.cX)
cy := float64(a.cY)
op := &ebiten.DrawImageOptions{}
op.GeoM.Translate(-100, -66)
op.GeoM.Rotate(-float64(p.increment) * a.rotationSpeed / (math.Pi * 120))
op.GeoM.Translate(100, 66)
//draw roid to temporary location
blendTmp.DrawImage(roids[0], op)
op = &ebiten.DrawImageOptions{}
op.Blend = ebiten.BlendSourceAtop
blendTmp.DrawImage(blendImage, op)
op = &ebiten.DrawImageOptions{}
op.GeoM.Translate(cx-float64(x), cy-float64(y))
screen.DrawImage(blendTmp, op)
}
}
func (p *Parallax) RepositionTest(screen *ebiten.Image) {
var path vector.Path
//set up origin and boundaries
x0 := float32(p.Dimensions.Width / 2)
y0 := float32(p.Dimensions.Height / 2)
xd := float32(100.0)
yd := float32(100.0)
//connect the dots
path.MoveTo(x0-xd, y0-yd)
path.LineTo(x0+xd, y0-yd)
path.LineTo(x0+xd, y0+yd)
path.LineTo(x0-xd, y0+yd)
path.LineTo(x0-xd, y0+yd)
path.Close()
var vs []ebiten.Vertex
var is []uint16
vs, is = path.AppendVerticesAndIndicesForFilling(nil, nil)
for i := range vs {
vs[i].SrcX = 1
vs[i].SrcY = 1
vs[i].ColorR = float32(0xff) / 0xFF
vs[i].ColorG = float32(0x00) / 0xFF
vs[i].ColorB = float32(0x00) / 0xFF
vs[i].ColorA = 1
}
screen.DrawTriangles(vs, is, whiteSubImage, nil)
}

View File

@@ -2,37 +2,13 @@ package splashmenu
import (
"cosmos/diego/groovy"
splashmenu "cosmos/diego/groovy/examples/splashmenu/fonts"
"image/color"
"log"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/examples/resources/fonts"
"github.com/hajimehoshi/ebiten/v2/text"
"golang.org/x/image/font"
"golang.org/x/image/font/opentype"
)
var (
splashFont font.Face
)
func init() {
tt, err := opentype.Parse(fonts.PressStart2P_ttf)
if err != nil {
log.Fatal(err)
}
const dpi = 72
splashFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
Size: 12,
DPI: dpi,
Hinting: font.HintingVertical,
})
if err != nil {
log.Fatal(err)
}
}
type Splash struct {
Dimensions groovy.Area
bgcolor color.RGBA
@@ -40,11 +16,6 @@ type Splash struct {
events map[groovy.SceneEvent]func()
}
// GetSceneEvents implements groovy.Scene.
func (*Splash) GetSceneEvents() []groovy.SceneEvent {
return nil
}
func NewSplash() Splash {
return Splash{
bgcolor: color.RGBA{0xFF, 0xFF, 0xFF, 0xFF},
@@ -52,11 +23,6 @@ func NewSplash() Splash {
}
}
func (s *Splash) Draw(screen *ebiten.Image) {
screen.Fill(s.bgcolor)
text.Draw(screen, "splash", splashFont, 40, 40, color.White)
}
func (s *Splash) Update() error {
s.increment++
@@ -74,11 +40,16 @@ func (s *Splash) Update() error {
return nil
}
func (s *Splash) Draw(screen *ebiten.Image) {
screen.Fill(s.bgcolor)
text.Draw(screen, "splash", splashmenu.SplashFont.Splash, 40, 40, color.White)
}
func (s *Splash) SetEventHandler(event groovy.SceneEvent, f func()) {
s.events[event] = f
}
// sets sene dimensions
func (s *Splash) SetDimensions(a groovy.Area) {
s.Dimensions = a
}
func (s Splash) SetEventHandler(event groovy.SceneEvent, f func()) {
s.events[event] = f
}

View File

@@ -137,3 +137,7 @@ func (m *Manager) SceneCount() uint {
func (m *Manager) GetScene(sceneId uint) Scene {
return m.scenes[sceneId]
}
func (a *Area) Area() int {
return a.Height * a.Width
}