diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..fa07951 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Package", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceFolder}" + } + ] +} diff --git a/game.conf b/game.conf index 3985946..906631b 100644 --- a/game.conf +++ b/game.conf @@ -1,5 +1,6 @@ game.title = "Project Ely" game.framerate = 60.0 +log.utcTime = false log.file = "output.log" log.writeToFile = false diff --git a/internal/config/config.go b/internal/config/config.go index 7ab9869..b4c4809 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -9,6 +9,7 @@ import ( var defaultConfig gosimpleconf.ConfigMap = gosimpleconf.ConfigMap{ "game.title": "Project Ely", "game.framerate": "60.0", + "log.utcTime": "false", "log.writeToFile": "false", } diff --git a/internal/config/logging.go b/internal/config/logging.go index 57a9d3d..1b3c7ce 100644 --- a/internal/config/logging.go +++ b/internal/config/logging.go @@ -9,19 +9,24 @@ import ( type logWriter struct { writeToFile bool + utcTime bool logFile *os.File } func (w *logWriter) Write(bytes []byte) (int, error) { - t := time.Now().UTC().Format(time.RFC3339) - return fmt.Fprintf(w.logFile, "%v %v", t, string(bytes)) + t := time.Now() + if w.utcTime { + t = t.UTC() + } + format := t.Format(time.RFC3339) + return fmt.Fprintf(w.logFile, "%v %v", format, string(bytes)) } func (w *logWriter) Cleanup() { defer w.logFile.Close() } -func SetupLogging(writeToFile bool, logFilename string) (*logWriter, error) { +func SetupLogging(writeToFile bool, utcTime bool, logFilename string) (*logWriter, error) { var err error log.SetFlags(0) @@ -35,6 +40,7 @@ func SetupLogging(writeToFile bool, logFilename string) (*logWriter, error) { writer := &logWriter{ writeToFile: writeToFile, + utcTime: utcTime, logFile: logFile, } log.SetOutput(writer) diff --git a/internal/game/camera.go b/internal/game/camera.go index 748c1a3..d439b6c 100644 --- a/internal/game/camera.go +++ b/internal/game/camera.go @@ -6,7 +6,7 @@ import "github.com/veandco/go-sdl2/sdl" // Since this is only a 2D game with SDL, the projection is relatively simple: window + camera = world. // https://gamedev.stackexchange.com/a/123844 type camera struct { - pos *sdl.Point + pos sdl.Point } func NewCamera() *camera { diff --git a/internal/game/entity/animation/entity_animation.go b/internal/game/entity/animation/entity_animation.go index 34a16d4..7361f02 100644 --- a/internal/game/entity/animation/entity_animation.go +++ b/internal/game/entity/animation/entity_animation.go @@ -5,7 +5,7 @@ import ( ) type SpriteAnimation interface { - Draw(frame int, worldPosition *sdl.Point, angle float64, center *sdl.Point, flip sdl.RendererFlip) error + Draw(frame int, worldPosition sdl.Point, angle float64, center sdl.Point, flip sdl.RendererFlip) error } // An entityAnimation will take a SpriteAnimation and may manipulate it somehow (e.g. flipped for walking the other direction) @@ -15,7 +15,7 @@ type entityAnimation struct { speed int length int angle float64 - center *sdl.Point + center sdl.Point flip sdl.RendererFlip } @@ -24,11 +24,11 @@ func NewEntityAnimation( speed int, length int, angle float64, - center *sdl.Point, + center sdl.Point, flip sdl.RendererFlip, -) *entityAnimation { +) entityAnimation { - e := entityAnimation{ + return entityAnimation{ spriteAnimation: spriteAnimation, speed: speed, length: length, @@ -36,18 +36,23 @@ func NewEntityAnimation( center: center, flip: flip, } - - return &e } -func (e *entityAnimation) Draw(frame int, windowPosition *sdl.Point) error { +func (e entityAnimation) Draw(frame int, windowPosition sdl.Point) error { return e.spriteAnimation.Draw(frame, windowPosition, e.angle, e.center, e.flip) } -func (e *entityAnimation) GetSpeed() int { +func (e entityAnimation) GetSpeed() int { return e.speed } func DefineAnimations() { DefinePenguinAnimations() } + +func getCenter(dimensions sdl.Point) sdl.Point { + x := dimensions.X / 2 + y := dimensions.Y / 2 + + return sdl.Point{X: x, Y: y} +} diff --git a/internal/game/entity/animation/penguin_animations.go b/internal/game/entity/animation/penguin_animations.go index 6e4c230..d807205 100644 --- a/internal/game/entity/animation/penguin_animations.go +++ b/internal/game/entity/animation/penguin_animations.go @@ -5,7 +5,7 @@ import ( "github.com/veandco/go-sdl2/sdl" ) -var PenguinAnimations map[int]*entityAnimation +var PenguinAnimations map[int]entityAnimation const ( PENGUIN_WALK_RIGHT int = iota @@ -29,21 +29,21 @@ func DefinePenguinAnimations() { var ( dimensions sdl.Point offset sdl.Point - center *sdl.Point + center sdl.Point length int speed int border int ) dimensions = sdl.Point{X: 13, Y: 17} - PenguinAnimations = make(map[int]*entityAnimation) + PenguinAnimations = make(map[int]entityAnimation) // Walking Right is in the spritesheet. speed = 5 offset = sdl.Point{X: 0, Y: 1} length = 5 - border = 1 // optional border around each sprite - center = nil // center is for rotation, nil will default to w/2 h/2 + border = 1 // optional border around each sprite + center = getCenter(dimensions) // center is for rotation, nil will default to w/2 h/2 walkRight := sprite.NewAnimation(filename, dimensions, offset, length, border) PenguinAnimations[PENGUIN_WALK_RIGHT] = NewEntityAnimation(walkRight, speed, length, 0, center, sdl.FLIP_NONE) diff --git a/internal/game/entity/types/common.go b/internal/game/entity/types/common.go index 5fa5ac3..80c70c1 100644 --- a/internal/game/entity/types/common.go +++ b/internal/game/entity/types/common.go @@ -6,7 +6,7 @@ import ( ) type EntityAnimation interface { - Draw(step int, windowPosition *sdl.Point) error + Draw(step int, windowPosition sdl.Point) error GetSpeed() int } @@ -21,12 +21,12 @@ const ( // The following are axis direction vectors based on world coordinates. // UP/DOWN is intentionally UP = positive (which is different from window coordinates) var ( - VEC_LEFT = &vector.Vec2F{X: -1, Y: 0} - VEC_RIGHT = &vector.Vec2F{X: 1, Y: 0} - VEC_UP = &vector.Vec2F{X: 0, Y: 1} - VEC_DOWN = &vector.Vec2F{X: 0, Y: -1} + VEC_LEFT = vector.Vec2F{X: -1, Y: 0} + VEC_RIGHT = vector.Vec2F{X: 1, Y: 0} + VEC_UP = vector.Vec2F{X: 0, Y: 1} + VEC_DOWN = vector.Vec2F{X: 0, Y: -1} - VEC_DIRECTIONS = []*vector.Vec2F{ + VEC_DIRECTIONS = []vector.Vec2F{ // Prefer left/right animations by checking them first in the list VEC_LEFT, VEC_RIGHT, @@ -35,7 +35,7 @@ var ( } ) -func determineClosestDirection(velocity *vector.Vec2F) int { +func determineClosestDirection(velocity vector.Vec2F) int { closest := DIR_RIGHT max := 0.0 buffer := 0.5 // This buffer lets us stick to the left/right animations for diagonal movement diff --git a/internal/game/entity/types/penguin.go b/internal/game/entity/types/penguin.go index 432c967..63e5649 100644 --- a/internal/game/entity/types/penguin.go +++ b/internal/game/entity/types/penguin.go @@ -20,27 +20,25 @@ type penguin struct { updateAnimation bool // if false, don't change the animation // Physical parameters - worldPosition *vector.Vec2F // where is the center of this object - velocity *vector.Vec2F // movement direction to be applied each tick - speed float64 // movement magnitude to multiply with the velocity + worldPosition vector.Vec2F // where is the center of this object + velocity vector.Vec2F // movement direction to be applied each tick + speed float64 // movement magnitude to multiply with the velocity } -func NewPenguin(renderer *sdl.Renderer) *penguin { +func NewPenguin(renderer *sdl.Renderer) penguin { position := vector.Vec2F{} velocity := vector.Vec2F{} speed := 1.0 - p := penguin{ + return penguin{ currentAnimation: animation.PenguinAnimations[animation.PENGUIN_DEFAULT], animationStep: 0, direction: DIR_RIGHT, - worldPosition: &position, + worldPosition: position, speed: speed, - velocity: &velocity, + velocity: velocity, } - - return &p } func (p *penguin) Draw() error { @@ -48,7 +46,7 @@ func (p *penguin) Draw() error { // TODO //windowPosition := worldPosToWindowPos() - windowPosition := &sdl.Point{ + windowPosition := sdl.Point{ X: int32(math.Round(p.worldPosition.X)), Y: int32(math.Round(-1 * p.worldPosition.Y)), } @@ -63,7 +61,7 @@ func (p *penguin) Draw() error { return nil } -func (p *penguin) SetPosition(vec *vector.Vec2F) { +func (p *penguin) SetPosition(vec vector.Vec2F) { p.worldPosition = vec } @@ -91,7 +89,7 @@ func (p *penguin) MoveX(x float64) { defer p.mx.Unlock() p.velocity.X = x - p.velocity.Normalize() + p.velocity = p.velocity.Normalized() p.updateAnimation = true } @@ -101,7 +99,7 @@ func (p *penguin) MoveY(y float64) { // (0,0) is the top left, so negative y moves up p.velocity.Y = y - p.velocity.Normalize() + p.velocity.Normalized() p.updateAnimation = true } diff --git a/internal/game/game.go b/internal/game/game.go index 8183a88..46362a4 100644 --- a/internal/game/game.go +++ b/internal/game/game.go @@ -78,7 +78,6 @@ func Run(ctx context.Context, configMap gosimpleconf.ConfigMap) error { err = fmt.Errorf("failed in InitSpriteCache: %w", err) return err } - defer sprite.CleanupSpriteCache() animation.DefineAnimations() @@ -93,7 +92,7 @@ func Run(ctx context.Context, configMap gosimpleconf.ConfigMap) error { // Let them control a penguin to start with player1 := player.NewPlayer(ctx, inputHandler) penguinEntity := types.NewPenguin(renderer) - penguinCmdHandler := command.NewCommandHandler(ctx, penguinEntity) + penguinCmdHandler := command.NewCommandHandler(ctx, &penguinEntity) penguinEntity.SetSpeed(2.0) entityList = append(entityList, penguinCmdHandler) player1.SetEntityChan(penguinCmdHandler.CommandRequest()) @@ -105,9 +104,9 @@ func Run(ctx context.Context, configMap gosimpleconf.ConfigMap) error { for i := 0; i < 10; i++ { entity := types.NewPenguin(renderer) - entityCmd := command.NewCommandHandler(ctx, entity) + entityCmd := command.NewCommandHandler(ctx, &entity) randomPos := vector.Vec2F{X: rand.Float64() * 500, Y: rand.Float64() * -500} - entity.SetPosition(&randomPos) + entity.SetPosition(randomPos) entity.SetAnimation(rand.Intn(animation.PENGUIN_NUM_ANIMS)) entityList = append(entityList, entityCmd) @@ -204,6 +203,8 @@ func Run(ctx context.Context, configMap gosimpleconf.ConfigMap) error { e.CloseRequests() } + sprite.CleanupSpriteCache() + wg.Wait() return nil diff --git a/internal/game/sprite/sprite_animation.go b/internal/game/sprite/sprite_animation.go index 86079ab..e38ea98 100644 --- a/internal/game/sprite/sprite_animation.go +++ b/internal/game/sprite/sprite_animation.go @@ -22,26 +22,24 @@ func NewAnimation( offset sdl.Point, length int, border int, -) *spriteAnimation { +) spriteAnimation { spritesheet := GetSpritesheet(filename) - a := spriteAnimation{ + return spriteAnimation{ spritesheet: spritesheet, dimensions: dimensions, offset: offset, length: length, border: border, } - - return &a } -func (a *spriteAnimation) Draw( +func (a spriteAnimation) Draw( frame int, - windowPosition *sdl.Point, + windowPosition sdl.Point, angle float64, - center *sdl.Point, + center sdl.Point, flip sdl.RendererFlip, ) error { @@ -64,7 +62,7 @@ func (a *spriteAnimation) Draw( H: height, } - err := a.checkBounds(§ion) + err := a.checkBounds(section) if err != nil { return err } @@ -76,7 +74,7 @@ func (a *spriteAnimation) Draw( H: height * 2, } - err = a.spritesheet.Draw(§ion, &placement, angle, center, flip) + err = a.spritesheet.Draw(section, placement, angle, center, flip) if err != nil { return err } @@ -84,7 +82,7 @@ func (a *spriteAnimation) Draw( return nil } -func (a *spriteAnimation) checkBounds(section *sdl.Rect) error { +func (a spriteAnimation) checkBounds(section sdl.Rect) error { width := a.spritesheet.surface.W height := a.spritesheet.surface.H @@ -103,7 +101,7 @@ func (a *spriteAnimation) checkBounds(section *sdl.Rect) error { } if outOfBounds { - return fmt.Errorf("draw section was out of bounds - section: %v, image: %v", *section, a.spritesheet.surface.Bounds()) + return fmt.Errorf("draw section was out of bounds - section: %v, image: %v", section, a.spritesheet.surface.Bounds()) } return nil diff --git a/internal/game/sprite/spritesheet.go b/internal/game/sprite/spritesheet.go index 4d1591a..a8b8e75 100644 --- a/internal/game/sprite/spritesheet.go +++ b/internal/game/sprite/spritesheet.go @@ -49,39 +49,33 @@ func NewSprite(renderer *sdl.Renderer, filename string) (*spritesheet, error) { } func (s *spritesheet) Cleanup() { - // Clean up image - defer func() { - if s.surface != nil { - sdl.Do(func() { - s.surface.Free() - }) - } - }() - - // Clean up spritesheet - defer func() { + sdl.Do(func() { + // Clean up spritesheet if s.texture != nil { - sdl.Do(func() { - err := s.texture.Destroy() - if err != nil { - log.Printf("error destroying spritesheet %v: %v\n", s.filename, err) - } - }) + err := s.texture.Destroy() + if err != nil { + log.Printf("error destroying spritesheet %v: %v\n", s.filename, err) + } } - }() + + // Clean up image + if s.surface != nil { + s.surface.Free() + } + }) } func (s *spritesheet) Draw( - section *sdl.Rect, - placement *sdl.Rect, + section sdl.Rect, + placement sdl.Rect, angle float64, - center *sdl.Point, + center sdl.Point, flip sdl.RendererFlip, ) error { var err error sdl.Do(func() { - err = s.renderer.CopyEx(s.texture, section, placement, angle, center, flip) + err = s.renderer.CopyEx(s.texture, §ion, &placement, angle, ¢er, flip) }) if err != nil { return err @@ -90,10 +84,9 @@ func (s *spritesheet) Draw( return nil } -func (s *spritesheet) Bounds() *sdl.Point { - p := sdl.Point{ +func (s *spritesheet) Bounds() sdl.Point { + return sdl.Point{ X: s.surface.W, Y: s.surface.H, } - return &p } diff --git a/internal/game/sprite/spritesheet_cache.go b/internal/game/sprite/spritesheet_cache.go index 989b3c2..124d568 100644 --- a/internal/game/sprite/spritesheet_cache.go +++ b/internal/game/sprite/spritesheet_cache.go @@ -28,20 +28,19 @@ var ( ) func InitSpriteCache(renderer *sdl.Renderer) error { - var err error - - defaultSprite, err = createDefaultSprite(renderer) - if err != nil { - log.Printf("failed to create DefaultSprite: %v", err) - return err - } - spriteCache = make(map[string]*spritesheet) for _, filename := range fileList { s, err := NewSprite(renderer, filename) if err != nil { log.Printf("error creating sprite %v, using DefaultSprite: %v", filename, err) + + defaultSprite, err = createDefaultSprite(renderer) + if err != nil { + log.Printf("failed to create DefaultSprite: %v", err) + return err + } + spriteCache[filename] = defaultSprite } else { spriteCache[filename] = s @@ -82,6 +81,7 @@ func createDefaultSprite(renderer *sdl.Renderer) (*spritesheet, error) { } s := spritesheet{ + filename: "DEFAULT_SPRITE", renderer: renderer, texture: texture, surface: surface, diff --git a/internal/vector/vector.go b/internal/vector/vector.go index ad34bf6..5404ce6 100644 --- a/internal/vector/vector.go +++ b/internal/vector/vector.go @@ -11,24 +11,28 @@ type Vec2F struct { Y float64 } -func (v *Vec2F) Zero() bool { +func (v Vec2F) Zero() bool { return v.X == 0.0 && v.Y == 0.0 } -func (v *Vec2F) LengthSquared() float64 { +func (v Vec2F) LengthSquared() float64 { return v.X*v.X + v.Y*v.Y } -func (v *Vec2F) Normalize() { +func (v Vec2F) Normalized() Vec2F { + output := Vec2F{X: v.X, Y: v.Y} + length := math.Hypot(v.X, v.Y) if length == 0 { - return + return output } - v.X = v.X / length - v.Y = v.Y / length + output.X = v.X / length + output.Y = v.Y / length + + return output } -func (v *Vec2F) Dot(other *Vec2F) float64 { +func (v Vec2F) Dot(other Vec2F) float64 { return v.X*other.X + v.Y*other.Y } diff --git a/main.go b/main.go index 2afa421..0f3acb7 100644 --- a/main.go +++ b/main.go @@ -58,8 +58,9 @@ func run(ctx context.Context) error { // Setup logging writeToFile := gosimpleconf.Bool(configMap["log.writeToFile"]) + utcTime := gosimpleconf.Bool(configMap["log.utcTime"]) logFilename := configMap["log.file"] - logWriter, err := config.SetupLogging(writeToFile, logFilename) + logWriter, err := config.SetupLogging(writeToFile, utcTime, logFilename) if err != nil { log.Fatalf("error setting up logging: %v\n", err) }