diff --git a/core/constants.go b/core/constants.go new file mode 100644 index 0000000..c10bdcf --- /dev/null +++ b/core/constants.go @@ -0,0 +1,8 @@ +package utils + +const ( + MapWidth = 100 + MapHeight = 100 + TileSize = 32 + TileHeight = 2.0 +) diff --git a/core/types.go b/core/types.go new file mode 100644 index 0000000..d4b585b --- /dev/null +++ b/core/types.go @@ -0,0 +1 @@ +package utils diff --git a/main.go b/main.go index 76c67b1..69fa296 100644 --- a/main.go +++ b/main.go @@ -1,22 +1,37 @@ package main import ( + "fmt" "math" rl "github.com/gen2brain/raylib-go/raylib" ) +const ( + MapWidth = 100 + MapHeight = 100 + TileSize = 32 + TileHeight = 2.0 +) + +var ( + cameraDistance = float32(64.0) + cameraYaw = float32(45.0) + cameraPitch = float32(30.0) +) + type Tile struct { X, Y int Height float32 Walkable bool } -const ( - MapWidth = 100 - MapHeight = 100 - TileSize = 32 -) +type Player struct { + PosActual rl.Vector3 + PosTile Tile + TargetPath []Tile + Speed float32 +} // Initialize the map with some height data func InitMap() [][]Tile { @@ -27,53 +42,241 @@ func InitMap() [][]Tile { mapGrid[x][y] = Tile{ X: x, Y: y, - Height: float32((x + y) % 10), // Example height - Walkable: true, // Set to false for obstacles + Height: 1.0 + float32(x%5), //float32((x + y) % 10), // Example height + Walkable: true, // Set to false for obstacles } } } return mapGrid } -type Player struct { - X, Y int // Current tile position - PosX, PosY float32 // Actual position for smooth movement - TargetPath []Tile - Speed float32 -} - func DrawMap(mapGrid [][]Tile) { for x := 0; x < MapWidth; x++ { for y := 0; y < MapHeight; y++ { tile := mapGrid[x][y] - // Simple top-down view: color based on height - color := rl.Color{R: uint8(tile.Height * 25), G: 100, B: 100, A: 255} - rl.DrawRectangle(int32(tile.X*TileSize), int32(tile.Y*TileSize-int(tile.Height)), TileSize, TileSize, color) + // 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 } } } func DrawPlayer(player Player, mapGrid [][]Tile) { // Get current tile height - currentTile := mapGrid[player.X][player.Y] - // Draw player at PosX, PosY adjusted by height - rl.DrawCircle(int32(player.PosX), int32(player.PosY-currentTile.Height), 10, rl.Red) + currentTile := mapGrid[player.PosTile.X][player.PosTile.Y] + // Draw player cube with height from the map + playerPos := rl.Vector3{ + X: float32(player.PosTile.X * TileSize), + Y: currentTile.Height * TileHeight, + Z: float32(player.PosTile.Y * TileSize), + } + rl.DrawCube(playerPos, 16, 16, 16, rl.Green) // Draw player cube } -func GetTileAtMouse(mapGrid [][]Tile) (Tile, bool) { +func GetTileAtMouse(mapGrid [][]Tile, camera *rl.Camera3D) (Tile, bool) { if !rl.IsMouseButtonPressed(rl.MouseLeftButton) { return Tile{}, false } - mouseX := rl.GetMouseX() - mouseY := rl.GetMouseY() - tileX := mouseX / TileSize - tileY := (mouseY + int32(mapGrid[tileX][0].Height)) / TileSize + mouse := rl.GetMousePosition() + + // Unproject mouse coordinates to 3D ray + var ray rl.Ray = rl.GetMouseRay(mouse, *camera) + + // Calculate the distance from the camera to the ray's intersection with the ground plane (Y=0) + dist := -ray.Position.Y / ray.Direction.Y + + // Calculate the intersection point + intersection := rl.NewVector3( + ray.Position.X+ray.Direction.X*dist, + ray.Position.Y+ray.Direction.Y*dist, + ray.Position.Z+ray.Direction.Z*dist, + ) + + // Convert the intersection point to tile coordinates + tileX := int(intersection.X / float32(TileSize)) + tileY := int(intersection.Z / float32(TileSize)) + if tileX >= 0 && tileX < MapWidth && tileY >= 0 && tileY < MapHeight { + fmt.Println("Clicked:", tileX, tileY) return mapGrid[tileX][tileY], true } return Tile{}, false } +func UpdatePlayer(player *Player, deltaTime float32, mapGrid [][]Tile, camera *rl.Camera) { + if len(player.TargetPath) > 0 { + targetTile := player.TargetPath[0] + + // Calculate the direction vector to the target + direction := rl.Vector3{ + X: float32(targetTile.X*TileSize) - player.PosActual.X, + Y: float32(targetTile.Y*TileSize) - player.PosActual.Y, + } + + // Normalize the direction vector to get smooth movement + dist := float32(math.Sqrt(float64(direction.X*direction.X + direction.Y*direction.Y))) + if dist > 0 { + direction.X /= dist + direction.Y /= dist + } + + // Move towards the target + newPosX := player.PosActual.X + direction.X*player.Speed*deltaTime + newPosY := player.PosActual.Y + direction.Y*player.Speed*deltaTime + + // Check if the player is moving into a non-walkable tile + if !mapGrid[int(newPosX)/TileSize][int(newPosY)/TileSize].Walkable { + return + } + + // Update player's position + player.PosActual.X = newPosX + player.PosActual.Y = newPosY + + // Check if the player reached the target tile + if dist < 1.0 { + player.PosActual.X = float32(targetTile.X * TileSize) + player.PosActual.Y = float32(targetTile.Y * TileSize) + player.PosTile = targetTile // Update the player's tile position + player.TargetPath = player.TargetPath[1:] // Move to the next tile in the path + } + + // Update camera target and position + camera.Target = player.PosActual + camera.Position.X = player.PosActual.X + camera.Position.Y = player.PosActual.Y + camera.Position.Z = player.PosActual.Z + cameraDistance + } +} + +func HandleInput(player *Player, mapGrid [][]Tile, camera *rl.Camera) { + clickedTile, clicked := GetTileAtMouse(mapGrid, camera) + if clicked { + path := FindPath(mapGrid, 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:] + } + } + } +} + +func main() { + rl.InitWindow(800, 600, "RuneScape-like Game") + defer rl.CloseWindow() + + frameCnt := 0 + + mapGrid := InitMap() + + player := Player{ + PosTile: Tile{X: 10, Y: 10}, + PosActual: rl.Vector3{X: float32(10 * TileSize), Y: float32(10 * TileSize), Z: float32(mapGrid[10][10].Height /* * TileHeight */)}, + Speed: 100.0, // pixels per second + } + + // Initialize 3D camera + + cameraDistance := float32(64.0) + cameraYaw := float32(45.0) + cameraPitch := float32(30.0) + + camera := rl.Camera3D{ + Position: rl.NewVector3(player.PosActual.X, player.PosActual.Y, player.PosActual.Z+cameraDistance), // Updated camera position + Target: player.PosActual, // The point the camera looks at (player) + Up: rl.NewVector3(0, 1, 0), // Camera up vector (y-axis is up) + Fovy: 45.0, // Field of view + Projection: rl.CameraPerspective, // Perspective camera mode + } + + rl.SetTargetFPS(60) + + for !rl.WindowShouldClose() { + deltaTime := rl.GetFrameTime() + + frameCnt++ + frameCnt %= 60 + + HandleInput(&player, mapGrid, &camera) + UpdatePlayer(&player, deltaTime, mapGrid, &camera) + + // Update camera based on mouse wheel + wheelMove := rl.GetMouseWheelMove() + if wheelMove != 0 { + cameraDistance += wheelMove * 5 + if cameraDistance < 10 { + cameraDistance = 10 + } else if cameraDistance > 100 { + cameraDistance = 100 + } + } + + // Update camera position directly above the player + camera.Target = player.PosActual // Ensure the camera always looks at the player + + // Update camera based on arrow keys (camera orbits player) + if rl.IsKeyDown(rl.KeyRight) { + cameraYaw -= 60 * deltaTime // Rotate right + } + if rl.IsKeyDown(rl.KeyLeft) { + cameraYaw += 60 * deltaTime // Rotate left + } + if rl.IsKeyDown(rl.KeyUp) { + cameraPitch += 60 * deltaTime // Tilt up + } + if rl.IsKeyDown(rl.KeyDown) { + cameraPitch -= 60 * deltaTime // Tilt down + } + + // Constrain camera pitch to avoid flipping + if cameraPitch > 85.0 { + cameraPitch = 85.0 + } else if cameraPitch < -10.0 { + cameraPitch = -10.0 + } + + // Convert spherical coordinates to cartesian to calculate the camera's position + camera.Position.X = player.PosActual.X + cameraDistance*float32(math.Cos(float64(cameraPitch)*math.Pi/180.0))*float32(math.Sin(float64(cameraYaw)*math.Pi/180.0)) + camera.Position.Y = player.PosActual.Y + cameraDistance*float32(math.Sin(float64(cameraPitch)*math.Pi/180.0)) + camera.Position.Z = player.PosActual.Z + cameraDistance*float32(math.Cos(float64(cameraPitch)*math.Pi/180.0))*float32(math.Cos(float64(cameraYaw)*math.Pi/180.0)) + + // Begin rendering + rl.BeginDrawing() + rl.ClearBackground(rl.RayWhite) + rl.BeginMode3D(camera) + + // Draw map and player + DrawMap(mapGrid) + DrawPlayer(player, mapGrid) + + rl.EndMode3D() + rl.EndDrawing() + + if frameCnt == 0 { + fmt.Printf("Player Pos: %v, Camera Pos: %v\n", player.PosActual, camera.Position) + } + + } +} + // pathfinding type Node struct { Tile Tile @@ -112,6 +315,7 @@ func FindPath(mapGrid [][]Tile, start, end Tile) []Tile { path = append([]Tile{node.Tile}, path...) node = node.Parent } + fmt.Printf("Path found: %v\n", path) return path } @@ -149,6 +353,7 @@ func FindPath(mapGrid [][]Tile, start, end Tile) []Tile { } // No path found + fmt.Println("No path found") return nil } @@ -183,81 +388,3 @@ func abs(x int) int { } return x } - -// end pathfinding - -func UpdatePlayer(player *Player, deltaTime float32, mapGrid [][]Tile) { - if len(player.TargetPath) > 0 { - targetTile := player.TargetPath[0] - targetX := float32(targetTile.X * TileSize) - targetY := float32(targetTile.Y*TileSize - int(targetTile.Height)) - - // Calculate direction - dirX := targetX - player.PosX - dirY := targetY - player.PosY - distance := float32(math.Sqrt(float64(dirX*dirX + dirY*dirY))) - - if distance < player.Speed*deltaTime { - // Snap to target tile - player.PosX = targetX - player.PosY = targetY - player.X = targetTile.X - player.Y = targetTile.Y - // Remove the reached tile from the path - player.TargetPath = player.TargetPath[1:] - } else { - // Move towards target - player.PosX += (dirX / distance) * player.Speed * deltaTime - player.PosY += (dirY / distance) * player.Speed * deltaTime - } - } -} - -func HandleInput(player *Player, mapGrid [][]Tile) { - clickedTile, clicked := GetTileAtMouse(mapGrid) - if clicked { - path := FindPath(mapGrid, mapGrid[player.X][player.Y], clickedTile) - if path != nil { - // Exclude the first tile (current position) - if len(path) > 1 { - player.TargetPath = path[1:] - } - } - } -} - -func main() { - rl.InitWindow(800, 600, "RuneScape-like Game") - defer rl.CloseWindow() - - mapGrid := InitMap() - - player := Player{ - X: 10, - Y: 10, - PosX: float32(10 * TileSize), - PosY: float32(10*TileSize - int(mapGrid[10][10].Height)), - Speed: 100.0, // pixels per second - } - - rl.SetTargetFPS(60) - - for !rl.WindowShouldClose() { - deltaTime := rl.GetFrameTime() - - // Handle input - HandleInput(&player, mapGrid) - - // Update player position - UpdatePlayer(&player, deltaTime, mapGrid) - - // Draw everything - rl.BeginDrawing() - rl.ClearBackground(rl.RayWhite) - - DrawMap(mapGrid) - DrawPlayer(player, mapGrid) - - rl.EndDrawing() - } -} diff --git a/utils/pathfinding.go b/utils/pathfinding.go new file mode 100644 index 0000000..d4b585b --- /dev/null +++ b/utils/pathfinding.go @@ -0,0 +1 @@ +package utils