Skip to content

Commit

Permalink
Add source
Browse files Browse the repository at this point in the history
  • Loading branch information
m110 committed Nov 22, 2023
1 parent 97d588f commit fb35204
Show file tree
Hide file tree
Showing 26 changed files with 626 additions and 0 deletions.
72 changes: 72 additions & 0 deletions assets/assets.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package assets

import (
"embed"
"image"
_ "image/png"
"io/fs"

"github.com/hajimehoshi/ebiten/v2"
"golang.org/x/image/font"
"golang.org/x/image/font/opentype"
)

//go:embed *
var assets embed.FS

var PlayerSprite = mustLoadImage("player.png")
var MeteorSprites = mustLoadImages("meteors/*.png")
var LaserSprite = mustLoadImage("laser.png")
var ScoreFont = mustLoadFont("font.ttf")

func mustLoadImage(name string) *ebiten.Image {
f, err := assets.Open(name)
if err != nil {
panic(err)
}
defer f.Close()

img, _, err := image.Decode(f)
if err != nil {
panic(err)
}

return ebiten.NewImageFromImage(img)
}

func mustLoadImages(path string) []*ebiten.Image {
matches, err := fs.Glob(assets, path)
if err != nil {
panic(err)
}

images := make([]*ebiten.Image, len(matches))
for i, match := range matches {
images[i] = mustLoadImage(match)
}

return images
}

func mustLoadFont(name string) font.Face {
f, err := assets.ReadFile(name)
if err != nil {
panic(err)
}

tt, err := opentype.Parse(f)
if err != nil {
panic(err)
}

face, err := opentype.NewFace(tt, &opentype.FaceOptions{
Size: 48,
DPI: 72,
Hinting: font.HintingVertical,
})
if err != nil {
panic(err)
}

return face
}
Binary file added assets/font.ttf
Binary file not shown.
Binary file added assets/laser.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/meteors/meteorBrown_big1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/meteors/meteorBrown_big2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/meteors/meteorBrown_big3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/meteors/meteorBrown_big4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/meteors/meteorBrown_med1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/meteors/meteorBrown_med3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/meteors/meteorGrey_big1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/meteors/meteorGrey_big2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/meteors/meteorGrey_big3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/meteors/meteorGrey_big4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/meteors/meteorGrey_med1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/meteors/meteorGrey_med2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/player.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
71 changes: 71 additions & 0 deletions game/bullet.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package game

import (
"math"

"github.com/hajimehoshi/ebiten/v2"

"github.com/ThreeDotsLabs/meteors/assets"
)

const (
bulletSpeedPerSecond = 350.0
)

type Bullet struct {
position Vector
rotation float64
sprite *ebiten.Image
}

func NewBullet(pos Vector, rotation float64) *Bullet {
sprite := assets.LaserSprite

bounds := sprite.Bounds()
halfW := float64(bounds.Dx()) / 2
halfH := float64(bounds.Dy()) / 2

pos.X -= halfW
pos.Y -= halfH

b := &Bullet{
position: pos,
rotation: rotation,
sprite: sprite,
}

return b
}

func (b *Bullet) Update() {
speed := bulletSpeedPerSecond / float64(ebiten.TPS())

b.position.X += math.Sin(b.rotation) * speed
b.position.Y += math.Cos(b.rotation) * -speed
}

func (b *Bullet) Draw(screen *ebiten.Image) {
bounds := b.sprite.Bounds()
halfW := float64(bounds.Dx()) / 2
halfH := float64(bounds.Dy()) / 2

op := &ebiten.DrawImageOptions{}
op.GeoM.Translate(-halfW, -halfH)
op.GeoM.Rotate(b.rotation)
op.GeoM.Translate(halfW, halfH)

op.GeoM.Translate(b.position.X, b.position.Y)

screen.DrawImage(b.sprite, op)
}

func (b *Bullet) Collider() Rect {
bounds := b.sprite.Bounds()

return NewRect(
b.position.X,
b.position.Y,
float64(bounds.Dx()),
float64(bounds.Dy()),
)
}
126 changes: 126 additions & 0 deletions game/game.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package game

import (
"fmt"
"image/color"
"time"

"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/text"

"github.com/ThreeDotsLabs/meteors/assets"
)

const (
screenWidth = 800
screenHeight = 600

meteorSpawnTime = 1 * time.Second

baseMeteorVelocity = 0.25
meteorSpeedUpAmount = 0.1
meteorSpeedUpTime = 5 * time.Second
)

type Game struct {
player *Player
meteorSpawnTimer *Timer
meteors []*Meteor
bullets []*Bullet

score int

baseVelocity float64
velocityTimer *Timer
}

