package hero import ( "image/color" "math" "github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2/vector" ) // Direction flags from the controls. type Input struct { Left bool Right bool Up bool Down bool } // Playfield limits for movement. type Bounds struct { Width float64 Height float64 } // Player avatar data. type Hero struct { X float64 Y float64 Radius float64 Speed float64 Color color.NRGBA } // Spawn settings for the avatar. type Config struct { StartX float64 StartY float64 Radius float64 Speed float64 Color color.NRGBA } // Builds an avatar from the config with fallbacks. func New(cfg Config) *Hero { if cfg.Radius <= 0 { cfg.Radius = 24 } if cfg.Speed <= 0 { cfg.Speed = 180 } if cfg.Color.A == 0 { cfg.Color = color.NRGBA{R: 210, G: 220, B: 255, A: 255} } return &Hero{ X: cfg.StartX, Y: cfg.StartY, Radius: cfg.Radius, Speed: cfg.Speed, Color: cfg.Color, } } // Applies movement input and clamps to the playfield. func (h *Hero) Update(input Input, dt float64, bounds Bounds) { dx, dy := 0.0, 0.0 if input.Left { dx -= 1 } if input.Right { dx += 1 } if input.Up { dy -= 1 } if input.Down { dy += 1 } if dx != 0 || dy != 0 { length := math.Hypot(dx, dy) dx /= length dy /= length } h.X += dx * h.Speed * dt h.Y += dy * h.Speed * dt maxX := math.Max(h.Radius, bounds.Width-h.Radius) maxY := math.Max(h.Radius, bounds.Height-h.Radius) h.X = clamp(h.X, h.Radius, maxX) h.Y = clamp(h.Y, h.Radius, maxY) } // Renders the avatar as a filled circle. func (h *Hero) Draw(screen *ebiten.Image) { vector.FillCircle( screen, float32(h.X), float32(h.Y), float32(h.Radius), h.Color, false, ) } func clamp(value, min, max float64) float64 { if value < min { return min } if value > max { return max } return value }