-
-
Notifications
You must be signed in to change notification settings - Fork 16
Looking around
This tutorial assumes basic knowledge of Unity engine and editor. You can follow it to get familiar with basic concepts of Osnowa, its code and the template game. While following it you're encouraged to have a look at code of classes that are mentioned. Just keep in mind you can spot undocumented or unfinished features since this project is in intensive development.
Notation:
something —> a Unity game object, can be found in the scene hierarchy
something
—> an object, class or symbol used in code
some asset —> a file you can find in the project explorer in Unity; can be previewed and edited in Unity's Inspector window
- when opened for first time, it'll take a while for Unity to import and compile stuff
- after it's done, open the scene named Scene in Assets folder.
- you can see almost an empty scene, just with a little sample of a tilemap.
Now GameScreensInitializer enables the NewGameScreen with the initial menu. The latter object contains MapGenerator.
In WorldGeneratorConfig asset you can modify the map size. Values that make most sense are between 100 and 1000. Frankly, the framework has been tested with square maps, but you can always try with rectangular ones.
What happens behind the scenes? You can see MapGenerator has dependent objects that are responsible for each step of map generation. The generators are run in order determined by their position on the list. Each of them uses ValueMap
object which basically is a wrapper around a two-dimensional float
array, usually using values between 0.0 and 1.0. For example for HeightMap the values indicate height and for VegetationMap they contain IDs of plants. The generators use some data from ValueMap
s from previous stages to fill their own ValueMap
s. ValueMap
s are used only for map generation process. The meaningful data from them is then transformed to some properties of OsnowaContext
that Osnowa is using in runtime. Here are maps in creation order:
-
HeightMap. It uses Perlin noise to create a sensible height map. You can play around with InitialHeightIngredientConfig and change the parameters. ValueToColor gradient let you modify the color depending on height. It calculates the value of
SeaLevel
inIExampleContext
based on the land ratio defined in the config. -
WaterMap. Based on the
SeaLevel
, it puts water tiles on positions that are below it. Keep in mind that "Putting tiles" is not about laying visible Unity tiles on the grid. During map generation the generators just fill two-dimensional arrays of positions with tile IDs. Real tiles will be generated when the player hits the Start button. - SoilMap. It just puts sand or soil tiles on the land based on the height above sea level.
-
VegetationMap. It creates plant tiles with a simple algorithm simulating multiple generations of spreading plants. It's configured in VegetationIngredientConfig asset and each plant has its config in its own asset, for example Tree1.
- first plants are spread randomly across whole map,
- then for a few iterations a score is calculated for each plant; if it's less than its
ScoreToDie
value, the plant dies, optionally leaving another plant in its place; if it's more thanScoreToSpreadSeeds
, duplicates of the plant are spread around its vicinity, - the score of each plant can be affected be several factors configured in its asset: soil type, height, amount of other plants in vicinity Rocks are also generated as "plants" and the effect is fine :)
-
WalkabilityMap. It just assigns 1 walkability for ground positions and 0 for non-ground positions. At the moment it doesn't matter because later the walkability will be recalculated basing on tiles. But you can modify the code to assign non-binary walkability, which can be useful for further stages. If you have varying walkability, you can for example create twisty roads by running SpatialAStar algorithm by
Pathfinder
class. - BuildingMap. It's work in progress. It's meant to generate some buildings around the map and place their tiles on it.
More about how tilemaps work in Osnowa here.
4. Start the game. After a while you can see the generated world together with your character. Again, it's good to understand what just happened.
-
WorldActorFiller
class was used to populate the world with the player and other actors. More about entity generation will be added to the docs soon. -
TilemapGenerator
usedTileMatricesByLayer
object inOsnowaContext
previously filled by the map generators to create and present actual Unity tiles. - Then the MenuMapGenerator's disabled itself by its script (revealing the created map) and activated some UI objects.
- The UI is mostly a stub. It's using old good Unity UI objects and components. Have a look at game objects being children of Player UI (for example SideBar) to find out how it's organised and how they interact with each other.
- The GameGrid object contains Unity Tilemaps — one for each layer. Layers are sorted basing on their OrderInLayer properties. Basically, Water tilemap is on the bottom and the tilemaps following it are more and more on the top. You can open Tile Palette window in Unity and use Select cursor in order to check what exact tiles are placed on given positions ("Focus on" select button can be helpful). You can even draw tiles if you create a tile palette, but keep in mind that walkability and light-passing properties of positions won't be automatically updated afterwards.
- GameGrid also contains Entities, which is a root of all GameObjects represeting Entitas actors in the game.
Have a look at Player object that should be at the top. It has EntityViewBehaviour
and EntityUiPresenter
components which are used for managing the Unity side of the entity. It also has Visuals child object which is used for displaying actor's sprite (Body) and UI.
- There is also a Game object collecting Entity_number objects that represent Entitas side of the entities. Click at Entity_0 which is the player entity (you can also just click the player with left mouse button in the Game window).
More about entities concept and ECS here.
In Unity inspector you can see all Entitas components attached to the player. Here is a description of some of them:
- Energy is used by Osnowa to give initiative (a turn) to entities. Basically, if an entity has more than 1.0 energy, it will be able to perform an action. Each action costs some energy, typically 1.0. After all entities capable of making actions have performed their turns, their Energy grows by EnergyGainPerSegment value (typically 1.0).
- PlayerControlled tells Osnowa that given entity is controlled by the player. If you take it from the player, it's entity will act on its own. If you assign it to other entity, you will control it.
- View component is a gate to entity's Unity representation which was described in c.
- Vision defines how far tha entity can see. If you change it for the player entity, it will affect your field of view. There is also EntitiesNoticed — a list of entities seen by the component holder.
- Skills tell the AI system what kinds of activities can the entity perform if it's not controlled by the player. More about AI here.
- Integrity is like health. If Integrity is less than MaxIntegrity, a health bar will appear above the entity. If it reaches 0, the entity will be destroyed. It's not called Health because it can also be used for entites like items.
- EntityHolder (work in progress) - used when an entity picks up another entity.
- Recipee - the name of the recipee used to create the entity. More about entity generation soon.
- Team (work in progress) - entities having different teams may be hostile for each other.
- Inventory (work in progress)
- BlockingPosition - work in progress; the desired behaviour is such: when an entity has this component and is tries to move, it shouldn't occupy a position where there is already another entity with BlockingPosition.
- You can use mouse scroll for zooming.
- Each you move is a single "action". Different actions have classes deriving from
GameAction
and contain some logic. - Notice that your field of view changes. High tiles like trees transform themselves to shorter variants if you come closer. Other entites disappear if they are no longer on visible positions. All of that is managed by
TilePresenter
using field of view calculated by BasicFovCalculator. - If you try to step on trees, rocks or water, you character just bumps. Technically,
JustMoveAction
is usingIGrid
interface to find out if the target position is walkable. Based on that, it plays proper animation and changes or doesn't change the player's Position component. The bump animation is using Unity's animation-related components. From code point of view, to play an animation for a (GameEntity
), you can use itsViewComponent
which hasIViewController
interface implemented byEntityViewBehaviour
. The latter acts as a Unity gateway for affecting the presentation part of the entity, for example to play an animation. - Try to attack another actor (just try to walk on it).
A few things happened now (all caused by calling AttackAction.Execute()
):
- The target received damage. Technically it was given a
ReceiveDamageComponent
which got picked byReceiveDamageSystem
. -
ReceiveDamageSystem
changed the value ofIntegrityComponent
of the target. It also calledTextEffectPresenter
to show a damage indicator on the screen. If the target had been non-aggressive until now, it now receivedAggressiveComponent
(also a message about that was written to the log on the sidebar by callingAddLogEntry()
onUiFacade
object). It won't attack you though (not in this version in Osnowa yet). -
IntegrityChangedSystem
detected what just happened and adjusted the health bar of the target. It also handled death of the target if the integrity fell to 0 or less.
Turn management in Osnowa is based on "energy" concept like in ADOM, explained well in Robert Nyström's article.
In short, entities may have Energy component which indicates their current energy and energy gain per turn (or “segment”). An entity with more than 1.0 energy can perform an action that consumes some of it (usually exactly 1.0 which means one typical turn). Then they have to wait unless they regain their energy again.
The TurnManager
class handles the game loop. Each time its Update()
function is called (it's basically like Update()
function of any Unity GameObject), it checks the entities with Energy component, chooses the ones that have more than 1.0 energy (they have EnergyReady
component) and gives them initiative, one by one.
An entity with initiative will use EntityController
to execute an action. If no action is executed (for example because the entity is controlled by player and it's waiting for input or because an animation of its previous action is still running), the initiative will NOT be passed to next entity. The process will be repeated in next Update()
calls.