From 87923a803e9643ea6e43bce17bef80195e5235d8 Mon Sep 17 00:00:00 2001 From: jcerise Date: Tue, 28 Apr 2020 00:08:43 -0600 Subject: [PATCH] Tutorial part 6: Populating the map --- components.go | 5 +++ gamedata/enemies.json | 86 +++++++++++++++++++++++++++++++++++++++++++ go.mod | 2 +- main.go | 61 ++++++++++++++++++++++++++---- systems.go | 52 ++++++++++++++++++++++++-- utils.go | 15 +++++++- 6 files changed, 208 insertions(+), 13 deletions(-) create mode 100644 gamedata/enemies.json diff --git a/components.go b/components.go index c109aec..c322828 100644 --- a/components.go +++ b/components.go @@ -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) } diff --git a/gamedata/enemies.json b/gamedata/enemies.json new file mode 100644 index 0000000..68bc487 --- /dev/null +++ b/gamedata/enemies.json @@ -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": {} + } + } + } +} \ No newline at end of file diff --git a/go.mod b/go.mod index 4949b1a..5cda928 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/main.go b/main.go index 740ff30..3501b0d 100644 --- a/main.go +++ b/main.go @@ -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" @@ -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() { @@ -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") @@ -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) @@ -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") diff --git a/systems.go b/systems.go index f210cd4..c8aad70 100644 --- a/systems.go +++ b/systems.go @@ -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" ) @@ -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) } } } @@ -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()) { @@ -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) + } + } +} diff --git a/utils.go b/utils.go index c320fab..5ba9f33 100644 --- a/utils.go +++ b/utils.go @@ -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 { @@ -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 + } + } } \ No newline at end of file