From 0037f25cca61805f0d5892de43f78596ee6bbfe2 Mon Sep 17 00:00:00 2001 From: cbeimers113 Date: Tue, 6 Jun 2023 20:02:26 -0400 Subject: [PATCH 1/3] Water bugfixes --- game/entity.go | 2 +- game/game.go | 4 ++++ game/gui.go | 2 +- game/quantity.go | 1 + game/tile.go | 40 ++++++++++++++++++++++++++++++---------- game/world.go | 23 +++++++++++++++++------ 6 files changed, 54 insertions(+), 18 deletions(-) diff --git a/game/entity.go b/game/entity.go index 99dad44..93c0c2e 100644 --- a/game/entity.go +++ b/game/entity.go @@ -52,7 +52,7 @@ func (entity *Entity) InfoString() (infoString string) { infoString += fmt.Sprintf("type=%s\n", tileData.Type.Name) infoString += fmt.Sprintf("temperature=%s\n", tileData.Temperature) infoString += fmt.Sprintf("water level=%s\n", tileData.WaterLevel) - infoString += fmt.Sprintf("elevation=%.2f\n", entity.GetElevation()) + infoString += fmt.Sprintf("elevation=%s\n", entity.GetElevation()) infoString += fmt.Sprintf("planted=%t\n", tileData.Planted) } case Plant: diff --git a/game/game.go b/game/game.go index e5c238b..8a29f59 100644 --- a/game/game.go +++ b/game/game.go @@ -1,6 +1,7 @@ package game import ( + "math/rand" "time" "github.com/g3n/engine/app" @@ -72,6 +73,9 @@ func Run() { var tickThreshold float32 = 1000 / float32(SimSpeed) var deltaTime float32 = 0 + // Seed the PRNG + rand.Seed(time.Now().UnixNano()) + Application.Run(func(renderer *renderer.Renderer, duration time.Duration) { Application.Gls().Clear(gls.DEPTH_BUFFER_BIT | gls.STENCIL_BUFFER_BIT | gls.COLOR_BUFFER_BIT) renderer.Render(Scene, Cam) diff --git a/game/gui.go b/game/gui.go index fc8fcd9..0681646 100644 --- a/game/gui.go +++ b/game/gui.go @@ -56,7 +56,7 @@ func infoText() (txt string) { txt += LookingAt.InfoString() } - txt += fmt.Sprintf("\nTotal volume of water:%.2f\n", TotalWaterVolume()) + txt += fmt.Sprintf("\nTotal water volume=%s\n", TotalWaterVolume()) return } diff --git a/game/quantity.go b/game/quantity.go index 6b5aecf..e2a0d2f 100644 --- a/game/quantity.go +++ b/game/quantity.go @@ -8,6 +8,7 @@ type Unit string const Celcius Unit = "°C" const Litre Unit = "L" const Gram Unit = "g" +const Metre Unit = "m" // Represents an amount of an element type Quantity struct { diff --git a/game/tile.go b/game/tile.go index a3f1f03..ea0e22d 100644 --- a/game/tile.go +++ b/game/tile.go @@ -112,13 +112,19 @@ func TypeIndex(tType TileType) int { // Update the tile's water level func (tile *Entity) updateWaterLevel() { if tileData, ok := tile.UserData().(*TileData); ok { - waterLevel := &tileData.WaterLevel.Value + waterLevel := tileData.WaterLevel.Value water := tileData.Water - water.SetVisible(*waterLevel > 0) - water.SetScaleY(LitresToCubicMetres(*waterLevel)) + water.SetScaleY(LitresToCubicMetres(waterLevel)) water.SetPositionY(DimensionsOf(water).Y) - // TODO: Lower opacity for low volumes + + // Lower water texture opacity for low water level + if imat := water.GetMaterial(0); imat != nil { + if ms, ok := imat.(*material.Standard); ok { + ms.SetTransparent(true) + ms.SetOpacity(math32.Min(1, waterLevel)) + } + } } } @@ -138,19 +144,28 @@ func (tile *Entity) AddWater(delta float32) float32 { } // Get the elevation of the top of the tile, including its water -func (tile *Entity) GetElevation() (elevation float32) { +func (tile *Entity) GetElevation() *Quantity { + var elevation float32 + if tileData, ok := tile.UserData().(*TileData); ok { elevation = tile.Position().Y elevation += tileData.Water.Position().Y elevation += LitresToCubicMetres(tileData.WaterLevel.Value) } - return + return &Quantity{ + Value: elevation, + Units: Metre, + } } // Perform per-frame updates to a Tile func UpdateTile(tile *Entity) { tile.doWaterSpread() + + if tileData, ok := tile.UserData().(*TileData); ok { + tileData.Water.SetVisible(tileData.WaterLevel.Value > 0) + } } // Spread water to other tiles @@ -164,27 +179,32 @@ func (tile *Entity) doWaterSpread() { continue } - if neighbour.GetElevation() < tile.GetElevation() { + if neighbour.GetElevation().Value < tile.GetElevation().Value { lowerNeighbours = append(lowerNeighbours, neighbour) } } + // Shuffle the lower neighbours to give some randomness to flow direction + rand.Shuffle(len(lowerNeighbours), func(i, j int) { + lowerNeighbours[i], lowerNeighbours[j] = lowerNeighbours[j], lowerNeighbours[i] + }) + for len(lowerNeighbours) > 0 { var neighbour *Entity // Find lowest neighbour for _, nb := range lowerNeighbours { - if neighbour == nil || nb.GetElevation() < neighbour.GetElevation() { + if neighbour == nil || nb.GetElevation().Value < neighbour.GetElevation().Value { neighbour = nb } } // Stop when this tile is already a local minimum - if neighbour.GetElevation() >= tile.GetElevation() { + if neighbour.GetElevation().Value >= tile.GetElevation().Value { break } - delta := (tile.GetElevation() - neighbour.GetElevation()) / float32(len(lowerNeighbours)) + delta := (tile.GetElevation().Value - neighbour.GetElevation().Value) / float32(len(lowerNeighbours)) neighbour.AddWater(delta - tile.AddWater(-delta)) // Remove neighbour from lowerNeighbours diff --git a/game/world.go b/game/world.go index eecbdf5..66efc65 100644 --- a/game/world.go +++ b/game/world.go @@ -85,12 +85,17 @@ func makeTilemap(heightmap [Width][Depth]float32, min, max float32) { for x := 0; x < Width; x++ { for z := 0; z < Depth; z++ { // Map the heightmap value to the TileTypes array to determine tile type - height := math32.Min(float32(len(TileTypes))*(heightmap[x][z]-min)/(max-min), float32(len(TileTypes)-1)) - tType := TileTypes[int(height)] + height := float32(len(TileTypes)) * (heightmap[x][z] - min) / (max - min) + tType := Stone + + if int(height) < len(TileTypes) { + tType = TileTypes[int(height)] + } + height /= 3 // Each tile spawns at 22°C with 10 L of water on top of it - tile := NewTile(x, z, height, 22.0, 10, tType) + tile := NewTile(x, z, height, 22.0, 0.01, tType) Scene.Add(tile.GetINode()) Tilemap[x][z] = tile } @@ -180,11 +185,17 @@ func UpdateWorld(deltaTime float32) { } // Calculate the total volume of liquid water -func TotalWaterVolume() (volume float32) { +func TotalWaterVolume() *Quantity { + var vol float32 + for _, entity := range Entities { if tileData, ok := entity.UserData().(*TileData); ok { - volume += tileData.WaterLevel.Value + vol += tileData.WaterLevel.Value } } - return + + return &Quantity{ + Value: vol, + Units: Litre, + } } From 33cfccd226789454207a75dd701dd35b20812a0a Mon Sep 17 00:00:00 2001 From: cbeimers113 Date: Tue, 6 Jun 2023 22:14:43 -0400 Subject: [PATCH 2/3] Water improvements, sim pausing --- game/creature.go | 4 +- game/game.go | 8 +++- game/gui.go | 24 ++++++++++- game/input.go | 6 ++- game/plant.go | 2 +- game/quantity.go | 7 +++- game/tile.go | 104 +++++++++++++++++++++++++++-------------------- game/world.go | 20 ++------- 8 files changed, 106 insertions(+), 69 deletions(-) diff --git a/game/creature.go b/game/creature.go index 717358f..93620fe 100644 --- a/game/creature.go +++ b/game/creature.go @@ -12,5 +12,7 @@ func OnLeftClickCreature(creature *Entity) { // Update a creature func UpdateCreature(creature *Entity) { - + if !IsPaused { + + } } \ No newline at end of file diff --git a/game/game.go b/game/game.go index 8a29f59..40b0329 100644 --- a/game/game.go +++ b/game/game.go @@ -21,11 +21,15 @@ var Scene *core.Node var Cam *camera.Camera var Win *window.GlfwWindow +// This determines if everything in the simulation is frozen, including the player +var IsFrozen bool + +// This determines if the simulation physics are paused, but the player can still interact with the simulation var IsPaused bool // Set whether the game is paused func SetPaused(paused bool) { - IsPaused = paused + IsFrozen = paused if Win != nil { switch paused { @@ -80,7 +84,7 @@ func Run() { Application.Gls().Clear(gls.DEPTH_BUFFER_BIT | gls.STENCIL_BUFFER_BIT | gls.COLOR_BUFFER_BIT) renderer.Render(Scene, Cam) - if !IsPaused { + if !IsFrozen { // TPS counter deltaTime += float32(duration.Milliseconds()) if deltaTime >= tickThreshold { diff --git a/game/gui.go b/game/gui.go index 0681646..cdf94a6 100644 --- a/game/gui.go +++ b/game/gui.go @@ -30,6 +30,7 @@ var exitButton *gui.Button // Simulation view components var simCursor *gui.Image var infoLabel *gui.Label +var pausedLabel *gui.Label // Tile context menu components var tileInfoLabel *gui.Label @@ -49,6 +50,10 @@ func infoText() (txt string) { txt += "Controls:\n" txt += "WASD to move\n" txt += "ESC to open menu\n" + txt += "Space to toggle simulation\n" + txt += "\n" + txt += "Left click a tile to add water\n" + txt += "Right click a tile to try to add a plant\n" // Append the WAILA (what am I looking at?) data if LookingAt != nil { @@ -56,11 +61,19 @@ func infoText() (txt string) { txt += LookingAt.InfoString() } - txt += fmt.Sprintf("\nTotal water volume=%s\n", TotalWaterVolume()) + txt += fmt.Sprintf("\nTotal water volume=%s\n", TotalWaterVolume) return } +// Update the "Simulation Running/Paused" status +func pausedStatus() string { + return fmt.Sprintf("Simulation %s", map[bool]string{ + true: "Paused", + false: "Running", + }[IsPaused]) +} + // Load the gui func LoadGui() { gui.Manager().Set(Scene) @@ -160,16 +173,25 @@ func registerSimulationView() { infoLabel.SetUserData(SimulationView) Scene.Add(infoLabel) + pausedLabel = gui.NewLabel(pausedStatus()) + pausedLabel.SetPosition((float32(width)-pausedLabel.ContentWidth())/2, 0) + pausedLabel.SetUserData(SimulationView) + Scene.Add(pausedLabel) + SetPaused(false) }, Close: func() { Scene.Remove(simCursor) Scene.Remove(infoLabel) + Scene.Remove(pausedLabel) }, Refresh: func() { + width, _ := Application.GetSize() infoLabel.SetText(infoText()) + pausedLabel.SetText(pausedStatus()) + pausedLabel.SetPosition((float32(width)-pausedLabel.ContentWidth())/2, 0) }, } } diff --git a/game/input.go b/game/input.go index 45e7b6d..46801cb 100644 --- a/game/input.go +++ b/game/input.go @@ -13,7 +13,7 @@ func KeyDown(evname string, ev interface{}) { switch kev.Key { case window.KeyEscape: - if IsPaused { + if IsFrozen { Views[SimulationView].Open(true) } else { Views[MainMenu].Open(true) @@ -26,6 +26,8 @@ func KeyDown(evname string, ev interface{}) { PlayerMoveX = 1 case window.KeyA: PlayerMoveX = -1 + case window.KeySpace: + IsPaused = !IsPaused } } @@ -57,7 +59,7 @@ func KeyHold(evname string, ev interface{}) { func MouseDown(evname string, ev interface{}) { me := ev.(*window.MouseEvent) - if !IsPaused && LookingAt != nil { + if !IsFrozen && LookingAt != nil { switch LookingAt.Type { case Tile: switch me.Button { diff --git a/game/plant.go b/game/plant.go index 73d3092..acfd467 100644 --- a/game/plant.go +++ b/game/plant.go @@ -73,7 +73,7 @@ func (plant *Entity) growPlant(plantData *PlantData) { // Perform per-frame updates to a plant func UpdatePlant(plant *Entity) { - if plantData, ok := plant.UserData().(*PlantData); ok { + if plantData, ok := plant.UserData().(*PlantData); ok && !IsPaused { plant.growPlant(plantData) } } diff --git a/game/quantity.go b/game/quantity.go index e2a0d2f..e13d03e 100644 --- a/game/quantity.go +++ b/game/quantity.go @@ -30,7 +30,12 @@ func (q *Quantity) String() (str string) { return } -// Convert from litres to cubic metres +// Convert from litres to cubic metres (dimensions of one tile is 1 cubic metre) func LitresToCubicMetres(litres float32) float32 { return litres / 1000 } + +// Convert from cubic metres to litres (dimensions of one tile is 1 cubic metre) +func CubicMetresToLitres(cubicMetres float32) float32 { + return cubicMetres * 1000 +} \ No newline at end of file diff --git a/game/tile.go b/game/tile.go index ea0e22d..178e872 100644 --- a/game/tile.go +++ b/game/tile.go @@ -38,6 +38,7 @@ type TileData struct { Neighbours Neighbourhood // Pointers to any neighbouring tiles Water *graphic.Mesh // This is the water tile object that can exist on top of the tile + WaterTick int // Dynamic properties Planted bool @@ -58,7 +59,7 @@ func OnRightClickTile(tile *Entity) { // Perform an action on a tile entity on left click func OnLeftClickTile(tile *Entity) { - tile.AddWater(100) + tile.AddWater(CubicMetresToLitres(1)) } // Spawn a hex tile of type tType at mapX, mapZ (tile precision), y (game world precision) @@ -69,6 +70,7 @@ func NewTile(mapX, mapZ int, height, temp, waterLevel float32, tType TileType) ( tile = NewEntity(tileMesh, Tile) waterMesh := createTileMesh("water") + TotalWaterVolume.Value += waterLevel waterMesh.SetName(tile.Name()) tileMesh.Add(waterMesh) tile.SetPosition(x, height, z) @@ -78,6 +80,7 @@ func NewTile(mapX, mapZ int, height, temp, waterLevel float32, tType TileType) ( MapZ: mapZ, Type: tType, Water: waterMesh, + WaterTick: rand.Intn(100), Temperature: &Quantity{temp, Celcius}, WaterLevel: &Quantity{waterLevel, Litre}, }) @@ -89,6 +92,7 @@ func NewTile(mapX, mapZ int, height, temp, waterLevel float32, tType TileType) ( func createTileMesh(texture string) (tileMesh *graphic.Mesh) { geom := CreateHexagon() mat := material.NewStandard(math32.NewColorHex(0x111111)) + mat.SetTransparent(texture == "water") tileMesh = graphic.NewMesh(geom, mat) if tex, ok := Texture(texture); ok { @@ -117,12 +121,12 @@ func (tile *Entity) updateWaterLevel() { water.SetScaleY(LitresToCubicMetres(waterLevel)) water.SetPositionY(DimensionsOf(water).Y) + water.SetVisible(tileData.WaterLevel.Value > 0) // Lower water texture opacity for low water level if imat := water.GetMaterial(0); imat != nil { if ms, ok := imat.(*material.Standard); ok { - ms.SetTransparent(true) - ms.SetOpacity(math32.Min(1, waterLevel)) + ms.SetOpacity(math32.Min(50, waterLevel) / 60) } } } @@ -134,9 +138,10 @@ func (tile *Entity) AddWater(delta float32) float32 { if tileData, ok := tile.UserData().(*TileData); ok { waterLevel := &tileData.WaterLevel.Value backflow := -(*waterLevel + delta) + pLevel := *waterLevel *waterLevel += delta *waterLevel = math32.Max(0, *waterLevel) - tile.updateWaterLevel() + TotalWaterVolume.Value += *waterLevel - pLevel return math32.Max(backflow, 0) } @@ -161,65 +166,76 @@ func (tile *Entity) GetElevation() *Quantity { // Perform per-frame updates to a Tile func UpdateTile(tile *Entity) { - tile.doWaterSpread() - - if tileData, ok := tile.UserData().(*TileData); ok { - tileData.Water.SetVisible(tileData.WaterLevel.Value > 0) + if !IsPaused { + tile.doWaterSpread() } + + tile.updateWaterLevel() } // Spread water to other tiles func (tile *Entity) doWaterSpread() { - if tileData, ok := tile.UserData().(*TileData); ok && tileData.WaterLevel.Value > 0 { - var lowerNeighbours []*Entity + if tileData, ok := tile.UserData().(*TileData); ok { + tileData.WaterTick++ - // Filter out the neighbouring tiles which aren't lower than this one - for _, neighbour := range tileData.Neighbours { - if neighbour == nil { - continue - } + if tileData.WaterTick > 10 && tileData.WaterLevel.Value > 0 { + var lowerNeighbours []*Entity - if neighbour.GetElevation().Value < tile.GetElevation().Value { - lowerNeighbours = append(lowerNeighbours, neighbour) + // Filter out the neighbouring tiles which aren't lower than this one + for _, neighbour := range tileData.Neighbours { + if neighbour == nil { + continue + } + + if neighbour.GetElevation().Value < tile.GetElevation().Value { + lowerNeighbours = append(lowerNeighbours, neighbour) + } } - } - // Shuffle the lower neighbours to give some randomness to flow direction - rand.Shuffle(len(lowerNeighbours), func(i, j int) { - lowerNeighbours[i], lowerNeighbours[j] = lowerNeighbours[j], lowerNeighbours[i] - }) + // Shuffle the lower neighbours to give some randomness to flow direction + rand.Shuffle(len(lowerNeighbours), func(i, j int) { + lowerNeighbours[i], lowerNeighbours[j] = lowerNeighbours[j], lowerNeighbours[i] + }) - for len(lowerNeighbours) > 0 { - var neighbour *Entity + // Distribute water to lower neighbours + for len(lowerNeighbours) > 0 { + var neighbour *Entity - // Find lowest neighbour - for _, nb := range lowerNeighbours { - if neighbour == nil || nb.GetElevation().Value < neighbour.GetElevation().Value { - neighbour = nb + // Find lowest neighbour + for _, nb := range lowerNeighbours { + if neighbour == nil || nb.GetElevation().Value < neighbour.GetElevation().Value { + neighbour = nb + } } - } - // Stop when this tile is already a local minimum - if neighbour.GetElevation().Value >= tile.GetElevation().Value { - break - } + // Stop when this tile is already a local minimum + if neighbour.GetElevation().Value >= tile.GetElevation().Value { + break + } - delta := (tile.GetElevation().Value - neighbour.GetElevation().Value) / float32(len(lowerNeighbours)) - neighbour.AddWater(delta - tile.AddWater(-delta)) + delta := CubicMetresToLitres(tile.GetElevation().Value-neighbour.GetElevation().Value) / float32(len(lowerNeighbours)) - // Remove neighbour from lowerNeighbours - for i, nb := range lowerNeighbours { - if nb == neighbour { - lowerNeighbours[i] = lowerNeighbours[len(lowerNeighbours)-1] - lowerNeighbours = lowerNeighbours[:len(lowerNeighbours)-1] + if δ := neighbour.AddWater(delta - tile.AddWater(-delta)); δ != 0 { + // TODO: This shouldn't ever be 0, but do something if it is + println(δ) + } + + // Remove neighbour from lowerNeighbours + for i, nb := range lowerNeighbours { + if nb == neighbour { + lowerNeighbours[i] = lowerNeighbours[len(lowerNeighbours)-1] + lowerNeighbours = lowerNeighbours[:len(lowerNeighbours)-1] + break + } + } + + // Stop when we run out of water to spread + if tileData.WaterLevel.Value == 0 { break } } - // Stop when we run out of water to spread - if tileData.WaterLevel.Value == 0 { - break - } + tileData.WaterTick = 0 } } } diff --git a/game/world.go b/game/world.go index 66efc65..7f6db0d 100644 --- a/game/world.go +++ b/game/world.go @@ -18,6 +18,7 @@ const Depth int = 64 var Sun *light.Ambient var Entities map[int]*Entity var Tilemap [Width][Depth]*Entity +var TotalWaterVolume *Quantity // Remove an entity from the world func RemoveEntity(entity *Entity) { @@ -95,7 +96,7 @@ func makeTilemap(heightmap [Width][Depth]float32, min, max float32) { height /= 3 // Each tile spawns at 22°C with 10 L of water on top of it - tile := NewTile(x, z, height, 22.0, 0.01, tType) + tile := NewTile(x, z, height, 22.0, 10, tType) Scene.Add(tile.GetINode()) Tilemap[x][z] = tile } @@ -154,6 +155,7 @@ func LoadWorld() { Sun = light.NewAmbient(&math32.Color{R: 1.0, G: 1.0, B: 1.0}, 8.0) Scene.Add(Sun) Entities = make(map[int]*Entity) + TotalWaterVolume = &Quantity{Units: Litre} CreateMap() CreateAtmosphere() @@ -183,19 +185,3 @@ func UpdateWorld(deltaTime float32) { } } } - -// Calculate the total volume of liquid water -func TotalWaterVolume() *Quantity { - var vol float32 - - for _, entity := range Entities { - if tileData, ok := entity.UserData().(*TileData); ok { - vol += tileData.WaterLevel.Value - } - } - - return &Quantity{ - Value: vol, - Units: Litre, - } -} From c377089e96ba4ce2ac45f0105605b191573c7eb0 Mon Sep 17 00:00:00 2001 From: cbeimers113 Date: Tue, 6 Jun 2023 22:27:03 -0400 Subject: [PATCH 3/3] Update README --- README.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 51a747d..9a9fd30 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,7 @@ go run main.go ### Current Features: - * Left click and drag to rotate around map - * Right click and drag to pan - * Scroll wheel to zoom in and out - * Right click a tile to plant a plant - * Plants will slowly grow - + * Controls and info section on HUD + * Randomly generated hextile map + * Water that flows down the map's topography + * Plants which will grow on tiles over time