Reorganize code

This commit is contained in:
2025-01-13 11:10:48 +01:00
parent 0a58e0453a
commit 91cdbab54a
16 changed files with 480 additions and 424 deletions

55
game/camera.go Normal file
View File

@ -0,0 +1,55 @@
package game
import (
"math"
rl "github.com/gen2brain/raylib-go/raylib"
)
var (
cameraDistance = float32(20.0)
cameraYaw = float32(145.0)
cameraPitch = float32(45.0)
)
func UpdateCamera(camera *rl.Camera3D, player rl.Vector3, deltaTime float32) {
wheelMove := rl.GetMouseWheelMove()
if wheelMove != 0 {
cameraDistance += -wheelMove * 5
if cameraDistance < 10 {
cameraDistance = 10
}
if cameraDistance > 250 {
cameraDistance = 250
}
}
if rl.IsKeyDown(rl.KeyRight) {
cameraYaw += 100 * deltaTime
}
if rl.IsKeyDown(rl.KeyLeft) {
cameraYaw -= 100 * deltaTime
}
if rl.IsKeyDown(rl.KeyUp) {
cameraPitch -= 50 * deltaTime
if cameraPitch < 20 {
cameraPitch = 20
}
}
if rl.IsKeyDown(rl.KeyDown) {
cameraPitch += 50 * deltaTime
if cameraPitch > 85 {
cameraPitch = 85
}
}
cameraYawRad := float64(cameraYaw) * rl.Deg2rad
cameraPitchRad := float64(cameraPitch) * rl.Deg2rad
camera.Position = rl.Vector3{
X: player.X + cameraDistance*float32(math.Cos(cameraYawRad))*float32(math.Cos(cameraPitchRad)),
Y: player.Y + cameraDistance*float32(math.Sin(cameraPitchRad)),
Z: player.Z + cameraDistance*float32(math.Sin(cameraYawRad))*float32(math.Cos(cameraPitchRad)),
}
camera.Target = player
}

168
game/game.go Normal file
View File

