Skip to content

Commit

Permalink
Add a CRT emulation mode
Browse files Browse the repository at this point in the history
  • Loading branch information
depsypher committed Jun 25, 2024
1 parent 1128bf5 commit 829fc77
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 7 deletions.
2 changes: 2 additions & 0 deletions app/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const (
GodModeButton Control = 3
PauseButton Control = 4
SoundButton Control = 5
CrtButton Control = 6
SkidMillis = 500
)

Expand Down Expand Up @@ -56,6 +57,7 @@ var (
GodModeButton: ebiten.KeyG,
PauseButton: ebiten.KeyP,
SoundButton: ebiten.KeyS,
CrtButton: ebiten.KeyC,
}

White = color.RGBA{
Expand Down
57 changes: 57 additions & 0 deletions app/crt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// A Kage port of https://www.shadertoy.com/view/Ms23DR
//
// The original license comment is:
// Loosely based on postprocessing shader by inigo quilez,
// License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
// https://creativecommons.org/licenses/by-nc-sa/3.0/deed.en

//go:build ignore

package app

//kage:unit pixels

func curve(uv vec2) vec2 {
uv = (uv - 0.5) * 2
uv *= 1.08
uv.x *= (1 + pow((abs(uv.y)/8), 2))
uv.y *= (1 + pow((abs(uv.x)/6), 2))
uv = uv/2 + 0.5
uv = uv*0.92 + 0.04

return uv
}

func Fragment(dst vec4, src vec2, color vec4) vec4 {
origin, size := imageSrcRegionOnTexture()
q := (src - origin) / size
uv := q
uv = curve(uv)

var col vec3
col.r = imageSrc0At(vec2(uv.x+0.001, uv.y+0.001)*size+origin).x + 0.05
col.g = imageSrc0At(vec2(uv.x+0.000, uv.y-0.002)*size+origin).y + 0.05
col.b = imageSrc0At(vec2(uv.x-0.002, uv.y+0.000)*size+origin).z + 0.05
col.r += 0.08 * imageSrc0At((0.75*vec2(0.025, -0.027)+vec2(uv.x+0.001, uv.y+0.001))*size+origin).x
col.g += 0.05 * imageSrc0At((0.75*vec2(-0.022, -0.02)+vec2(uv.x+0.000, uv.y-0.002))*size+origin).y
col.b += 0.08 * imageSrc0At((0.75*vec2(-0.02, -0.018)+vec2(uv.x-0.002, uv.y+0.000))*size+origin).z

col = clamp(col*0.6+0.4*col*col, 0, 1)

vig := (16.0 * uv.x * uv.y * (1 - uv.x) * (1 - uv.y))
col *= vec3(pow(vig, 0.3))
col *= vec3(0.95, 1.05, 0.95)
col *= 2.8

scans := clamp(0.35+0.35*sin(uv.y*size.y*1.5), 0, 1)
s := pow(scans, 1.7)
col *= vec3(0.4 + 0.7*s)

if uv.x < 0.0 || uv.x > 1.0 || uv.y < 0 || uv.y > 1 {
col *= 0
}

col *= (1 - 0.35*vec3(clamp((mod(src.x, 2)-1)*2, 0, 1)))

return vec4(col, 1)
}
1 change: 1 addition & 0 deletions entity/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type GameState struct {
Keys map[app.Control]bool
GodMode bool
SoundOn bool
CrtOn bool
Pause bool
Debug string
Sounds audio.GameSounds
Expand Down
47 changes: 40 additions & 7 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
_ "embed"
"errors"
"fmt"
"github.com/depsypher/gojoust/app"
Expand All @@ -16,6 +17,9 @@ import (

var (
ss *entity.Sheet

//go:embed app/crt.go
crt_go []byte
)

func init() {
Expand All @@ -32,6 +36,8 @@ type Game struct {
inited bool
ss entity.Sheet
state *entity.GameState
screen *ebiten.Image
crt *ebiten.Shader
}

func (g *Game) init() {
Expand All @@ -41,6 +47,7 @@ func (g *Game) init() {
Keys: make(map[app.Control]bool),
GodMode: false,
SoundOn: true,
CrtOn: true,
WaveStart: time.Now(),
}

Expand All @@ -65,6 +72,12 @@ func (g *Game) init() {
errSound := errors.New("error loading sounds")
log.Fatal(errors.Join(errSound, err))
}

s, err := ebiten.NewShader(crt_go)
if err != nil {
return
}
g.crt = s
}()
}

Expand All @@ -87,6 +100,10 @@ func (g *Game) Update() error {
g.state.Keys[app.SoundButton] = true
g.state.SoundOn = !g.state.SoundOn
})
toggle(app.CrtButton, g.state.Keys, func() {
g.state.Keys[app.CrtButton] = true
g.state.CrtOn = !g.state.CrtOn
})

if time.Now().After(g.state.WaveStart.Add(time.Duration(3) * time.Second)) {
if len(g.state.Buzzards) < 3 && time.Now().After(g.state.NextSpawn) {
Expand All @@ -112,24 +129,40 @@ func (g *Game) Update() error {
}

func (g *Game) Draw(screen *ebiten.Image) {
w, h := screen.Bounds().Dx(), screen.Bounds().Dy()
if g.screen == nil {
g.screen = ebiten.NewImage(w, h)
} else {
g.screen.Clear()
}

if g.state.GodMode {
ebitenutil.DebugPrint(screen, fmt.Sprintf("FPS: %3.2f\nTPS: %3.2f", ebiten.ActualFPS(), ebiten.ActualTPS()))
ebitenutil.DebugPrintAt(screen, g.state.Debug, 70, 0)
ebitenutil.DebugPrintAt(screen, fmt.Sprintf("%f", g.state.Player.Y), 70, 20)
ebitenutil.DebugPrint(g.screen, fmt.Sprintf("FPS: %3.2f\nTPS: %3.2f", ebiten.ActualFPS(), ebiten.ActualTPS()))
ebitenutil.DebugPrintAt(g.screen, g.state.Debug, 70, 0)
ebitenutil.DebugPrintAt(g.screen, fmt.Sprintf("%f", g.state.Player.Y), 70, 20)

for _, lane := range app.Lanes {
y := float32(lane)
vector.StrokeLine(screen, 0, y, app.ScreenWidth, y, 1, app.Yellow, false)
vector.StrokeLine(g.screen, 0, y, app.ScreenWidth, y, 1, app.Yellow, false)
}
}
for _, cliff := range g.state.Cliffs {
cliff.Sprite.DrawSprite(screen)
cliff.Sprite.DrawSprite(g.screen)
}
for _, b := range g.state.Buzzards {
b.Draw(screen)
b.Draw(g.screen)
}

g.state.Player.Draw(screen)
g.state.Player.Draw(g.screen)

if g.state.CrtOn {
op := &ebiten.DrawRectShaderOptions{}
op.Images[0] = g.screen
screen.DrawRectShader(w, h, g.crt, op)
} else {
op := &ebiten.DrawImageOptions{}
screen.DrawImage(g.screen, op)
}
}

func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
Expand Down

0 comments on commit 829fc77

Please sign in to comment.