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(20.0) cameraYaw = float32(145.0) cameraPitch = float32(90.0) ) type Tile struct { X, Y int Height float32 Walkable bool } type Player struct { PosActual rl.Vector3 PosTile Tile TargetPath []Tile Speed float32 } // 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), //float32((x + y) % 10), // Example height Walkable: true, // Set to false for obstacles } } } return mapGrid } func DrawMap(mapGrid [][]Tile) { 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 } } } func DrawPlayer(player Player, mapGrid [][]Tile) { // Get current tile height 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, camera *rl.Camera3D) (Tile, bool) { if !rl.IsMouseButtonPressed(rl.MouseLeftButton) { return Tile{}, false } 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 } } } 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 camera := rl.Camera3D{ Position: rl.NewVector3(player.PosActual.X+cameraDistance, player.PosActual.Y+cameraDistance, player.PosActual.Z+cameraDistance), 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 } camera.Target = player.PosActual rl.SetTargetFPS(60) for !rl.WindowShouldClose() { deltaTime := rl.GetFrameTime() frameCnt++ frameCnt %= 60 // Update camera based on mouse wheel wheelMove := rl.GetMouseWheelMove() if wheelMove != 0 { cameraDistance += -wheelMove * 5 if cameraDistance < 1 { cameraDistance = 1 } else if cameraDistance > 100 { cameraDistance = 100 } } // 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 } HandleInput(&player, mapGrid, &camera) UpdatePlayer(&player, deltaTime, mapGrid, &camera) camera.Target = player.PosActual // 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 Parent *Node G, H, F float32 } func FindPath(mapGrid [][]Tile, start, end Tile) []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 { // Find node with lowest F current := openList[0] currentIndex := 0 for i, node := range openList { if node.F < current.F { current = node currentIndex = i } } // Move current to closed list openList = append(openList[:currentIndex], openList[currentIndex+1:]...) 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 { path := []Tile{} node := current for node != nil { path = append([]Tile{node.Tile}, path...) node = node.Parent } fmt.Printf("Path found: %v\n", path) return path } // Generate neighbors neighbors := GetNeighbors(mapGrid, 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) } } } } // No path found fmt.Println("No path found") return nil } func heuristic(a, b Tile) float32 { return float32(abs(a.X-b.X) + abs(a.Y-b.Y)) } func distance(a, b Tile) float32 { _ = a _ = b return 1.0 //uniform cost for now } func GetNeighbors(mapGrid [][]Tile, tile Tile) []Tile { directions := [][2]int{ {1, 0}, {-1, 0}, {0, 1}, {0, -1}, {1, 1}, {-1, -1}, {1, -1}, {-1, 1}, } neighbors := []Tile{} for _, dir := range directions { nx, ny := tile.X+dir[0], tile.Y+dir[1] if nx >= 0 && nx < MapWidth && ny >= 0 && ny < MapHeight { neighbors = append(neighbors, mapGrid[nx][ny]) } } return neighbors } func abs(x int) int { if x < 0 { return -x } return x }