package gameelement import ( "fmt" "image" "image/color" "math" "math/rand/v2" "mover/assets" "mover/elements" "mover/fonts" "mover/gamedata" "mover/weapons" "github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2/text" "github.com/hajimehoshi/ebiten/v2/vector" ) type Canvas struct { Sprite *ebiten.Image collisionMask *ebiten.Image projectileMask *ebiten.Image laserMask *ebiten.Image heroCollisionMask *ebiten.Image heroCollisionCpy *ebiten.Image eventmap map[gamedata.GameEvent]func() initialized bool goblinspawned bool goblindead bool lastInputs gamedata.GameInputs runtime float64 counter int score int splashes []*elements.Splash wpdrops []*elements.WeaponDrop hero *elements.Hero charge *elements.Explosion goblin *elements.FlyGoblin enemies []elements.Enemies projectiles []*elements.Projectile laser *elements.Laser gameover bool lasercoords []gamedata.Coordinates holster *weapons.Holster } func NewCanvas(a gamedata.Area) *Canvas { c := &Canvas{ Sprite: ebiten.NewImage(a.Width, a.Height), projectileMask: ebiten.NewImage(a.Width, a.Height), collisionMask: ebiten.NewImage(a.Width, a.Height), laserMask: ebiten.NewImage(a.Width, a.Height), heroCollisionMask: ebiten.NewImage(48, 48), heroCollisionCpy: ebiten.NewImage(48, 48), hero: elements.NewHero(), charge: elements.NewExplosion(), laser: elements.NewLaser(gamedata.Coordinates{X: 320, Y: 240}, 0), initialized: false, gameover: false, goblinspawned: false, goblindead: false, score: 0, runtime: 0., counter: 0, holster: weapons.NewHolster(), } c.laserMask.Clear() c.eventmap = make(map[gamedata.GameEvent]func()) c.lasercoords = make([]gamedata.Coordinates, 4) return c } func (c *Canvas) SetInputs(gi gamedata.GameInputs) { c.lastInputs = gi } func (c *Canvas) Update() error { if !c.initialized { c.Initialize() } else { c.UpdateHero() c.UpdateWeaponDrops() c.UpdateWeapons() c.UpdateProjectiles() c.UpdateCharge() c.UpdateEnemies() c.SpawnEnemies() c.CleanupTargets() c.UpdateSplashes() c.CleanSplashes() c.CleanupDrops() c.counter++ } return nil } func (c *Canvas) Draw(drawimg *ebiten.Image) { c.Sprite.Clear() c.projectileMask.Clear() //c.laserMask.Clear() //vector.DrawFilledCircle(c.Sprite, float32(c.hero.Pos.X), float32(c.hero.Pos.Y), 100, color.White, true) //render heor c.hero.Draw() op := &ebiten.DrawImageOptions{} op.GeoM.Translate(c.hero.Pos.X-48/2, c.hero.Pos.Y-48/2) c.Sprite.DrawImage(c.hero.Sprite, op) //render weapon if !c.gameover { op.GeoM.Reset() op.GeoM.Translate(0, -16) op.GeoM.Rotate(c.lastInputs.ShotAngle) op.GeoM.Translate(c.hero.Pos.X, c.hero.Pos.Y) c.Sprite.DrawImage(assets.ImageBank[assets.Weapon], op) } //draw enemy shadows for _, es := range c.enemies { if es.GetEnemyState() < gamedata.EnemyStateExploding { dx := float64(assets.ImageBank[assets.FlyEyeShadow].Bounds().Dx()) / 2 dy := float64(assets.ImageBank[assets.FlyEyeShadow].Bounds().Dy()) / 2 sx := float64(es.GetSprite().Bounds().Dx()) / 48 sy := float64(es.GetSprite().Bounds().Dy()) / 48 op := &ebiten.DrawImageOptions{} op.GeoM.Translate(-dx, -dy) op.GeoM.Scale(sx, sy) op.GeoM.Translate(es.GetPosition().X, es.GetPosition().Y+float64(es.GetSprite().Bounds().Dx())/2) c.Sprite.DrawImage(assets.ImageBank[assets.FlyEyeShadow], op) } } //draw weapon drops for _, drop := range c.wpdrops { drop.Draw() op := &ebiten.DrawImageOptions{} op.GeoM.Translate(drop.GetPosition().X-float64(drop.Sprite.Bounds().Dx())/2, drop.GetPosition().Y-float64(drop.Sprite.Bounds().Dy())/2) c.Sprite.DrawImage(drop.Sprite, op) } //draw enemies for _, e := range c.enemies { e.Draw() xshift := float64(e.GetSprite().Bounds().Dx() / 2) yshift := float64(e.GetSprite().Bounds().Dy() / 2) op := &ebiten.DrawImageOptions{} op.GeoM.Translate(-xshift, -yshift) op.GeoM.Rotate(e.GetAngle()) op.GeoM.Translate(e.GetPosition().X, e.GetPosition().Y) c.Sprite.DrawImage(e.GetSprite(), op) //do we need a health bar for this enemy? if e.Health() > 0 { hbWidth := float64(e.MaxHealth())*2 + 4 p1 := float64(e.GetSprite().Bounds().Dx()) p0 := e.GetPosition().X - p1/2 x0 := p0 - (hbWidth-p1)/2 y0 := e.GetPosition().Y - 2/3.*float64(e.GetSprite().Bounds().Dy()) vector.DrawFilledRect(c.Sprite, float32(x0), float32(y0), float32(e.MaxHealth())*2+4, 12, color.Black, true) vector.DrawFilledRect(c.Sprite, float32(x0+2), float32(y0+2), float32(e.Health())*2, 8, color.RGBA{R: 0xff, G: 0x00, B: 0x00, A: 0xff}, true) } } //draw projectiles for _, p := range c.projectiles { vector.DrawFilledCircle(c.projectileMask, float32(p.Pos.X), float32(p.Pos.Y), 3, color.White, true) } c.Sprite.DrawImage(c.projectileMask, nil) //draw laser(s) if c.laser.IsFiring() { c.laser.Draw() c.Sprite.DrawImage(c.laserMask, nil) } //c.Sprite.DrawImage(c.laser.Sprite, op) vector.StrokeCircle(c.Sprite, float32(c.charge.Origin.X), float32(c.charge.Origin.Y), float32(c.charge.Radius), 3, color.White, true) //TEMPORARY let's see how far off the beam we are //vector.StrokeLine(c.Sprite, float32(c.lasercoords[2].X), float32(c.lasercoords[2].Y), float32(c.lasercoords[3].X), float32(c.lasercoords[3].Y), 2, color.White, true) //let's render our laser 'splashes' for _, sp := range c.splashes { sp.Draw() op := &ebiten.DrawImageOptions{} op.GeoM.Translate(sp.GetPosition().X-128/2, sp.GetPosition().Y-128/2) c.Sprite.DrawImage(sp.Sprite, op) } if !c.gameover { c.runtime = float64(c.counter) / 60. } s := fmt.Sprintf("%02.3f", c.runtime) if !c.gameover { text.Draw(c.Sprite, "TIME: "+s, fonts.SurviveFont.Arcade, 640/2-250, 25, color.White) text.Draw(c.Sprite, fmt.Sprintf("SCORE: %d", c.score*10), fonts.SurviveFont.Arcade, 640/2+100, 25, color.White) } else { if (c.counter/30)%2 == 0 { text.Draw(c.Sprite, "TIME: "+s, fonts.SurviveFont.Arcade, 640/2-250, 25, color.White) text.Draw(c.Sprite, fmt.Sprintf("SCORE: %d", c.score*10), fonts.SurviveFont.Arcade, 640/2+100, 25, color.White) } text.Draw(c.Sprite, "PRESS START TO TRY AGAIN", fonts.SurviveFont.Arcade, 640/2-150, 480/2, color.White) } op.GeoM.Reset() drawimg.DrawImage(c.Sprite, op) } func (c *Canvas) Initialize() { c.InitializeHero() c.CleanSplashes() c.ResetWeaponDrops() c.enemies = c.enemies[:0] c.gameover = false c.initialized = true c.score = 0 c.counter = 0 c.runtime = 0. c.goblinspawned = false c.goblindead = false //temporary c.hero.Action = elements.HeroActionDefault c.holster.SetActiveWeapon(gamedata.WeaponTypeGun) c.laser.SetFiring(false) } func (c *Canvas) UpdateHero() { c.hero.Update() if !c.gameover { c.UpdateHeroPosition() c.ComputeHeroCollisions() } } func (c *Canvas) UpdateHeroPosition() { if c.lastInputs.XAxis >= 0.15 || c.lastInputs.XAxis <= -0.15 { c.hero.Left = c.lastInputs.XAxis < 0 c.hero.Pos.X += c.lastInputs.XAxis * 5 } if c.lastInputs.YAxis >= 0.15 || c.lastInputs.YAxis <= -0.15 { c.hero.Pos.Y += c.lastInputs.YAxis * 5 } } func (c *Canvas) ComputeHeroCollisions() { for _, e := range c.enemies { //compute collision with hero if c.hero.Pos.X >= e.GetPosition().X-float64(e.GetSprite().Bounds().Dx())/2 && c.hero.Pos.X <= e.GetPosition().X+float64(e.GetSprite().Bounds().Dx())/2 && c.hero.Pos.Y >= e.GetPosition().Y-float64(e.GetSprite().Bounds().Dy())/2 && c.hero.Pos.Y <= e.GetPosition().Y+float64(e.GetSprite().Bounds().Dy())/2 && e.GetEnemyState() < gamedata.EnemyStateDying { // target.Action < elements.MoverActionDying && g.hero.Action < elements.HeroActionDying { c.heroCollisionMask.Clear() c.heroCollisionMask.DrawImage(c.hero.Sprite, nil) op := &ebiten.DrawImageOptions{} op.GeoM.Reset() op.Blend = ebiten.BlendSourceIn op.GeoM.Translate((c.hero.Pos.X-e.GetPosition().X)-float64(e.GetSprite().Bounds().Dx())/2, (c.hero.Pos.Y-e.GetPosition().Y)-float64(e.GetSprite().Bounds().Dy())/2) c.heroCollisionMask.DrawImage(e.GetSprite(), op) if c.HasCollided(c.heroCollisionMask, 48*48*4) { c.hero.SetHit() c.gameover = true if c.eventmap[gamedata.GameEventPlayerDeath] != nil { c.eventmap[gamedata.GameEventPlayerDeath]() } } } } } func (c *Canvas) AddProjectiles() { //add new projectiles if c.lastInputs.Shot && c.counter%14 == 0 { loc := gamedata.Coordinates{ X: c.hero.Pos.X, Y: c.hero.Pos.Y, } angle := c.lastInputs.ShotAngle velocity := 5. c.projectiles = append(c.projectiles, elements.NewProjectile(loc, angle, velocity)) if c.hero.Upgrade { c.projectiles = append(c.projectiles, elements.NewProjectile(loc, angle+math.Pi, velocity)) } if c.eventmap[gamedata.GameEventNewShot] != nil { c.eventmap[gamedata.GameEventNewShot]() } } } func (c *Canvas) InitializeHero() { //recenter the hero pos := gamedata.Coordinates{ X: float64(c.Sprite.Bounds().Dx() / 2), Y: float64(c.Sprite.Bounds().Dy() / 2), } c.hero.SetOrigin(pos) } func (c *Canvas) UpdateProjectiles() { i := 0 for _, p := range c.projectiles { p.Update() projectilevalid := true if p.Pos.X < -640/2 || p.Pos.X > 1.5*640 || p.Pos.Y < -480/2 || p.Pos.Y > 1.5*480 { projectilevalid = false } for _, e := range c.enemies { if p.Pos.X >= e.GetPosition().X-float64(e.GetSprite().Bounds().Dx())/2 && p.Pos.X <= e.GetPosition().X+float64(e.GetSprite().Bounds().Dx())/2 && p.Pos.Y >= e.GetPosition().Y-float64(e.GetSprite().Bounds().Dy())/2 && p.Pos.Y <= e.GetPosition().Y+float64(e.GetSprite().Bounds().Dy())/2 && e.IsToggled() && e.GetEnemyState() < gamedata.EnemyStateDying { c.collisionMask.Clear() c.collisionMask.DrawImage(c.projectileMask, nil) op := &ebiten.DrawImageOptions{} op.GeoM.Reset() op.Blend = ebiten.BlendSourceIn op.GeoM.Translate(e.GetPosition().X-float64(e.GetSprite().Bounds().Dx())/2, e.GetPosition().Y-float64(e.GetSprite().Bounds().Dy())/2) c.collisionMask.DrawImage(e.GetSprite(), op) if c.HasCollided(c.collisionMask, 640*480*4) { projectilevalid = false e.SetHit() if c.eventmap[gamedata.GameEventTargetHit] != nil { c.eventmap[gamedata.GameEventTargetHit]() } } } } if projectilevalid { c.projectiles[i] = p i++ } } for j := i; j < len(c.projectiles); j++ { c.projectiles[j] = nil } c.projectiles = c.projectiles[:i] } func (c *Canvas) UpdateLaser() { c.laser.Update() c.laser.SetFiring(c.lastInputs.Shot) if c.lastInputs.Shot { c.laser.SetPosition(c.hero.Pos) c.laser.SetAngle(c.lastInputs.ShotAngle) c.laserMask.Clear() lpos := c.laser.GetPosition() op := &ebiten.DrawImageOptions{} op.GeoM.Reset() //op.GeoM.Translate(-float64(c.laser.Sprite.Bounds().Dx())/2, -float64(c.laser.Sprite.Bounds().Dy())/2) op.GeoM.Translate(0, -float64(c.laser.Sprite.Bounds().Dy())/2) op.GeoM.Rotate(c.laser.GetAngle()) op.GeoM.Translate(lpos.X, lpos.Y) c.laserMask.DrawImage(c.laser.Sprite, op) //c.LaserAttempt1() //c.LaserAttempt2() //c.LaserAttempt3() c.LaserAttempt4() } } func (c *Canvas) UpdateCharge() { if c.lastInputs.Charge && !c.charge.Active && !c.gameover { c.charge.SetOrigin(c.hero.Pos) c.charge.Reset() c.charge.ToggleActivate() if c.eventmap[gamedata.GameEventCharge] != nil { c.eventmap[gamedata.GameEventCharge]() } } c.charge.Update() if c.charge.Active { if c.charge.Radius > math.Sqrt(640*640+480*480) { c.charge.ToggleActivate() c.charge.Reset() c.ResetTargetTouches() } for _, e := range c.enemies { dx := e.GetPosition().X - c.hero.Pos.X dy := e.GetPosition().Y - c.hero.Pos.Y r := math.Sqrt(dx*dx + dy*dy) if r >= c.charge.Radius-5 && r <= c.charge.Radius+5 && !e.IsTouched() && e.GetEnemyState() <= gamedata.EnemyStateHit { e.SetToggle() e.SetTouched() } } } } func (c *Canvas) ResetTargetTouches() { for _, e := range c.enemies { e.ClearTouched() } } func (c *Canvas) UpdateEnemies() { //update existing enemies for _, e := range c.enemies { if !c.gameover { e.SetTarget(c.hero.Pos) if e.GetEnemyState() == gamedata.EnemyStateExploding && !e.ExplosionInitiated() { if c.eventmap[gamedata.GameEventExplosion] != nil { c.eventmap[gamedata.GameEventExplosion]() } e.SetExplosionInitiated() } } else { e.SetTarget(e.GetPosition()) } e.Update() } } func (c *Canvas) SpawnEnemies() { if !c.gameover { if !c.goblinspawned || c.goblindead { c.SpawnFlyEyes() } if !c.goblinspawned && c.counter > 2400 && !c.goblindead { c.SpawnGoblin() } } } func (c *Canvas) SpawnFlyEyes() { //spawn new enemies f := 40000 / (c.counter + 1) if c.counter%f == 0 { newenemy := elements.NewFlyEye() x0 := rand.Float64() * 640 y0 := rand.Float64() * 480 quadrant := rand.IntN(3) switch quadrant { case 0: newenemy.SetPosition(gamedata.Coordinates{X: x0, Y: -48}) case 1: newenemy.SetPosition(gamedata.Coordinates{X: x0, Y: 480 + 48}) case 2: newenemy.SetPosition(gamedata.Coordinates{X: -48, Y: y0}) case 3: newenemy.SetPosition(gamedata.Coordinates{X: 640 + x0, Y: y0}) } newenemy.SetTarget(c.hero.Pos) c.enemies = append(c.enemies, newenemy) } } func (c *Canvas) SpawnGoblin() { newfg := elements.NewFlyGoblin() newfg.SetDeathEvent(c.GoblinDeathEvent) newfg.SetFireballCallback(c.GoblinFireballEvent) x0 := rand.Float64() * 640 y0 := rand.Float64() * 480 quadrant := rand.IntN(3) switch quadrant { case 0: newfg.SetPosition(gamedata.Coordinates{X: x0, Y: -96}) case 1: newfg.SetPosition(gamedata.Coordinates{X: x0, Y: 480 + 48}) case 2: newfg.SetPosition(gamedata.Coordinates{X: -96, Y: y0}) case 3: newfg.SetPosition(gamedata.Coordinates{X: 640 + x0, Y: y0}) } c.goblin = newfg c.enemies = append(c.enemies, newfg) c.goblinspawned = true } func (c *Canvas) HasCollided(mask *ebiten.Image, size int) bool { var result bool = false var pixels []byte = make([]byte, size) mask.ReadPixels(pixels) for i := 0; i < len(pixels); i = i + 4 { if pixels[i+3] != 0 { result = true break } } return result } func (c *Canvas) RegisterEvents(e gamedata.GameEvent, f func()) { c.eventmap[e] = f } func (c *Canvas) CleanupTargets() { // remove dead targets by iterating over all targets i := 0 for _, e := range c.enemies { //compute odds for dropping an item on dead enemies if e.GetEnemyState() == elements.MoverActionDead { if rand.Float64() > 0.98 { drop := elements.NewWeaponDrop(gamedata.WeaponTypeLaser) drop.SetPosition(e.GetPosition()) c.wpdrops = append(c.wpdrops, drop) } } //moving valid targets to the front of the slice if e.GetEnemyState() < elements.MoverActionDead && !(e.GetPosition().X < -640*2 || e.GetPosition().X > 640*2 || e.GetPosition().Y > 480*2 || e.GetPosition().Y < -480*2) { c.enemies[i] = e i++ } } //then culling the last elements of the slice, and conveniently we can update //our base score with the number of elements removed (bonuses calculated elsewhere) if len(c.enemies)-i > 0 { c.score += len(c.enemies) - i } for j := i; j < len(c.enemies); j++ { c.enemies[j] = nil } c.enemies = c.enemies[:i] } func (c *Canvas) GoblinDeathEvent() { c.goblindead = true c.goblinspawned = false c.score += 10 } func (c *Canvas) GoblinFireballEvent() { if !c.gameover { velocity := 8. dx := c.hero.Pos.X - c.goblin.GetPosition().X dy := c.hero.Pos.Y - c.goblin.GetPosition().Y angle := math.Atan2(dy, dx) //add some randomness to the angle arand := rand.Float64() * math.Pi / 3 newfb := elements.NewFireBall(angle+arand, velocity) newfb.SetPosition(c.goblin.GetPosition()) c.enemies = append(c.enemies, newfb) if c.eventmap[gamedata.GameEventFireball] != nil { c.eventmap[gamedata.GameEventFireball]() } } } func IsPixelColliding(img1, img2 *ebiten.Image, offset1, offset2 image.Point) bool { // Get the pixel data from both images bounds1 := img1.Bounds() bounds2 := img2.Bounds() // Create slices to hold the pixel data pixels1 := make([]byte, 4*bounds1.Dx()*bounds1.Dy()) // RGBA (4 bytes per pixel) pixels2 := make([]byte, 4*bounds2.Dx()*bounds2.Dy()) // Read pixel data from the images img1.ReadPixels(pixels1) img2.ReadPixels(pixels2) // Determine the overlapping rectangle rect1 := bounds1.Add(offset1) rect2 := bounds2.Add(offset2) intersection := rect1.Intersect(rect2) if intersection.Empty() { return false // No overlap } // Check pixel data in the overlapping region for y := intersection.Min.Y; y < intersection.Max.Y; y++ { for x := intersection.Min.X; x < intersection.Max.X; x++ { // Calculate the indices in the pixel slices idx1 := ((y-offset1.Y)*bounds1.Dx() + (x - offset1.X)) * 4 idx2 := ((y-offset2.Y)*bounds2.Dx() + (x - offset2.X)) * 4 // Extract alpha values (transparency) alpha1 := pixels1[idx1+3] alpha2 := pixels2[idx2+3] // If both pixels are non-transparent, there's a collision if alpha1 > 0 && alpha2 > 0 { return true } } } return false // No collision detected } // RotatePoint rotates a point (x, y) around an origin (ox, oy) by a given angle (in radians). func RotatePoint(x, y, ox, oy, angle float64) (float64, float64) { sin, cos := math.Sin(angle), math.Cos(angle) dx, dy := x-ox, y-oy return ox + dx*cos - dy*sin, oy + dx*sin + dy*cos } // IsPixelCollidingWithRotation checks for pixel-perfect collision between two rotated images. func IsPixelCollidingWithRotation(img1, img2 *ebiten.Image, center1, center2 image.Point, angle1, angle2 float64) bool { // Get pixel data bounds1 := img1.Bounds() bounds2 := img2.Bounds() pixels1 := make([]byte, 4*bounds1.Dx()*bounds1.Dy()) pixels2 := make([]byte, 4*bounds2.Dx()*bounds2.Dy()) img1.ReadPixels(pixels1) img2.ReadPixels(pixels2) // Loop through all pixels in the bounding boxes of the first image for y1 := bounds1.Min.Y; y1 < bounds1.Max.Y; y1++ { for x1 := bounds1.Min.X; x1 < bounds1.Max.X; x1++ { // Get alpha for the pixel in img1 idx1 := (y1*bounds1.Dx() + x1) * 4 alpha1 := pixels1[idx1+3] if alpha1 == 0 { continue // Skip transparent pixels } // Rotate this pixel to its global position globalX, globalY := RotatePoint(float64(x1), float64(y1), float64(bounds1.Dx()/2), float64(bounds1.Dy()/2), angle1) globalX += float64(center1.X) globalY += float64(center1.Y) // Transform global position to img2's local space localX, localY := RotatePoint(globalX-float64(center2.X), globalY-float64(center2.Y), 0, 0, -angle2) // Check if the transformed position is within img2's bounds lx, ly := int(localX)+bounds2.Dx()/2, int(localY)+bounds2.Dy()/2 if lx < 0 || ly < 0 || lx >= bounds2.Dx() || ly >= bounds2.Dy() { continue } // Get alpha for the pixel in img2 idx2 := (ly*bounds2.Dx() + lx) * 4 alpha2 := pixels2[idx2+3] if alpha2 > 0 { return true // Collision detected } } } return false // No collision } func (c *Canvas) LaserAttempt1() { //for _, e := range c.enemies { /* rgba1 := c.laserMask.SubImage(c.laserMask.Bounds()).(*image.RGBA) rgba2 := e.GetSprite().SubImage(e.GetSprite().Bounds()).(*image.RGBA) */ // Check collision /* if IsPixelCollidingWithRotation(c.laser.Sprite, e.GetSprite(), image.Pt(int(c.laser.GetPosition().X), int(c.laser.GetPosition().Y)), image.Pt(int(e.GetPosition().X), int(e.GetPosition().Y)), c.laser.GetAngle(), 0, ) { println("Pixel-perfect collision detected!") } */ /* c.collisionMask.Clear() c.collisionMask.DrawImage(c.laserMask, nil) op := &ebiten.DrawImageOptions{} op.GeoM.Reset() op.Blend = ebiten.BlendDestinationIn op.GeoM.Translate(e.GetPosition().X-float64(e.GetSprite().Bounds().Dx())/2, e.GetPosition().Y-float64(e.GetSprite().Bounds().Dy())/2) c.collisionMask.DrawImage(e.GetSprite(), op) */ /* if c.HasCollided(c.collisionMask, 640*480*4) { if c.eventmap[gamedata.GameEventTargetHit] != nil { c.eventmap[gamedata.GameEventTargetHit]() } fmt.Println("enemy sliced") } */ //} } // try to find if the enemy is along the laser line first, then apply pixel collision func (c *Canvas) LaserAttempt2() { for _, e := range c.enemies { a := c.lastInputs.ShotAngle x0 := c.hero.Pos.X y0 := c.hero.Pos.Y thresh := 25. x := e.GetPosition().X y := math.Tan(a)*(x-x0) + y0 var laserd bool = false if !math.IsNaN(math.Tan(a)) { if math.Abs(e.GetPosition().Y-y) <= thresh { laserd = true } else { if math.Abs(e.GetPosition().X-x0) <= thresh { laserd = true } } } if laserd { //check for pixel collision if IsPixelColliding(c.laserMask, e.GetSprite(), image.Pt(0, 0), image.Pt(int(e.GetPosition().X), int(e.GetPosition().Y))) { e.SetHit() if c.eventmap[gamedata.GameEventTargetHit] != nil { c.eventmap[gamedata.GameEventTargetHit]() } fmt.Println("laser'd") } } } } // straight up just pixel collision check, expensive though func (c *Canvas) LaserAttempt3() { for _, e := range c.enemies { if IsPixelColliding(c.laserMask, e.GetSprite(), image.Pt(0, 0), image.Pt(int(e.GetPosition().X), int(e.GetPosition().Y))) { e.SetHit() if c.eventmap[gamedata.GameEventTargetHit] != nil { c.eventmap[gamedata.GameEventTargetHit]() } fmt.Println("laser'd") } } } // straight up just pixel collision check, expensive though func (c *Canvas) LaserAttempt4() { for _, e := range c.enemies { c.lasercoords[2] = e.GetPosition() x0 := c.hero.Pos.X y0 := c.hero.Pos.Y x1 := e.GetPosition().X y1 := e.GetPosition().Y a := c.lastInputs.ShotAngle var d float64 = 100 /* if !math.IsNaN(math.Tan(a)) { m0 := math.Tan(a) if m0 == 0 { d = math.Abs(y1 - y0) fmt.Printf("horizontal beam\n") c.lasercoords[3] = gamedata.Coordinates{X: x1, Y: y0} } else { m1 := -1 / m0 if (m0 - m1) != 0 { xi := (y1 + x0*m0 - y0 - m1*x1) / (m0 - m1) yi := xi*m0 - x0*m0 + y0 c.lasercoords[3] = gamedata.Coordinates{X: xi, Y: yi} d = math.Sqrt(math.Pow(x1-xi, 2) + math.Pow(y1-yi, 2)) fmt.Printf("%f \n", d) } else { } } fmt.Printf("%f \n", a) } else { c.lasercoords[3] = gamedata.Coordinates{X: x1, Y: y1} d = math.Abs(x1 - x0) fmt.Printf("vertical beam\n") } */ if math.Abs(math.Mod(a, math.Pi)) == math.Pi/2 { // Check for vertical beam d = math.Abs(x1 - x0) c.lasercoords[3] = gamedata.Coordinates{X: x0, Y: y1} // Align on x-axis //fmt.Printf("vertical beam\n") } else if math.Tan(a) == 0 { // Check for horizontal beam d = math.Abs(y1 - y0) c.lasercoords[3] = gamedata.Coordinates{X: x1, Y: y0} // Align on y-axis //fmt.Printf("horizontal beam\n") } else { // General case m0 := math.Tan(a) m1 := -1 / m0 xi := (y1 + x0*m0 - y0 - m1*x1) / (m0 - m1) yi := xi*m0 - x0*m0 + y0 c.lasercoords[3] = gamedata.Coordinates{X: xi, Y: yi} d = math.Sqrt(math.Pow(x1-xi, 2) + math.Pow(y1-yi, 2)) //fmt.Printf("%f \n", d) } //fmt.Printf("%f \n", a) if d <= float64(e.GetSprite().Bounds().Dx()) && e.GetEnemyState() <= gamedata.EnemyStateHit { if IsPixelColliding(c.laserMask, e.GetSprite(), image.Pt(0, 0), image.Pt(int(e.GetPosition().X), int(e.GetPosition().Y))) { e.SetHit() newsplash := elements.NewSplash() //newsplash.SetPosition(c.lasercoords[3]) newsplash.SetPosition(e.GetPosition()) c.splashes = append(c.splashes, newsplash) if c.eventmap[gamedata.GameEventTargetHit] != nil { c.eventmap[gamedata.GameEventTargetHit]() } //fmt.Println("laser'd") } } } } func (c *Canvas) UpdateSplashes() { for _, sp := range c.splashes { sp.Update() } } func (c *Canvas) CleanSplashes() { i := 0 for _, sp := range c.splashes { if sp.GetAlpha() > 0 { c.splashes[i] = sp i++ } } for j := i; j < len(c.splashes); j++ { c.splashes[j] = nil } c.splashes = c.splashes[:i] } func (c *Canvas) CleanupDrops() { i := 0 for _, drop := range c.wpdrops { if !drop.IsCollected() { c.wpdrops[i] = drop i++ } } for j := i; j < len(c.wpdrops); j++ { c.wpdrops[j] = nil } c.wpdrops = c.wpdrops[:i] } func (c *Canvas) UpdateWeapons() { if !c.gameover { //check for weapon inputs if c.lastInputs.CycleWeapon { c.holster.CycleWeapon() } //now let's update some shit based on the weapon switch c.holster.GetActiveWeapon().GetWeaponType() { case gamedata.WeaponTypeGun: c.AddProjectiles() case gamedata.WeaponTypeLaser: c.UpdateLaser() } } else { c.laser.SetFiring(false) } } func (c *Canvas) UpdateWeaponDrops() { //do we have any drops? let's calculate the chances // for _, drop := range c.wpdrops { drop.Update() //has the hero collided with any? add to holster //boundary box collision check if c.hero.Pos.X >= drop.GetPosition().X-float64(drop.Sprite.Bounds().Dx())/2 && c.hero.Pos.X <= drop.GetPosition().X+float64(drop.Sprite.Bounds().Dx())/2 && c.hero.Pos.Y >= drop.GetPosition().Y-float64(drop.Sprite.Bounds().Dy())/2 && c.hero.Pos.Y <= drop.GetPosition().Y+float64(drop.Sprite.Bounds().Dy())/2 { //fmt.Println("hero trying to pick up weapon maybe") if IsPixelColliding( c.hero.Sprite, drop.Sprite, image.Pt(int(c.hero.Pos.X), int(c.hero.Pos.Y)), image.Pt(int(drop.GetPosition().X), int(drop.GetPosition().Y)), ) { fmt.Println("weapon acquired") drop.SetCollected(true) c.holster.AddWeapon(weapons.NewLaser()) } } } } func (c *Canvas) ResetWeaponDrops() { for i := range c.wpdrops { c.wpdrops[i] = nil } c.wpdrops = c.wpdrops[:0] c.holster = weapons.NewHolster() }