package main import ( "bytes" _ "embed" "fmt" "image" "image/color" "math" "github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2/text" ) const ( ASB_HEIGHT = 50 ASB_BUFFER = 10 ASB_WIDTH = 1400 ASB_COLOR = 0x5d2f6a ASB_MARGIN = 10 //scales and offsets ASB_GRAD_TOP = 0x457cf1 //0xff887c //0x9933cc ASB_GRAD_BOTTOM = 0x2051bf //0xb6325f //0x5d2f6a ASB_FLAME_SCALE = 1.56 ASB_FLAME_OFFSET = 15 ASB_MASK_WIDTH = 10 ASB_MASK_HEIGHT = ASB_HEIGHT ASB_STRFADE_XOFFSET = 80 ASB_STRFADE_YOFFSET = 50 ASB_STRFADE_VALUE = 0x50 //scoreboard ticker related ASB_TICK_PIXEL_SCALE = 2 //how many pixels per cycle for ticker animation ASB_TICK_MAX_ADJUST = 2 * ASB_HEIGHT ) var ( //go:embed resources/images/barmask.png barmaskAsset_png []byte assetsBarmask *ebiten.Image //go:embed resources/images/crown.png crownAsset_png []byte assetsCrown *ebiten.Image ) type AnimatedScoreBar struct { ScoreImage *ebiten.Image name string target float64 value float64 gradient *Gradient barcolors []color.RGBA character *Character flames *Flame barbuffer *ebiten.Image order int //ranking sf float64 //scaling factor crown *ebiten.Image leader bool cycle int } func NewAnimatedScoreBar(t CharacterType, s string) *AnimatedScoreBar { a := &AnimatedScoreBar{ name: s, target: 0, value: 0, sf: 1, ScoreImage: ebiten.NewImage(ASB_WIDTH, ASB_HEIGHT), barbuffer: ebiten.NewImage(ASB_WIDTH, ASB_HEIGHT), gradient: NewGradient(ASB_WIDTH, ASB_HEIGHT), character: NewCharacter(t), flames: NewFlame(), crown: ebiten.NewImageFromImage(assetsCrown), order: 0, leader: false, barcolors: []color.RGBA{HexToRGBA(ASB_GRAD_TOP), HexToRGBA(ASB_GRAD_BOTTOM)}, } a.character.RandomizeCycleStart() a.SetBarColors(a.barcolors[GRAD_IDX_TOP], a.barcolors[GRAD_IDX_BOTTOM]) return a } func (a *AnimatedScoreBar) RefreshBar() { a.barbuffer.DrawImage(a.gradient.Scene, nil) //mask start of the bar for smooth edges op := &ebiten.DrawImageOptions{} op.Blend = ebiten.BlendDestinationOut a.barbuffer.DrawImage(assetsBarmask.SubImage(image.Rect(0, 0, ASB_MASK_WIDTH, ASB_MASK_HEIGHT)).(*ebiten.Image), op) //add name to the fill bar, which will gradually display as the value increases text.Draw(a.barbuffer, a.name, DashFont.TeamBarName, ASB_MARGIN, ASB_HEIGHT-ASB_MARGIN, color.White) } func (a *AnimatedScoreBar) GetCharacterType() CharacterType { return a.character.GetType() } func (a *AnimatedScoreBar) Name() string { return a.name } func (a *AnimatedScoreBar) SetTarget(t float64) { a.target = t } func (a *AnimatedScoreBar) GetTarget() float64 { return a.target } func (a *AnimatedScoreBar) GetValue() float64 { return a.value } func (a *AnimatedScoreBar) ResetLead() { a.leader = false } func (a *AnimatedScoreBar) SetLead() { a.leader = true } func (a *AnimatedScoreBar) Animate() { a.ScoreImage.Clear() //a.AddFadedTeamName() //peeps complained, so we cut this a.AddScoreBar() a.AddCharacter() a.AddTextScore() a.AdjustValue() a.AddLeadIndicator() a.CycleUpdate() } func (a *AnimatedScoreBar) CycleUpdate() { a.cycle++ } func (a *AnimatedScoreBar) AddLeadIndicator() { if a.leader { a.ScoreImage.DrawImage(a.crown, nil) } } func (a *AnimatedScoreBar) AddFadedTeamName() { //alpha blended black for the team name c := HexToRGBA(0x000000) c.A = ASB_STRFADE_VALUE text.Draw(a.ScoreImage, a.name, DashFont.TeamBackgroundName, 1000, ASB_STRFADE_YOFFSET, c) } func (a *AnimatedScoreBar) SetOrder(order int) { a.order = order } func (a *AnimatedScoreBar) GetOrder() int { return a.order } // draw our score bar by using subset of full bar func (a *AnimatedScoreBar) AddScoreBar() { //ss = subset start, se = subset end ssx := 0 ssy := 0 sex := int(a.value * a.sf) sey := ASB_HEIGHT barSubImage := a.barbuffer.SubImage(image.Rect(ssx, ssy, sex, sey)).(*ebiten.Image) a.ScoreImage.DrawImage(barSubImage, nil) //add trailing image mask to round out the edges op := &ebiten.DrawImageOptions{} op.Blend = ebiten.BlendDestinationOut op.GeoM.Translate(float64(sex)-ASB_MASK_WIDTH, float64(sey)-ASB_MASK_HEIGHT) msx := ASB_MASK_WIDTH msy := 0 mex := ASB_MASK_WIDTH + ASB_MASK_WIDTH mey := ASB_MASK_HEIGHT a.ScoreImage.DrawImage(assetsBarmask.SubImage(image.Rect(msx, msy, mex, mey)).(*ebiten.Image), op) } func (a *AnimatedScoreBar) AddCharacter() { op := &ebiten.DrawImageOptions{} //if we're on the move, let's add some flair to the character if a.target != a.value { op.GeoM.Scale(ASB_FLAME_SCALE, ASB_FLAME_SCALE) op.GeoM.Rotate(-math.Pi / 4) op.GeoM.Translate(a.value*a.sf+ASB_BUFFER-ASB_FLAME_OFFSET*5/2, ASB_FLAME_OFFSET*3/2) a.ScoreImage.DrawImage(a.flames.Sprite, op) a.flames.Animate() } op = &ebiten.DrawImageOptions{} op.GeoM.Translate(a.value*a.sf+ASB_BUFFER, 0) a.ScoreImage.DrawImage(a.character.Sprite, op) a.character.Animate() } func (a *AnimatedScoreBar) AddTextScore() { var ypos int M := ASB_HEIGHT tx := int(a.value*a.sf) + ASB_BUFFER + a.character.GetWidth() //animate ticker movement for the team name adjust := ASB_TICK_MAX_ADJUST //max adjustment if a.cycle%adjust < M/ASB_TICK_PIXEL_SCALE { ypos = 2*M - ASB_TICK_PIXEL_SCALE*a.cycle%adjust } else if a.cycle%adjust > 2*M/ASB_TICK_PIXEL_SCALE { ypos = M - (ASB_TICK_PIXEL_SCALE*(a.cycle-2*M/ASB_TICK_PIXEL_SCALE))%adjust } else { ypos = M } text.Draw(a.ScoreImage, a.name, DashFont.Title, tx, ypos, color.White) //now do the same for the points, offset by the height of the ticker bcycle := a.cycle - M/ASB_TICK_PIXEL_SCALE adjust = 2 * M if bcycle%adjust < 2*M/ASB_TICK_PIXEL_SCALE { ypos = 3*M - ASB_TICK_PIXEL_SCALE*bcycle%adjust } else if bcycle%adjust > 3*M/ASB_TICK_PIXEL_SCALE { ypos = M - (ASB_TICK_PIXEL_SCALE*(bcycle-3*M/ASB_TICK_PIXEL_SCALE))%adjust //M - (pixel_scale*(bcycle-2*M/pixel_scale))%ma } else { ypos = M } text.Draw(a.ScoreImage, fmt.Sprintf("%0.f", a.value), DashFont.Title, tx, ypos, color.White) text.Draw(a.ScoreImage, "POINTS", DashFont.Title, tx+ASB_BUFFER*7, ypos, color.White) } func (a *AnimatedScoreBar) AdjustValue() { if a.value < a.target { a.value++ } } // set our point scale factor (scalefactor = pixels per score point) func (a *AnimatedScoreBar) SetScaleFactor(sf float64) { a.sf = sf } func (a *AnimatedScoreBar) Reset() { a.value = 0 } func init() { img, _, err := image.Decode(bytes.NewReader(barmaskAsset_png)) if err != nil { DashLogger.Fatal(err) } assetsBarmask = ebiten.NewImageFromImage(img) img, _, err = image.Decode(bytes.NewReader(crownAsset_png)) if err != nil { DashLogger.Fatal(err) } assetsCrown = ebiten.NewImageFromImage(img) } func (a *AnimatedScoreBar) SetBarColors(top, bottom color.RGBA) { //fill bar gradient a.gradient.SetColors(top, bottom) a.RefreshBar() }