Difference between revisions of "Map Mode"
(→Concepts: - added section about the map state stack)
(→Other Map Files)
|Line 190:||Line 190:|
=== Other Map Files ===
=== Other Map Files ===
Below is a list of other files
Below is a list of other files to maps.
=== Classes ===
=== Classes ===
Revision as of 23:40, 15 August 2016
Map mode is responsible for a large portion of the game experience and the source code is the largest and most complex of any other game mode. Because of this size and the number of components in play to make map mode function properly, it is vital that we as keep this code organized so that it is easy to understand, maintain, and extend. The purpose of this page is to provide an overview of how this code works. Links to other pages of map mode documentation can be found below. These pages go into great detail about their inner workings and assume that the reader has a general understanding of map mode.
The rest of this page is broken up into three sections. First, we explain the concepts of map mode. Concepts are the fundamental ideas in the design of the map mode code and help the programmer to get an overview of the various elements that are found in Allacrost maps. The structures section goes into more specifics, providing a list and brief explanation of all classes and files in map mode, and how those structures are organized together. Finally we cover the operation of map mode. Here we describe the major steps that transpire when a map is loaded, updated, or drawn to the screen.
- 1 Concepts
- 2 Structures
- 3 Operation
The concepts of Allacrost maps that are important to understand in order to make sense of how the map code operates,and why it is designed in the manner that it is. Many of these concepts are intertwined with each other, which makes it difficult to explain them individually. First let's briefly outline them together as a group.
- The most common three visual elements found on maps are tiles, objects, and the GUI.
- The map environments (terrain, structures, etc.) are constructed by the map tiles.
- Each tile is 32x32 pixels in size and are stored in tileset images that hold 256 tiles each (512x512 pixels).
- Map objects are any entity on a map which are not stored in a tileset and they can be virtually any size.
- Map sprites are a type of map object which move around on the screen.
- All map objects have an invisible collision rectangle that is not allowed to overlap the collision grid or the collision rectangle of other objects
- Tiles and objects are organized into map layers. A layer can contain either tiles or objects, but not both. A map can have any number of layers.
- Map layers are drawn in a defined to construct the map and are always present on the screen.
- The GUI elements are drawn above all tiles and objects.
Once you've got a firm grasp on the above, read on for an overview of the more advanced concepts.
- An invisible collision grid determines where map objects may and may not go.
- Each grid element is 16x16 pixels, meaning there are four grid elements for each tile location on the map
- The grid state is set according to the properties of the tiles that cover each grid element.
- A map context is a different view within the same map (for example, the exterior of a town is context #1, and the interior of a house is context #2)
- Only one context is visible to the player at a time
- Every context has the same layers and layer ordering, but different tiles
- Likewise, every context effectively has its own collision grid
- Map objects can only exist in one context at a time, and are allowed to move between contexts
- A map has a minimum of one context and a maximum of 32 contexts
There's much more to maps than what was described, but this covers the core functionality of what elements are visible on the map and how those elements interact with one another. Read on to learn a little more about these specific concepts, as well as dialogues, events, zones, and other features.
Map mode uses a coordinate system that is 64.0 in length and 48.0 in height. In a 1024x768 resolution, this system divides the screen up into 16x16 pixel squares, the same dimensions as the collision grid. The coordinates increase from left to right and from top to bottom, so the coordinates of the top left and bottom right corners of the screen are (0.0, 0.0) and (64.0, 48.0) respectively. Having the coordinate system correspond to the same dimensions of the collision grid greatly simplifies many drawing operations.
A separate 1024x768 coordinate system is used for drawing GUI elements on the map, such as the stamina meter and the dialogue window.
All tiles are 32x32 pixel images that are extracted from a larger tileset image. Each tileset image is 512x512 pixels and can therefore hold 256 tile images. Both tiles and tilesets do not deviate from these styles. Both images use the PNG image format and native transparency is used. Each tileset image has a corresponding Lua file created called a tileset definition file (TDF). The TDF defines a number of properties about the images in the tileset, including which of the four quadrants are collidable (can not be walked on) and the frames and timings of animated tiles.
A map has at least one layer of tiles, and almost always have several. A tile image can be drawn on any layer and at any position on the layer. Most maps employ either three or four tile layers, depending on the needs of the map. As a typical example, a map may have a "ground" tile layer to place the surfaces that sprites and other objects are standing upon, a "detail" layer to place embellishments on the ground such as flowers or stones, and an "upper" layer for tiles that are to be drawn above sprites, like the tops of trees and buildings. The number and ordering of both tile and object layers is defined by the map script, so any combination of layers is possible. Most tiles are static, but some may be animated. Animated tiles have all their frames in the same tileset image and are built when the tileset is loaded.
One unique property to tile layers is that a tile layer may have all of it's collision data disabled. That is, any tiles placed on such a layer are declared to be fully un-collidable, even if they have collision data set in their corresponding TDF. This is done so that tiles that exist in a layer above objects (the tops of buildings, for instance) can be walked on, since the tiles are drawn over the objects instead of underneath them.
Objects encompass all tangible map entities which are not tiles. There are many types of map objects and include sprites, treasures, or stationary structures such as a statue. Each object's position is represented as the X/Y coordinates for the bottom-center point of the object. All objects can only exist in a single map context at a time, but may change contexts during the course of a map.
Objects have two rectangles aligned to their bottom-center coordinate: an image rectangle for the object's graphics, and an invisible collision rectangle. These two rectangles are not always the same size, and often the collision rectangle is made slightly smaller than the image rectangle. The size of these rectangles is normally static throughout the course of the map, but may be modified under different circumstances. The object holds the height and half-width of both of these rectangles, allowing us to compute all of the boundaries for each. There is no limit to the size of objects.
Specific types of objects have a number of different properties that determine their behavior on a map. All objects have these three base properties. - Updatable: objects can only update if they have this property enabled. Updating an object usually involves changing their position during movement or which animation frame to draw - Visible: objects can only be seen by the player when this property is enabled - Collidable: if disabled, this object does not collide with anything and it turns off any collision detection with other objects as well
Like tiles, map objects also exist on layers. Whereas a tile layer contains only tiles, an object layer contains only objects. A map may contain any number of object layers, but is guaranteed to have at least one. Objects may transition from one layer to another through events or movement on the map, such as walking up a staircase. Objects can only collide and interact with one another if they are on the same layer. Most commonly, object layers are ordered in between tile layers, so that objects are drawn above elements on the ground, but are covered by structures such as archways and rooftops.
An overview of the various object classes is provided below. Sprites are a common type of map object seen on a map, representing the player, NPCs, and enemies. The size of a typical adult human sprite is 32x64 pixels, or one tile wide and two tiles tall.
|MapObject||An abstract base class inherited by all other object classes|
|PhysicalObject||A simple and stationary object, such as a statue|
|TreasureObject||A stationary object (like a treasure chest) that contains drunes and/or inventory for the player to procure|
|VirtualSprite||A mobile object with no image data that is never seen by the player|
|MapSprite||A mobile object that can be animated and interacted with through dialogue or other means|
|EnemySprite||A mobile object representing an enemy, which may causes a battle to occur should the player's sprite come into contact with it|
The map camera is always focused on a single map object. The camera is nothing more than a pointer to an active map object. Each map screen is drawn such that the focus is on whatever object the camera is pointing to, which is most often the character sprite that the player is controlling. The camera attempts to keep that object in the center of the screen, but if the object is too close to the edges of the map then the camera adjusts so that the view does not show any part of the screen beyond the map boundaries.
Each map has a special VirtualSprite object (called the virtual focus) created to serve as a focal point for the map when the camera needs to focus on an arbitrary location where no visible sprite exists. This is useful when trying to pan the map to observe a far away event that occurs off the screen from the current camera location. While setting the camera to focus on a specific object is simple to do, it can be visually jarring to the player for the camera to instantly jump to a new position. Therefore we often use a timed move, which is instructing the map code to move the camera to a new object over a defined period of time. The map code will then automatically handle a smooth movement from the old camera focus to the new focus.
Treasures are a type of object and come in two varieties. The first type of treasure is a visible entity on the map, such as a chest, that the player can walk up to and interact with to open it and procure its contents. The second type is completely invisible to the player, except for an occasional sparkle animation that is displayed. When the player interacts with either type, a small menu window pops up listing the contents of the treasure.
TODO: Audio sources have not yet been implemented in maps
MapMode maintains its own stack of states that a map may be in. The behavior and visuals of a map changes with the stack. The standard state of a maps is called "explore", where the player has control of a sprite and can move them around, interact with other objects on the map, or bring up the party menu. Another common state is "scene", which looks exactly the same as explore, but ignores some user inputs so that the player has no control and is more or less a spectator. There are also states for "dialogue" and "treasure", which indicate that processing of the game logic and user inputs should be passed to the supervisor classes in control of that functionality.
Every map has two different groups of records that are maintained: local records and global records. A record is nothing more than a string and signed integer pair, and they are used in various ways to record things that have occurred on the map. This could be anything from the player selecting a certain option in a dialogue to a counter for how many treasures have been discovered. The string identifying the record must be unique among all other records stored in the group.
The major difference between the global and local record is persistence. Local records are volatile and get destroyed as soon as the map mode instance is deleted. Global records are retained in the GlobalManager code and are written to the save file when the user saves the game. These records are then reloaded whenever the map is revisited. The MapMode code automatically creates some global records on its own. For example, it saves the number of times a player has seen a particular dialogue, or to determine whether or not a player has already retrieved a treasure.
Map zones describe an area of the map. The programmer can create a map zone on a granularity of 16x16 square pixel elements (the same as the collision grid) and they may be of any size and shape that can be described in rectangular sections. The sections are allowed to overlap, although it is typically inefficient for them to do so. Map zones are used for a variety of purposes. One such purpose is to trigger scripted events, such as when the player moves their character sprite over a visible floor switch. Enemy zones are used defines the area where enemy sprites will spawn and roam on a map.
Dialogues are an important part of maps as they are the primary means through which the player interacts with other characters and learns the information needed to proceed through the game. A single dialogue window is drawn on the bottom of the screen when a dialogue is active. Dialogues are designed in such a way that they do not need to flow linearly from one line to the next. Dialogues may also be injected with options to present to the player for them to select. The set of options allow the dialogue to "branch" in different directions based upon the option that the player selected. Every line and every option in a dialogue may be configured to either set records or launch map events once they are read or .
Every line in a dialogue must have a speaker, which is a sprite. The name and portrait image of the sprite, if available, are displayed for the speaker of the current line. Dialogues can occur between any number of speakers and most have at least two. Dialogues can be attached to any map sprite, and doing so means that if the player speaks to that sprite, the dialogue is activated. If the sprite has more than one attached dialogue, then they are cycled through on each initiation of speaking with the sprite. When a sprite has a dialogue attached, a "talk" icon will appear above the sprite if the player sprite is close enough to the sprite.
Dialogues additionally have the ability to set the maximum number of times that it may be viewed before becoming unavailable. This is used, for example, for dialogues that we only want the player to be presented with a single time. Once a dialogue becomes unavailable, unless the max view count is increased or the view counter is reduced. We may use this feature to initially set a dialogue with a maximum view count of zero, and change it to a non-zero value after particular conditions are met.
Map events are the fundamental mechanism by which sequences of actions take place on the map. You can think of event as a small component in a sequence of changes that need to take place to display a certain scene. For example consider a sequence where: the direction a sprite is facing is changed, another sprite is walked to a new destination, a dialogue begins, a sound is played, the context of the player's sprite is modified. Each of these would be represented as a MapEvent, and then all the events are chained together in a sequence. All events have a start function and an update function. The start function is executed only once when the event begins. The update function runs on every update of the game loop until the defined conditions are met for the event to end.
Event sequence are constructed by adding links to the individual map events. When a map event is started, it is examined to see if it contains any links to other events, and those linked events are started at the appropriate time. Events can be started either at the same time as the parent event, or when the parent event finishes. You can also add a wait time to delay the start of a linked event. Event links can be constructed in such a way that the sequence loops, or an event may even link itself so the single event repeats indefinitely. This great flexibility carries with it some risk of unintended loop sequences, so event links should be added with care. Any number of event sequences may be active at any time.
There are a number of different event classes, all deriving from the abstract MapEvent class. Some of the most common event classes include playing a sound, instructing a sprite to move to a certain destination on the map, and changing one or more properties of a sprite. There is also a custom event class that allows its start and update functions to be implemented entirely in Lua.
Map contexts are one of the more complicated and unique concepts of Allacrost maps. A map context is essentially "a map within a map" without having to create and load and entirely different map. For example, the outside area of a town may be one context, while the interior of a home in that town may be another context. Contexts allow us to quickly and seamlessly transition to a different area of the map. Sprites and other map objects are allowed to traverse between one context and another through context switch points, such as the doorway to a home. Each map must have at least one context and can have a maximum of 32 contexts. The first context in a map is called the base context. Each context has its own separate tile grid and collision grid. This means that when switching from one context to the next, all the map tiles visible on the screen will be changed to represent the view of the new context.
Map objects exist on exactly one context and objects can not interact with one another unless they are in the same context. The context of the object that is pointed to by the map camera is called the active context. Therefore changing the map camera to point to a different object may change the context that is visible on the screen if that object exists in a different context. It is not uncommon for a map sprite to change their context multiple times on a map, while stationary map objects will likely never change contexts.
One final feature of contexts is the context inheritance. If a context inherits from one another, this means that it uses all the tiles from the inheriting context in its own tile grid, then overwrites some of the tiles to create a derivation of that original tile grid. For example, consider a map with a building that appears normally, then gets destroyed after a certain event. Without context inheritance, the map maker would have to recreate the entire area of the map for the second context, instead of simply changing only the tiles that are applicable to the area of the building. In this way, context inheritance greatly expedites map design.
Now with an understanding of the concepts behind map mode concepts, lets discuss how we represent and implement them in the code.
The highest level class in the map code is the MapMode class, which derives from the GameMode class defined in the mode management engine. Because there is so much to manage and process for each map, there are a number of "supervisor" classes that handle a specific task set so that the MapMode class can remain a reasonable size. The MapMode class creates an instance of each of these supervisor classes upon construction. The table below lists all supervisor classes and what types of data and operations that they are responsible for. Additional data and responsibilities of the map code not listed in the table are usually managed by the MapMode class directly.
|Supervisor Class Name||Data and Responsibilities|
|TileSupervisor||Holds all map tile data and is responsible for all tile update and drawing operations|
|ObjectSupervisor||Holds the collision grid and manages all map objects. This including collision detection, pathfinding, and drawing of objects|
|DialogueSupervisor||Holds all dialogue data and handles all dialogue processing processes|
|EventSupervisor||Responsible for the management of processing all map events and event links|
|TreasureSupervisor||Displays the contents of procured treasures in a GUI window and manages user input while this window is active|
As is the case with any complex game mode, the code, graphics, and visual assets are spread across multiple files and directories. Every map in Allacrost is defined through two Lua files, named the map data file and map script file.
The C++ code is located in src/modes/map/. The main files are map.h and map.cpp, which implement the MapMode class and do the highest level map processing. Usually this means determining what state the map is in and calling the appropriate supervisors to handle updates.
Map Data File
The map data file (MDF) is generated completely by the Allacrost editor. The MDF defines the dimensions of the map, the number of contexts on the map, the number of tile layers, what tilesets are used by the map, and where the tiles are located for each context. MDFs can be shared between multiple map script files to add different objects, events, and other map content. Map data files are located in lua/data/maps/. See Map_File if you wish to learn more information about the structure and contents of the MDF.
Map Script File
The map script file (MSF) defines all other content on the map that the MDF does not, and also implements custom code written for a specific map. There are very few requirements for the structure of MSFs, as they only need to define which MDF the script uses, the ordering of the tile and object layers, and a small number of basic functions (load, update, draw) that every map must have. The load function is run only once when the MapMode instance is created. The update function is called by the main MapMode execution loop every frame. And likewise the draw function is called by MapMode every frame to handle any custom graphics or other effects.
The short list of requirements for MSFs allow them to be extremely flexible in how they structure their data and operations. However, most MSFs should follow a common set of organizations to make it easier to understand maps and know what routines are called when and where data is defined. Map script files are located in the directory lua/scripts/maps/ and typically follow a naming format similar to "a01_capital_attack.lua". The 01 specifies which chapter of the game the map is built for, and the 'a' exists because the script filename is used as a key in looking up map record data, and these keys can not begin with a number.
Other Map Files
Below is a list of other files and directories with a strong relation to maps.
|File Name||Class Name||Class Purpose|
|map||MapMode||Handles the game execution while the player is exploring maps|
|map_dialogue||MapDialogue||Represents dialogues between characters on a map|
|MapDialogueOptions||A container class for option sets presented in dialogue|
|DialogueWindow||A display window for all GUI controls and graphics necessary to execute a dialogue|
|DialogueSupervisor||Manages dialogue execution on maps|
|map_events||EventLink||Small container class holding information about how an event is linked to one of its child events|
|MapEvent||Abstract class that represents a single event that occurs on a map|
|DialogueEvent||Event which activates one of the map's dialogues|
|ShopEvent||Causes the player to enter a newly created instance of ShopMode|
|SoundEvent||Plays a sound file exactly one time|
|MapTransitionEvent||Causes a new map to be loaded, ends the current map, and sends the player to the new map|
|BattleEncounterEvent||Creates a BattleMode object and sends the player off to fight the battle|
|CustomEvent||An event that is scripted entirely in Lua|
|SpriteEvent||Abstract class that performs an action on a single sprite|
|ChangePropertySpriteEvent||Changes one or more properties of one or more sprites (such as visibility, direction, etc)|
|AnimateSpriteEvent||Displays a specific animation for the sprite|
|RandomMoveSpriteEvent||Moves a sprite in a random direction for a certain amount of time|
|PathMoveSpriteEvent||Moves a sprite from their current location to destination coordinates using a computed path|
|CustomSpriteEvent||A sprite event that is scripted entirely in Lua|
|EventSupervisor||A helper class to MapMode responsible that manages, processes, and launches all map events|
|map_objects||MapObject||An abstract class that represents objects on a map|
|PhysicalObject||Represents visible objects on the map that have no motion|
|TreasureObject||Represents an obtainable treasure on the map which the player may access|
|ObjectLayer||Represents a layer of objects on the map|
|ObjectSupervisor||A helper class to MapMode responsible for management of all object and sprite data|
|map_sprites||VirtualSprite||A special type of sprite with no physical image|
|MapSprite||A mobile map object with which the player can interact with|
|EnemySprite||A mobile map object that induces a battle to occur if the player touches it|
|map_tiles||MapTile||Represents a single image tile on the map|
|TileLayer||An object to represent one of the draw layer of map tiles|
|TileSupervisor||A helper class to MapMode responsible for all tile data and operations|
|map_treasure||MapTreasure||Represents a treasure on the map which the player may access|
|TreasureSupervisor||Displays the contents of a discovered treasure in a menu window|
|map_utils||MapRectangle||Represents a rectangular section of a map|
|MapFrame||Retains information about how the next map frame should be drawn|
|MapLayer||An abstract class representing a single draw layer of map visuals (tiles or objects)|
|PathNode||Defines coordinates and path scoring, used in finding paths for sprites to travel|
|MapRecordData||Used for holding data to be set into either the global or local map records|
|MapEventData||Used for holding data related to launching map events|
|map_zones||ZoneSection||Represents a rectangular area on a map|
|MapZone||Represents a zone on a map that can take any shape|
|EnemyZone||Represents an area where enemy sprites spawn and roam in|
|ContextZone||Represents an area where the active map context may switch|
The MapMode class has a state member that determines how to update and draw the map and how to process input commands from the player. The current state also determines which of the supervisor classes should be called to update the state of the map and draw graphics to the screen. The MapMode class state member can be set at any time from anywhere in the map code or map script file. The table below lists all map states, the active supervisors in each (active indicating that it has update/draw calls being made to it by MapMode), and an explanation of each state.
|Supervisor Class Name||Data and Responsibilities|
|Explore State||TileSupervisor, ObjectSupervisor||The default map state where the player is free to explore and interact with the map|
|Dialogue State||TileSupervisor, ObjectSupervisor, DialogueSupervisor||Active when a dialogue is taking place. The dialogue window is present on the screen during this state.|
|Treasure State||TileSupervisor, ObjectSupervisor, TreasureSupervisor||Active after the player has procured a treasure. The treasure menu is present on the screen during this state.|
Typically a map starts in the explore state, although this is not always the case. The other states are entered from the explore state by the user triggering an interaction with the map either intentionally (by talking to a NPC sprite) or unintentionally (by moving their character over a hidden switch). The state that is triggered will run its course and then usually returns to the explore state, although it may choose another state to put the map in once it is finished. As a quick example, imagine we are in the explore state when the user talks to a nearby NPC, moving the map to the dialogue state. In the middle of the dialogue, we may be sent to the event state due to a sudden occurrence like an explosion. A second dialogue to occur immediately after the event, sending the map back into the dialogue state before finishing and returning to the explore state.
To create and load a new map instance, first a new MapMode object is created. The MapMode constructor requires a string argument, which represents the name of the map file. The constructor will verify that it can access and open the map file and then create instances of all supervisor classes as well as any miscellaneous setup for its other members. The MapMode class will extract the basic properties from the map, such as the name of the map and the number of contexts it uses, load all audio files used on the map, and create objects to represent each type of enemy that the player may encounter on the map. The supervisor classes may also have their Load functions called if available to extract their relevant data from the map file. Finally, MapMode will call the Load function defined in the map file. This allows each map to perform any custom load operations that may be necessary, such as changing the state of map objects or dialogues from their defaults.
The TileSupervisor has a particularly complex load function. It has to open every tileset definition file for every tileset used by the map. It first determines which tiles of the tileset are used and which should be discarded (so that we don't waste video memory with tile images that will not be used). It also determines if any tiles are animated and collects all the animation frames and timing into a single animated image. And then it must perform a somewhat complex index translation so that the map tiles are referencing the correct images in the image vector. The ObjectManager has a much easier task of loading the collision grid from the map file data.
Load operations for map can take a long time (a few seconds) depending on the size of the map. To mask this latency, we perform a couple tricks. First, we do a gradual fade to blackness out of the current map to buy us some time. Second, we have the ability to pre-load maps in a different thread while the current map is active. So if the player exits the current map to another, we may already have the map pre-loaded and ready to be used immediately.
The steps that the Update function performs vary based on the current map state. In general, the following operations are performed in this order.
- Call the map file's Update script function
- Accept and process user input
- Update the display status of all animated tiles
- Update all zones and objects on the map
User input is handled in multiple places in the map code. In the explore state, it is handled by the MapMode class. In the dialogue and treasure states, it is handled in the DialogueSupervisor and TreasureSupevisor class respectively. The most complex portion of the update operation is that of updating all map sprites. Map sprites in motion have to be constantly re-examined for collision detection and to process the next action in the case of sprites not controlled directly by the player. We also have to figure out if their path is blocked and needs to be re-computed, which direction they are facing and which animation should be drawn for that sprite in the next frame display. Each map object has its own implementation of an Update function and each object is updated in significantly different ways.
Before any draw operations begin, the very first thing that the draw code does is to figure out the position of the map camera. This information allows the draw code to know which tiles and objects would be visible on the screen should they be drawn. After this information is determined, the map file's Draw function is called. Usually this will do nothing more than draw the tile and object layers, although it may do additional drawing operations. For example, a map with fog would require the script to call a function in the video engine to generate that visual effect. After the map file completes its draw code, all active GUI displays are drawn.
The standard order of draw layers in map mode is below. Note, however, that a map script does have the ability to draw these layers in any order that it wishes (very rare) or insert other draw calls in between any of the draw layers (not uncommon). Recall that ground objects are drawn in two passes to allow them to go over and under certain structures.
- Tiles: lower layer
- Tiles: middle layer
- Objects: ground layer (pass 1)
- Objects: pass layer
- Objects: ground layer (pass 2)
- Tiles: upper layer
- Objects: sky layer
MapMode is really the cornerstone mode of operation for the game. Most other game modes are entered from map mode and they return control of the game to map mode when they are finished. The table below illustrates how map mode is entered from other game modes and under what conditions it exits to other game modes.
TODO: create the table showing battle mode, menu mode, shop mode, boot mode, map mode, pause/quit mode transitions.