Reorganize code
This commit is contained in:
parent
0a58e0453a
commit
91cdbab54a
41
assets/assets.go
Normal file
41
assets/assets.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package assets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gitea.boner.be/bdnugget/goonscape/types"
|
||||||
|
rl "github.com/gen2brain/raylib-go/raylib"
|
||||||
|
)
|
||||||
|
|
||||||
|
func LoadModels() ([]types.ModelAsset, error) {
|
||||||
|
goonerModel := rl.LoadModel("resources/models/goonion.obj")
|
||||||
|
goonerTexture := rl.LoadTexture("resources/models/goonion.png")
|
||||||
|
rl.SetMaterialTexture(goonerModel.Materials, rl.MapDiffuse, goonerTexture)
|
||||||
|
|
||||||
|
coomerModel := rl.LoadModel("resources/models/coomer.obj")
|
||||||
|
coomerTexture := rl.LoadTexture("resources/models/coomer.png")
|
||||||
|
rl.SetMaterialTexture(coomerModel.Materials, rl.MapDiffuse, coomerTexture)
|
||||||
|
|
||||||
|
shrekeModel := rl.LoadModel("resources/models/shreke.obj")
|
||||||
|
shrekeTexture := rl.LoadTexture("resources/models/shreke.png")
|
||||||
|
rl.SetMaterialTexture(shrekeModel.Materials, rl.MapDiffuse, shrekeTexture)
|
||||||
|
|
||||||
|
return []types.ModelAsset{
|
||||||
|
{Model: goonerModel, Texture: goonerTexture},
|
||||||
|
{Model: coomerModel, Texture: coomerTexture},
|
||||||
|
{Model: shrekeModel, Texture: shrekeTexture},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadMusic(filename string) (rl.Music, error) {
|
||||||
|
return rl.LoadMusicStream(filename), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func UnloadModels(models []types.ModelAsset) {
|
||||||
|
for _, model := range models {
|
||||||
|
rl.UnloadModel(model.Model)
|
||||||
|
rl.UnloadTexture(model.Texture)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func UnloadMusic(music rl.Music) {
|
||||||
|
rl.UnloadMusicStream(music)
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package main
|
package game
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
@ -6,8 +6,13 @@ import (
|
|||||||
rl "github.com/gen2brain/raylib-go/raylib"
|
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) {
|
func UpdateCamera(camera *rl.Camera3D, player rl.Vector3, deltaTime float32) {
|
||||||
// Update camera based on mouse wheel
|
|
||||||
wheelMove := rl.GetMouseWheelMove()
|
wheelMove := rl.GetMouseWheelMove()
|
||||||
if wheelMove != 0 {
|
if wheelMove != 0 {
|
||||||
cameraDistance += -wheelMove * 5
|
cameraDistance += -wheelMove * 5
|
||||||
@ -19,7 +24,6 @@ func UpdateCamera(camera *rl.Camera3D, player rl.Vector3, deltaTime float32) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Orbit camera around the player using arrow keys
|
|
||||||
if rl.IsKeyDown(rl.KeyRight) {
|
if rl.IsKeyDown(rl.KeyRight) {
|
||||||
cameraYaw += 100 * deltaTime
|
cameraYaw += 100 * deltaTime
|
||||||
}
|
}
|
||||||
@ -39,16 +43,13 @@ func UpdateCamera(camera *rl.Camera3D, player rl.Vector3, deltaTime float32) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate the new camera position using spherical coordinates
|
|
||||||
cameraYawRad := float64(cameraYaw) * rl.Deg2rad
|
cameraYawRad := float64(cameraYaw) * rl.Deg2rad
|
||||||
cameraPitchRad := float64(cameraPitch) * rl.Deg2rad
|
cameraPitchRad := float64(cameraPitch) * rl.Deg2rad
|
||||||
cameraPos := rl.Vector3{
|
|
||||||
|
camera.Position = rl.Vector3{
|
||||||
X: player.X + cameraDistance*float32(math.Cos(cameraYawRad))*float32(math.Cos(cameraPitchRad)),
|
X: player.X + cameraDistance*float32(math.Cos(cameraYawRad))*float32(math.Cos(cameraPitchRad)),
|
||||||
Y: player.Y + cameraDistance*float32(math.Sin(cameraPitchRad)),
|
Y: player.Y + cameraDistance*float32(math.Sin(cameraPitchRad)),
|
||||||
Z: player.Z + cameraDistance*float32(math.Sin(cameraYawRad))*float32(math.Cos(cameraPitchRad)),
|
Z: player.Z + cameraDistance*float32(math.Sin(cameraYawRad))*float32(math.Cos(cameraPitchRad)),
|
||||||
}
|
}
|
||||||
|
camera.Target = player
|
||||||
// Update the camera's position and target
|
|
||||||
camera.Position = cameraPos
|
|
||||||
camera.Target = rl.NewVector3(player.X, player.Y, player.Z)
|
|
||||||
}
|
}
|
168
game/game.go
Normal file
168
game/game.go
Normal 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
31
game/input.go
Normal 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
|
||||||
|
}
|
@ -1,14 +1,18 @@
|
|||||||
package main
|
package game
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"gitea.boner.be/bdnugget/goonscape/types"
|
||||||
|
)
|
||||||
|
|
||||||
type Node struct {
|
type Node struct {
|
||||||
Tile Tile
|
Tile types.Tile
|
||||||
Parent *Node
|
Parent *Node
|
||||||
G, H, F float32
|
G, H, F float32
|
||||||
}
|
}
|
||||||
|
|
||||||
func FindPath(start, end Tile) []Tile {
|
func FindPath(start, end types.Tile) []types.Tile {
|
||||||
openList := []*Node{}
|
openList := []*Node{}
|
||||||
closedList := make(map[[2]int]bool)
|
closedList := make(map[[2]int]bool)
|
||||||
|
|
||||||
@ -17,7 +21,6 @@ func FindPath(start, end Tile) []Tile {
|
|||||||
openList = append(openList, startNode)
|
openList = append(openList, startNode)
|
||||||
|
|
||||||
for len(openList) > 0 {
|
for len(openList) > 0 {
|
||||||
// Find node with lowest F
|
|
||||||
current := openList[0]
|
current := openList[0]
|
||||||
currentIndex := 0
|
currentIndex := 0
|
||||||
for i, node := range openList {
|
for i, node := range openList {
|
||||||
@ -27,23 +30,20 @@ func FindPath(start, end Tile) []Tile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move current to closed list
|
|
||||||
openList = append(openList[:currentIndex], openList[currentIndex+1:]...)
|
openList = append(openList[:currentIndex], openList[currentIndex+1:]...)
|
||||||
closedList[[2]int{current.Tile.X, current.Tile.Y}] = true
|
closedList[[2]int{current.Tile.X, current.Tile.Y}] = true
|
||||||
|
|
||||||
// Check if reached the end
|
|
||||||
if current.Tile.X == end.X && current.Tile.Y == end.Y {
|
if current.Tile.X == end.X && current.Tile.Y == end.Y {
|
||||||
path := []Tile{}
|
path := []types.Tile{}
|
||||||
node := current
|
node := current
|
||||||
for node != nil {
|
for node != nil {
|
||||||
path = append([]Tile{node.Tile}, path...)
|
path = append([]types.Tile{node.Tile}, path...)
|
||||||
node = node.Parent
|
node = node.Parent
|
||||||
}
|
}
|
||||||
fmt.Printf("Path found: %v\n", path)
|
fmt.Printf("Path found: %v\n", path)
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate neighbors
|
|
||||||
neighbors := GetNeighbors(current.Tile)
|
neighbors := GetNeighbors(current.Tile)
|
||||||
for _, neighbor := range neighbors {
|
for _, neighbor := range neighbors {
|
||||||
if !neighbor.Walkable || closedList[[2]int{neighbor.X, neighbor.Y}] {
|
if !neighbor.Walkable || closedList[[2]int{neighbor.X, neighbor.Y}] {
|
||||||
@ -75,32 +75,30 @@ func FindPath(start, end Tile) []Tile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// No path found
|
|
||||||
fmt.Println("No path found")
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func heuristic(a, b Tile) float32 {
|
func heuristic(a, b types.Tile) float32 {
|
||||||
return float32(abs(a.X-b.X) + abs(a.Y-b.Y))
|
return float32(abs(a.X-b.X) + abs(a.Y-b.Y))
|
||||||
}
|
}
|
||||||
|
|
||||||
func distance(a, b Tile) float32 {
|
func distance(a, b types.Tile) float32 {
|
||||||
_ = a
|
return 1.0 // uniform cost for now
|
||||||
_ = b
|
|
||||||
return 1.0 //uniform cost for now
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetNeighbors(tile Tile) []Tile {
|
func GetNeighbors(tile types.Tile) []types.Tile {
|
||||||
directions := [][2]int{
|
directions := [][2]int{
|
||||||
{1, 0}, {-1, 0}, {0, 1}, {0, -1},
|
{1, 0}, {-1, 0}, {0, 1}, {0, -1},
|
||||||
{1, 1}, {-1, -1}, {1, -1}, {-1, 1},
|
{1, 1}, {-1, -1}, {1, -1}, {-1, 1},
|
||||||
}
|
}
|
||||||
neighbors := []Tile{}
|
neighbors := []types.Tile{}
|
||||||
|
grid := GetMapGrid()
|
||||||
for _, dir := range directions {
|
for _, dir := range directions {
|
||||||
nx, ny := tile.X+dir[0], tile.Y+dir[1]
|
nx, ny := tile.X+dir[0], tile.Y+dir[1]
|
||||||
if nx >= 0 && nx < MapWidth && ny >= 0 && ny < MapHeight {
|
if nx >= 0 && nx < types.MapWidth && ny >= 0 && ny < types.MapHeight {
|
||||||
neighbors = append(neighbors, mapGrid[nx][ny])
|
if grid[nx][ny].Walkable {
|
||||||
|
neighbors = append(neighbors, grid[nx][ny])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return neighbors
|
return neighbors
|
45
game/utils.go
Normal file
45
game/utils.go
Normal 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
39
game/world.go
Normal 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
|
||||||
|
}
|
@ -1 +1 @@
|
|||||||
Subproject commit a459e8b4a5cfa02f0ee588596dba5ce26afac39f
|
Subproject commit 4b73492ffc0824faccddd81053c08d3d7607919a
|
94
input.go
94
input.go
@ -1,94 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
pb "gitea.boner.be/bdnugget/goonserver/actions"
|
|
||||||
rl "github.com/gen2brain/raylib-go/raylib"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetTileAtMouse(camera *rl.Camera3D) (Tile, bool) {
|
|
||||||
if !rl.IsMouseButtonPressed(rl.MouseLeftButton) {
|
|
||||||
return Tile{}, false
|
|
||||||
}
|
|
||||||
mouse := rl.GetMousePosition()
|
|
||||||
ray := rl.GetMouseRay(mouse, *camera)
|
|
||||||
|
|
||||||
for x := 0; x < MapWidth; x++ {
|
|
||||||
for y := 0; y < MapHeight; y++ {
|
|
||||||
tile := mapGrid[x][y]
|
|
||||||
|
|
||||||
// Define the bounding box for each tile based on its position and height
|
|
||||||
tilePos := rl.NewVector3(float32(x*TileSize), tile.Height*TileHeight, float32(y*TileSize))
|
|
||||||
boxMin := rl.Vector3Subtract(tilePos, rl.NewVector3(TileSize/2, TileHeight/2, TileSize/2))
|
|
||||||
boxMax := rl.Vector3Add(tilePos, rl.NewVector3(TileSize/2, TileHeight/2, TileSize/2))
|
|
||||||
|
|
||||||
// Check if the ray intersects the bounding box
|
|
||||||
if RayIntersectsBox(ray, boxMin, boxMax) {
|
|
||||||
fmt.Println("Clicked:", tile.X, tile.Y)
|
|
||||||
return tile, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Tile{}, false
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandleInput(player *Player, camera *rl.Camera) {
|
|
||||||
clickedTile, clicked := GetTileAtMouse(camera)
|
|
||||||
if clicked {
|
|
||||||
path := FindPath(mapGrid[player.PosTile.X][player.PosTile.Y], clickedTile)
|
|
||||||
if path != nil {
|
|
||||||
// Exclude the first tile (current position)
|
|
||||||
if len(path) > 1 {
|
|
||||||
player.TargetPath = path[1:]
|
|
||||||
player.ActionQueue = append(player.ActionQueue, &pb.Action{
|
|
||||||
Type: pb.Action_MOVE,
|
|
||||||
X: int32(clickedTile.X),
|
|
||||||
Y: int32(clickedTile.Y),
|
|
||||||
PlayerId: player.ID,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to test ray-box intersection (slab method)
|
|
||||||
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
|
|
||||||
}
|
|
137
main.go
137
main.go
@ -3,148 +3,45 @@ package main
|
|||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
|
"gitea.boner.be/bdnugget/goonscape/game"
|
||||||
|
"gitea.boner.be/bdnugget/goonscape/network"
|
||||||
rl "github.com/gen2brain/raylib-go/raylib"
|
rl "github.com/gen2brain/raylib-go/raylib"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
cameraDistance = float32(20.0)
|
|
||||||
cameraYaw = float32(145.0)
|
|
||||||
cameraPitch = float32(45.0) // Adjusted for a more overhead view
|
|
||||||
mapGrid = InitMap()
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
rl.InitWindow(1024, 768, "GoonScape")
|
rl.InitWindow(1024, 768, "GoonScape")
|
||||||
defer rl.CloseWindow()
|
defer rl.CloseWindow()
|
||||||
rl.InitAudioDevice()
|
rl.InitAudioDevice()
|
||||||
defer rl.CloseAudioDevice()
|
defer rl.CloseAudioDevice()
|
||||||
|
|
||||||
player := Player{
|
game := game.New()
|
||||||
PosActual: rl.NewVector3(5*TileSize, 0, 5*TileSize),
|
if err := game.LoadAssets(); err != nil {
|
||||||
PosTile: mapGrid[5][5],
|
log.Fatalf("Failed to load assets: %v", err)
|
||||||
Speed: 50.0,
|
|
||||||
TargetPath: []Tile{},
|
|
||||||
}
|
}
|
||||||
|
defer game.Cleanup()
|
||||||
|
|
||||||
camera := rl.Camera3D{
|
conn, playerID, err := network.ConnectToServer()
|
||||||
Position: rl.NewVector3(0, 10, 10), // Will be updated every frame
|
|
||||||
Target: player.PosActual,
|
|
||||||
Up: rl.NewVector3(0, 1, 0), // Y is up in 3D
|
|
||||||
Fovy: 45.0,
|
|
||||||
Projection: rl.CameraPerspective,
|
|
||||||
}
|
|
||||||
|
|
||||||
conn, playerID, err := ConnectToServer()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to connect to server: %v", err)
|
log.Fatalf("Failed to connect to server: %v", err)
|
||||||
}
|
}
|
||||||
log.Printf("Player ID: %d", playerID)
|
|
||||||
player.ID = playerID
|
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
otherPlayers := make(map[int32]*Player)
|
game.Player.ID = playerID
|
||||||
|
modelIndex := int(playerID) % len(game.Models)
|
||||||
|
game.Player.Model = game.Models[modelIndex].Model
|
||||||
|
game.Player.Texture = game.Models[modelIndex].Texture
|
||||||
|
|
||||||
go HandleServerCommunication(conn, playerID, &player, otherPlayers)
|
go network.HandleServerCommunication(conn, playerID, game.Player, game.OtherPlayers)
|
||||||
|
|
||||||
models, err := LoadModels()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to load models: %v", err)
|
|
||||||
}
|
|
||||||
defer UnloadModels(models)
|
|
||||||
|
|
||||||
modelIndex := int(playerID) % len(models)
|
|
||||||
player.Model = models[modelIndex].Model
|
|
||||||
player.Texture = models[modelIndex].Texture
|
|
||||||
|
|
||||||
music, err := LoadMusic("resources/audio/GoonScape2.mp3")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to load music: %v", err)
|
|
||||||
}
|
|
||||||
defer UnloadMusic(music)
|
|
||||||
rl.PlayMusicStream(music)
|
|
||||||
rl.SetMusicVolume(music, 0.5)
|
|
||||||
|
|
||||||
|
rl.PlayMusicStream(game.Music)
|
||||||
|
rl.SetMusicVolume(game.Music, 0.5)
|
||||||
rl.SetTargetFPS(60)
|
rl.SetTargetFPS(60)
|
||||||
|
|
||||||
for !rl.WindowShouldClose() {
|
for !rl.WindowShouldClose() {
|
||||||
|
rl.UpdateMusicStream(game.Music)
|
||||||
rl.UpdateMusicStream(music)
|
|
||||||
|
|
||||||
// Time management
|
|
||||||
deltaTime := rl.GetFrameTime()
|
deltaTime := rl.GetFrameTime()
|
||||||
|
|
||||||
// Handle input
|
game.Update(deltaTime)
|
||||||
HandleInput(&player, &camera)
|
game.Render()
|
||||||
|
|
||||||
// Update player
|
|
||||||
if len(player.TargetPath) > 0 {
|
|
||||||
player.MoveTowards(player.TargetPath[0], deltaTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update camera
|
|
||||||
UpdateCamera(&camera, player.PosActual, deltaTime)
|
|
||||||
|
|
||||||
// Rendering
|
|
||||||
rl.BeginDrawing()
|
|
||||||
rl.ClearBackground(rl.RayWhite)
|
|
||||||
rl.BeginMode3D(camera)
|
|
||||||
DrawMap()
|
|
||||||
DrawPlayer(&player, player.Model)
|
|
||||||
|
|
||||||
for id, other := range otherPlayers {
|
|
||||||
if len(other.TargetPath) > 0 {
|
|
||||||
other.MoveTowards(other.TargetPath[0], deltaTime)
|
|
||||||
}
|
|
||||||
DrawPlayer(other, models[int(id)%len(models)].Model)
|
|
||||||
}
|
|
||||||
|
|
||||||
rl.EndMode3D()
|
|
||||||
rl.DrawFPS(10, 10)
|
|
||||||
rl.EndDrawing()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadModels() ([]struct {
|
|
||||||
Model rl.Model
|
|
||||||
Texture rl.Texture2D
|
|
||||||
}, error) {
|
|
||||||
goonerModel := rl.LoadModel("resources/models/goonion.obj")
|
|
||||||
goonerTexture := rl.LoadTexture("resources/models/goonion.png")
|
|
||||||
rl.SetMaterialTexture(goonerModel.Materials, rl.MapDiffuse, goonerTexture)
|
|
||||||
|
|
||||||
coomerModel := rl.LoadModel("resources/models/coomer.obj")
|
|
||||||
coomerTexture := rl.LoadTexture("resources/models/coomer.png")
|
|
||||||
rl.SetMaterialTexture(coomerModel.Materials, rl.MapDiffuse, coomerTexture)
|
|
||||||
|
|
||||||
shrekeModel := rl.LoadModel("resources/models/shreke.obj")
|
|
||||||
shrekeTexture := rl.LoadTexture("resources/models/shreke.png")
|
|
||||||
rl.SetMaterialTexture(shrekeModel.Materials, rl.MapDiffuse, shrekeTexture)
|
|
||||||
|
|
||||||
return []struct {
|
|
||||||
Model rl.Model
|
|
||||||
Texture rl.Texture2D
|
|
||||||
}{
|
|
||||||
{Model: goonerModel, Texture: goonerTexture},
|
|
||||||
{Model: coomerModel, Texture: coomerTexture},
|
|
||||||
{Model: shrekeModel, Texture: shrekeTexture},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func UnloadModels(models []struct {
|
|
||||||
Model rl.Model
|
|
||||||
Texture rl.Texture2D
|
|
||||||
}) {
|
|
||||||
for _, model := range models {
|
|
||||||
rl.UnloadModel(model.Model)
|
|
||||||
rl.UnloadTexture(model.Texture)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func LoadMusic(filename string) (rl.Music, error) {
|
|
||||||
music := rl.LoadMusicStream(filename)
|
|
||||||
return music, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func UnloadMusic(music rl.Music) {
|
|
||||||
rl.UnloadMusicStream(music)
|
|
||||||
}
|
|
||||||
|
48
map.go
48
map.go
@ -1,48 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import rl "github.com/gen2brain/raylib-go/raylib"
|
|
||||||
|
|
||||||
// Initialize the map with some height data
|
|
||||||
func InitMap() [][]Tile {
|
|
||||||
mapGrid := make([][]Tile, MapWidth)
|
|
||||||
for x := 0; x < MapWidth; x++ {
|
|
||||||
mapGrid[x] = make([]Tile, MapHeight)
|
|
||||||
for y := 0; y < MapHeight; y++ {
|
|
||||||
mapGrid[x][y] = Tile{
|
|
||||||
X: x,
|
|
||||||
Y: y,
|
|
||||||
Height: 1.0 + float32(x%5), // Example height
|
|
||||||
Walkable: true, // Set to false for obstacles
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mapGrid
|
|
||||||
}
|
|
||||||
|
|
||||||
func DrawMap() {
|
|
||||||
for x := 0; x < MapWidth; x++ {
|
|
||||||
for y := 0; y < MapHeight; y++ {
|
|
||||||
tile := mapGrid[x][y]
|
|
||||||
// Interpolate height between adjacent tiles for a smoother landscape
|
|
||||||
height := tile.Height
|
|
||||||
if x > 0 {
|
|
||||||
height += mapGrid[x-1][y].Height
|
|
||||||
}
|
|
||||||
if y > 0 {
|
|
||||||
height += mapGrid[x][y-1].Height
|
|
||||||
}
|
|
||||||
if x > 0 && y > 0 {
|
|
||||||
height += mapGrid[x-1][y-1].Height
|
|
||||||
}
|
|
||||||
height /= 4.0
|
|
||||||
// Draw each tile as a 3D cube based on its height
|
|
||||||
tilePos := rl.Vector3{
|
|
||||||
X: float32(x * TileSize), // X-axis for horizontal position
|
|
||||||
Y: height * TileHeight, // Y-axis for height (Z in 3D is Y here)
|
|
||||||
Z: float32(y * TileSize), // Z-axis for depth (Y in 3D is Z here)
|
|
||||||
}
|
|
||||||
color := rl.Color{R: uint8(height * 25), G: 100, B: 100, A: 64}
|
|
||||||
rl.DrawCube(tilePos, TileSize, TileHeight, TileSize, color) // Draw a cube representing the tile
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,24 +1,23 @@
|
|||||||
package main
|
package network
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"gitea.boner.be/bdnugget/goonscape/types"
|
||||||
pb "gitea.boner.be/bdnugget/goonserver/actions"
|
pb "gitea.boner.be/bdnugget/goonserver/actions"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ConnectToServer() (net.Conn, int32, error) {
|
func ConnectToServer() (net.Conn, int32, error) {
|
||||||
// Attempt to connect to the server
|
conn, err := net.Dial("tcp", types.ServerAddr)
|
||||||
conn, err := net.Dial("tcp", serverAddr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to dial server: %v", err)
|
log.Printf("Failed to dial server: %v", err)
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("Connected to server. Waiting for player ID...")
|
log.Println("Connected to server. Waiting for player ID...")
|
||||||
// Buffer for incoming server message
|
|
||||||
buf := make([]byte, 1024)
|
buf := make([]byte, 1024)
|
||||||
n, err := conn.Read(buf)
|
n, err := conn.Read(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -26,9 +25,6 @@ func ConnectToServer() (net.Conn, int32, error) {
|
|||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Received data: %x", buf[:n])
|
|
||||||
|
|
||||||
// Unmarshal server message to extract the player ID
|
|
||||||
var response pb.ServerMessage
|
var response pb.ServerMessage
|
||||||
if err := proto.Unmarshal(buf[:n], &response); err != nil {
|
if err := proto.Unmarshal(buf[:n], &response); err != nil {
|
||||||
log.Printf("Failed to unmarshal server response: %v", err)
|
log.Printf("Failed to unmarshal server response: %v", err)
|
||||||
@ -40,33 +36,28 @@ func ConnectToServer() (net.Conn, int32, error) {
|
|||||||
return conn, playerID, nil
|
return conn, playerID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func HandleServerCommunication(conn net.Conn, playerID int32, player *Player, otherPlayers map[int32]*Player) {
|
func HandleServerCommunication(conn net.Conn, playerID int32, player *types.Player, otherPlayers map[int32]*types.Player) {
|
||||||
buf := make([]byte, 4096)
|
buf := make([]byte, 4096)
|
||||||
|
|
||||||
// Ticker for sending actions
|
actionTicker := time.NewTicker(types.ClientTickRate)
|
||||||
actionTicker := time.NewTicker(ClientTickRate)
|
|
||||||
defer actionTicker.Stop()
|
defer actionTicker.Stop()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for range actionTicker.C {
|
for range actionTicker.C {
|
||||||
player.Lock()
|
player.Lock()
|
||||||
if len(player.ActionQueue) > 0 {
|
if len(player.ActionQueue) > 0 {
|
||||||
// Bundle all actions that occurred during this tick
|
|
||||||
actions := make([]*pb.Action, len(player.ActionQueue))
|
actions := make([]*pb.Action, len(player.ActionQueue))
|
||||||
copy(actions, player.ActionQueue) // Copy to avoid holding lock while sending
|
copy(actions, player.ActionQueue)
|
||||||
|
|
||||||
// Create a batch message
|
|
||||||
batch := &pb.ActionBatch{
|
batch := &pb.ActionBatch{
|
||||||
PlayerId: playerID,
|
PlayerId: playerID,
|
||||||
Actions: actions,
|
Actions: actions,
|
||||||
Tick: player.CurrentTick,
|
Tick: player.CurrentTick,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear the action queue after copying
|
|
||||||
player.ActionQueue = player.ActionQueue[:0]
|
player.ActionQueue = player.ActionQueue[:0]
|
||||||
player.Unlock()
|
player.Unlock()
|
||||||
|
|
||||||
// Serialize and send the batch
|
|
||||||
data, err := proto.Marshal(batch)
|
data, err := proto.Marshal(batch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to marshal action batch: %v", err)
|
log.Printf("Failed to marshal action batch: %v", err)
|
||||||
@ -83,7 +74,6 @@ func HandleServerCommunication(conn net.Conn, playerID int32, player *Player, ot
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Main loop to handle receiving updates from the server
|
|
||||||
for {
|
for {
|
||||||
n, err := conn.Read(buf)
|
n, err := conn.Read(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -97,14 +87,11 @@ func HandleServerCommunication(conn net.Conn, playerID int32, player *Player, ot
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update game state with received data
|
|
||||||
player.Lock()
|
player.Lock()
|
||||||
player.CurrentTick = serverMessage.CurrentTick
|
player.CurrentTick = serverMessage.CurrentTick
|
||||||
|
|
||||||
// Check for desync
|
|
||||||
tickDiff := serverMessage.CurrentTick - player.CurrentTick
|
tickDiff := serverMessage.CurrentTick - player.CurrentTick
|
||||||
if tickDiff > MaxTickDesync {
|
if tickDiff > types.MaxTickDesync {
|
||||||
// Force resync if we're too far behind
|
|
||||||
for _, state := range serverMessage.Players {
|
for _, state := range serverMessage.Players {
|
||||||
if state.PlayerId == playerID {
|
if state.PlayerId == playerID {
|
||||||
player.ForceResync(state)
|
player.ForceResync(state)
|
||||||
@ -114,16 +101,15 @@ func HandleServerCommunication(conn net.Conn, playerID int32, player *Player, ot
|
|||||||
}
|
}
|
||||||
player.Unlock()
|
player.Unlock()
|
||||||
|
|
||||||
// Update other players
|
|
||||||
for _, state := range serverMessage.Players {
|
for _, state := range serverMessage.Players {
|
||||||
if state.PlayerId == playerID {
|
if state.PlayerId == playerID {
|
||||||
continue // Skip self
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if otherPlayer, exists := otherPlayers[state.PlayerId]; exists {
|
if otherPlayer, exists := otherPlayers[state.PlayerId]; exists {
|
||||||
otherPlayer.UpdatePosition(state, ServerTickRate)
|
otherPlayer.UpdatePosition(state, types.ServerTickRate)
|
||||||
} else {
|
} else {
|
||||||
otherPlayers[state.PlayerId] = NewPlayer(state)
|
otherPlayers[state.PlayerId] = types.NewPlayer(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
101
player.go
101
player.go
@ -1,101 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
pb "gitea.boner.be/bdnugget/goonserver/actions"
|
|
||||||
rl "github.com/gen2brain/raylib-go/raylib"
|
|
||||||
)
|
|
||||||
|
|
||||||
func DrawPlayer(player *Player, model rl.Model) {
|
|
||||||
// Draw the player based on its actual position (PosActual) and current tile height
|
|
||||||
playerPos := rl.Vector3{
|
|
||||||
X: player.PosActual.X,
|
|
||||||
Y: mapGrid[player.PosTile.X][player.PosTile.Y].Height*TileHeight + 16.0,
|
|
||||||
Z: player.PosActual.Z,
|
|
||||||
}
|
|
||||||
|
|
||||||
rl.DrawModel(model, playerPos, 16, rl.White)
|
|
||||||
|
|
||||||
// Draw highlight around target tile
|
|
||||||
if len(player.TargetPath) > 0 {
|
|
||||||
targetTile := player.TargetPath[len(player.TargetPath)-1] // last tile in the slice
|
|
||||||
targetPos := rl.Vector3{
|
|
||||||
X: float32(targetTile.X * TileSize),
|
|
||||||
Y: mapGrid[targetTile.X][targetTile.Y].Height * TileHeight,
|
|
||||||
Z: float32(targetTile.Y * TileSize),
|
|
||||||
}
|
|
||||||
rl.DrawCubeWires(targetPos, TileSize, TileHeight, TileSize, rl.Green)
|
|
||||||
|
|
||||||
nextTile := player.TargetPath[0] // first tile in the slice
|
|
||||||
nextPos := rl.Vector3{
|
|
||||||
X: float32(nextTile.X * TileSize),
|
|
||||||
Y: mapGrid[nextTile.X][nextTile.Y].Height * TileHeight,
|
|
||||||
Z: float32(nextTile.Y * TileSize),
|
|
||||||
}
|
|
||||||
rl.DrawCubeWires(nextPos, TileSize, TileHeight, TileSize, rl.Yellow)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (player *Player) MoveTowards(target Tile, deltaTime float32) {
|
|
||||||
// Calculate the direction vector to the target tile
|
|
||||||
targetPos := rl.Vector3{
|
|
||||||
X: float32(target.X * TileSize),
|
|
||||||
Y: mapGrid[target.X][target.Y].Height * TileHeight,
|
|
||||||
Z: float32(target.Y * TileSize),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate direction and normalize it for smooth movement
|
|
||||||
direction := rl.Vector3Subtract(targetPos, player.PosActual)
|
|
||||||
distance := rl.Vector3Length(direction)
|
|
||||||
if distance > 0 {
|
|
||||||
direction = rl.Vector3Scale(direction, player.Speed*deltaTime/distance)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move the player towards the target tile
|
|
||||||
if distance > 1.0 {
|
|
||||||
player.PosActual = rl.Vector3Add(player.PosActual, direction)
|
|
||||||
} else {
|
|
||||||
// Snap to the target tile when close enough
|
|
||||||
player.PosActual = targetPos
|
|
||||||
player.PosTile = target // Update player's tile
|
|
||||||
if len(player.TargetPath) > 1 {
|
|
||||||
player.TargetPath = player.TargetPath[1:] // Move to next tile in path if any
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Player) UpdatePosition(state *pb.PlayerState, tickRate time.Duration) {
|
|
||||||
targetTile := Tile{X: int(state.X), Y: int(state.Y)}
|
|
||||||
if p.PosTile != targetTile {
|
|
||||||
p.PosTile = targetTile
|
|
||||||
p.LastUpdateTime = time.Now()
|
|
||||||
p.InterpolationProgress = 0
|
|
||||||
p.TargetPath = []Tile{targetTile}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Player) ForceResync(state *pb.PlayerState) {
|
|
||||||
p.PosTile = Tile{X: int(state.X), Y: int(state.Y)}
|
|
||||||
p.PosActual = rl.Vector3{
|
|
||||||
X: float32(state.X * TileSize),
|
|
||||||
Y: float32(state.Y * TileHeight),
|
|
||||||
Z: float32(state.Y * TileSize),
|
|
||||||
}
|
|
||||||
p.TargetPath = nil
|
|
||||||
p.ActionQueue = nil
|
|
||||||
p.InterpolationProgress = 1.0
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPlayer(state *pb.PlayerState) *Player {
|
|
||||||
return &Player{
|
|
||||||
PosActual: rl.Vector3{
|
|
||||||
X: float32(state.X * TileSize),
|
|
||||||
Y: float32(state.Y * TileHeight),
|
|
||||||
Z: float32(state.Y * TileSize),
|
|
||||||
},
|
|
||||||
PosTile: Tile{X: int(state.X), Y: int(state.Y)},
|
|
||||||
Speed: 50.0,
|
|
||||||
ID: state.PlayerId,
|
|
||||||
}
|
|
||||||
}
|
|
76
types/player.go
Normal file
76
types/player.go
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
pb "gitea.boner.be/bdnugget/goonserver/actions"
|
||||||
|
rl "github.com/gen2brain/raylib-go/raylib"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p *Player) MoveTowards(target Tile, deltaTime float32, mapGrid [][]Tile) {
|
||||||
|
p.Lock()
|
||||||
|
defer p.Unlock()
|
||||||
|
|
||||||
|
targetPos := rl.Vector3{
|
||||||
|
X: float32(target.X * TileSize),
|
||||||
|
Y: mapGrid[target.X][target.Y].Height * TileHeight,
|
||||||
|
Z: float32(target.Y * TileSize),
|
||||||
|
}
|
||||||
|
|
||||||
|
direction := rl.Vector3Subtract(targetPos, p.PosActual)
|
||||||
|
distance := rl.Vector3Length(direction)
|
||||||
|
if distance > 0 {
|
||||||
|
direction = rl.Vector3Scale(direction, p.Speed*deltaTime/distance)
|
||||||
|
}
|
||||||
|
|
||||||
|
if distance > 1.0 {
|
||||||
|
p.PosActual = rl.Vector3Add(p.PosActual, direction)
|
||||||
|
} else {
|
||||||
|
p.PosActual = targetPos
|
||||||
|
p.PosTile = target
|
||||||
|
if len(p.TargetPath) > 1 {
|
||||||
|
p.TargetPath = p.TargetPath[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPlayer(state *pb.PlayerState) *Player {
|
||||||
|
return &Player{
|
||||||
|
PosActual: rl.Vector3{
|
||||||
|
X: float32(state.X * TileSize),
|
||||||
|
Y: float32(state.Y * TileHeight),
|
||||||
|
Z: float32(state.Y * TileSize),
|
||||||
|
},
|
||||||
|
PosTile: Tile{X: int(state.X), Y: int(state.Y)},
|
||||||
|
Speed: 50.0,
|
||||||
|
ID: state.PlayerId,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Player) UpdatePosition(state *pb.PlayerState, tickRate time.Duration) {
|
||||||
|
p.Lock()
|
||||||
|
defer p.Unlock()
|
||||||
|
|
||||||
|
targetTile := Tile{X: int(state.X), Y: int(state.Y)}
|
||||||
|
if p.PosTile != targetTile {
|
||||||
|
p.PosTile = targetTile
|
||||||
|
p.LastUpdateTime = time.Now()
|
||||||
|
p.InterpolationProgress = 0
|
||||||
|
p.TargetPath = []Tile{targetTile}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Player) ForceResync(state *pb.PlayerState) {
|
||||||
|
p.Lock()
|
||||||
|
defer p.Unlock()
|
||||||
|
|
||||||
|
p.PosTile = Tile{X: int(state.X), Y: int(state.Y)}
|
||||||
|
p.PosActual = rl.Vector3{
|
||||||
|
X: float32(state.X * TileSize),
|
||||||
|
Y: float32(state.Y * TileHeight),
|
||||||
|
Z: float32(state.Y * TileSize),
|
||||||
|
}
|
||||||
|
p.TargetPath = nil
|
||||||
|
p.ActionQueue = nil
|
||||||
|
p.InterpolationProgress = 1.0
|
||||||
|
}
|
@ -1,11 +1,11 @@
|
|||||||
package main
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
pb "gitea.boner.be/bdnugget/goonserver/actions"
|
pb "gitea.boner.be/bdnugget/goonserver/actions"
|
||||||
rl "github.com/gen2brain/raylib-go/raylib"
|
rl "github.com/gen2brain/raylib-go/raylib"
|
||||||
|
|
||||||
"sync"
|
|
||||||
time "time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Tile struct {
|
type Tile struct {
|
||||||
@ -28,3 +28,21 @@ type Player struct {
|
|||||||
LastUpdateTime time.Time
|
LastUpdateTime time.Time
|
||||||
InterpolationProgress float32
|
InterpolationProgress float32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ModelAsset struct {
|
||||||
|
Model rl.Model
|
||||||
|
Texture rl.Texture2D
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
MapWidth = 50
|
||||||
|
MapHeight = 50
|
||||||
|
TileSize = 32
|
||||||
|
TileHeight = 2.0
|
||||||
|
|
||||||
|
// RuneScape-style tick rate (600ms)
|
||||||
|
ServerTickRate = 600 * time.Millisecond
|
||||||
|
ClientTickRate = 50 * time.Millisecond
|
||||||
|
MaxTickDesync = 5
|
||||||
|
ServerAddr = "localhost:6969"
|
||||||
|
)
|
Loading…
x
Reference in New Issue
Block a user