356 lines
7.6 KiB
Go
356 lines
7.6 KiB
Go
package game
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
|
|
"git.wisellama.rocks/Wisellama/carpy-breakout/pkg/breakout"
|
|
"git.wisellama.rocks/Wisellama/carpy-breakout/pkg/config"
|
|
"git.wisellama.rocks/Wisellama/carpy-breakout/pkg/globject"
|
|
"git.wisellama.rocks/Wisellama/carpy-breakout/pkg/glshader"
|
|
gl "github.com/go-gl/gl/v3.1/gles2"
|
|
"github.com/go-gl/mathgl/mgl32"
|
|
"github.com/veandco/go-sdl2/sdl"
|
|
)
|
|
|
|
type GLObject interface {
|
|
Update()
|
|
GLDraw()
|
|
GLInit(glProgram uint32)
|
|
ToggleWireframe()
|
|
}
|
|
|
|
type GameWindow struct {
|
|
SDLWindow *sdl.Window
|
|
GLContext *sdl.GLContext
|
|
GLProgram uint32
|
|
Globject []GLObject
|
|
Camera *globject.Camera
|
|
Paddle *breakout.Paddle
|
|
Ball *breakout.Ball
|
|
Targets *breakout.Targets
|
|
SideWalls *breakout.SideWalls
|
|
AABB *globject.AABB // This AABB represents the inside playable area
|
|
|
|
keystates map[sdl.Keycode]bool
|
|
mouseMoved bool
|
|
running bool
|
|
fullscreen bool
|
|
freelook bool
|
|
}
|
|
|
|
func NewGameWindow(title string) (*GameWindow, error) {
|
|
gameWindow := GameWindow{
|
|
running: true,
|
|
fullscreen: false,
|
|
freelook: false,
|
|
keystates: make(map[sdl.Keycode]bool),
|
|
}
|
|
|
|
window, err := sdl.CreateWindow(
|
|
title,
|
|
sdl.WINDOWPOS_UNDEFINED,
|
|
sdl.WINDOWPOS_UNDEFINED,
|
|
config.SDL_WINDOW_WIDTH,
|
|
config.SDL_WINDOW_HEIGHT,
|
|
config.SDL_WINDOW_FLAGS)
|
|
if err != nil {
|
|
log.Println("Failed creating SDL window")
|
|
return nil, err
|
|
}
|
|
gameWindow.SDLWindow = window
|
|
|
|
context, err := window.GLCreateContext()
|
|
if err != nil {
|
|
log.Println("Failed Creating GL context.")
|
|
return nil, err
|
|
}
|
|
gameWindow.GLContext = &context
|
|
|
|
return &gameWindow, err
|
|
}
|
|
|
|
func (w *GameWindow) GLInit(glProgram uint32) {
|
|
gl.UseProgram(glProgram)
|
|
|
|
w.GLProgram = glProgram
|
|
|
|
w.WindowProjection()
|
|
}
|
|
|
|
func (w *GameWindow) GLInitDefault() error {
|
|
glProgram, err := glshader.NewDefaultProgram()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
w.GLInit(glProgram)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (w *GameWindow) AddObject(object GLObject) {
|
|
w.Globject = append(w.Globject, object)
|
|
}
|
|
|
|
func (w *GameWindow) Destroy() {
|
|
err := w.SDLWindow.Destroy()
|
|
if err != nil {
|
|
log.Println(err)
|
|
}
|
|
|
|
sdl.GLDeleteContext(*w.GLContext)
|
|
}
|
|
|
|
func (w *GameWindow) ToggleFullscreen() {
|
|
var err error
|
|
if w.fullscreen {
|
|
err = w.SDLWindow.SetFullscreen(config.SDL_WINDOW_FLAGS)
|
|
if err != nil {
|
|
log.Printf("error setting default window flags: %v", err)
|
|
}
|
|
} else {
|
|
err = w.SDLWindow.SetFullscreen(config.SDL_FULLSCREEN_WINDOW_FLAGS)
|
|
if err != nil {
|
|
log.Printf("error setting fullscreen window flags: %v", err)
|
|
}
|
|
}
|
|
|
|
w.fullscreen = !w.fullscreen
|
|
}
|
|
|
|
func (w *GameWindow) ToggleFreelook() {
|
|
w.freelook = !w.freelook
|
|
}
|
|
|
|
func (w *GameWindow) IsFreelookOn() bool {
|
|
return w.freelook
|
|
}
|
|
|
|
func (w *GameWindow) IsRunning() bool {
|
|
return w.running
|
|
}
|
|
|
|
func (w *GameWindow) StopRunning() {
|
|
w.running = false
|
|
}
|
|
|
|
func (w *GameWindow) WindowProjection() {
|
|
width, height := w.SDLWindow.GLGetDrawableSize()
|
|
projection := mgl32.Perspective(mgl32.DegToRad(45.0), float32(width)/float32(height), 0.01, 1000.0)
|
|
glshader.SetUniformMatrix4f(w.GLProgram, "projection", &projection)
|
|
|
|
gl.Viewport(0, 0, width, height)
|
|
}
|
|
|
|
func (w *GameWindow) SetPaddle(paddle *breakout.Paddle) {
|
|
w.Paddle = paddle
|
|
}
|
|
|
|
func (w *GameWindow) SetBall(ball *breakout.Ball) {
|
|
w.Ball = ball
|
|
}
|
|
|
|
func (w *GameWindow) SetTargets(targets *breakout.Targets) {
|
|
w.Targets = targets
|
|
}
|
|
|
|
func (w *GameWindow) SetSideWalls(sidewalls *breakout.SideWalls) {
|
|
w.SideWalls = sidewalls
|
|
}
|
|
|
|
func (w *GameWindow) SetCamera(camera *globject.Camera) {
|
|
w.Camera = camera
|
|
}
|
|
|
|
func (w *GameWindow) ToggleWireframe() {
|
|
// TODO figure out concurrency
|
|
// var wg sync.WaitGroup
|
|
// for _, o := range w.Globject {
|
|
// wg.Add(1)
|
|
// go func() {
|
|
// o.ToggleWireframe()
|
|
// wg.Done()
|
|
// }()
|
|
// }
|
|
// wg.Wait()
|
|
|
|
for _, o := range w.Globject {
|
|
o.ToggleWireframe()
|
|
}
|
|
}
|
|
|
|
func (w *GameWindow) GLDraw() {
|
|
model := mgl32.Ident4()
|
|
|
|
// Probably can't do concurrent drawing due to OpenGL
|
|
for _, o := range w.Globject {
|
|
glshader.SetUniformMatrix4f(w.GLProgram, "model", &model)
|
|
o.GLDraw()
|
|
}
|
|
}
|
|
|
|
func (w *GameWindow) Update() {
|
|
// TODO figure out concurrency
|
|
// var wg sync.WaitGroup
|
|
// for _, o := range w.Globject {
|
|
// wg.Add(1)
|
|
// go func() {
|
|
// o.Update()
|
|
// wg.Done()
|
|
// }()
|
|
// }
|
|
// wg.Wait()
|
|
|
|
if w.IsFreelookOn() {
|
|
w.handleFreelookMovement()
|
|
} else if !w.mouseMoved {
|
|
w.handleGameKeyboardMovement()
|
|
}
|
|
w.mouseMoved = false
|
|
|
|
w.Camera.Update()
|
|
|
|
w.Ball.Update()
|
|
w.Ball.HandlePaddleCollision(w.Paddle)
|
|
w.Ball.HandleTargetCollisions(w.Targets)
|
|
w.Ball.HandleSideWallsCollisions(w.SideWalls)
|
|
|
|
w.Paddle.Update()
|
|
w.ensurePaddleBoundary()
|
|
}
|
|
|
|
func (w *GameWindow) HandleEvents() {
|
|
for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() {
|
|
switch e := event.(type) {
|
|
case *sdl.WindowEvent:
|
|
w.WindowProjection()
|
|
case *sdl.QuitEvent:
|
|
log.Println("Quiting")
|
|
w.StopRunning()
|
|
case *sdl.MouseMotionEvent:
|
|
w.handleMouseMovementEvent(e)
|
|
case *sdl.MouseButtonEvent:
|
|
fmt.Printf("[%d ms] MouseButton\ttype:%d\tid:%d\tx:%d\ty:%d\tbutton:%d\tstate:%d\n",
|
|
e.Timestamp, e.Type, e.Which, e.X, e.Y, e.Button, e.State)
|
|
case *sdl.MouseWheelEvent:
|
|
fmt.Printf("[%d ms] MouseWheel\ttype:%d\tid:%d\tx:%d\ty:%d\n",
|
|
e.Timestamp, e.Type, e.Which, e.X, e.Y)
|
|
case *sdl.KeyboardEvent:
|
|
w.HandleKeyboardEvent(e)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (w *GameWindow) HandleKeyboardEvent(e *sdl.KeyboardEvent) {
|
|
// Key toggles (directly use the keyboard events to something)
|
|
if e.Keysym.Sym == sdl.K_ESCAPE {
|
|
log.Println("Esc quitting")
|
|
w.StopRunning()
|
|
}
|
|
if e.Keysym.Sym == sdl.K_f {
|
|
if e.Type == sdl.KEYDOWN {
|
|
w.ToggleFullscreen()
|
|
}
|
|
}
|
|
if e.Keysym.Sym == sdl.K_l {
|
|
if e.Type == sdl.KEYDOWN {
|
|
w.ToggleWireframe()
|
|
}
|
|
}
|
|
if e.Keysym.Sym == sdl.K_k {
|
|
if e.Type == sdl.KEYDOWN {
|
|
w.ToggleFreelook()
|
|
}
|
|
}
|
|
|
|
// Key states (just set a boolean whether the key is actively being pressed)
|
|
if e.Type == sdl.KEYDOWN {
|
|
w.keystates[e.Keysym.Sym] = true
|
|
} else if e.Type == sdl.KEYUP {
|
|
w.keystates[e.Keysym.Sym] = false
|
|
}
|
|
}
|
|
|
|
func (w *GameWindow) handleFreelookMovement() {
|
|
// Forward/Back
|
|
if w.keystates[sdl.K_w] {
|
|
w.Camera.SetZVelocity(1)
|
|
} else if w.keystates[sdl.K_s] {
|
|
w.Camera.SetZVelocity(-1)
|
|
} else {
|
|
w.Camera.SetZVelocity(0)
|
|
}
|
|
|
|
// Left/Right
|
|
if w.keystates[sdl.K_d] {
|
|
w.Camera.SetXVelocity(1)
|
|
} else if w.keystates[sdl.K_a] {
|
|
w.Camera.SetXVelocity(-1)
|
|
} else {
|
|
w.Camera.SetXVelocity(0)
|
|
}
|
|
|
|
// Up/Down
|
|
if w.keystates[sdl.K_e] {
|
|
w.Camera.SetYVelocity(1)
|
|
} else if w.keystates[sdl.K_q] {
|
|
w.Camera.SetYVelocity(-1)
|
|
} else {
|
|
w.Camera.SetYVelocity(0)
|
|
}
|
|
|
|
// Spin
|
|
if w.keystates[sdl.K_x] {
|
|
w.Camera.SetRotationVelocity(1)
|
|
} else if w.keystates[sdl.K_z] {
|
|
w.Camera.SetRotationVelocity(-1)
|
|
} else {
|
|
w.Camera.SetRotationVelocity(0)
|
|
}
|
|
|
|
// Faster
|
|
if w.keystates[sdl.K_LSHIFT] {
|
|
w.Camera.SetFastMoveVelocity(2)
|
|
} else {
|
|
w.Camera.SetFastMoveVelocity(0)
|
|
}
|
|
}
|
|
|
|
func (w *GameWindow) handleMouseMovementEvent(e *sdl.MouseMotionEvent) {
|
|
if e.XRel != 0 || e.YRel != 0 {
|
|
w.mouseMoved = true
|
|
if w.IsFreelookOn() {
|
|
w.Camera.Rotate(e.XRel, e.YRel, 0)
|
|
} else {
|
|
// Paddle movement
|
|
v := float32(e.XRel) / 50.0
|
|
w.Paddle.Box.SetXVelocity(v)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (w *GameWindow) handleGameKeyboardMovement() {
|
|
if w.keystates[sdl.K_d] {
|
|
w.Paddle.Box.SetXVelocity(0.5)
|
|
} else if w.keystates[sdl.K_a] {
|
|
w.Paddle.Box.SetXVelocity(-0.5)
|
|
} else {
|
|
w.Paddle.Box.SetXVelocity(0)
|
|
}
|
|
}
|
|
|
|
func (w *GameWindow) ensurePaddleBoundary() {
|
|
b := w.Paddle.Box
|
|
offset := b.Width / 2.0
|
|
inside := w.AABB
|
|
furthestLeft := inside.BottomLeft.X() + offset
|
|
furthestRight := inside.TopRight.X() - offset
|
|
|
|
if b.Translation.X() < furthestLeft {
|
|
w.Paddle.Box.Translation[0] = furthestLeft
|
|
} else if b.Translation.X() > furthestRight {
|
|
w.Paddle.Box.Translation[0] = furthestRight
|
|
}
|
|
}
|