Class VisionFramework
java.lang.Object
com.github.yellowstonegames.grid.VisionFramework
- All Implemented Interfaces:
com.github.yellowstonegames.core.ISerializersNeeded
- Direct Known Subclasses:
VisionFrameworkRgb
public class VisionFramework
extends Object
implements com.github.yellowstonegames.core.ISerializersNeeded
A "one-stop shop" for handling field-of-view, line-of-sight, (colorful) light sources, and more.
Encapsulates currently-visible and previously-seen cell data, and allows updating and modifying light levels/colors.
This controls a
After creating a VisionFramework, You can call
To recap, the methods here get called in this order:
This class uses the Oklab color space throughout. Use
LightingManager, which manages LightSource objects. This tracks which cells on a
place-map grid have been seen before, which are currently visible, which just became visible and which just became
hidden, among other things. This tracks a map as a char[][], typically but not always using box-drawing
characters, but these chars can merely be used as abbreviations for what graphic to display in a non-text-based game.
This modifies the box-drawing characters it is given based on which walls are actually visible.
After creating a VisionFramework, You can call
restart(char[][], CoordFloatOrderedMap, int) when you have a
2D char array to use as the map and one or more viewer positions. After that, add light sources to lighting.
Then you can render the map "background" colors by calling update(float) (every frame) and getting the
colors/opacities for anything that can move around (monsters, some traps or effects) using
getForegroundColor(int, int, float) (also every frame, once per creature/effect). The update() and
getForegroundColor() methods take a float, millisSinceLastMove, which is measured in milliseconds since the last
action the player took; this allows cells to fade into or out of view over a short period of time. The background
colors are available at backgroundColors once update() has been called. When the player character or
characters change position (or any special vision the player has moves, like a remote camera), call
moveViewer(Coord, Coord). You probably want to move any light source a character carries immediately before
calling moveViewer(), so the new position shows the changed lighting right away; moving lights uses
LightingManager.moveLight(Coord, Coord). When a small part of the world changes, call
editSingle(Coord, char) (such as for a door opening), or editAll(char[][]) if a large
part of the world changes at once. After any viewer, light source, or map feature changes (or several if they all
changed at once), you should call finishChanges(), which updates prunedPlaceMap with the actually
visible map cells, handles lighting changes, and so on.
To recap, the methods here get called in this order:
- Call
restart(char[][], Coord, float)when an area map is loaded, giving it the viewer(s) and their vision range(s). - Call
LightingManager.addLight(Coord, Radiance)onlightingfor every light source, or otherwise addLightSourcevalues to thatLightingManager. - Every "turn" (when an input is entered), call
LightingManager.moveLight(Coord, Coord)if a light source moved. - Every "turn" (when an input is entered), call
removeViewer(Coord)if a viewer was removed. - Every "turn" (when an input is entered), call
moveViewer(Coord, Coord)if a viewer moved. - Every "turn" (when an input is entered), call
editSingle(Coord, char)if a map cell was altered (such as a door opening). - Every "turn" (when an input is entered), call
editAll(char[][])if the whole current map was altered (such as going downstairs to a new area). - Every "turn" (when an input is entered), if any of the previous every-turn methods was called, call
finishChanges()to complete the change. - Every frame, call
update(float), passing it the number of milliseconds since the last turn was handled (this number can be altered). - Every frame, call
getForegroundColor(Coord, float)for every position with a moving creature or object in it, passing it a position to query and the number of milliseconds since the last turn was handled (this number can be altered). - You can get the current colors every frame from
backgroundColors, which update() changes.
This class uses the Oklab color space throughout. Use
VisionFrameworkRgb if you prefer RGBA colors.-
Field Summary
FieldsModifier and TypeFieldDescriptionint[][]The background color tints; these are the finished colors produced byupdate(float).A small rim of cells just beyond the player's vision that blocks pathfinding to areas we can't see a path to.protected final RegionUsed as temporary storage by methods that can reuse a Region to avoid allocation.Contains only the cells that have a value greater than 0 inlighting.fovResult, meaning they are visible right now.All cells we were able to see, but just became hidden on this turn or short period of time.All cells we just became able to see on this turn or short period of time.Handles all light sources, with varying colors, strengths, and flicker/strobe patterns.char[][]The place map using box-drawing characters or'#'for walls, and any other chars for other terrain.All cells that became visible for the first time on this turn or short period of time.intThe y-size of all 2D arrays here (the second index).intThe x-size of all 2D arrays here (the first index).int[][]The background color tints from just before the latest change; these are used to handle transitions out of view, when a cell has just become hidden.float[][]The value thatlighting.fovResultheld in the previous turn or short period of time.char[][]The same aslinePlaceMap, but with any branches of walls that can't be seen trimmed off to only show what is actually visible given the current field of view and the cells seen earlier.intThe int color used to tint cells that could have been seen previously, but aren't currently visible.All cells that we have seen in the past, on this place map.Maps the positions of "things that can view the map for the player" to how far each of those things can see. -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionvoideditAll(char[][] newPlaceMap) Fully replaces the contents oflinePlaceMapwith those ofnewPlaceMap, and changes the light resistances for the whole place map inlighting.voideditSingle(int newX, int newY, char newCell) Changes the char atnewX,newYto benewCelland adjusts the light resistance for that cell only inlighting.voideditSingle(Coord position, char newCell) Changes the char atpositionto benewCelland adjusts the light resistance for that cell only inlighting.booleanvoidThis completes the changes started bymoveViewer(int, int, int, int),editSingle(int, int, char), oreditAll(char[][])and updates the lighting according to those changes.intgetForegroundColor(int x, int y, float millisSinceLastMove) For a "foreground" creature or effect that can move between cells, call this every frame to get the color to draw that thing with.intgetForegroundColor(Coord pos, float millisSinceLastMove) For a "foreground" creature or effect that can move between cells, call this every frame to get the color to draw that thing with.inthashCode()booleanmoveViewer(int oldX, int oldY, int newX, int newY) If a viewer is present atoldX,oldYinviewersand no view is present atnewX,newY, this moves the viewer tonewX,newYand returns true.booleanmoveViewer(Coord previousPosition, Coord nextPosition) If a viewer is present atpreviousPositioninviewersand no view is present atnextPosition, this moves the viewer tonextPositionand returns true.voidputViewer(int x, int y, float viewRange) Adds a viewer toviewerswith the given viewing distance, if there is no viewer already present atx,y.voidAdds a viewer toviewerswith the given viewing distance, if there is no viewer already present atposition.booleanremoveViewer(int x, int y) If a viewer is present atx,yinviewers, this removes that viewer and returns true.booleanremoveViewer(Coord position) If a viewer is present atpositioninviewers, this removes that viewer and returns true.voidrestart(char[][] place, CoordFloatOrderedMap viewers) Some form of restart() must be called when the map is first created and whenever the whole local map changes.voidrestart(char[][] place, CoordFloatOrderedMap viewers, int baseColor) Some form of restart() must be called when the map is first created and whenever the whole local map changes.voidSome form of restart() must be called when the map is first created and whenever the whole local map changes.voidSome form of restart() must be called when the map is first created and whenever the whole local map changes.toString()voidupdate(float millisSinceLastMove) Updates the lighting effects and writes tobackgroundColorsso the current tint color is stored in every cell of backgroundColors, for every cell in the place map.
-
Field Details
-
placeWidth
public int placeWidthThe x-size of all 2D arrays here (the first index). -
placeHeight
public int placeHeightThe y-size of all 2D arrays here (the second index). -
linePlaceMap
public char[][] linePlaceMapThe place map using box-drawing characters or'#'for walls, and any other chars for other terrain. In most roguelikes, there would be one of these per dungeon floor. -
prunedPlaceMap
public char[][] prunedPlaceMapThe same aslinePlaceMap, but with any branches of walls that can't be seen trimmed off to only show what is actually visible given the current field of view and the cells seen earlier. In most roguelikes, there would be one of these per dungeon floor. -
lighting
Handles all light sources, with varying colors, strengths, and flicker/strobe patterns. -
previousLightLevels
public float[][] previousLightLevelsThe value thatlighting.fovResultheld in the previous turn or short period of time. In most roguelikes, there would only need to be one of these variables at a time. -
backgroundColors
public int[][] backgroundColorsThe background color tints; these are the finished colors produced byupdate(float). These colors are overwritten every time update() is called.
This uses packed Oklab int colors, which avoid the overhead of creating new Color objects. UseDescriptiveColorto get, describe, or create Oklab int colors. You can convert to RGBA8888 (which libGDX uses) withDescriptiveColor.oklabIntToFloat(int)if you want a "packed float color" that can be given to libGDX directly, or to a Color usingColor.rgba8888ToColor(changingColor, DescriptiveColor.toRGBA8888(oklabColor)).
In most roguelikes, there would be one of these per dungeon floor. -
previousBackgroundColors
public int[][] previousBackgroundColorsThe background color tints from just before the latest change; these are used to handle transitions out of view, when a cell has just become hidden. -
blockage
A small rim of cells just beyond the player's vision that blocks pathfinding to areas we can't see a path to. In most roguelikes, this would be temporary and only one would need to exist in total. -
seen
All cells that we have seen in the past, on this place map. In most roguelikes, there would be one of these per dungeon floor, and it would persist. -
justSeen
All cells we just became able to see on this turn or short period of time. In most roguelikes, this would be temporary and only one would need to exist in total. -
justHidden
All cells we were able to see, but just became hidden on this turn or short period of time. In most roguelikes, this would be temporary and only one would need to exist in total. -
newlyVisible
All cells that became visible for the first time on this turn or short period of time. These cells will be fading in from fully-transparent, rather than from a previously-seen gray color. -
inView
Contains only the cells that have a value greater than 0 inlighting.fovResult, meaning they are visible right now. -
buffer
Used as temporary storage by methods that can reuse a Region to avoid allocation. -
viewers
Maps the positions of "things that can view the map for the player" to how far each of those things can see. In a traditional roguelike, there is probably just one viewer here unless the game includes remote viewing in some form. In a party-based game, each member of the exploration party is probably a viewer. -
rememberedColor
public int rememberedColorThe int color used to tint cells that could have been seen previously, but aren't currently visible. In VisionFramework, this defaults to the Oklab int color0xFF7F7F50, which is fully opaque, pure gray, and has about 30% lightness. You can get Oklab int colors usingDescriptiveColor. In VisionFrameworkRgb, this defaults to the RGBA8888 color 0x505050FF, which is also fully opaque, pure gray, and has about 30% lightness. You can get RGBA8888 int colors usingDescriptiveColorRgb.
-
-
Constructor Details
-
VisionFramework
public VisionFramework()The empty constructor. You can callrestart(char[][], CoordFloatOrderedMap, int)when you have a 2D char array to use as the map and one or more viewer positions. See theclass docsfor more usage information.
-
-
Method Details
-
restart
Some form of restart() must be called when the map is first created and whenever the whole local map changes.
This overload simplifies the viewers to just the common case of one viewer, the player character. You can specify anfovRangefor how much the player can see without a light source, and you can also choose to add a light at the player's position withlightingand itsLightingManager.addLight(Coord, Radiance)method. Remember to move the player withmoveViewer(Coord, Coord)and any light they carry withLightingManager.moveLight(Coord, Coord). If the player's FOV range changes, you can update it withputViewer(Coord, float)using the player's current position.- Parameters:
place- a 2D char array representing a local map;'#'or box drawing characters represent wallsplayerPosition- where the one viewer will be put in the place; must be in-bounds for placefovRange- how far, in grid cells, the player can see without needing a light source
-
restart
Some form of restart() must be called when the map is first created and whenever the whole local map changes.
This overload simplifies the viewers to just the common case of one viewer, the player character. You can specify anfovRangefor how much the player can see without a light source, and you can also choose to add a light at the player's position withlightingand itsLightingManager.addLight(Coord, Radiance)method. Remember to move the player withmoveViewer(Coord, Coord)and any light they carry withLightingManager.moveLight(Coord, Coord). If the player's FOV range changes, you can update it withputViewer(Coord, float)using the player's current position.
This overload allows specifying abaseColorthat will be used cells that were seen previously but can't be seen now; typically this is dark gray or very close to that, and it is an Oklab int color as produced byDescriptiveColor. The default, if not specified, isrememberedColor.- Parameters:
place- a 2D char array representing a local map;'#'or box drawing characters represent wallsplayerPosition- where the one viewer will be put in the place; must be in-bounds for placefovRange- how far, in grid cells, the player can see without needing a light sourcebaseColor- the packed Oklab color used for previously-seen, but not in-view, cells
-
restart
Some form of restart() must be called when the map is first created and whenever the whole local map changes.
This overload allows having one or more viewer positions at the start, with all viewers sharing seen information with each other (enemy characters are not generally viewers here). Each Coord position for a viewer is associated with how much that viewer can see without a light source; you can also choose to add a light at a viewer's position withlightingand itsLightingManager.addLight(Coord, Radiance)method. Remember to move any viewer withmoveViewer(Coord, Coord)and any light they carry withLightingManager.moveLight(Coord, Coord). If a viewer's FOV range changes, you can update it withputViewer(Coord, float)using that viewer's current position. You can also add new viewers that way.- Parameters:
place- a 2D char array representing a local map;'#'or box drawing characters represent wallsviewers- a CoordFloatOrderedMap of the positions of viewers to their viewing ranges; directly referenced
-
restart
Some form of restart() must be called when the map is first created and whenever the whole local map changes.
This overload allows having one or more viewer positions at the start, with all viewers sharing seen information with each other (enemy characters are not generally viewers here). Each Coord position for a viewer is associated with how much that viewer can see without a light source; you can also choose to add a light at a viewer's position withlightingand itsLightingManager.addLight(Coord, Radiance)method. Remember to move any viewer withmoveViewer(Coord, Coord)and any light they carry withLightingManager.moveLight(Coord, Coord). If a viewer's FOV range changes, you can update it withputViewer(Coord, float)using that viewer's current position. You can also add new viewers that way.
This overload allows specifying abaseColorthat will be used cells that were seen previously but can't be seen now; typically this is dark gray or very close to that, and it is an Oklab int color as produced byDescriptiveColor. The default, if not specified, isrememberedColor.- Parameters:
place- a 2D char array representing a local map;'#'or box drawing characters represent wallsviewers- a CoordFloatOrderedMap of the positions of viewers to their viewing ranges; directly referencedbaseColor- the packed Oklab color used for previously-seen, but not in-view, cells
-
editSingle
public void editSingle(int newX, int newY, char newCell) Changes the char atnewX,newYto benewCelland adjusts the light resistance for that cell only inlighting. You must callfinishChanges()when you are done changing the place map, in order to update the lighting with the latest changes.- Parameters:
newX- the x-position to change, as an intnewY- the y-position to change, as an intnewCell- the char value to use at the given position (inlinePlaceMap)
-
editSingle
Changes the char atpositionto benewCelland adjusts the light resistance for that cell only inlighting. You must callfinishChanges()when you are done changing the place map, in order to update the lighting with the latest changes.- Parameters:
position- the position to change, as a non-null CoordnewCell- the char value to use at the given position (inlinePlaceMap)
-
editAll
public void editAll(char[][] newPlaceMap) Fully replaces the contents oflinePlaceMapwith those ofnewPlaceMap, and changes the light resistances for the whole place map inlighting. The existing place map should really be the same size as the new place map.You must callfinishChanges()when you are done changing the place map, in order to update the lighting with the latest changes.- Parameters:
newPlaceMap- the replacement 2D char array to use aslinePlaceMap
-
moveViewer
public boolean moveViewer(int oldX, int oldY, int newX, int newY) If a viewer is present atoldX,oldYinviewersand no view is present atnewX,newY, this moves the viewer tonewX,newYand returns true. Otherwise, this does nothing and returns false. You must callfinishChanges()when you are done changing the place map or viewers, in order to update the lighting with the latest changes.
If a viewer represents a character with a light source, you should probably have the light source known vialighting, and moving the light source should useLightingManager.moveLight(int, int, int, int).- Parameters:
oldX- the x-position of the viewer to move, if one is presentoldY- the y-position of the viewer to move, if one is presentnewX- the x-position to move the viewer to, if possiblenewY- the y-position to move the viewer to, if possible- Returns:
- true if the viewer moved, or false otherwise
-
moveViewer
If a viewer is present atpreviousPositioninviewersand no view is present atnextPosition, this moves the viewer tonextPositionand returns true. Otherwise, this does nothing and returns false. You must callfinishChanges()when you are done changing the place map or viewers, in order to update the lighting with the latest changes.
If a viewer represents a character with a light source, you should probably have the light source known vialighting, and moving the light source should useLightingManager.moveLight(int, int, int, int).- Parameters:
previousPosition- the position of the viewer to move, if one is presentnextPosition- the position to move the viewer to, if possible- Returns:
- true if the viewer moved, or false otherwise
-
removeViewer
public boolean removeViewer(int x, int y) If a viewer is present atx,yinviewers, this removes that viewer and returns true. Otherwise, this does nothing and returns false. You must callfinishChanges()when you are done changing the place map or viewers, in order to update the lighting with the latest changes.
If a viewer represents a character with a light source, you should probably have the light source known vialighting, and removing the light source should useLightingManager.removeLight(int, int).- Parameters:
x- the x-position of the viewer to remove, if one is presenty- the y-position of the viewer to remove, if one is present- Returns:
- true if a viewer was removed, or false otherwise
-
removeViewer
If a viewer is present atpositioninviewers, this removes that viewer and returns true. Otherwise, this does nothing and returns false. You must callfinishChanges()when you are done changing the place map or viewers, in order to update the lighting with the latest changes.
If a viewer represents a character with a light source, you should probably have the light source known vialighting, and removing the light source should useLightingManager.removeLight(int, int).- Parameters:
position- the position of the viewer to remove, if one is present- Returns:
- true if a viewer was removed, or false otherwise
-
putViewer
public void putViewer(int x, int y, float viewRange) Adds a viewer toviewerswith the given viewing distance, if there is no viewer already present atx,y. If a viewer is present atx,yinviewers, then this edits its viewing distance. You must callfinishChanges()when you are done changing the place map or viewers, in order to update the lighting with the latest changes.
If a viewer represents a character with a light source, you should probably have the light source known vialighting, and adding a light source should useLightingManager.addLight(int, int, Radiance).- Parameters:
x- the x-position of the viewer to place or edity- the y-position of the viewer to place or editviewRange- the viewing distance to use for the placed or edited viewer
-
putViewer
Adds a viewer toviewerswith the given viewing distance, if there is no viewer already present atposition. If a viewer is present atpositioninviewers, then this edits its viewing distance. You must callfinishChanges()when you are done changing the place map or viewers, in order to update the lighting with the latest changes.
If a viewer represents a character with a light source, you should probably have the light source known vialighting, and adding a light source should useLightingManager.addLight(Coord, Radiance).- Parameters:
position- the position of the viewer to place or editviewRange- the viewing distance to use for the placed or edited viewer
-
finishChanges
public void finishChanges()This completes the changes started bymoveViewer(int, int, int, int),editSingle(int, int, char), oreditAll(char[][])and updates the lighting according to those changes. This affects almost all variables present in this object. It should be noted that the methods that change a place map only changelinePlaceMap; that is because the end of this method uses linePlaceMap and the currently seen cells to updateprunedPlaceMap. Rendering should typically determine what to draw by using prunedPlaceMap. -
getForegroundColor
For a "foreground" creature or effect that can move between cells, call this every frame to get the color to draw that thing with. The color is a packed Oklab int, asDescriptiveColorproduces. This can return 0 if a creature or thing cannot be seen and is not fading in or out of view. Otherwise, the int color this returns will be white with some level of transparency -- if the creature is in view now and was in view previously, then it will be opaque white, otherwise it will have some transparency between 0 and 1. This takes a float argument,millisSinceLastMove, that is the number of milliseconds since the player last entered an input that changed the map or its inhabitants. This is used to handle fading creatures into view when the player's input suddenly revealed those creatures, or fading them out of view if they become hidden. You don't need to give milliseconds precisely; while the input is effectively clamped between 0 and 1000, you can multiply the actual milliseconds that have passed by (for example) 4 to reduce the time a fade effect takes to complete (to a quarter-second). Multiplying by a large number will make fades instantaneous.- Parameters:
pos- the position to get the "foreground" color for; does not actually have to have a creature in itmillisSinceLastMove- how many milliseconds have elapsed since the human player last entered an input, for fading- Returns:
- the packed Oklab int color to tint any foreground creature or object with at
pos
-
getForegroundColor
public int getForegroundColor(int x, int y, float millisSinceLastMove) For a "foreground" creature or effect that can move between cells, call this every frame to get the color to draw that thing with. The color is a packed Oklab int, asDescriptiveColorproduces. This can return 0 if a creature or thing cannot be seen and is not fading in or out of view. Otherwise, the int color this returns will be white with some level of transparency -- if the creature is in view now and was in view previously, then it will be opaque white, otherwise it will have some transparency between 0 and 1. Note that because white is the lightest color that can be represented, and it is the "neutral color" for a tint like this, there is no way for this tint to be used to lighten a sprite or other visual object.
This takes a float argument,millisSinceLastMove, that is the number of milliseconds since the player last entered an input that changed the map or its inhabitants. This is used to handle fading creatures into view when the player's input suddenly revealed those creatures, or fading them out of view if they become hidden. You don't need to give milliseconds precisely; while the input is effectively clamped between 0 and 1000, you can multiply the actual milliseconds that have passed by (for example) 4 to reduce the time a fade effect takes to complete (to a quarter-second). Multiplying by a large number will make fades instantaneous.- Parameters:
x- the x-position to get the "foreground" color for; does not actually have to have a creature in ity- the y-position to get the "foreground" color for; does not actually have to have a creature in itmillisSinceLastMove- how many milliseconds have elapsed since the human player last entered an input, for fading- Returns:
- the packed Oklab int color to tint any foreground creature or object with at
x,y
-
update
public void update(float millisSinceLastMove) Updates the lighting effects and writes tobackgroundColorsso the current tint color is stored in every cell of backgroundColors, for every cell in the place map. Call this every frame while lights are updating (for flicker and strobe effects); this does not need to be called if the game has rendering paused for any reason. This takes a float argument,millisSinceLastMove, that is the number of milliseconds since the player last entered an input that changed the map or its inhabitants. This is used to handle fading creatures into view when the player's input suddenly revealed those creatures, or fading them out of view if they become hidden. You don't need to give milliseconds precisely; while the input is effectively clamped between 0 and 1000, you can multiply the actual milliseconds that have passed by (for example) 4 to reduce the time a fade effect takes to complete (to a quarter-second). Multiplying by a large number will make fades instantaneous.
This setsbackgroundColorsto hold visible Oklab int colors where a cell is visible.- Parameters:
millisSinceLastMove- how many milliseconds have elapsed since the human player last entered an input, for fading
-
equals
-
hashCode
-
toString
-
getSerializersNeeded
-