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  }