Graphics
The Graphics
module provides access to all drawing related topics within the engine.
Because the Graphics
module is the biggest and most powerful module it provides multiple sub modules for different graphic related tasks.
Basics
This section describes the basic concepts and datatypes used when working with graphics in ScrewBox.
Coordinates
The Graphics
module uses a pixel-perfect coordinate system based on an Offset
, which represents the distance from the upper-left pixel on the screen, Size
, which is used to describe the dimension
of any screen-related object, and ScreenBounds
, which describes the combination of both, an area anywhere on the screen.
Order of drawing tasks
The order of drawing tasks is set by the execution order of the EntitySystem
.
Learn more about the execution order of entity systems in Environment.
The one exception of this rule is when using a SpriteBatch
.
The entries of the SpriteBatch
have an individual order which determines the order of drawing.
Sprites and Frames
By far the most important class when adding graphics to your game will be sprites.
A Sprite
is a still or animated image.
The single images contained in a Sprite
are frames.
Every frame can use an individual showing duration.
The sprite will be rendered as infinite loop of it's frames.
Any sprite will need at leas one Frame
.
To create a sprite add one ore more resource images into your src/main/resource
folder and load the image as shown below.
Also you can use any sprite from the SpriteBundle (see Asset Bundle) to get started right away.
If you just need a placeholder image there is a special method to create a prototype sprite in any specified size.
// create a sprite with a single image
Sprite player = Sprite.fromFile("player.png");
// create an animated sprite from a single image
Sprite playerWalking = Sprite.animatedAssetFromFile("player_walking.png", Size.square(16), Duration.ofMillis(100));
// use a pre defined sprite shipped with the engine
Sprite dot = SpriteBundle.DOT_RED.get();
// create a 10x40 pixels placeholder graphic
Sprite placeholder = Sprite.placeholder(Color.RED, Size.of(10, 40))
Sprites should only be created once and be reused when possible. This will reduce cpu load an waiting times, especially when using shaders. Background loading is also supported using Assets.
Graphics
The Graphics
class itself provies access to the submodules but also adds a huge number of useful functions:
// returns list of supported resultions on the current os
graphics.supportedResolutions();
// returns list of supported font names on the current os
graphics.availableFonts();
// maps the position on the `Screen` to a position in the world
// also works when using split screen!
Vector worldPosition = graphics.toWorld(Offset.at(10, 20));
// enable split screen (see guide)
engine.graphics().enableSplitScreenMode(options);
Configuration
Graphics.configuration()
will allow customizing system load and quality.
Options that can be specified:
Option | Description |
---|---|
resolution | window resolution, also screen resolution when using fullscreen |
isFullscreen | enable or disable fullscreen mode |
useAntialiasing | enable or disable antialiasing (performance heavy when drawing shapes) |
isAutoEnableLight | auto enable light when interacting with light |
isLightEnabled | use light (will make screen black when no light source is present) |
lightmapBlur | specify the blurring of the light map |
lightmapScale | specify the scale of the light map, lower values will be more detailed but slower |
lightFalloff | specify how lights will blur to darkness |
backgroundColor | specify the background color of the screen |
overlayShader | specify a shader that is used on every sprite drawn |
Currently there is no way to preserve the configuration when quitting the game. If you need this please tell me by commenting on https://github.com/srcimon/screwbox/issues/439.
Canvas
Use the Graphics.canvas()
to draw directly to the screen.
Every frame the Canvas
will be cleared again.
So every drawing task has to be repeated in every frame.
The Canvas
has a lot of distinct drawing methods available.
Most of this drawing methods use an option object that contains all drawing options for the specific task.
This limits the number of parameters for the drawing method.
These option classes are immutable and use a builder pattern.
See examples:
// will fill the whole canvas with red
canvas.fillWith(Color.RED);
// will draw a small half transparent white rectangle
canvas.drawRectangle(Offset.at(10, 20), Size.of(10,4), RectangleDrawOptions.filled(Color.WHITE.opacity(0.5));
// will draw the player sprite image using double size
canvas.drawSprite(player, Offset.at(100, 10), SpriteDrawOptions.scaled(2));
World
The Graphics.world()
is similar to the Canvas
but provides methods that can be used to simplify drawing
by using world coordinates instead of screen coordinates.
Using World
is also recommended when using Split screen.
World
uses the Camera
to bind a world to screen coordinate.
Screen
The Graphics.screen()
can be used to setup the actual drawing area on the game Window.
Also the Screen
allows rotating the whole viewport.
This will result in a huge performance drop but may create some nice effects.
This is also used by the camera shake to apply the swing effect.
Camera
Screwbox uses a viewport concept.
Within the game there is at least one viewport that has individual camera control.
Enabling split screen will create new viewports.
The camera of each viewport can be controlled individually.
To receive the current camera use engine.graphics().camera()
.
Automatic camera control
The simplest way to move the camera within the game world is to simply attach the camera to an entity.
This can be done by adding a CameraTargetComponent
to the entity.
By changing the viewportId
property you can select the target viewport for the camera.
This is only relevant when using split screen.
Don't forget to enable processing of the CameraTargetComponent
by calling environment.enableAllFeatures()
.
Manual camera control
You can also obtain manual Camera
controls using engine.graphics().camera()
.
This allows some more specific controls like changing zoom or instant movement to a specified position.
Sadly you cannot set any zoom value.
This is due to zoom restriction which can also be changed via Camera
,
but also pixel perfect mechanism that is in place to prevent graphic glitches.
Camera shake
Camera
also allows setting of a short or infinite shake effect.
The method uses the specified CameraShakeOptions
to apply the effect.
See example code:
camera.shake(CameraShakeOptions
.lastingForDuration(Duration.oneSecond())
.strength(4)
.ease(Ease.SINE_IN_OUT)
.swing(Rotation.degrees(10)));
The shake effect won't affect the position of the Camera
.
To receive the actual position including the camera shake use camera.focus()
.
Light
You can also add light to the game scene by adding lights manually or automated.
When adding your first light or shadow caster this will result in automatically activating light rendering which
could lead to a black screen when the light is currently not in the viewing area.
To avoid this you can disable the auto activation using the GraphicsConfiguration
.
To add lights manually use Graphics.light()
.
This class also allows specifying the ambient light value that can be important when you only want to add a
little dynamic to the scene without darken it too much.
You can use the Light
class directly for adding lights and shadows:
graphics().light().addPointLight(position, 40, Color.BLACK);
The following types of lights and shadows are supported:
Type | Description |
---|---|
cone light | a directed light cone that will be affected by shadow casters |
point light | a radial light source that will be affected by shadow casters |
spot light | a radial light source that won't be affected by shadow casters |
shadow caster | area that cast shadows and also can block lights when rendered on top |
orthographic wall | an orthographic wall that can be illuminated but will cast shadows (used in common rpg perspective) |
aerial light | a rectangular light without falloff |
ambient light | specifies maximum darkness of the whole screen |
light glow | a glow effect that doesn't illuminate the area |
It's highly recommended to add the StaticShadowCasterComponent
to any shadow casting entity that will not move to massively
improve rendering performance.
The recommended way to add light to your scenes is by using the corresponding components of the ecs. See Components Overview.
Advanced topics
Automate drawing using the ecs
The recommended way to draw Sprites
in your game is by adding a RenderComponent
to your game entities.
The rendering of entities using this component is massively optimized for best performance and will also support
reflection effects using the ReflectionComponent
.
This example code will add a red image attached to the mouse cursor.
engine.environment()
.enableRendering()
.addEntity(new Entity("animated cursor")
.add(new RenderComponent(SpriteBundle.DOT_RED))
.add(new TransformComponent())
.add(new CursorAttachmentComponent()));
Reflections
To add reflections simply add a ReflectionComponent
to any entity that will act as a mirror and reflect everything above.
Only entities using the RenderComponent
will be reflected.
The ReflectionComponent
also supports to different kinds of animation: projection and postfilter.
Shaders
When rendering a Sprite
using the RenderComponent
or by manual drawing on the Canvas
you can specify
SpriteDrawOptions
to customize the drawing process.
SpriteDrawOptions
allow scaling, rotating, flipping the drawing.
But they also allow specifying a Shader
.
Shaders will create animated images from any source (animated or still).
They have their own customization class named ShaderSetup
to customize the animation.
ScrewBox ships some pre defined shaders. (See Shaders).
Shaders will animate the sprite on the fly, but will reuse the calculated images once they are created.
This process is quite cpu heavy and should be done upfront before entering a Scene.
To prepare a shader upfront use prepareShader
.
So to animate your cursor from the example code just add the shader:
var sprite = SpriteBundle.DOT_RED.prepareShader(ShaderBundle.IRIS_SHOT);
var options = SpriteDrawOptions.originalSize().shader(ShaderBundle.IRIS_SHOT);
engine.environment()
.enableRendering()
.addEntity(new Entity("animated cursor")
.add(new RenderComponent(sprite, options)
.add(new TransformComponent())
.add(new CursorAttachmentComponent()));
Some examples for a still image animated using shaders. For a complete reference see Shaders.