Skip to main content

Using Vector Graphics Nodes

Dora SSR is built on the nanovg library, providing a powerful set of vector graphic drawing capabilities. This tutorial will guide you through using these vector graphics features in Dora SSR. You will learn how to create vector graphic nodes, render shapes, apply transformations, and load and display SVG files.

1. Introduction

Vector graphics allow for the creation of scalable, resolution-independent images using mathematical equations rather than pixel grids. Dora SSR's vector graphics capabilities are based on the nanovg library, offering a fundamental vector drawing API similar to HTML5 Canvas. These features can be integrated with Dora SSR's scene node system.

In this tutorial, we will explore how to use Dora SSR's vector graphics API to:

  • Create vector graphic nodes (VGNode)
  • Draw vector graphics directly on the screen
  • Render shapes and paths
  • Apply transformations
  • Load and display SVG files

2. Using VGNode

VGNode is a node in Dora SSR used to statically render vector graphics onto a texture, which can then be mounted in the game scene for display. It internally contains a framebuffer texture for drawing vector graphics and is itself a scene node object. To create a VGNode, you need to specify its dimensions, as well as optional scaling and anti-aliasing parameters.

2.1 Creating a Node Object

local VGNode <const> = require("VGNode")

-- Create a VGNode with a width of 800, height of 600, default scaling, and anti-aliasing
local node = VGNode(
800, -- Width of the framebuffer texture
600, -- Height of the framebuffer texture
1.0, -- Optional scaling factor for rendering units. Default is `1.0`
1 -- Optional anti-aliasing factor. Default is `1`
)

This will create an instance of VGNode, which we will use to render vector graphics onto its associated framebuffer texture.

2.2 Rendering Vector Graphics

To render vector graphics, use the render method of the VGNode instance. You need to pass a function (closure) that contains drawing commands using the nvg module. These drawing commands will draw shapes onto the VGNode's framebuffer texture.

The nvg module provides functions for drawing basic shapes such as rectangles, circles, lines, and paths. You can also customize the appearance of shapes using colors and brushes.

Example: Drawing a Rectangle

local VGNode <const> = require("VGNode")
local nvg <const> = require("nvg")
local Color <const> = require("Color")

local node = VGNode(200, 150)

node:render(function()
nvg.BeginPath() -- Start a new path
nvg.Rect(0, 0, 200, 150) -- x, y, width, height
nvg.FillColor(Color(255, 0, 0, 255)) -- Red
nvg.Fill()
end)

The above code draws a red rectangle with a width of 200 and a height of 150. The render method will only be executed once when called for the first time, and the rendering result will be automatically cached for reuse.

Tip

When rendering vector graphics with VGNode, the origin of the coordinate system is at the top left corner of the node texture, with the x-axis extending right and the y-axis extending down. The graphic calculation units are the texture pixels multiplied by the scaling value.

Example: Drawing a Circle

local VGNode <const> = require("VGNode")
local nvg <const> = require("nvg")
local Color <const> = require("Color")

local node = VGNode(100, 100)

node:render(function()
nvg.BeginPath()
nvg.Circle(50, 50, 50) -- Center x, y and radius
nvg.FillColor(Color(255, 255, 0, 255)) -- Yellow
nvg.Fill()
end)

This code draws a yellow circle with a radius of 50. The render method will only be executed once when called for the first time, and the rendering result will be automatically cached for reuse.

3. Directly Drawing Vector Graphics on the Screen

In addition to using VGNode, Dora SSR also supports directly drawing vector graphics on the top layer of the screen buffer. By calling the drawing functions from the nvg module in each frame, you can draw vector graphics directly on the screen without the need for VGNode.

local nvg <const> = require("nvg")
local Color <const> = require("Color")
local threadLoop <const> = require("threadLoop")

threadLoop(function()
nvg.BeginPath()
nvg.Rect(100, 100, 200, 150)
nvg.FillColor(Color(255, 0, 0, 255))
nvg.Fill()
end)

The above code draws a red rectangle on the screen. Note that this method draws directly to the screen buffer and will overwrite the underlying game scene. It is more suitable for drawing game HUD interfaces, displaying debug information, etc.

Tip

When using this drawing method, the origin of the coordinate system is at the top left corner of the screen, with the x-axis extending right and the y-axis extending down. In the visible area of the screen, the maximum value of the x-coordinate is App.visualSize.width, and the maximum value of the y-coordinate is App.visualSize.height.

3.1 Combining Game Scene Nodes and Vector Graphics

You can use the nvg.ApplyTransform(node) function to apply the transformation of a scene node to the vector graphics being drawn. This allows you to change the position, rotation, and scaling of the vector graphics in the game scene more conveniently by controlling the node's transformation.

