github.com/kintar/etxt@v0.0.9/examples/ebiten/shaking/main.go (about) 1 package main 2 3 import "os" 4 import "log" 5 import "fmt" 6 import "time" 7 import "image/color" 8 import "math/rand" 9 10 import "github.com/hajimehoshi/ebiten/v2" 11 import "golang.org/x/image/math/fixed" 12 13 import "github.com/kintar/etxt" 14 import "github.com/kintar/etxt/emask" 15 16 // mmmmm... no, this wasn't social commentary on parenting 17 18 type Game struct { 19 txtRenderer *etxt.Renderer 20 childX int 21 parentX int 22 parent *ebiten.Image 23 child *ebiten.Image 24 // ...add a variable for prolonged trauma? 25 } 26 27 func (self *Game) Layout(w int, h int) (int, int) { return w, h } 28 func (self *Game) Update() error { 29 self.parentX, _ = ebiten.CursorPosition() 30 if self.parentX > self.childX { 31 dist := self.parentX - self.childX 32 if dist > 22 { 33 self.childX += 1 34 } 35 } else if self.parentX < self.childX { 36 dist := self.childX - self.parentX 37 if dist > 22 { 38 self.childX -= 1 39 } 40 } 41 return nil 42 } 43 44 func (self *Game) Draw(screen *ebiten.Image) { 45 // dark background 46 screen.Fill(color.RGBA{0, 0, 0, 255}) 47 48 // get shake level 49 shakeLevel := self.parentX - self.childX 50 if shakeLevel < 0 { 51 shakeLevel = -shakeLevel 52 } 53 if shakeLevel >= 22 { 54 shakeLevel -= 22 55 } else { 56 shakeLevel = 0 57 } 58 shakeLevel = shakeLevel / 16 59 60 // draw parent 61 w, h := screen.Size() 62 opts := ebiten.DrawImageOptions{} 63 opts.GeoM.Translate(float64(self.parentX-6), float64(h-32)) 64 screen.DrawImage(self.parent, &opts) 65 66 // draw children 67 opts = ebiten.DrawImageOptions{} 68 opts.GeoM.Translate(float64(self.childX-5), float64(h-20)) 69 opts.ColorM.Scale(0.5, 0.5, 0.5, 1.0) 70 screen.DrawImage(self.child, &opts) 71 72 // draw text 73 self.txtRenderer.SetTarget(screen) 74 self.txtRenderer.Traverse("I'm not afraid!", fixed.P(w/2, h/2), 75 func(dot fixed.Point26_6, _ rune, glyphIndex etxt.GlyphIndex) { 76 if shakeLevel > 0 { 77 dot.X += fixed.Int26_6(rand.Intn(shakeLevel+1) * 64) 78 dot.Y += fixed.Int26_6(rand.Intn(shakeLevel+1) * 64) 79 } 80 mask := self.txtRenderer.LoadGlyphMask(glyphIndex, dot) 81 self.txtRenderer.DefaultDrawFunc(dot, mask, glyphIndex) 82 }) 83 } 84 85 func main() { 86 // seed rand 87 rand.Seed(time.Now().UnixNano()) 88 89 // get font path 90 if len(os.Args) != 2 { 91 msg := "Usage: expects one argument with the path to the font to be used\n" 92 fmt.Fprint(os.Stderr, msg) 93 os.Exit(1) 94 } 95 96 // parse font 97 font, fontName, err := etxt.ParseFontFrom(os.Args[1]) 98 if err != nil { 99 log.Fatal(err) 100 } 101 fmt.Printf("Font loaded: %s\n", fontName) 102 103 // create cache 104 cache := etxt.NewDefaultCache(1024 * 1024 * 1024) // 1GB cache 105 106 // create and configure renderer 107 renderer := etxt.NewStdRenderer() 108 renderer.SetCacheHandler(cache.NewHandler()) 109 renderer.SetSizePx(58) 110 renderer.SetFont(font) 111 renderer.SetAlign(etxt.YCenter, etxt.XCenter) 112 renderer.SetColor(color.RGBA{255, 255, 255, 255}) // white 113 114 // create the parent (human) shape with a trapezoid and a circle 115 expectedParts := 10 116 shape := emask.NewShape(expectedParts) 117 shape.MoveTo(-2, 24) // move to the top of the trapezoid 118 shape.LineTo(-7, 0) 119 shape.LineTo(7, 0) 120 shape.LineTo(2, 24) 121 shape.LineTo(-2, 24) // close the trapezoid 122 shape.MoveTo(0, 24) // move to the start of the circle (bottom) 123 shape.QuadTo(-8, 24, -8, 32) // draw first quarter (to left-middle) 124 shape.QuadTo(-8, 40, 0, 40) // draw second quarter (to top) 125 shape.QuadTo(8, 40, 8, 32) // draw third quarter (to right-middle) 126 shape.QuadTo(8, 24, 0, 24) // close the shape 127 pixelAligned := fixed.Point26_6{} 128 mask, err := emask.Rasterize(shape.Segments(), renderer.GetRasterizer(), pixelAligned) 129 if err != nil { 130 log.Fatal(err) 131 } 132 parentImg := ebiten.NewImageFromImage(mask) // * 133 // * Notice that Ebitengine won't preserve the mask bounds, so we won't be 134 // able to use them to position the image... we will do it by hand. 135 136 // create the kid, which is the same but smaller 137 shape.Reset() 138 shape.MoveTo(-2, 16) // move to the top of the trapezoid 139 shape.LineTo(-5, 0) 140 shape.LineTo(5, 0) 141 shape.LineTo(2, 16) 142 shape.LineTo(-2, 16) // close the trapezoid 143 shape.MoveTo(0, 16) // move to the start of the circle (bottom) 144 shape.QuadTo(-6, 16, -6, 22) // draw first quarter (to left-middle) 145 shape.QuadTo(-6, 28, 0, 28) // draw second quarter (to top) 146 shape.QuadTo(6, 28, 6, 22) // draw third quarter (to right-middle) 147 shape.QuadTo(6, 16, 0, 16) // close the shape 148 mask, err = emask.Rasterize(shape.Segments(), renderer.GetRasterizer(), pixelAligned) 149 if err != nil { 150 log.Fatal(err) 151 } 152 childImg := ebiten.NewImageFromImage(mask) 153 154 // run the game 155 ebiten.SetWindowTitle("etxt/examples/ebiten/shaking") 156 ebiten.SetWindowSize(640, 480) 157 err = ebiten.RunGame(&Game{renderer, 500, 500, parentImg, childImg}) 158 if err != nil { 159 log.Fatal(err) 160 } 161 }