World
From Future Skill
Revision as of 10:08, 29 January 2024 by Henrik Rostedt (talk | contribs) (Created page with "This article goes through how to use the World library to create a topdown or isometric world. == Overview == The World library is split into two main parts: * Core function...")
This article goes through how to use the World library to create a topdown or isometric world.
Contents
Overview
The World library is split into two main parts:
- Core functionality, focused on flexibility and reuse
- Prefabricated content, focused on easy of use
This article will focus on the prefab content, but include enough core details to be able to take full advantage of the library.
The worlds created using this library are divided into descrete spaces, usually a square grid. So positions will be based on integer coordinates.
The prefabricated content supports both topdown and isometric perspectives, and it is very easy to switch between the two as needed. Additional perspectives are possible, but not supported by the prefab content.
All classes, types, and constants mentioned in this article can be imported from lib.world
.
Easiest way to get started is to pick up one of the templates at the end of the article, and then jump to relevant sections as questions come up.
Setup
To use the prefab content you will have to use the WorldSetup
class.
The following sections will assume that you have an instance of this class called setup
.
To start, just create the setup object like this setup = WorldSetup()
.
The class has the following attributes:
directions
-list[tuple[int, int]]
- The allowed movement directions, see directions, defaults to
CARDINAL_DIRECTIONS
basic
-bool
- If
True
will draw colored geometric shapes rather than the image-based tiles and walls perspective
-"topdown" | "isometric"
- The perspective to use for the prefab content, defautls to
"topdown"
resolution
-int
- The "unit size", the size of a tile in topdown, will be used for all content
block_corners
-bool
- If
True
will block diagonal movement next to a wall, should generally be enabled when usingSQUARE_DIRECTIONS
Here is an example of creating the setup object:
setup = WorldSetup( directions=SQUARE_DIRECTIONS, perspective="isometric", block_corners=True, )
Directions
You can specify which directions exist in the world, which will affect how actors can move.
By default only the cardinal directions exist:
Cardinal.LEFT
= (-1, 0)Cardinal.RIGHT
= (1, 0)Cardinal.UP
= (0, -1)Cardinal.DOWN
= (0, 1)
But these can be extended with diagonals:
Ordinal.LEFT_UP
= (-1, -1)Ordinal.RIGHT_UP
= (1, -1)Ordinal.LEFT_DOWN
= (-1, 1)Ordinal.RIGHT_DOWN
= (1, 1)
For convenience, the following direction list constants are available:
CARDINAL_DIRECTIONS
ORDINAL_DIRECTIONS
SQUARE_DIRECTIONS
You can specify any other set of directions you want, but it will seldom make sense to do so.
World
To create the world itself you can use world = setup.create_world()
.
This section will not go into detail of how the World
object works, that will be covered throughout this article.
Rendering
The world library can be used without rendering, but generally you do want to show the results.
When using the library you will initialize and make changes to the world in the setup_state
and update_state
methods.
Then you will want to render and re-render the world in the setup_canvas
/setup_view
and update_canvas
/update_view
methods.
When doing the initial render you can choose how the rendered element will behave:
- Use
world.render_resizable()
to get an element which dynamically scales the world to fit within the size of the element. Recommended for smaller worlds. - Use
world.render_scrollable()
to get a scroll area containing the world. The size of the world is based on theresolution
. Recommended for larger worlds.
No matter which way you create the initial element, you need to call world.render_changes()
when you want the world to be re-rendered. No need to do anything else.
See the templates section for examples of how to do this in practice.
Tiles
Worlds consist mainly of tiles, which are used to both define the walkable area of the world and the visual tiles the world consist of.
The library comes with prefabricated graphical tiles and has built-in support for plain colored tiles, but it is also possible to make custom tiles.
There are a few ways to add tiles to the world, which to use is partly up to need and partly up to preference.
First way is to include a tiles
argument to setup.create_world()
.
This can take the form of a dictionary with coordinates as keys and tiles as values:
world = setup.create_world(tiles={ (0, 0): "grass", (1, 0): "grass", (0, 1): "gravel", })
Alternatively the tiles
argument can be a string as described in tile map:
world = setup.create_world(tiles="gg\nr")
Second way is to use the World
methods:
world.add_tile(coordinate, data)
- Where
data
depends on what types of tiles you are using - For example adding a prefab tile:
world.add_tile((1, 1), {"name": "grass"})
- Or if using basic tiles:
world.add_tile((1, 1), {"color": "black"})
world.fill(top_left, bottom_right, data)
- Which will add the same tile to the area spanned by
top_left
andbottom_right
- For example:
world.fill((0, 0), (1, 1), {"name": "mud", "color": "tan"})
It is possible to get all tile coordinates using world.get_tile_coordinates()
.
And you can remove a tile using world.remove_tile(coordinate)
.
Prefab tiles
Constant | Name | Code |
---|---|---|
Tile.BLOCKED | blocked | b |
Tile.GRASS | grass | g |
Tile.GRASS_2 | grass_2 | G |
Tile.GRAVEL | gravel | r |
Tile.MUD | mud | m |
Tile.WATER | water | w |
Tile map
The tile map string is a conveninet way of specifying what tiles to use.
Each tile is specified using a code (see table in previous section), use .
to mark gaps.
To be able to add line breaks you will have to use triple "
for the strings.
Or you can use the special sequence \n
instead of adding a line break.
Leading and trailing whitespace is ignored, which allows indenting the tile maps.
Here are some examples:
# Example from main section ex1 = "gg\nr" # Equivalent to ex1 ex2 = """ gg r """ # An example of using gaps ex3 = """ .wr. mrrg mrgg .rg. """ # Equivalent to ex3, but hard to read ex4 = """.wr mrrg mrgg\n.rg"""
Walls
It is possible to place walls between tiles in the world, these will block movement between the tiles.
While tiles are identified using a single coordinate, walls are identified using a pair of coordinates. The pair is the coordinates of the tiles the wall is placed between. E.g. if the wall should be placed between tiles (0, 1) and (1, 1) then the coordinate pair is ((0, 1), (1, 1)).
Like tiles, the library comes with prefabricated graphical walls and support for plain colored walls. And it is possible to make custom walls.
Walls are added to the world in a similar way as tiles, except there is no equivalent to tile maps.
First way is to include a walls
argument to setup.create_world()
.
This takes the form of a dictionary with coordinate pairs as keys and walls as values:
world = setup.create_world(tiles={ ((0, 0), (1, 0)): "hedge", ((0, 0), (0, 1)): "stone", })
Second way is to use the World
method:
world.add_wall(coordinate_pair, data)
- Where
data
depends on what types of walls you are using - For example adding a prefab wall:
world.add_wall(((0, 1), (1, 1)), {"name": "hedge_2"})
- Or if using basic walls:
world.add_wall(((1, 0), (1, 1)), {"color": "orange", "height": 0.3})
And you can remove a wall using world.remove_wall(coordinate_pair)
.
Prefab walls
Constant | Name |
---|---|
Wall.BRICK_FENCE | brick_fence |
Wall.HEDGE | hedge |
Wall.HEDGE_2 | hedge_2 |
Wall.PICKET_FENCE | picket_fence |
Wall.STONE | stone |
Block corners
Due to the geometry of a square grid, you will generally only want to have visual walls between tiles in the cardinal directions.
However, when allowing diagonal movement (e.g. using directions=SQUARE_DIRECTIONS
) the characters can move diagonally across the corners.
This will look incorrect and not make intuitive sense for the users.
To solve this you can set block_corners=True
in the WorldSetup
or World
constructors.
This will automatically add invisible walls at the corners next to walls so that movement is blocked as expected.
Note that these walls must be manually removed if needed.