local nvg <const> = require("nvg")
local Color <const> = require("Color")
local Node <const> = require("Node")
local Size <const> = require("Size")
local Scale <const> = require("Scale")

local node = Node()
node.size = Size(200, 150)
node:schedule(function()
nvg.ApplyTransform(node)
nvg.BeginPath()
nvg.Rect(0, 0, 200, 150)
nvg.FillColor(Color(255, 0, 0, 255))
nvg.Fill()
end)

node.x = 50
node.y = 50
node.angle = 45
node:perform(Scale(0.5, 0, 1))

The above code creates a node and draws a red rectangle onto it. Then, by setting the position and rotation of the node, the display effect of the rectangle is controlled, and the scaling of the rectangle is controlled through performing scaling actions on the node.

Tip

After using the nvg.ApplyTransform(node) function, the coordinate system's origin for the vector graphics will change to the bottom left corner of the scene node, with the x-axis extending right and the y-axis extending up. The node's scaling, rotation, and position transformations will be applied to the drawing of the vector graphics.

4. Using Colors and Brushes

You can use colors and brushes to customize the appearance of shapes.

Fill and Stroke Colors

  • nvg.FillColor(color): Sets the fill color.
  • nvg.StrokeColor(color): Sets the stroke (outline) color.
  • nvg.StrokeWidth(width): Sets the stroke width.

Example: Fill and Stroke

local nvg <const> = require("nvg")
local Color <const> = require("Color")
local threadLoop <const> = require("threadLoop")

threadLoop(function()
nvg.BeginPath()
nvg.Rect(10, 10, 100, 80)
nvg.FillColor(Color(255, 0, 255, 255)) -- Pink fill
nvg.Fill()
nvg.StrokeColor(Color(255, 255, 0, 255)) -- Yellow stroke
nvg.StrokeWidth(5)
nvg.Stroke()
end)

5. Applying Transformations

Transformations allow you to manipulate shapes through translation, rotation, or scaling.

Transformation Functions

  • nvg.Translate(x, y): Moves the coordinate system.
  • nvg.Rotate(angle): Rotates the coordinate system (angle in degrees).
  • nvg.Scale(sx, sy): Scales the coordinate system.
  • nvg.Save(): Saves the current transformation state.
  • nvg.Restore(): Restores the last saved state.

Example: Rotating a Rectangle

local nvg <const> = require("nvg")
local Color <const> = require("Color")
local threadLoop <const> = require("threadLoop")

local function Rect(x, y, angle)
nvg.Save()
nvg.Translate(x, y) -- Move
nvg.Rotate(angle) -- Rotate
nvg.BeginPath()
nvg.Rect(-100, -50, 200, 100) -- Center-aligned rectangle
nvg.FillColor(Color(255, 128, 0, 128)) -- Brown
nvg.Fill()
nvg.Restore()
end

threadLoop(function()
-- Draw rectangles with transformations that do not affect each other
Rect(300, 200, 45)
Rect(100, 200, 90)
end)

The above code draws two rotating rectangles, one rotating 45 degrees clockwise and the other 90 degrees clockwise. Their rotation centers are at the center of the rectangles, and their respective geometric transformations do not affect each other.

6. Loading and Rendering SVG Files

Dora SSR provides an SVG class for loading and rendering SVG files. Note that currently, only a subset of simplified SVG files processed with the picosvg tool is supported.

6.1 Loading and Directly Rendering SVG Graphics

local SVG <const> = require("SVG")
local threadLoop <const> = require("threadLoop")

local svg = SVG("Image/Dora.svg")

threadLoop(function()
-- Call the SVG instance's `render` method in each frame to display it.
svg:render()
end)

6.2 Controlling SVG Graphics Rendering with Scene Nodes

You can use scene nodes to control the position, rotation, and scaling of SVG graphics.

local SVG <const> = require("SVG")
local Node <const> = require("Node")
local nvg <const> = require("nvg")

local svg = SVG("Image/Dora.svg")

local node = Node()
node:schedule(function()
nvg.ApplyTransform(node)
nvg.Scale(0.2, -0.2)
nvg.Translate(-566.5, -566.5)
svg:render()
end)

node.angle = 45

The above code creates a node and binds the SVG graphics rendering to that node. Then, by setting the rotation of the node, the display effect of the SVG graphics is controlled.

7. Conclusion

In this tutorial, we introduced how to use Dora SSR's vector graphics capabilities to draw shapes, apply transformations, and render SVG files. With these tools, you can create rich vector graphics and animations for your applications.

Remember, the nvg module also offers more advanced drawing and text rendering functions. You can explore functions such as nvg.Text, nvg.FontFace, and gradient brushes like nvg.LinearGradient for more complex graphics.

Feel free to experiment with different shapes, colors, and transformations to create unique vector graphic projects in Dora SSR!