Skip to content

Commit

Permalink
Tutorial part 6: Populating the map
Browse files Browse the repository at this point in the history
  • Loading branch information
jcerise committed Apr 28, 2020
1 parent 347cd2f commit 87923a8
Show file tree
Hide file tree
Showing 6 changed files with 208 additions and 13 deletions.
5 changes: 5 additions & 0 deletions components.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,8 @@ func (mc MovementComponent) TypeOf() reflect.Type { return reflect.TypeOf(mc) }
type BlockingComponent struct {}

func (bc BlockingComponent) TypeOf() reflect.Type { return reflect.TypeOf(bc) }

// SimpeAIComponent is a basic AI. The entity will move randomy around the map.
type SimpleAiComponent struct {}

func (sc SimpleAiComponent) TypeOf() reflect.Type { return reflect.TypeOf(sc) }
86 changes: 86 additions & 0 deletions gamedata/enemies.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
{
"level_1": {
"small_rat": {
"components": {
"position": {},
"appearance": {
"Name": "Small rat",
"Description": "A very small rat. It doesn't look very tough.",
"Glyph": {
"Char": "r",
"Color": "#8B4513"
},
"Layer": 1
},
"blocking": {},
"simpleai": {}
}
},
"large_rat": {
"components": {
"position": {},
"appearance": {
"Name": "Large rat",
"Description": "A very large rat. It's big...but still doesn't look too tough.",
"Glyph": {
"Char": "R",
"Color": "#D2691E"
},
"Layer": 1
},
"blocking": {},
"simpleai": {}
}
},
"cave_bat": {
"components": {
"position": {},
"appearance": {
"Name": "Cave bat",
"Description": "A small, gray, bat. It's fast, bot not a threat. By itself.",
"Glyph": {
"Char": "b",
"Color": "gray"
},
"Layer": 1
},
"blocking": {},
"simpleai": {}
}
}
},
"level_2": {
"pissed_rat": {
"components": {
"position": {},
"appearance": {
"Name": "Small, pissed off, rat",
"Description": "A very small rat. It looks really pissed off.",
"Glyph": {
"Char": "r",
"Color": "red"
},
"Layer": 1
},
"blocking": {},
"simpleai": {}
}
},
"kobold": {
"components": {
"position": {},
"appearance": {
"Name": "Kobold",
"Description": "A small humanoid creature, it is clothed in rags, and is carrying a rock. Looks like it knows how to use it, too.",
"Glyph": {
"Char": "k",
"Color": "brown"
},
"Layer": 1
},
"blocking": {},
"simpleai": {}
}
}
}
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ module gogue-rogue-example

go 1.14

require github.com/gogue-framework/gogue v0.0.3-alpha
require github.com/gogue-framework/gogue v0.0.4-alpha.0.20200426052624-9eb56197a153
61 changes: 54 additions & 7 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"github.com/gogue-framework/gogue/camera"
"github.com/gogue-framework/gogue/data"
"github.com/gogue-framework/gogue/ecs"
"github.com/gogue-framework/gogue/fov"
"github.com/gogue-framework/gogue/gamemap"
Expand Down Expand Up @@ -40,6 +41,11 @@ var (
player int
playerFOV fov.FieldOfVision
torchRadius int

// Data Loading
dataLoader *data.FileLoader
entityLoader *data.EntityLoader
enemies map[string]interface{}
)

func init() {
Expand All @@ -64,7 +70,8 @@ func init() {
Description: "The player character",
}
playerMovement := MovementComponent{}
player = ecsController.CreateEntity([]ecs.Component{playerPosition, playerAppearance, playerMovement})
playerBlocks := BlockingComponent{}
player = ecsController.CreateEntity([]ecs.Component{playerPosition, playerAppearance, playerMovement, playerBlocks})

wallGlyph = ui.NewGlyph("#", "white", "gray")
floorGlyph = ui.NewGlyph(".", "white", "gray")
Expand All @@ -80,6 +87,11 @@ func init() {
playerFOV.InitializeFOV()
torchRadius = 5
playerFOV.SetTorchRadius(torchRadius)

// Data loading - load data from relevant data definition files
dataLoader, _ = data.NewFileLoader("gamedata")
entityLoader = data.NewEntityLoader(ecsController)
loadData()
}

// SetupGameMap initializes a new GameMap, with a fixed width and height (this can be larger than the game window)
Expand Down Expand Up @@ -111,27 +123,62 @@ func registerScreens() {

// registerComponent attaches, via a defined name, any component classes we want to use with the ECS controller
func registerComponents() {
ecsController.MapComponentClass("position", &PositionComponent{})
ecsController.MapComponentClass("appearance", &AppearanceComponent{})
ecsController.MapComponentClass("movement", &MovementComponent{})
ecsController.MapComponentClass("position", PositionComponent{})
ecsController.MapComponentClass("appearance", AppearanceComponent{})
ecsController.MapComponentClass("movement", MovementComponent{})
ecsController.MapComponentClass("blocking", BlockingComponent{})
ecsController.MapComponentClass("simpleai", SimpleAiComponent{})
}

func registerSystems() {
render := SystemRender{ecsController: ecsController}
input := SystemInput{ecsController: ecsController}
simpleAi := SystemSimpleAi{ecsController: ecsController, mapSurface: gameMap}

ecsController.AddSystem(input, 0)
ecsController.AddSystem(render, 1)
ecsController.AddSystem(simpleAi, 1)
ecsController.AddSystem(render, 2)
}

// loadData loads game data (enemies definitions, item definitions, map progression data, etc) via Gogues data file and
// entity loader. Any entities loaded are stored in string indexed maps, making it easy to pull out and create an entity
// via its defined name
func loadData() {
enemies, _ = dataLoader.LoadDataFromFile("enemies.json")
}

// placeEnemies places a handful of enemies at random around the level
func placeEnemies(numEnemies int) {
for i := 0; i < numEnemies; i++ {
var entityKeys []string
// Build an index for each entity, so we can randomly choose one
for key := range enemies["level_1"].(map[string]interface{}) {
entityKeys = append(entityKeys, key)
}

// Now, randomly pick an entity
entityKey := entityKeys[rng.Range(0, len(entityKeys))]
entity := enemies["level_1"].(map[string]interface{})[entityKey].(map[string]interface{})

// Create the entity based off the chosen item
loadedEntity := entityLoader.CreateSingleEntity(entity)

randomTile := gameMap.FloorTiles[rng.Range(0, len(gameMap.FloorTiles))]
ecsController.UpdateComponent(loadedEntity, PositionComponent{}.TypeOf(), PositionComponent{X: randomTile.X, Y: randomTile.Y})
}
}


func main() {
// Register all screens
registerScreens()
registerSystems()
excludedSystems := []reflect.Type{reflect.TypeOf(SystemRender{})}

// Initialize the game map
gameMap = SetupGameMap()
placeEnemies(100)

registerSystems()
excludedSystems := []reflect.Type{reflect.TypeOf(SystemRender{})}

// Set the current screen to the title
_ = screenManager.SetScreenByName("title")
Expand Down
52 changes: 48 additions & 4 deletions systems.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"github.com/gogue-framework/gogue/ecs"
"github.com/gogue-framework/gogue/gamemap"
"github.com/gogue-framework/gogue/ui"
)

Expand All @@ -20,11 +21,13 @@ func (sr SystemRender) Process() {
// Get the coordinates of the entity, in reference to where the camera currently is.
cameraX, cameraY := gameCamera.ToCameraCoordinates(pos.X, pos.Y)

// Clear the cell this entity occupies, so it is the only glyph drawn there
for i := 1; i <= 2; i++ {
ui.ClearArea(cameraX, cameraY, 1, 1, i)
if gameMap.IsVisibleToPlayer(pos.X, pos.Y) {
// Clear the cell this entity occupies, so it is the only glyph drawn there
for i := 1; i <= 2; i++ {
ui.ClearArea(cameraX, cameraY, 1, 1, i)
}
ui.PrintGlyph(cameraX, cameraY, appearance.Glyph, "", appearance.Layer)
}
ui.PrintGlyph(cameraX, cameraY, appearance.Glyph, "", appearance.Layer)
}
}
}
Expand All @@ -48,6 +51,11 @@ func (si SystemInput) Process() {
return
}

if key == ui.KeyZ {
// Make all Tiles visible
MakeAllVisible(gameMap)
}

// Fow now, just handle movement
if ecsController.HasComponent(player, PositionComponent{}.TypeOf()) && ecsController.HasComponent(player, AppearanceComponent{}.TypeOf()) {

Expand Down Expand Up @@ -84,3 +92,39 @@ func (si SystemInput) Process() {
}
}
}

type SystemSimpleAi struct {
ecsController *ecs.Controller
mapSurface *gamemap.GameMap
}

func (sas SystemSimpleAi) Process() {
// Process all entities that have the simple AI component attached to them
// For now, just have them print something
for _, entity := range sas.ecsController.GetEntitiesWithComponent(SimpleAiComponent{}.TypeOf()) {
//Act
if sas.ecsController.HasComponent(entity, AppearanceComponent{}.TypeOf()) {
// For the time being, just have the AI move around randomly. This will be fleshed out in time.
pos := sas.ecsController.GetComponent(entity, PositionComponent{}.TypeOf()).(PositionComponent)
dx := rng.RangeNegative(-1, 1)
dy := rng.RangeNegative(-1, 1)

var newX, newY int

if !sas.mapSurface.IsBlocked(pos.X + dx, pos.Y + dy) && !GetBlockingEntities(pos.X + dx, pos.Y + dy, sas.ecsController){
newX = dx + pos.X
newY = dy + pos.Y
} else {
newX = pos.X
newY = pos.Y
}

cameraX, cameraY := gameCamera.ToCameraCoordinates(pos.X, pos.Y)
ui.PrintGlyph(cameraX, cameraY, ui.EmptyGlyph, "", 1)


newPos := PositionComponent{X: newX, Y: newY}
sas.ecsController.UpdateComponent(entity, PositionComponent{}.TypeOf(), newPos)
}
}
}
15 changes: 14 additions & 1 deletion utils.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package main

import "github.com/gogue-framework/gogue/ecs"
import (
"github.com/gogue-framework/gogue/ecs"
"github.com/gogue-framework/gogue/gamemap"
)

// GetBlockingEntities returns true if there is an entity at the location, and that entity has the Blocking component
func GetBlockingEntities(x, y int, entityController *ecs.Controller) bool {
Expand All @@ -13,4 +16,14 @@ func GetBlockingEntities(x, y int, entityController *ecs.Controller) bool {
}

return false
}

// Debug Utils
// MakeAllVisible makes all map Tiles visible to the player
func MakeAllVisible(mapSurface *gamemap.GameMap) {
for x := 0; x < mapSurface.Width; x++ {
for y := 0; y < mapSurface.Height; y++ {
mapSurface.Tiles[x][y].Visible = true
}
}
}

0 comments on commit 87923a8

Please sign in to comment.