@ -0,0 +1,168 @@
package game
import (
"gitea.boner.be/bdnugget/goonscape/assets"
"gitea.boner.be/bdnugget/goonscape/types"
pb "gitea.boner.be/bdnugget/goonserver/actions"
rl "github.com/gen2brain/raylib-go/raylib"
)
type Game struct {
Player *types.Player
OtherPlayers map[int32]*types.Player
Camera rl.Camera3D
Models []types.ModelAsset
Music rl.Music
}
func New() *Game {
InitWorld()
game := &Game{
Player: &types.Player{
PosActual: rl.NewVector3(5*types.TileSize, 0, 5*types.TileSize),
PosTile: GetTile(5, 5),
Speed: 50.0,
TargetPath: []types.Tile{},
},
OtherPlayers: make(map[int32]*types.Player),
Camera: rl.Camera3D{
Position: rl.NewVector3(0, 10, 10),
Target: rl.NewVector3(0, 0, 0),
Up: rl.NewVector3(0, 1, 0),
Fovy: 45.0,
Projection: rl.CameraPerspective,
},
}
return game
}
func (g *Game) LoadAssets() error {
var err error
g.Models, err = assets.LoadModels()
if err != nil {
return err
}
g.Music, err = assets.LoadMusic("resources/audio/GoonScape2.mp3")
if err != nil {
return err
}
return nil
}
func (g *Game) Update(deltaTime float32) {
g.HandleInput()
if len(g.Player.TargetPath) > 0 {
g.Player.MoveTowards(g.Player.TargetPath[0], deltaTime, GetMapGrid())
}
for _, other := range g.OtherPlayers {
if len(other.TargetPath) > 0 {
other.MoveTowards(other.TargetPath[0], deltaTime, GetMapGrid())
}
}
UpdateCamera(&g.Camera, g.Player.PosActual, deltaTime)
}
func (g *Game) DrawMap() {
for x := 0; x < types.MapWidth; x++ {
for y := 0; y < types.MapHeight; y++ {
height := GetTileHeight(x, y)
// Interpolate height for smoother landscape
if x > 0 {
height += GetTileHeight(x-1, y)
}
if y > 0 {
height += GetTileHeight(x, y-1)
}
if x > 0 && y > 0 {
height += GetTileHeight(x-1, y-1)
}
height /= 4.0
tilePos := rl.Vector3{
X: float32(x * types.TileSize),
Y: height * types.TileHeight,
Z: float32(y * types.TileSize),
}
color := rl.Color{R: uint8(height * 25), G: 100, B: 100, A: 64}
rl.DrawCube(tilePos, types.TileSize, types.TileHeight, types.TileSize, color)
}
}
}
func (g *Game) DrawPlayer(player *types.Player, model rl.Model) {
player.Lock()
defer player.Unlock()
grid := GetMapGrid()
playerPos := rl.Vector3{
X: player.PosActual.X,
Y: grid[player.PosTile.X][player.PosTile.Y].Height*types.TileHeight + 16.0,
Z: player.PosActual.Z,
}
rl.DrawModel(model, playerPos, 16, rl.White)
if len(player.TargetPath) > 0 {
targetTile := player.TargetPath[len(player.TargetPath)-1]
targetPos := rl.Vector3{
X: float32(targetTile.X * types.TileSize),
Y: grid[targetTile.X][targetTile.Y].Height * types.TileHeight,
Z: float32(targetTile.Y * types.TileSize),
}
rl.DrawCubeWires(targetPos, types.TileSize, types.TileHeight, types.TileSize, rl.Green)
nextTile := player.TargetPath[0]
nextPos := rl.Vector3{
X: float32(nextTile.X * types.TileSize),
Y: grid[nextTile.X][nextTile.Y].Height * types.TileHeight,
Z: float32(nextTile.Y * types.TileSize),
}
rl.DrawCubeWires(nextPos, types.TileSize, types.TileHeight, types.TileSize, rl.Yellow)
}
}
func (g *Game) Render() {
rl.BeginDrawing()
rl.ClearBackground(rl.RayWhite)
rl.BeginMode3D(g.Camera)
g.DrawMap()
g.DrawPlayer(g.Player, g.Player.Model)
for id, other := range g.OtherPlayers {
g.DrawPlayer(other, g.Models[int(id)%len(g.Models)].Model)
}
rl.EndMode3D()
rl.DrawFPS(10, 10)
rl.EndDrawing()
}
func (g *Game) Cleanup() {
assets.UnloadModels(g.Models)
assets.UnloadMusic(g.Music)
}
func (g *Game) HandleInput() {
clickedTile, clicked := g.GetTileAtMouse()
if clicked {
path := FindPath(GetTile(g.Player.PosTile.X, g.Player.PosTile.Y), clickedTile)
if path != nil && len(path) > 1 {
g.Player.Lock()
g.Player.TargetPath = path[1:]
g.Player.ActionQueue = append(g.Player.ActionQueue, &pb.Action{
Type: pb.Action_MOVE,
X: int32(clickedTile.X),
Y: int32(clickedTile.Y),
PlayerId: g.Player.ID,
})
g.Player.Unlock()
}
}
}

31
game/input.go Normal file
View File

@ -0,0 +1,31 @@
package game
import (
"fmt"
"gitea.boner.be/bdnugget/goonscape/types"
rl "github.com/gen2brain/raylib-go/raylib"
)
func (g *Game) GetTileAtMouse() (types.Tile, bool) {
if !rl.IsMouseButtonPressed(rl.MouseLeftButton) {
return types.Tile{}, false
}
mouse := rl.GetMousePosition()
ray := rl.GetMouseRay(mouse, g.Camera)
for x := 0; x < types.MapWidth; x++ {
for y := 0; y < types.MapHeight; y++ {
tile := GetTile(x, y)
tilePos := rl.NewVector3(float32(x*types.TileSize), tile.Height*types.TileHeight, float32(y*types.TileSize))
boxMin := rl.Vector3Subtract(tilePos, rl.NewVector3(types.TileSize/2, types.TileHeight/2, types.TileSize/2))
boxMax := rl.Vector3Add(tilePos, rl.NewVector3(types.TileSize/2, types.TileHeight/2, types.TileSize/2))
if RayIntersectsBox(ray, boxMin, boxMax) {
fmt.Printf("Clicked: %d, %d\n", tile.X, tile.Y)
return tile, true
}
}
}
return types.Tile{}, false
}

112
game/pathfinding.go Normal file
View File

