417 lines
11 KiB
Go
417 lines
11 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
|
|
rl "github.com/gen2brain/raylib-go/raylib"
|
|
)
|
|
|
|
const (
|
|
MapWidth = 50
|
|
MapHeight = 50
|
|
TileSize = 32
|
|
TileHeight = 2.0
|
|
)
|
|
|
|
var (
|
|
cameraDistance = float32(20.0)
|
|
cameraYaw = float32(145.0)
|
|
cameraPitch = float32(45.0) // Adjusted for a more overhead view
|
|
)
|
|
|
|
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), // 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, model *rl.Model, mapGrid [][]Tile) {
|
|
// 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.DrawCube(playerPos, 16, 16, 16, rl.Green) // Draw player cube
|
|
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 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
|
|
ray := rl.GetMouseRay(mouse, *camera)
|
|
|
|
// Calculate the distance from the camera to the ray's intersection with the ground plane (Y=0)
|
|
if ray.Direction.Y == 0 {
|
|
return Tile{}, false
|
|
}
|
|
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 (player *Player) MoveTowards(target Tile, deltaTime float32, mapGrid [][]Tile) {
|
|
// 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
|
|
player.TargetPath = player.TargetPath[1:] // Move to next tile in path if any
|
|
}
|
|
}
|
|
|
|
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 UpdateCamera(camera *rl.Camera3D, player rl.Vector3, deltaTime float32) {
|
|
// Update camera based on mouse wheel
|
|
wheelMove := rl.GetMouseWheelMove()
|
|
if wheelMove != 0 {
|
|
cameraDistance += -wheelMove * 5
|
|
if cameraDistance < 10 {
|
|
cameraDistance = 10
|
|
}
|
|
if cameraDistance > 250 {
|
|
cameraDistance = 250
|
|
}
|
|
}
|
|
|
|
// Orbit camera around the player using arrow keys
|
|
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
|
|
}
|
|
}
|
|
|
|
// Calculate the new camera position using spherical coordinates
|
|
cameraYawRad := float64(cameraYaw) * rl.Deg2rad
|
|
cameraPitchRad := float64(cameraPitch) * rl.Deg2rad
|
|
cameraPos := 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)),
|
|
}
|
|
|
|
// Update the camera's position and target
|
|
camera.Position = cameraPos
|
|
camera.Target = rl.NewVector3(player.X, player.Y, player.Z)
|
|
}
|
|
|
|
func main() {
|
|
rl.InitWindow(800, 600, "GoonScape")
|
|
defer rl.CloseWindow()
|
|
rl.InitAudioDevice()
|
|
defer rl.CloseAudioDevice()
|
|
|
|
playerModel := rl.LoadModel("resources/models/goonion.obj")
|
|
defer rl.UnloadModel(playerModel)
|
|
playerTexture := rl.LoadTexture("resources/models/goonion.png")
|
|
defer rl.UnloadTexture(playerTexture)
|
|
rl.SetMaterialTexture(playerModel.Materials, rl.MapDiffuse, playerTexture)
|
|
|
|
coomerModel := rl.LoadModel("resources/models/coomer.obj")
|
|
defer rl.UnloadModel(coomerModel)
|
|
coomerTexture := rl.LoadTexture("resources/models/coomer.png")
|
|
defer rl.UnloadTexture(coomerTexture)
|
|
rl.SetMaterialTexture(coomerModel.Materials, rl.MapDiffuse, coomerTexture)
|
|
|
|
mapGrid := InitMap()
|
|
|
|
player := Player{
|
|
PosActual: rl.NewVector3(5*TileSize, 0, 5*TileSize),
|
|
PosTile: mapGrid[5][5],
|
|
Speed: 50.0,
|
|
TargetPath: []Tile{},
|
|
}
|
|
|
|
camera := rl.Camera3D{
|
|
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,
|
|
}
|
|
|
|
rl.SetTargetFPS(60)
|
|
|
|
// Music
|
|
music := rl.LoadMusicStream("resources/audio/GoonScape2.mp3")
|
|
rl.PlayMusicStream(music)
|
|
rl.SetMusicVolume(music, 0.5)
|
|
defer rl.UnloadMusicStream(music)
|
|
|
|
for !rl.WindowShouldClose() {
|
|
rl.UpdateMusicStream(music)
|
|
|
|
// Time management
|
|
deltaTime := rl.GetFrameTime()
|
|
|
|
// Handle input
|
|
HandleInput(&player, mapGrid, &camera)
|
|
|
|
// Update player
|
|
if len(player.TargetPath) > 0 {
|
|
player.MoveTowards(player.TargetPath[0], deltaTime, mapGrid)
|
|
}
|
|
|
|
// Update camera
|
|
UpdateCamera(&camera, player.PosActual, deltaTime)
|
|
|
|
// Rendering
|
|
rl.BeginDrawing()
|
|
rl.ClearBackground(rl.RayWhite)
|
|
rl.BeginMode3D(camera)
|
|
DrawMap(mapGrid)
|
|
DrawPlayer(player, &playerModel, mapGrid)
|
|
|
|
rl.DrawModel(coomerModel, rl.NewVector3(5*TileSize+32, 32, 5*TileSize+32), 16, rl.White)
|
|
|
|
rl.DrawFPS(10, 10)
|
|
|
|
rl.EndMode3D()
|
|
rl.EndDrawing()
|
|
}
|
|
}
|
|
|
|
// 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
|
|
}
|