func NewGame() *Game {
g := &Game{
meteorSpawnTimer: NewTimer(meteorSpawnTime),
baseVelocity: baseMeteorVelocity,
velocityTimer: NewTimer(meteorSpeedUpTime),
}

g.player = NewPlayer(g)

return g
}

func (g *Game) Update() error {
g.velocityTimer.Update()
if g.velocityTimer.IsReady() {
g.velocityTimer.Reset()
g.baseVelocity += meteorSpeedUpAmount
}

g.player.Update()

g.meteorSpawnTimer.Update()
if g.meteorSpawnTimer.IsReady() {
g.meteorSpawnTimer.Reset()

m := NewMeteor(g.baseVelocity)
g.meteors = append(g.meteors, m)
}

for _, m := range g.meteors {
m.Update()
}

for _, b := range g.bullets {
b.Update()
}

// Check for meteor/bullet collisions
for i, m := range g.meteors {
for j, b := range g.bullets {
if m.Collider().Intersects(b.Collider()) {
g.meteors = append(g.meteors[:i], g.meteors[i+1:]...)
g.bullets = append(g.bullets[:j], g.bullets[j+1:]...)
g.score++
}
}
}

// Check for meteor/player collisions
for _, m := range g.meteors {
if m.Collider().Intersects(g.player.Collider()) {
g.Reset()
break
}
}

return nil
}

func (g *Game) Draw(screen *ebiten.Image) {
g.player.Draw(screen)

for _, m := range g.meteors {
m.Draw(screen)
}

for _, b := range g.bullets {
b.Draw(screen)
}

text.Draw(screen, fmt.Sprintf("%06d", g.score), assets.ScoreFont, screenWidth/2-100, 50, color.White)
}

func (g *Game) AddBullet(b *Bullet) {
g.bullets = append(g.bullets, b)
}

func (g *Game) Reset() {
g.player = NewPlayer(g)
g.meteors = nil
g.bullets = nil
g.score = 0
g.meteorSpawnTimer.Reset()
g.baseVelocity = baseMeteorVelocity
g.velocityTimer.Reset()
}

func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
return screenWidth, screenHeight
}
93 changes: 93 additions & 0 deletions game/meteor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package game

import (
"math"
"math/rand"

"github.com/hajimehoshi/ebiten/v2"

"github.com/ThreeDotsLabs/meteors/assets"
)

const (
rotationSpeedMin = -0.02
rotationSpeedMax = 0.02
)

type Meteor struct {
position Vector
rotation float64
movement Vector
rotationSpeed float64
sprite *ebiten.Image
}

func NewMeteor(baseVelocity float64) *Meteor {
target := Vector{
X: screenWidth / 2,
Y: screenHeight / 2,
}

angle := rand.Float64() * 2 * math.Pi
r := screenWidth / 2.0

pos := Vector{
X: target.X + math.Cos(angle)*r,
Y: target.Y + math.Sin(angle)*r,
}

velocity := baseVelocity + rand.Float64()*1.5

direction := Vector{
X: target.X - pos.X,
Y: target.Y - pos.Y,
}
normalizedDirection := direction.Normalize()

movement := Vector{
X: normalizedDirection.X * velocity,
Y: normalizedDirection.Y * velocity,
}

sprite := assets.MeteorSprites[rand.Intn(len(assets.MeteorSprites))]

m := &Meteor{
position: pos,
movement: movement,
rotationSpeed: rotationSpeedMin + rand.Float64()*(rotationSpeedMax-rotationSpeedMin),
sprite: sprite,
}
return m
}

func (m *Meteor) Update() {
m.position.X += m.movement.X
m.position.Y += m.movement.Y
m.rotation += m.rotationSpeed
}

func (m *Meteor) Draw(screen *ebiten.Image) {
bounds := m.sprite.Bounds()
halfW := float64(bounds.Dx()) / 2
halfH := float64(bounds.Dy()) / 2

op := &ebiten.DrawImageOptions{}
op.GeoM.Translate(-halfW, -halfH)
op.GeoM.Rotate(m.rotation)
op.GeoM.Translate(halfW, halfH)

op.GeoM.Translate(m.position.X, m.position.Y)

screen.DrawImage(m.sprite, op)
}

func (m *Meteor) Collider() Rect {
bounds := m.sprite.Bounds()

return NewRect(
m.position.X,
m.position.Y,
float64(bounds.Dx()),
float64(bounds.Dy()),
)
}
Loading

0 comments on commit fb35204

Please sign in to comment.