@ -0,0 +1,112 @@
package game
import (
"fmt"
"gitea.boner.be/bdnugget/goonscape/types"
)
type Node struct {
Tile types.Tile
Parent *Node
G, H, F float32
}
func FindPath(start, end types.Tile) []types.Tile {
openList := []*Node{}
closedList := make(map[[2]int]bool)
startNode := &Node{Tile: start, G: 0, H: heuristic(start, end)}
startNode.F = startNode.G + startNode.H
openList = append(openList, startNode)
for len(openList) > 0 {
current := openList[0]
currentIndex := 0
for i, node := range openList {
if node.F < current.F {
current = node
currentIndex = i
}
}
openList = append(openList[:currentIndex], openList[currentIndex+1:]...)
closedList[[2]int{current.Tile.X, current.Tile.Y}] = true
if current.Tile.X == end.X && current.Tile.Y == end.Y {
path := []types.Tile{}
node := current
for node != nil {
path = append([]types.Tile{node.Tile}, path...)
node = node.Parent
}
fmt.Printf("Path found: %v\n", path)
return path
}
neighbors := GetNeighbors(current.Tile)
for _, neighbor := range neighbors {
if !neighbor.Walkable || closedList[[2]int{neighbor.X, neighbor.Y}] {
continue
}
tentativeG := current.G + distance(current.Tile, neighbor)
inOpen := false
var existingNode *Node
for _, node := range openList {
if node.Tile.X == neighbor.X && node.Tile.Y == neighbor.Y {
existingNode = node
inOpen = true
break
}
}
if !inOpen || tentativeG < existingNode.G {
newNode := &Node{
Tile: neighbor,
Parent: current,
G: tentativeG,
H: heuristic(neighbor, end),
}
newNode.F = newNode.G + newNode.H
if !inOpen {
openList = append(openList, newNode)
}
}
}
}
return nil
}
func heuristic(a, b types.Tile) float32 {
return float32(abs(a.X-b.X) + abs(a.Y-b.Y))
}
func distance(a, b types.Tile) float32 {
return 1.0 // uniform cost for now
}
func GetNeighbors(tile types.Tile) []types.Tile {
directions := [][2]int{
{1, 0}, {-1, 0}, {0, 1}, {0, -1},
{1, 1}, {-1, -1}, {1, -1}, {-1, 1},
}
neighbors := []types.Tile{}
grid := GetMapGrid()
for _, dir := range directions {
nx, ny := tile.X+dir[0], tile.Y+dir[1]
if nx >= 0 && nx < types.MapWidth && ny >= 0 && ny < types.MapHeight {
if grid[nx][ny].Walkable {
neighbors = append(neighbors, grid[nx][ny])
}
}
}
return neighbors
}
func abs(x int) int {
if x < 0 {
return -x
}
return x
}

45
game/utils.go Normal file
View File

@ -0,0 +1,45 @@
package game
import (
rl "github.com/gen2brain/raylib-go/raylib"
)
func RayIntersectsBox(ray rl.Ray, boxMin, boxMax rl.Vector3) bool {
tmin := (boxMin.X - ray.Position.X) / ray.Direction.X
tmax := (boxMax.X - ray.Position.X) / ray.Direction.X
if tmin > tmax {
tmin, tmax = tmax, tmin
}
tymin := (boxMin.Z - ray.Position.Z) / ray.Direction.Z
tymax := (boxMax.Z - ray.Position.Z) / ray.Direction.Z
if tymin > tymax {
tymin, tymax = tymax, tymin
}
if (tmin > tymax) || (tymin > tmax) {
return false
}
if tymin > tmin {
tmin = tymin
}
if tymax < tmax {
tmax = tymax
}
tzmin := (boxMin.Y - ray.Position.Y) / ray.Direction.Y
tzmax := (boxMax.Y - ray.Position.Y) / ray.Direction.Y
if tzmin > tzmax {
tzmin, tzmax = tzmax, tzmin
}
if (tmin > tzmax) || (tzmin > tmax) {
return false
}
return true
}

39
game/world.go Normal file
View File

@ -0,0 +1,39 @@
package game
import (
"gitea.boner.be/bdnugget/goonscape/types"
)
var (
mapGrid [][]types.Tile
)
func GetMapGrid() [][]types.Tile {
return mapGrid
}
func InitWorld() {
mapGrid = make([][]types.Tile, types.MapWidth)
for x := 0; x < types.MapWidth; x++ {
mapGrid[x] = make([]types.Tile, types.MapHeight)
for y := 0; y < types.MapHeight; y++ {
mapGrid[x][y] = types.Tile{
X: x,
Y: y,
Height: 1.0 + float32(x%5),
Walkable: true,
}
}
}
}
func GetTile(x, y int) types.Tile {
if x >= 0 && x < types.MapWidth && y >= 0 && y < types.MapHeight {
return mapGrid[x][y]
}
return types.Tile{}
}
func GetTileHeight(x, y int) float32 {
return mapGrid[x][y].Height
}