2024-09-30 10:04:38 +02:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"math"
|
|
|
|
|
|
|
|
rl "github.com/gen2brain/raylib-go/raylib"
|
|
|
|
)
|
|
|
|
|
|
|
|
type Tile struct {
|
|
|
|
X, Y int
|
|
|
|
Height float32
|
|
|
|
Walkable bool
|
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
|
|
|
MapWidth = 100
|
|
|
|
MapHeight = 100
|
|
|
|
TileSize = 32
|
|
|
|
)
|
|
|
|
|
|
|
|
// 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: 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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
|
|
|
func GetTileAtMouse(mapGrid [][]Tile) (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
|
|
|
|
if tileX >= 0 && tileX < MapWidth && tileY >= 0 && tileY < MapHeight {
|
|
|
|
return mapGrid[tileX][tileY], true
|
|
|
|
}
|
|
|
|
return Tile{}, false
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
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
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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()
|
|
|
|
}
|
|
|
|
}
|