diff --git a/internal/animation/animation_list.go b/internal/animation/animation_list.go new file mode 100644 index 0000000..80ce378 --- /dev/null +++ b/internal/animation/animation_list.go @@ -0,0 +1,21 @@ +package animation + +// AnimationMap maps each animation ID to the actual animation +var AnimationMap map[int]entityAnimation + +// Enum containing IDs of all animations that exist to make them easy to reference. +const ( + PENGUIN_WALK_RIGHT int = iota + PENGUIN_WALK_LEFT + PENGUIN_WALK_UP + PENGUIN_WALK_DOWN + PENGUIN_STATIONARY_RIGHT + PENGUIN_STATIONARY_LEFT + PENGUIN_STATIONARY_UP + PENGUIN_STATIONARY_DOWN +) + +func DefineAnimations() { + AnimationMap = make(map[int]entityAnimation) + DefinePenguinAnimations() +} diff --git a/internal/animation/animation_tracker.go b/internal/animation/animation_tracker.go new file mode 100644 index 0000000..667b693 --- /dev/null +++ b/internal/animation/animation_tracker.go @@ -0,0 +1,35 @@ +package animation + +import rl "github.com/gen2brain/raylib-go/raylib" + +type animationTracker struct { + currentAnimation entityAnimation + animationStep int +} + +func NewAnimationTracker() *animationTracker { + return &animationTracker{} +} + +func (a *animationTracker) Draw(windowPosition rl.Vector2, color rl.Color) error { + step := a.animationStep / a.currentAnimation.GetSpeed() + + err := a.currentAnimation.Draw(step, windowPosition, color) + if err != nil { + return err + } + + a.animationStep = 1 + a.animationStep + + return nil +} + +func (a *animationTracker) SetAnimation(id int) { + newAnim := AnimationMap[id] + + if newAnim != a.currentAnimation { + a.animationStep = 0 + } + + a.currentAnimation = newAnim +} diff --git a/internal/game/entity/animation/entity_animation.go b/internal/animation/entity_animation.go similarity index 96% rename from internal/game/entity/animation/entity_animation.go rename to internal/animation/entity_animation.go index b481900..5e84e3a 100644 --- a/internal/game/entity/animation/entity_animation.go +++ b/internal/animation/entity_animation.go @@ -60,10 +60,6 @@ func (e entityAnimation) GetSpeed() int { return e.speed } -func DefineAnimations() { - DefinePenguinAnimations() -} - func getCenter(dimensions rl.Vector2) rl.Vector2 { x := dimensions.X / 2.0 y := dimensions.Y / 2.0 diff --git a/internal/animation/penguin_animations.go b/internal/animation/penguin_animations.go new file mode 100644 index 0000000..1b4a619 --- /dev/null +++ b/internal/animation/penguin_animations.go @@ -0,0 +1,85 @@ +package animation + +import ( + "math/rand" + + "git.wisellama.rocks/Project-Ely/project-ely/internal/sprite" + rl "github.com/gen2brain/raylib-go/raylib" +) + +const ( + PENGUIN_DEFAULT = PENGUIN_STATIONARY_RIGHT +) + +var penguinAnimations []int + +func DefinePenguinAnimations() { + filename := sprite.DELILAHWALKING + + penguinAnimations = make([]int, 0) + penguinAnimations = append(penguinAnimations, PENGUIN_WALK_RIGHT) + penguinAnimations = append(penguinAnimations, PENGUIN_WALK_LEFT) + penguinAnimations = append(penguinAnimations, PENGUIN_WALK_UP) + penguinAnimations = append(penguinAnimations, PENGUIN_WALK_DOWN) + penguinAnimations = append(penguinAnimations, PENGUIN_STATIONARY_RIGHT) + penguinAnimations = append(penguinAnimations, PENGUIN_STATIONARY_LEFT) + penguinAnimations = append(penguinAnimations, PENGUIN_STATIONARY_UP) + penguinAnimations = append(penguinAnimations, PENGUIN_STATIONARY_DOWN) + + var ( + dimensions rl.Vector2 + offset rl.Vector2 + center rl.Vector2 + length int + speed int + border int + ) + + dimensions = rl.Vector2{X: 13, Y: 17} + + // Walking Right is in the spritesheet. + speed = 5 + offset = rl.Vector2{X: 0, Y: 1} + length = 5 + 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) + AnimationMap[PENGUIN_WALK_RIGHT] = NewEntityAnimation(walkRight, speed, length, 0, center, FLIP_NONE) + + // Walking Left is just that flipped. + AnimationMap[PENGUIN_WALK_LEFT] = NewEntityAnimation(walkRight, speed, length, 0, center, FLIP_HORIZONTAL) + + // Stationary Right/Left is just the first frame. + length = 1 + stationaryRight := sprite.NewAnimation(filename, dimensions, offset, length, border) + AnimationMap[PENGUIN_STATIONARY_RIGHT] = NewEntityAnimation(stationaryRight, speed, length, 0, center, FLIP_NONE) + AnimationMap[PENGUIN_STATIONARY_LEFT] = NewEntityAnimation(stationaryRight, speed, length, 0, center, FLIP_HORIZONTAL) + + // Walk Up + length = 4 + offset = rl.Vector2{X: 0, Y: 3} + walkUp := sprite.NewAnimation(filename, dimensions, offset, length, border) + AnimationMap[PENGUIN_WALK_UP] = NewEntityAnimation(walkUp, speed, length, 0, center, FLIP_NONE) + + // Stationary Up + length = 1 + stationaryUp := sprite.NewAnimation(filename, dimensions, offset, length, border) + AnimationMap[PENGUIN_STATIONARY_UP] = NewEntityAnimation(stationaryUp, speed, length, 0, center, FLIP_NONE) + + // Walk Down + length = 4 + offset = rl.Vector2{X: 0, Y: 0} + walkDown := sprite.NewAnimation(filename, dimensions, offset, length, border) + AnimationMap[PENGUIN_WALK_DOWN] = NewEntityAnimation(walkDown, speed, length, 0, center, FLIP_NONE) + + // Stationary Down + length = 1 + stationaryDown := sprite.NewAnimation(filename, dimensions, offset, length, border) + AnimationMap[PENGUIN_STATIONARY_DOWN] = NewEntityAnimation(stationaryDown, speed, length, 0, center, FLIP_NONE) +} + +func RandomPenguinAnimation() int { + n := len(penguinAnimations) + i := rand.Intn(n) + return penguinAnimations[i] +} diff --git a/internal/game/entity/types/common.go b/internal/entity/common.go similarity index 89% rename from internal/game/entity/types/common.go rename to internal/entity/common.go index e931eba..4854197 100644 --- a/internal/game/entity/types/common.go +++ b/internal/entity/common.go @@ -1,4 +1,4 @@ -package types +package entity import ( rl "github.com/gen2brain/raylib-go/raylib" @@ -9,6 +9,11 @@ type EntityAnimation interface { GetSpeed() int } +type AnimationTracker interface { + Draw(windowPosition rl.Vector2, color rl.Color) error + SetAnimation(id int) +} + const ( // These line up with the VEC_DIRECTIONS slice DIR_LEFT int = iota diff --git a/internal/entity/penguin.go b/internal/entity/penguin.go new file mode 100644 index 0000000..c9bcc3d --- /dev/null +++ b/internal/entity/penguin.go @@ -0,0 +1,114 @@ +package entity + +import ( + "log" + "math" + + "git.wisellama.rocks/Project-Ely/project-ely/internal/animation" + "git.wisellama.rocks/Project-Ely/project-ely/internal/physics" + rl "github.com/gen2brain/raylib-go/raylib" +) + +type penguin struct { + animation AnimationTracker + object physics.Object + direction int + staticAnimation bool + color rl.Color +} + +func NewPenguin() *penguin { + object := physics.NewObject() + anim := animation.NewAnimationTracker() + anim.SetAnimation(animation.PENGUIN_DEFAULT) + + return &penguin{ + animation: anim, + object: object, + staticAnimation: false, + color: rl.White, + } +} + +func (p *penguin) Draw() error { + + position := p.object.GetPosition() + + // TODO? + //windowPosition := worldPosToWindowPos() + windowPosition := rl.Vector2{ + X: float32(math.Round(float64(position.X))), + Y: float32(-1 * math.Round(float64(position.Y))), + } + + return p.animation.Draw(windowPosition, p.color) +} + +func (p *penguin) Update() error { + p.SetMoveAnimation() + + return p.object.Update() +} + +func (p *penguin) GetObject() physics.Object { + return p.object +} + +func (p *penguin) GetAnimationTracker() AnimationTracker { + return p.animation +} + +// ToggleStaticAnimation will set whether or not this entity should ever update its animation loop or not. +// True means this will never update, it is static. +// False means this will update the animation loop based on physical properties of this entity. +func (p *penguin) ToggleStaticAnimation() { + p.staticAnimation = !p.staticAnimation +} + +func (p *penguin) SetMoveAnimation() { + if p.staticAnimation { + return + } + + velocity := p.object.GetVelocity() + if velocity.X == 0 && velocity.Y == 0 { + // Stay facing whatever direction we were facing + direction := p.direction + switch direction { + case DIR_LEFT: + p.animation.SetAnimation(animation.PENGUIN_STATIONARY_LEFT) + case DIR_RIGHT: + p.animation.SetAnimation(animation.PENGUIN_STATIONARY_RIGHT) + case DIR_UP: + p.animation.SetAnimation(animation.PENGUIN_STATIONARY_UP) + case DIR_DOWN: + p.animation.SetAnimation(animation.PENGUIN_STATIONARY_DOWN) + default: + log.Printf("unknown direction: %v", direction) + p.animation.SetAnimation(animation.PENGUIN_DEFAULT) + } + + } else { + // Figure out which way we are facing now that we're moving + direction := determineClosestDirection(velocity) + p.direction = direction + + switch direction { + case DIR_LEFT: + p.animation.SetAnimation(animation.PENGUIN_WALK_LEFT) + case DIR_RIGHT: + p.animation.SetAnimation(animation.PENGUIN_WALK_RIGHT) + case DIR_UP: + p.animation.SetAnimation(animation.PENGUIN_WALK_UP) + case DIR_DOWN: + p.animation.SetAnimation(animation.PENGUIN_WALK_DOWN) + default: + log.Printf("unknown direction: %v", direction) + p.animation.SetAnimation(animation.PENGUIN_DEFAULT) + } + } +} + +func (p *penguin) SetColor(color rl.Color) { + p.color = color +} diff --git a/internal/game.go b/internal/game.go new file mode 100644 index 0000000..105f0b9 --- /dev/null +++ b/internal/game.go @@ -0,0 +1,135 @@ +package internal + +import ( + "context" + "fmt" + "log" + "math/rand" + + "git.wisellama.rocks/Project-Ely/project-ely/internal/animation" + "git.wisellama.rocks/Project-Ely/project-ely/internal/channels" + "git.wisellama.rocks/Project-Ely/project-ely/internal/entity" + "git.wisellama.rocks/Project-Ely/project-ely/internal/player" + "git.wisellama.rocks/Project-Ely/project-ely/internal/sprite" + "git.wisellama.rocks/Wisellama/gosimpleconf" + rl "github.com/gen2brain/raylib-go/raylib" +) + +// Drawables can be rendered with OpenGL. See the 'entity' and 'animation' packages. +type Drawable interface { + Draw() error +} + +// Objects can have physical interactions. See the 'entity' and 'physics' packages. +type Object interface { + Update() error +} + +// Run is the main function to start the game. +func Run(ctx context.Context, configMap gosimpleconf.ConfigMap) error { + var err error + + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + framerate64 := gosimpleconf.Int64(configMap["game.framerate"]) + framerate := int32(framerate64) + + // Initialize the RayLib window + channels.RL.Do(func() { + rl.InitWindow(800, 600, configMap["game.title"]) + rl.SetTargetFPS(framerate) + }) + defer func() { + channels.RL.Do(func() { + rl.CloseWindow() + }) + }() + + // Initialize our sprites and animations + sprite.InitSpriteCache() + animation.DefineAnimations() + + drawables := make([]Drawable, 0) + objects := make([]Object, 0) + + center := rl.Vector2{X: float32(rl.GetScreenWidth()) / 2, Y: float32(rl.GetScreenHeight()) / 2 * -1} + player1 := player.NewPlayer(ctx) + player1Penguin := entity.NewPenguin() + player1Penguin.SetColor(rl.Gold) + player1Penguin.GetObject().SetPosition(center) + player1.SetControlledObject(player1Penguin.GetObject()) + drawables = append(drawables, player1Penguin) + objects = append(objects, player1Penguin) + + for i := 0; i < 10; i++ { + p := entity.NewPenguin() + p.GetObject().SetPosition(rl.Vector2{ + X: rand.Float32() * float32(rl.GetScreenWidth()), + Y: rand.Float32() * float32(rl.GetScreenHeight()*-1), + }) + p.GetAnimationTracker().SetAnimation(animation.RandomPenguinAnimation()) + p.ToggleStaticAnimation() + + drawables = append(drawables, p) + objects = append(objects, p) + } + + // And now starting the main loop + running := true + for running { + channels.RL.Do(func() { + if rl.WindowShouldClose() { + cancel() + } + }) + + // Allow us to exit early if the context is done + select { + case <-ctx.Done(): + running = false + default: + // Keep running + } + if !running { + break + } + + player1.Update() + + // Update physics + // TODO probably move out to something else to handle collisions. + for _, o := range objects { + err = o.Update() + if err != nil { + log.Printf("error updating physics: %v", err) + } + } + + channels.RL.Do(func() { + rl.BeginDrawing() + }) + + // Draw stuff + for _, d := range drawables { + err = d.Draw() + if err != nil { + log.Printf("error drawing: %v", err) + } + } + + channels.RL.Do(func() { + rl.ClearBackground(rl.Black) + rl.DrawText("Some Text!", 190, 200, 20, rl.Blue) + rl.DrawText(fmt.Sprintf("%v FPS", rl.GetFPS()), 190, 250, 20, rl.Blue) + }) + + channels.RL.Do(func() { + rl.EndDrawing() + }) + } + + sprite.CleanupSpriteCache() + + return nil +} diff --git a/internal/game/entity/animation/penguin_animations.go b/internal/game/entity/animation/penguin_animations.go deleted file mode 100644 index bd77060..0000000 --- a/internal/game/entity/animation/penguin_animations.go +++ /dev/null @@ -1,80 +0,0 @@ -package animation - -import ( - "git.wisellama.rocks/Project-Ely/project-ely/internal/game/sprite" - rl "github.com/gen2brain/raylib-go/raylib" -) - -var PenguinAnimations map[int]entityAnimation - -const ( - PENGUIN_WALK_RIGHT int = iota - PENGUIN_WALK_LEFT - PENGUIN_WALK_UP - PENGUIN_WALK_DOWN - PENGUIN_STATIONARY_RIGHT - PENGUIN_STATIONARY_LEFT - PENGUIN_STATIONARY_UP - PENGUIN_STATIONARY_DOWN -) - -const ( - PENGUIN_NUM_ANIMS = 8 - PENGUIN_DEFAULT = PENGUIN_STATIONARY_RIGHT -) - -func DefinePenguinAnimations() { - filename := sprite.DELILAHWALKING - - var ( - dimensions rl.Vector2 - offset rl.Vector2 - center rl.Vector2 - length int - speed int - border int - ) - - dimensions = rl.Vector2{X: 13, Y: 17} - PenguinAnimations = make(map[int]entityAnimation) - - // Walking Right is in the spritesheet. - speed = 5 - offset = rl.Vector2{X: 0, Y: 1} - length = 5 - 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, FLIP_NONE) - - // Walking Left is just that flipped. - PenguinAnimations[PENGUIN_WALK_LEFT] = NewEntityAnimation(walkRight, speed, length, 0, center, FLIP_HORIZONTAL) - - // Stationary Right/Left is just the first frame. - length = 1 - stationaryRight := sprite.NewAnimation(filename, dimensions, offset, length, border) - PenguinAnimations[PENGUIN_STATIONARY_RIGHT] = NewEntityAnimation(stationaryRight, speed, length, 0, center, FLIP_NONE) - PenguinAnimations[PENGUIN_STATIONARY_LEFT] = NewEntityAnimation(stationaryRight, speed, length, 0, center, FLIP_HORIZONTAL) - - // Walk Up - length = 4 - offset = rl.Vector2{X: 0, Y: 3} - walkUp := sprite.NewAnimation(filename, dimensions, offset, length, border) - PenguinAnimations[PENGUIN_WALK_UP] = NewEntityAnimation(walkUp, speed, length, 0, center, FLIP_NONE) - - // Stationary Up - length = 1 - stationaryUp := sprite.NewAnimation(filename, dimensions, offset, length, border) - PenguinAnimations[PENGUIN_STATIONARY_UP] = NewEntityAnimation(stationaryUp, speed, length, 0, center, FLIP_NONE) - - // Walk Down - length = 4 - offset = rl.Vector2{X: 0, Y: 0} - walkDown := sprite.NewAnimation(filename, dimensions, offset, length, border) - PenguinAnimations[PENGUIN_WALK_DOWN] = NewEntityAnimation(walkDown, speed, length, 0, center, FLIP_NONE) - - // Stationary Down - length = 1 - stationaryDown := sprite.NewAnimation(filename, dimensions, offset, length, border) - PenguinAnimations[PENGUIN_STATIONARY_DOWN] = NewEntityAnimation(stationaryDown, speed, length, 0, center, FLIP_NONE) -} diff --git a/internal/game/entity/command/commands.go b/internal/game/entity/command/commands.go deleted file mode 100644 index 37341cc..0000000 --- a/internal/game/entity/command/commands.go +++ /dev/null @@ -1,22 +0,0 @@ -package command - -// List of keys supported by all entities -const ( - MOVE_X int = iota - MOVE_Y - SET_SPEED - SET_POSITION - SET_ANIMATION -) - -type Command struct { - key int - value float32 -} - -func NewCommand(key int, value float32) Command { - return Command{ - key: key, - value: value, - } -} diff --git a/internal/game/entity/command/handler.go b/internal/game/entity/command/handler.go deleted file mode 100644 index e8e25c9..0000000 --- a/internal/game/entity/command/handler.go +++ /dev/null @@ -1,170 +0,0 @@ -package command - -import ( - "context" - "log" - "sync" - "time" - - "git.wisellama.rocks/Project-Ely/project-ely/internal/channels" -) - -type Entity interface { - Draw() error - Update() error - MoveX(x float32) - MoveY(y float32) - SetSpeed(s float32) -} - -type CommandHandler struct { - ctx context.Context - timeout time.Duration - entity Entity - commandChan chan Command - drawRequestChan chan struct{} - drawResponseChan chan struct{} - updateRequestChan chan struct{} - updateResponseChan chan struct{} -} - -func NewCommandHandler(ctx context.Context, entity Entity) *CommandHandler { - commandChan := make(chan Command, 10) - drawRequestChan := make(chan struct{}) - drawResponseChan := make(chan struct{}) - updateRequestChan := make(chan struct{}) - updateResponseChan := make(chan struct{}) - - defaultTimeout := time.Second - return &CommandHandler{ - ctx: ctx, - timeout: defaultTimeout, - entity: entity, - commandChan: commandChan, - drawRequestChan: drawRequestChan, - drawResponseChan: drawResponseChan, - updateRequestChan: updateRequestChan, - updateResponseChan: updateResponseChan, - } -} - -func (c *CommandHandler) closeResponses() { - close(c.drawResponseChan) - close(c.updateResponseChan) -} - -func (c *CommandHandler) CloseRequests() { - close(c.drawRequestChan) - close(c.updateRequestChan) - close(c.commandChan) -} - -func (c *CommandHandler) CommandRequest() chan Command { - return c.commandChan -} - -func (c *CommandHandler) DrawRequest() chan struct{} { - return c.drawRequestChan -} - -func (c *CommandHandler) DrawResponse() chan struct{} { - return c.drawResponseChan -} - -func (c *CommandHandler) UpdateRequest() chan struct{} { - return c.updateRequestChan -} - -func (c *CommandHandler) UpdateResponse() chan struct{} { - return c.updateResponseChan -} - -func (c *CommandHandler) Run() { - defer c.closeResponses() - - wg := sync.WaitGroup{} - - running := true - for running { - select { - case <-c.drawRequestChan: - wg.Add(1) - go func() { - defer wg.Done() - c.DrawWithTimeout() - c.drawResponseChan <- struct{}{} - }() - case <-c.updateRequestChan: - wg.Add(1) - go func() { - defer wg.Done() - c.UpdateWithTimeout() - c.updateResponseChan <- struct{}{} - }() - case cmd := <-c.commandChan: - wg.Add(1) - go func(cmd Command) { - defer wg.Done() - c.HandleWithTimeout(cmd) - }(cmd) - case <-c.ctx.Done(): - // Graceful shutdown - running = false - } - } - - wg.Wait() - - // Finish up anything in the queues - for cmd := range c.commandChan { - c.HandleWithTimeout(cmd) - } - for range c.drawRequestChan { - c.UpdateWithTimeout() - } - for range c.drawRequestChan { - c.DrawWithTimeout() - } -} - -func (c *CommandHandler) HandleWithTimeout(cmd Command) { - err := channels.RunWithTimeout(c.timeout, func() error { - return c.Handle(cmd) - }) - if err != nil { - log.Printf("handle with timeout error: %v\n", err) - } -} - -func (c *CommandHandler) Handle(cmd Command) error { - switch cmd.key { - case MOVE_X: - c.entity.MoveX(cmd.value) - case MOVE_Y: - c.entity.MoveY(cmd.value) - case SET_SPEED: - c.entity.SetSpeed(cmd.value) - default: - log.Printf("unknown entity command: %v", cmd.key) - } - - return nil -} - -func (c *CommandHandler) DrawWithTimeout() { - err := channels.RunWithTimeout(c.timeout, func() error { - return c.entity.Draw() - }) - if err != nil { - log.Printf("draw with timeout error: %v\n", err) - } -} - -func (c *CommandHandler) UpdateWithTimeout() { - err := channels.RunWithTimeout(c.timeout, func() error { - return c.entity.Update() - }) - if err != nil { - log.Printf("update with timeout error: %v\n", err) - } -} diff --git a/internal/game/entity/types/penguin.go b/internal/game/entity/types/penguin.go deleted file mode 100644 index 84a48f8..0000000 --- a/internal/game/entity/types/penguin.go +++ /dev/null @@ -1,166 +0,0 @@ -package types - -import ( - "log" - "math" - "sync" - - "git.wisellama.rocks/Project-Ely/project-ely/internal/game/entity/animation" - rl "github.com/gen2brain/raylib-go/raylib" -) - -type penguin struct { - mx sync.RWMutex - - // Animation parameters - currentAnimation EntityAnimation // pointer to current animation loop - animationStep int // index of animation loop - direction int // direction facing for sprite animations - updateAnimation bool // if false, don't change the animation - - // Physical parameters - worldPosition rl.Vector2 // where is the center of this object - velocity rl.Vector2 // movement direction to be applied each tick - speed float32 // movement magnitude to multiply with the velocity -} - -func NewPenguin() penguin { - position := rl.Vector2{} - velocity := rl.Vector2{} - speed := float32(1) - - return penguin{ - currentAnimation: animation.PenguinAnimations[animation.PENGUIN_DEFAULT], - animationStep: 0, - direction: DIR_RIGHT, - - worldPosition: position, - speed: speed, - velocity: velocity, - } -} - -func (p *penguin) Draw() error { - step := p.animationStep / p.currentAnimation.GetSpeed() - - // TODO - //windowPosition := worldPosToWindowPos() - windowPosition := rl.Vector2{ - X: float32(math.Round(float64(p.worldPosition.X))), - Y: float32(math.Round(float64(-1 * p.worldPosition.Y))), - } - - err := p.currentAnimation.Draw(step, windowPosition, rl.White) - if err != nil { - return err - } - - p.animationStep = 1 + p.animationStep - - return nil -} - -func (p *penguin) SetPosition(vec rl.Vector2) { - p.worldPosition = vec -} - -func (p *penguin) SetDirection(dir int) { - p.direction = dir -} - -func (p *penguin) SetAnimation(id int) { - a, exists := animation.PenguinAnimations[id] - - if !exists { - log.Printf("animation does not exist: %v", id) - a = animation.PenguinAnimations[animation.PENGUIN_DEFAULT] - } - - if a != p.currentAnimation { - p.animationStep = 0 - } - - p.currentAnimation = a -} - -func (p *penguin) MoveX(x float32) { - p.mx.Lock() - defer p.mx.Unlock() - - p.velocity.X = x - if p.velocity.X != 0 || p.velocity.Y != 0 { - p.velocity = rl.Vector2Normalize(p.velocity) - } - p.updateAnimation = true -} - -func (p *penguin) MoveY(y float32) { - p.mx.Lock() - defer p.mx.Unlock() - - p.velocity.Y = y - if p.velocity.X != 0 || p.velocity.Y != 0 { - p.velocity = rl.Vector2Normalize(p.velocity) - } - p.updateAnimation = true -} - -func (p *penguin) SetSpeed(s float32) { - p.speed = s -} - -func (p *penguin) GetSpeed() float32 { - return p.speed -} - -func (p *penguin) SetMoveAnimation() { - if p.velocity.X == 0 && p.velocity.Y == 0 { - // Stay facing whatever direction we were facing - switch p.direction { - case DIR_LEFT: - p.SetAnimation(animation.PENGUIN_STATIONARY_LEFT) - case DIR_RIGHT: - p.SetAnimation(animation.PENGUIN_STATIONARY_RIGHT) - case DIR_UP: - p.SetAnimation(animation.PENGUIN_STATIONARY_UP) - case DIR_DOWN: - p.SetAnimation(animation.PENGUIN_STATIONARY_DOWN) - default: - log.Printf("unknown direction: %v", p.direction) - p.SetAnimation(animation.PENGUIN_DEFAULT) - } - - } else { - // Figure out which way we are facing now that we're moving - p.direction = determineClosestDirection(p.velocity) - - switch p.direction { - case DIR_LEFT: - p.SetAnimation(animation.PENGUIN_WALK_LEFT) - case DIR_RIGHT: - p.SetAnimation(animation.PENGUIN_WALK_RIGHT) - case DIR_UP: - p.SetAnimation(animation.PENGUIN_WALK_UP) - case DIR_DOWN: - p.SetAnimation(animation.PENGUIN_WALK_DOWN) - default: - log.Printf("unknown direction: %v", p.direction) - p.SetAnimation(animation.PENGUIN_DEFAULT) - } - } -} - -func (p *penguin) Update() error { - if p.updateAnimation { - p.SetMoveAnimation() - p.updateAnimation = false - } - - x := p.velocity.X * p.speed - y := p.velocity.Y * p.speed - - p.worldPosition.X += x - p.worldPosition.Y += y - - return nil -} diff --git a/internal/game/game.go b/internal/game/game.go deleted file mode 100644 index 4f24064..0000000 --- a/internal/game/game.go +++ /dev/null @@ -1,142 +0,0 @@ -package game - -import ( - "context" - "fmt" - "math/rand" - "sync" - - "git.wisellama.rocks/Project-Ely/project-ely/internal/channels" - "git.wisellama.rocks/Project-Ely/project-ely/internal/game/entity/animation" - "git.wisellama.rocks/Project-Ely/project-ely/internal/game/entity/command" - "git.wisellama.rocks/Project-Ely/project-ely/internal/game/entity/types" - "git.wisellama.rocks/Project-Ely/project-ely/internal/game/player" - "git.wisellama.rocks/Project-Ely/project-ely/internal/game/sprite" - "git.wisellama.rocks/Wisellama/gosimpleconf" - rl "github.com/gen2brain/raylib-go/raylib" -) - -type EntityCmdHandler interface { - Run() - CloseRequests() - CommandRequest() chan command.Command - DrawRequest() chan struct{} - DrawResponse() chan struct{} - UpdateRequest() chan struct{} - UpdateResponse() chan struct{} -} - -// Run is the main function to start the game. -func Run(ctx context.Context, configMap gosimpleconf.ConfigMap) error { - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - framerate64 := gosimpleconf.Int64(configMap["game.framerate"]) - framerate := int32(framerate64) - - // Initialize the RayLib window - channels.RL.Do(func() { - rl.InitWindow(800, 600, configMap["game.title"]) - rl.SetTargetFPS(framerate) - }) - defer func() { - channels.RL.Do(func() { - rl.CloseWindow() - }) - }() - - // Initialize our sprites and animations - sprite.InitSpriteCache() - animation.DefineAnimations() - - entityList := make([]EntityCmdHandler, 0) - - wg := sync.WaitGroup{} - - playerPenguinEntity := types.NewPenguin() - playerPenguinEntity.SetSpeed(2) - playerPenguinEntity.SetPosition(rl.Vector2{X: 100, Y: -100}) - playerPenguinCmd := command.NewCommandHandler(ctx, &playerPenguinEntity) - playerPenguin := player.NewPlayer(ctx) - playerPenguin.SetEntityChan(playerPenguinCmd.CommandRequest()) - entityList = append(entityList, playerPenguinCmd) - - wg.Add(1) - go func() { - defer wg.Done() - playerPenguinCmd.Run() - }() - - for i := 0; i < 10; i++ { - entity := types.NewPenguin() - entityCmd := command.NewCommandHandler(ctx, &entity) - randomPos := rl.Vector2{X: rand.Float32() * 500, Y: rand.Float32() * -500} - entity.SetPosition(randomPos) - entity.SetAnimation(rand.Intn(animation.PENGUIN_NUM_ANIMS)) - entityList = append(entityList, entityCmd) - - wg.Add(1) - go func() { - defer wg.Done() - entityCmd.Run() - }() - } - - // And now starting the main loop - running := true - for running { - channels.RL.Do(func() { - if rl.WindowShouldClose() { - cancel() - } - }) - - // Allow us to exit early if the context is done - select { - case <-ctx.Done(): - running = false - default: - // Keep running - } - if !running { - break - } - - // Update players - playerPenguin.Update() - - // Start drawing - channels.RL.Do(func() { - rl.BeginDrawing() - }) - - // Tell everything to Update and Draw - for _, e := range entityList { - e.UpdateRequest() <- struct{}{} - e.DrawRequest() <- struct{}{} - } - // Wait for each entity to finish their Draw and Update commands before proceeding - for _, e := range entityList { - <-e.UpdateResponse() - <-e.DrawResponse() - } - - // Finish drawing - channels.RL.Do(func() { - rl.ClearBackground(rl.Black) - rl.DrawText("Some Text!", 190, 200, 20, rl.Blue) - rl.DrawText(fmt.Sprintf("%v FPS", rl.GetFPS()), 190, 250, 20, rl.Blue) - - rl.EndDrawing() - }) - } - - for _, e := range entityList { - e.CloseRequests() - } - sprite.CleanupSpriteCache() - - wg.Wait() - - return nil -} diff --git a/internal/physics/object.go b/internal/physics/object.go new file mode 100644 index 0000000..a488840 --- /dev/null +++ b/internal/physics/object.go @@ -0,0 +1,87 @@ +package physics + +import rl "github.com/gen2brain/raylib-go/raylib" + +// Object defines the interface for physical objects. +// +// This is defined here in the object.go file because after +// passing an 'object' to an 'entity' (e.g. 'penguin'), +// then to a 'player', as well as the main function, +// we needed almost all of the functions for the whole +// interface defined in each place. +type Object interface { + Update() error + + GetPosition() rl.Vector2 + GetVelocity() rl.Vector2 + GetAcceleration() rl.Vector2 + GetBaseAcceleration() rl.Vector2 + + SetPosition(pos rl.Vector2) + SetVelocity(vel rl.Vector2) + SetAcceleration(acc rl.Vector2) + SetBaseAcceleration(base rl.Vector2) +} + +type object struct { + position rl.Vector2 + velocity rl.Vector2 + acceleration rl.Vector2 + baseAcceleration rl.Vector2 // some default speed for this object +} + +func NewObject() *object { + pos := rl.Vector2{X: 0.0, Y: 0.0} + vel := rl.Vector2{X: 0.0, Y: 0.0} + acc := rl.Vector2{X: 0.0, Y: 0.0} + base := rl.Vector2{X: 1.0, Y: 1.0} + + return &object{ + position: pos, + velocity: vel, + acceleration: acc, + baseAcceleration: base, + } +} + +func (o *object) Update() error { + p := o.position + v := o.velocity + a := o.acceleration + + v = rl.Vector2Add(v, a) + o.velocity = v + + p = rl.Vector2Add(p, v) + o.position = p + + return nil +} + +func (o *object) SetPosition(pos rl.Vector2) { + o.position = pos +} + +func (o *object) SetVelocity(vel rl.Vector2) { + o.velocity = vel +} +func (o *object) SetAcceleration(acc rl.Vector2) { + o.acceleration = acc +} + +func (o *object) SetBaseAcceleration(base rl.Vector2) { + o.baseAcceleration = base +} + +func (o *object) GetPosition() rl.Vector2 { + return o.position +} +func (o *object) GetVelocity() rl.Vector2 { + return o.velocity +} +func (o *object) GetAcceleration() rl.Vector2 { + return o.acceleration +} +func (o *object) GetBaseAcceleration() rl.Vector2 { + return o.baseAcceleration +} diff --git a/internal/game/player/player.go b/internal/player/player.go similarity index 58% rename from internal/game/player/player.go rename to internal/player/player.go index c9745af..96228a3 100644 --- a/internal/game/player/player.go +++ b/internal/player/player.go @@ -5,10 +5,13 @@ import ( "time" "git.wisellama.rocks/Project-Ely/project-ely/internal/channels" - "git.wisellama.rocks/Project-Ely/project-ely/internal/game/entity/command" rl "github.com/gen2brain/raylib-go/raylib" ) +type PhysicalObject interface { + SetVelocity(vel rl.Vector2) +} + // A player represents a collection of stuff controlled by the user's input. // It contains the camera used to view the world, // the viewport for the part of the screen to draw to (splitscreen support), @@ -17,7 +20,7 @@ type player struct { ctx context.Context timeout time.Duration - entityChan chan command.Command + controlledObject PhysicalObject } func NewPlayer(ctx context.Context) *player { @@ -30,8 +33,8 @@ func NewPlayer(ctx context.Context) *player { return &p } -func (p *player) SetEntityChan(e chan command.Command) { - p.entityChan = e +func (p *player) SetControlledObject(c PhysicalObject) { + p.controlledObject = c } func (p *player) Update() { @@ -40,38 +43,43 @@ func (p *player) Update() { down bool left bool right bool - speed bool + fast bool ) channels.RL.Do(func() { - speed = rl.IsKeyDown(rl.KeyLeftShift) + fast = rl.IsKeyDown(rl.KeyLeftShift) left = rl.IsKeyDown(rl.KeyA) || rl.IsKeyDown(rl.KeyLeft) right = rl.IsKeyDown(rl.KeyD) || rl.IsKeyDown(rl.KeyRight) up = rl.IsKeyDown(rl.KeyW) || rl.IsKeyDown(rl.KeyUp) down = rl.IsKeyDown(rl.KeyS) || rl.IsKeyDown(rl.KeyDown) }) - // Speed - if speed { - p.entityChan <- command.NewCommand(command.SET_SPEED, 4) - } else { - p.entityChan <- command.NewCommand(command.SET_SPEED, 2) - } + baseVel := rl.Vector2{X: 1.0, Y: 1.0} + velocity := rl.Vector2{X: 0.0, Y: 0.0} // Move horizontal if right { - p.entityChan <- command.NewCommand(command.MOVE_X, 1.0) + velocity.X = baseVel.X } else if left { - p.entityChan <- command.NewCommand(command.MOVE_X, -1.0) - } else if !right && !left { - p.entityChan <- command.NewCommand(command.MOVE_X, 0.0) + velocity.X = baseVel.X * -1 } // Move vertical if up { - p.entityChan <- command.NewCommand(command.MOVE_Y, 1.0) + velocity.Y = baseVel.Y } else if down { - p.entityChan <- command.NewCommand(command.MOVE_Y, -1.0) - } else if !up && !down { - p.entityChan <- command.NewCommand(command.MOVE_Y, 0.0) + velocity.Y = baseVel.Y * -1 } + + // If we're moving diagonal, normalize the vector to avoid weird diagonal super-speed + if velocity.X != 0 || velocity.Y != 0 { + velocity = rl.Vector2Normalize(velocity) + } + + // Faster speed + if fast { + velocity.X *= 2 + velocity.Y *= 2 + } + + p.controlledObject.SetVelocity(velocity) } diff --git a/internal/game/sprite/files.go b/internal/sprite/files.go similarity index 100% rename from internal/game/sprite/files.go rename to internal/sprite/files.go diff --git a/internal/game/sprite/sprite_animation.go b/internal/sprite/sprite_animation.go similarity index 100% rename from internal/game/sprite/sprite_animation.go rename to internal/sprite/sprite_animation.go diff --git a/internal/game/sprite/spritesheet.go b/internal/sprite/spritesheet.go similarity index 100% rename from internal/game/sprite/spritesheet.go rename to internal/sprite/spritesheet.go diff --git a/internal/game/sprite/spritesheet_cache.go b/internal/sprite/spritesheet_cache.go similarity index 100% rename from internal/game/sprite/spritesheet_cache.go rename to internal/sprite/spritesheet_cache.go diff --git a/main.go b/main.go index 496e105..833f532 100644 --- a/main.go +++ b/main.go @@ -4,8 +4,8 @@ import ( "log" "os" + "git.wisellama.rocks/Project-Ely/project-ely/internal" "git.wisellama.rocks/Project-Ely/project-ely/internal/channels" - "git.wisellama.rocks/Project-Ely/project-ely/internal/game" "git.wisellama.rocks/Wisellama/gopackagebase" "git.wisellama.rocks/Wisellama/gosimpleconf" ) @@ -36,7 +36,7 @@ func main() { // Run the program log.Printf("=== Starting %v ===", baseConfig.ConfigMap["game.title"]) channels.RL.Main(func() { - err = game.Run(baseConfig.Ctx, baseConfig.ConfigMap) + err = internal.Run(baseConfig.Ctx, baseConfig.ConfigMap) }) if err != nil { log.Printf("%v\n", err)