Introduction to Scion 2D

Logo

Scion 2D is a simple 2D game engine written in C++ that I am creating for my YouTube channel for educational purposes and to become a better programmer. There is still lots of work to do; however, we are making some headway.

Technologies

  • OpenGL 4.5.
  • Sol3 - for Lua bindings.
  • Lua 5.3 - Scripting Language.
  • SOIL - for texture loading.
  • ENTT - for the ECS (Entity Component System).
  • SDL2 - for all windowing/input devices/ and much more.
  • SDL Mixer - for music and soundFX.
  • Box2D - for all the Physics
  • ImGui - TODO: Imgui is in the future when we create the actual editor.

About

Asset Manager

The Asset Manager is what is used to store the games textures, fonts, music, soundFx, and shaders. Before the assets can be used in the game, they must be added into the asset manager successfully.

Functions:

add_texture

FunctionDescription
add_texture(string, string, bool)Attemps to add a new texture to the asset manager. It takes a string for the asset name, a string for the texture filepath, and a flag to set the texture to pixel art. Returns true if successful, false otherwise

Example:

-- Add the hero texture to the asset manager
if AssetManager.add_texture("hero", "./assets/textures/hero.png", true) then 
	print("Added texture [hero] successfully")
else
	print("Failed to add the [hero] texture.")
end

-- Consider adding all the textures and their paths to a table and looping.

add_music

FunctionDescription
add_music(string, string)Attemps to add a new music object to the asset manager. It takes a string for the asset name, a string for the music filepath. Returns true if successful, false otherwise

Example:

-- Add the title music to the asset manager
if AssetManager.add_music("title", "./assets/music/title.mp3") then 
	print("Added music [title] to Asset Manager successfully")
else
	print("Failed to add the [title] music to the Asset Manager.")
end

add_soundfx

FunctionDescription
add_soundfx(string, string)Attemps to add a new soundFx object to the asset manager. It takes a string for the asset name, a string for the soundFx filepath. Returns true if successful, false otherwise

Example:

-- Add the sword SoundFx to the asset manager
if AssetManager.add_soundfx("sword", "./assets/soundfx/sword.wav") then 
	print("Added soundFx [sword] to Asset Manager successfully")
else
	print("Failed to add the [sword] soundFx to the Asset Manager.")
end

add_font

FunctionDescription
add_font(string, string, float)Attemps to add a new font .ttf object to the asset manager. It takes a string for the font name, a string for the font filepath, and a float for the font size (default 32.0). Returns true if successful, false otherwise

Example:

-- Add the Pixel font to the asset manager
if AssetManager.add_font("pixel", "./assets/fonts/pixel.ttf", 32.0) then 
	print("Added Font [pixel] to Asset Manager successfully")
else
	print("Failed to add the [pixel] font to the Asset Manager.")
end

Sample Lua Load Assets Function


-- Sample Assets table
AssetDefs = 
{
	textures = 
	{
		{ name = "test_texture", path = "assets/textures/test_texture.png", pixel_art = true },	
	},
	music = 
	{
		{ name = "test_music", path = "assets/music/test_music.mp3" },	
	},
	sound_fx = 
	{
		{ name = "test_sound", path = "assets/soundfx/test_sound.wav"},
	}
}

-- Sample LoadAssets function takes in the above assets table and loads 
-- the entries into the AssetManager
function LoadAssets(assets)
	for k, v in pairs(assets.textures) do
		if not AssetManager.add_texture(v.name, v.path, v.pixel_art) then
			print("Failed to load texture [" ..v.name .."] at path [" ..v.path .."]")
		else
			print("Loaded Texture [" ..v.name .."]")
		end
	end

	for k, v in pairs(assets.music) do 
		if not AssetManager.add_music(v.name, v.path) then 
			print("Failed to load music [" ..v.name .."] at path [" ..v.path .."]")
		else
			print("Loaded music [" ..v.name .."]")
		end
	end

	for k, v in pairs(assets.sound_fx) do
		if not AssetManager.add_soundfx(v.name, v.path) then
			print("Failed to load soundfx [" ..v.name .."] at path [" ..v.path .."]")
		else
			print("Loaded soundfx [" ..v.name .."]")
		end
	end
	-- TODO: Add other loading of assets as needed
end

Entity Component System

For our ECS we are using wrappers around the wonderful battle tested ENTT library! We have lua bindings for the registry, so we can get runtime views, entities, components and more.

Registry

TODO:

Functions

Runtime View Runtime views iterate over all of the entities that have all the given components that are passed into the function.

FunctionDescription
for_each(sol::function)This function will iterate over all of the entities that are in the runtime_view. It takes in a sol::function as a callback. The callback takes in the current entity so the user can perform actions on that entity.
exclude(sol::variadic_args)The exclude function takes in a variable amount of args from the lua script. The arguments that are to be the desired components that we want to exclude from the runtime view. This should be done upon creation of the runtime view before using the for_each(...) function.

Registry

FunctionDescription
get_entities(sol::variadic_args)The get_entities function takes in a variable amount of arguments. The arguments are to be the components that we want the entities to have. This will return a runtime_view of all the entities that have all of the entered components.
clear()Clears the entire registry of the entities and the components.

Example

	-- Get all the entities that have a Transform and Sprite Component
	-- Exclude entities with UI Components
	local view = Registry.get_entities(Transform, Sprite).exclude(UIComponent)

	-- Loop through the view and hide all of the sprites
	view:for_each(
		-- The for_each function takes in a callback function
		function (entity)
			local sprite = entity:get_component(Sprite)
			sprite.bHidden = true
		end
	)

Entity

An entity is a game object that consists of a unique id number and is created and monitored by the Registry. Each entity can have various components. What this means is that there are pools of components and those components are owned by each of the specific entity ids.

Even though the entity is just an ID, our implementation of the entity uses an entt::entity as it's id and also has some built in functions that we can use.

By default each entity has a name and a group to aid in the identification of the entity. We can use the name or the group to filter the entities more thoroughly. See the below example:

-- Grab all the entities that have a sprite component
local view = Registry.get_entities(Sprite)

-- Create a simple table that will hold all the enemy entities
local Enemies = {}

-- Loop through the view and place all of the entities that
-- have a group name of "enemy" into the table
view:for_each(
	function (entity)
		if entity:group() == "enemy" then
			table.insert(Enemies, Entity(entity:id()))
		end
	end
)

-- Loop through the enemies and do some work
for k, v in ipairs(Enemies) do 
	local get transform = v:get_component(Transform)
	-- ...More work here with the enemies
end

Contstructors

ConstructorDescription
Entity(string name, string group)This entity ctor takes in two arguments. A string for the name and a string for the group. The name and group can be empty strings "".
Entity(uint32_t id)This entity ctor takes in the unique id of the entity and returns an entity object. The id number is a uint32.
-- Create a new entity
local entity = Entity("name", "group")

-- Create a duplicate lua entity from the previous entity's id
local entityDupe = Entity(entity:id())

Components

This is a list of the components that can be used with the engine and their available functions and member variables.

Transform

The transform component contains the position, rotation, and scale of the entity.

Properties:

PropertyFunctionality
positionA vec2 [x, y] for the world coordinates of the entity.
scaleA vec2 [x, y] for the scale. Used to increase/decrease the size of an entity.
rotationA float value for the rotation of the entity in degrees.

Use Example:

-- Create a new entity
local entity = Entity("tag", "group")

-- Add a new transform component at position [50, 50], scale of 1 and no rotation. 
local transform = entity:add_component(Transform(vec2(50, 50), vec2(1, 1), 0.0))

-- access the transform properties
transform.position = vec2(100, 100)
transform.scale = vec2(2, 1)
transform.rotation = 45.0

Sprite

The sprite component contains all of the necessary properities for the sprite of an entity. A spirte has a width, height, layer, start_x, start_y, texture_name and UV values.

Properties:

PropertyTypeFunctionality
widthfloatwidth of the sprite in pixels
heightfloatheight of the sprite in pixels
layerintthe layer of the sprite determines when it gets rendered. A lower layer will be rendered first.
start_xintdetermines the start x position for use in a texture atlas. If it is a single texture use 0.
start_yintdetermines the start y position for use in a texture atlas. If it is a single texture use 0.
bHiddenboolused to hide the texture when we don't want it to be seen.
texture_namestringthe name of the sprite to use. The name MUST exist in the Asset Manager prior to use.
colorColorThe color of the sprite. Defaults to white or Color(255, 255, 255, 255).

Functions:

FunctionDescription
generate_uvs()This function will take the width, height of the actual texture, and the start positions to populate the UV values for the texture atlas. UV values are u, v, uv_width, uv_height.

Use Example:

-- Create a new entity
local entity = Entity("tag", "group")

-- Add a new sprite component
local sprite = entity:add_component(
	Sprite(
		"hero_sprite",	-- texture_name
		32,		-- width 
		32,		-- height
		0, 		-- start_x
		0,		-- start_y
		2		-- layer
	)
)

-- After the sprite is created, you have to generate the uvs
sprite:generate_uvs()

Animation

The animation component is used to animate the sprite. It assumes that the sprite is part of a texture atlas and will adjust the current uv values based on the current frame.

Properties:

PropertyTypeFunctionality
num_framesintThe number of frames in the current animation.
frame_rateintThe speed of the animation. How fast the frames change per second.
frame_offsetintThe offset from the start position. If the offset is not used, a start position of 0 is assumed.
current_frameintThe frame that the animation is currently on.
start_timeintThe start time defaults to the current ticks upon creation. Used in the frame calculations.
bVerticalboolDetermines the direction of the animation scroll. True = vertically, False = horizontally.
bLoopedboolIf the animation is looped, it will reset the current frame to 0 once it reaches num_frames. If not looped, the user must call the reset() function to reset the animation.

Functions:

FunctionDescription
resetResets the current frame back to zero and sets the start_time to the current running ticks.

Use Example:

-- Create a new entity
local entity = Entity("tag", "group")

-- Add a new animation component
local animation = entity:add_component(
	Animation(
		4,		-- Number of frames 
		15,		-- Frame Rate
		0,		-- Frame Offset
		false,	-- bVertical == false == horizontal scroll
		true	-- Looped
	)
)

BoxCollider

The box collider is an axis-aligned rectangle that is used for collision detection. The collider positions itself based on the entity's Transform position.

Properties:

PropertyTypeFunctionality
widthintThe width of the collider.
heightintThe height of the collider.
offsetvec2An offset of the origin. The origin is based on the Transform position [x, y].
bCollidingboolA flag used to inform if the collider is touching another collider.

Use Example:

-- Create a new entity, if not already created.
local entity = Entity("tag", "group")

-- Add a new transform component at position [50, 50], scale of 1 and no rotation. 
local boxCollider = entity:add_component(
	BoxCollider(
		32,			-- width
		32,			-- height
		vec2(0, 0)		-- offset
	)
)

CircleCollider

The circle collider 2D circle that is used for collision detection. The collider positions itself based on the entity's Transform position.

Properties:

PropertyTypeFunctionality
radiusfloatThe radius of the circle collider.
offsetvec2An offset of the origin. The origin is based on the Transform position [x, y].
bCollidingboolA flag used to inform if the collider is touching another collider.

Use Example:

-- Create a new entity, if not already created.
local entity = Entity("tag", "group")

-- Add a new transform component at position [50, 50], scale of 1 and no rotation. 
local circleCollider = entity:add_component(
	CicleCollider(
		16,			-- radius
		vec2(0, 0)		-- offset
	)
)

Physics Component

The physics component controls all of the physics properties of an entity.

BodyType:

The rigid body type is an enum that is used to determine the type of body to be used. These are based on the Box2D b2BodyType. There are three rigid body types to choose from:

  • STATIC
    • Static bodies cannot be moved by other bodies. All movement must be done by the user.
  • KINEMATIC
    • Similar to a static body, that it is not affected by dynamic bodies; however, kinematic bodies can move. Perfect for moving platforms or walls.
  • DYNAMIC
    • Dynamic bodies are movable and will consume all the affects of forces and other bodies.

Use Example:

-- The use of the BodyType enum can be used as follows.
BodyType.Static 
BodyType.Kinematic
BodyType.Dynamic

-- These are normally used when creating the Physics Attributes. 
-- This will set the body type so the physics system knows how it should react to the body.

PhysicsAttributes:

These are the attributes that make up the Physics Component.

Properties:

PropertyTypeFunctionality
eTypeBodyTypeenum for the type of body to use for the PhysicsComp.
densityfloatThe density of the body. Kg/m^2 .
frictionfloatThe coefficient of friction. Usually between values [0 - 1]
restitutionfloatThe coefficient of restitution. Determines the elasticity or bouncyness of a body. Usually between [0 - 1].
restitutionThresholdfloatThe velocity that a body must be >= before the restitution takes affect.
radiusfloatThe radius of the circle collider. ONLY USED IF THE SHAPE IS A CIRCLE.
gravityScalefloatThe scale at which gravity is applied to the body. It is treated as a multiplier. A gravity scale of 1.0 would be normal gravity, where as 0.0 would be no gravity.
positionvec2The [x, y] coordinates of the body in the world.
scalevec2The scale [x, y] of the collider. Used as a multiplied of the radius or box size.
boxSizevec2The size [x == width, y == height] of the collider. ONLY USED IF THE SHAPE IS A BOX.
offsetvec2The offset from the origin of the collider.
bCircleboolFlag used when creating the body and to used the radius upon creation.
bBoxShapeboolFlag used when creating the body and to used the boxSize upon creation.
bFixedRotationboolFlag that prevents the body from rotating.

Use Example:

-- Create a default Physics Attributes object
local physicsAttribs = PhysicsAttributes()

-- Adjust properties to desired values, properties that are unchanged use default
physicsAttribs.eType = BodyType.Dynamic
physicsAttribs.density = 100.0			-- Set the density to 100 kg/m^2
physicsAttribs.friction = 0.2			-- Set the coefficient of friction to 0.2
physicsAttribs.restitution = 0.1		-- Set the coefficient of restitution to 0.2
physicsAttribs.radius = circle.radius 		-- Use the entity's CircleCollider radius
physicsAttribs.gravityScale = 2.0		-- Multiply the force of gravity by 2.0
physicsAttribs.position = transform.position	-- Use the entity's Transform position
physicsAttribs.scale = transform.scale		-- Use the entity's Transform scale
physicsAttribs.bCircle = true			-- Make the Fixture a circle
physicsAttribs.bFixedRotation = false		-- Allow rotation

PhysicsComp:

The actual physics component that can be added to the entity. The PhysicsComp takes in the PhysicsAttributes in order to create the underlying body, fixtures and set thier initial values. The user cannot access the actual physics body; however, has access to functions that will apply the desired changes to the rigid body.

Functions:

linear_impulse

FunctionDescription
linear_impulse(vec2)Applies an impulse at the position of the body. This will immediately modify the velocity of the rigid body.

Example:

-- Get the physics component from the entity
local physics = entity:get_component(Physics)

-- Apply a linear impulse 
physics:linear_impulse(vec2(10000, 100))

angular_impulse

FunctionDescription
angular_impulse(float)Applies an angular impulse on the rigid body. Takes a float value for the impulse.

Example:

-- Get the physics component from the entity
local physics = entity:get_component(Physics)

-- Apply an angular impulse 
physics:angular_impulse(45)

set_linear_velocity

FunctionDescription
set_linear_velocity(vec2)Sets the linear velocity of the center mass of the rigid body.

Example:

-- Get the physics component from the entity
local physics = entity:get_component(Physics)

-- Move the entity to the right with a velocity of 50 
physics:set_linear_velocity(vec2(50, 0))

get_linear_velocity

FunctionDescription
get_linear_velocity()Returns a vec2 of the current linear velocity of the rigid body.

Example:

-- Get the physics component from the entity
local physics = entity:get_component(Physics)

-- Get the linear velocity
local velocity = physics:get_linear_velocity()

set_angular_velocity

FunctionDescription
set_angular_velocity(float)Sets the angular velocity of the rigid body in radians/second.

Example:

-- Get the physics component from the entity
local physics = entity:get_component(Physics)

-- Set the angular velocity
physics:set_angular_velocity(60)

get_angular_velocity

FunctionDescription
get_angular_velocity()Returns a float for the angular velocity of the rigid body in radians/second.

Example:

-- Get the physics component from the entity
local physics = entity:get_component(Physics)

-- Get the angular velocity or Omega
local omega = physics:get_angular_velocity()

set_gravity_scale

FunctionDescription
set_gravity_scale(float)Sets the gravity scale of the rigid body to increase/decrease the forcec of gravity that will be applied.

Example:

-- Get the physics component from the entity
local physics = entity:get_component(Physics)

-- Set the angular velocity
physics:set_gravity_scale(2.0)

Music and SoundFx

The Music and Sound players allow the user to play their desired music or sounds in the game. By default, there are 16 channels available to the user to use. There is only one channel that is dedicated for music, meaning that we can only have one music song playing at a time.

Music Player

The music player will only play sounds that have already been loaded into the Asset Manager. The music player is automatically opened when the engine boots up.

  • Valid music file formats:
    • MP3
    • WAV
    • OGG

Functions:

play

FunctionDescription
play(string, int)The play function takes in a string for the music name to be played and an int value for the number of loops. To loop indefinitely, set the loops to -1 or call the overloaded play function.
play(string)This is an overload of the play function which calls the first play function and sets the loops automatically to -1. Takes in a string for the name of the song to be played.

Example:

-- Play the title music
Music.play("title", -1) -- play the title music and loop continuously.

top


stop

FunctionDescription
stop()Stops the currently playing music. Only one song can play at a time, so no string is needed for the music name.

Example:

-- Stop the music
Music.stop()

top


pause

FunctionDescription
pause()Pauses the currently playing music. Only one song can play at a time, so no string is needed for the music name.

Example:

-- Pause the music
Music.pause()

top


resume

FunctionDescription
resume()Resume the currently paused music from the position where it was paused.

Example:

-- Resume the music
Music.resume()

top


set_volume

FunctionDescription
set_volume(int)Sets the volume of the music player. Takes in an int value between [0 - 100].

Example:

-- Set the volume to 50%
Music.set_volume(50)

top


is_playing

FunctionDescription
is_playing()Checks to see if the music channel is currently playing. Returns true if the music is playing, false otherwise.

Example:

-- Check if the music player is playing.
if Music.is_playing() then 
	Music.set_volume(50)
end

top


Sound Player

The sound player is used when we want small chunk soundFx played. The Sound Player, like the Music Player will only play sounds that have already been loaded into the Asset Manager. However, unlike the music player, the user must specify the channel that we want the music to be played on.

If the user chooses -1 for the channel, the sound FX will be played on any open channel. There there are no open channels, the Sound Player will log an error.

  • Valid sound fx file formats:
    • MP3
    • WAV

Functions:

play

FunctionDescription
play(string, int, int)The play function takes in a string for the soundFx name to be played and an int value for the number of loops and another int for the channel to play the sound on.
play(string)This is an overload of the play function which calls the first play function and sets the loops automatically to 0, to only loop once, and sets the channel to -1, so it will play on any open channel. Takes in a string for the name of the soundFx to be played.

Example:

-- Play the sword_slash soundFX
Sound.play("sword_slash", 0, -1) -- Don't loop and play on any open channel.
Sound.play("sword_slash")	-- Same as above; however, uses the default values.

top


stop

FunctionDescription
stop(int)stop function takes in an int for the channel that we want to stop playing. -1 will stop all channels.

Example:

-- Stop channel 5
Sound.stop(5)

top


set_volume

FunctionDescription
set_volume(int, int)Sets the volume of the specified channel. The first value is the channel, the second value is the volume [0 - 100]. If the channel is -1, it will set the volume to all channels to the specified volume.

Example:

-- Set the volume off all channels to 50%
Sound.set_volume(-1, 50)

top


Input Management

Keyboard

Currently, there can only be one keyboard present at any time. Users can use the keyboard inputs to control game actions, such as:

  • Player Movement
  • Menu Selections
  • and much more

Mapped Keys:

Here is a list of the mapped keys and their names when using the keyboard:


Registerd Typewriter Keys

KEY_AKEY_BKEY_CKEY_DKEY_EKEY_FKEY_GKEY_H
KEY_IKEY_JKEY_KKEY_LKEY_MKEY_NKEY_OKEY_P
KEY_QKEY_RKEY_SKEY_TKEY_UKEY_VKEY_WKEY_X
KEY_YKEY_ZKEY_CKEY_DKEY_EKEY_FKEY_GKEY_H
KEY_0KEY_1KEY_2KEY_3KEY_4KEY_5KEY_6KEY_7
KEY_8KEY_9
KEY_ENTERKEY_BACKSPACEKEY_ESCKEY_SPACEKEY_LALTKEY_LSHIFT
KEY_LCTRLKEY_RCTRLKEY_RALTKEY_RSHIFT

Registerd Function Keys

KEY_F1KEY_F2KEY_F3KEY_F4KEY_F5KEY_F6KEY_F7KEY_F8
KEY_F9KEY_F10KEY_F11KEY_F12

Registerd Cursor Control Keys

KEY_UPKEY_RIGHTKEY_DOWNKEY_LEFT

Keyboard Functions:

FunctionDescription
just_pressed(key)The just pressed function is used when the user only wants the key to register once per press, and the reset upon release.
just_released(key)This will return true whenever the key argument has been released.
pressed(key)Returns true whenever the button is held down. Used when the user needs continuous input from the key.

Example Usage:

local physics = entity:get_component(Physics)

-- Set the linear velocity of the entity based on the WASD keys
if Keyboard.pressed(KEY_W) then 
	physics:set_linear_velocity(vec2(0, 50))
elseif Keyboard.pressed(KEY_S) then 
	physics:set_linear_velocity(vec2(0, -50))
elseif Keyboard.pressed(KEY_A) then 
	physics:set_linear_velocity(vec2(-50, 0))
elseif Keyboard.pressed(KEY_D) then 
	physics:set_linear_velocity(vec2(50, 0))
end 

-- Have the entity jump if space bar is pressed 
if Keyboard.just_pressed(KEY_SPACE) then 
	physics:linear_impulse(vec2(0, -10000)
end 

Mouse

Gamepad

The gamepad is based on an XBox-style gamepad. Scion2D supports up to 4 controllers/gamepads. The gamepad usually will have two analog thumb sticks, a D-Pad, four face buttons, two shoulder buttons, two analog trigger buttons, and two center buttons.

Action Buttons

ButtonTypeDescription
GP_BTN_Aaction buttonOne of the four action buttons on the controller. Usually the A button on the XBox and the Circle button on a playstation style controller.
GP_BTN_Baction buttonOne of the four action buttons on the controller. Usually the B button on the XBox and the Circle button on a playstation style controller.
GP_BTN_Xaction buttonOne of the four action buttons on the controller. Usually the X button on the XBox and the Circle button on a playstation style controller.
GP_BTN_Aaction buttonOne of the four action buttons on the controller. Usually the Y button on the XBox and the Circle button on a playstation style controller.

Special Buttons

ButtonTypeDescription
GP_BTN_BACKspecial buttonOne of the three special buttons on the controller. Usually the SELECT button on the XBox and the Circle button on a playstation style controller.
GP_BTN_STARTspecial buttonOne of the three special buttons on the controller. Usually the START button on the XBox and the Circle button on a playstation style controller.
GP_BTN_GUIDEspecial buttonOne of the three special buttons on the controller. May not be supported by your controller.

Axes

The axes are buttons and joysticks that use analog input values rather than a digital/bool input.

ButtonTypeDescription
AXIS_X1x axisAxis X1 is the horizontal axis of the LEFT analog stick. The analog values are -32768 for fully left and 32767 for fully right.
AXIS_Y1y axisAxis Y1 is the vertical axis of the LEFT analog stick. The analog values are -32768 for fully up and 32767 for fully down.
AXIS_X2x axisAxis X2 is the horizontal axis of the RIGHT analog stick. The analog values are -32768 for fully left and 32767 for fully right.
AXIS_Y2y axisAxis Y2 is the vertical axis of the RIGHT analog stick. The analog values are -32768 for fully up and 32767 for fully down.
AXIS_Z1z axisAxis Z1 is the axis for the LEFT trigger analog. The analog values are -32768 for fully released and 32767 for fully pressed.
AXIS_Z2y axisAxis Z2 is the vertical axis of the RIGHT trigger analog. The analog values are -32768 for fully released and 32767 for fully pressed.

Hat Values

Not all controllers have hats. The hat is basically the DPAD; however, it also has the diagonal directions. We use the hat values based on a number that is returned depending on the direction that is pressed. The typical hat values are as follows:

DirectionHat Value
UP1
RIGHT2
UP RIGHT3
DOWN4
DOWN RIGHT6
LEFT8
UP LEFT9
DOWN LEFT12

Functions:

All of the controller functions take the index of which controller is being used. We are allowed up to 4 controllers in the engine. The index is basically the SLOT of the controller.

FunctionDescription
just_pressed(index, btn)The just pressed function is used when the user only wants the btn to register once per press, and the reset upon released.
just_released(index, btn)This will return true whenever the btn argument has been released.
pressed(index, btn)Returns true whenever the button is held down. Used when the user needs continuous input from the btn.
get_axis_position(index, axis)This will return an analog value for the desired axis. The value returned will be between -32768 and 32767. Please see the Axes for more information.
get_hat_value(index)This will return the current value of the controllers HAT, if the controller has a hat. Please see the Hat Values for more information.

Examples

Left Stick Analog

	-- Get current x speed from the analog value
	local x_analog = Gamepad.get_axis_position(1, AXIS_X1) 
	local x_multiplier = x_analog / 32768
	local x_speed = speed * x_multiplier

	-- Get current y speed from the analog value
	local y_analog = Gamepad.get_axis_position(1, AXIS_Y1) 
	local y_multiplier = y_analog / 32768
	local y_speed = speed * y_multiplier

	-- Set the linear velocity
	local physics = entity:get_component(Physics)
	physics:set_linear_velocity(x_speed, y_speed)
	
	-- This does not take into consideration the dead zone of the axis
	-- The dead zone varies from controller to controller

Using hat values for movement

	-- Get the hat value for controller 1
	local direction = Gamepad.get_hat_value(1)
	
	-- move the player based on the current hat value
	if direction == 0 then 
		-- TODO: Stop the player
	elseif direction == 1 then 
		-- TODO: Move the player up
	elseif direction == 2 then 
		-- TODO: Move the player right
	elseif direction == 3 then 
		-- TODO: Move the player up to the right
	elseif direction == 4 then 
		-- TODO: Move the player down
	elseif direction == 6 then 
		-- TODO: Move the player down to the right
	elseif direction == 8 then 
		-- TODO: Move the player left
	elseif direction == 9 then 
		-- TODO: Move the player up to the left
	elseif direction == 12 then 
		-- TODO: Move the player down to the left
	end

	-- This is a simplification of what we could use
	-- the hat values for.

Using just_pressed for UI

	if Gamepad.just_pressed(1, GP_DPAD_UP) then 
		-- Move cursor up
	elseif Gamepad.just_pressed(1, GP_DPAD_RIGHT) then 
		-- Move cursor right
	elseif Gamepad.just_pressed(1, GP_DPAD_DOWN) then 
		-- Move cursor down
	elseif Gamepad.just_pressed(1, GP_DPAD_LEFT) then 
		-- Move cursor left
	end

Camera

The camera is a simple 2D camera that uses orthographic projection under the hood. You can use the camera to adjust the position of the game world, convert screen coordinates to world coordinates, zoom in and zoom out by setting the scale of the camera, and more.

The user cannot current create there own instance of the camera; however, they can access the main camera and use the functions provided.

The camera is basically a Rect that will show what is inside of it. It has a position, scale, width, and height.

TODO: Eventually, all games will have a settings file to allow the creator to adjust the camera width/height, along with the window width for each game. That is currently under construction.

Functions:

FunctionDescription
get()The get function will return an reference to the camera.
position()This will return the current position of the top left corner or the camera.
set_position(vec2)In order to move the camera, the user must set the position of the camera. It takes in a vec2 for the new position.
scale()This will return the current scale of the camera. The scale is a float value. Default scale is 1.0
set_scale(float)This will set the new scale of the camera. It will allow the user to zoom in and out.
width()This will return the current width of the camera. The width of the camera is set based on the width of the window when the game is created.
height()This will return the current height of the camera. The height of the camera is set based on the height of the window when the game is created.

Examples

Move Camera Position

	-- Get an instance of the camera
	local cam = Camera.get()
	
	-- Get the camera position, since it does not have a ctor
	-- we use the '.' operator rather than the ':'.
	local cam_pos = cam.position()

	-- Move the camera in the x direction on every update
	cam.set_position(vec2(cam_pos.x + 10, cam_pos.y))

Set Camera Scale

	-- Get an instance of the camera
	local cam = Camera.get()
	
	-- Set the camera scale to 4. This will zoom in by 4x. 
	-- So if the sprite is 16x16px, it will now look 64x64px.
	cam.set_scale(4.0)

These functions will control the main camera. If you want to use the camera to follow a specific player or entity, try to make use of the Follow Camera or create a lua class that will control the camera any way you see fit.

Follow Camera

The FollowCamera can be used to follow the movement of a specific character or entity in your game.


FollowCamParams

The follow camera params are the parameters that are used to help control the follow camera.

Properties:

PropertyTypeFunctionality
min_xfloatThe minimum x position in the world that the camera can be. The camera will stop moving once it reaches this position.
min_yfloatThe minimum y position in the world that the camera can be. The camera will stop moving once it reaches this position.
max_xfloatThe maximum x position in the world that the camera can be. The camera will stop moving once it reaches this position.
max_yfloatThe maximum y position in the world that the camera can be. The camera will stop moving once it reaches this position.
scalefloatUsually the current scale of the main camera. Used in the movement and boundary calculations internally.
springbackfloatThis controls how fast the camera follows the player. Range [0 - 1]. If the springback is set to 1, the camera will follow directly with the player. Values less than 1 will lag behind the player and catch up when the player stops or the camera boundaries have been reached.

Examples

	-- There are two ctors for FollowCamParams
	-- First takes a lua table {}
	local followCamParams1 = FollowCamParams(
		{
			min_x = 0,
			min_y = 0,
			max_x = WindowWidth(),	-- Get the Window Width from the engine
			max_y = WindowHeight(),	-- Get the Window Height from the engine
			scale = 4.0,		-- Set the camera scale to 4 
			springback = 0.2		-- Follow slightly behind the character
		}
	)

	-- The Second ctor uses arguments
	local followCamParams2 = FollowCamParams(
		0, 0, WindowWidth(), WindowHeight(), 4.0, 0.2
	)

FollowCamera Functionality

In order to create a new follow camera, we need to pass in the Desired FollowCamParams and the Entity that we want the camera to follow.

FunctionDescription
update()Updates the position of the camera based on the FollowCamParams and the current position of the player. The camera will center around the player, unless the camera has reached it's boundaries.
set_params(FollowCamParams)Re-sets the follow cam parameters to the new desired parameters
set_entity(Entity)Sets a new entity for the camera to follow. This allows the user to change what the camera is following on the fly.
set_springback(float)This will adjust the springback to the desired value. The springback is clamped to [0 - 1].
get_params()Returns the current FollowCamParams

Example

	-- Create a new followCam 
	local followCam = FollowCamera(
		FollowCamParams(0, 0, WindowWidth(), WindowHeight(), 4.0, 0.5),
		playerEntity -- We need to pass in the entity we want the camera to follow.
	)
	
	-- We need to always update the camera in our main update loop
	-- This will ensure that the camera is following the player's
	-- movement
	followCam:update()

Lua Functions



S2D_RunScript

FunctionDescription
S2D_RunScript(string)Attemps to run the passed in lua script. It takes a string for the script path. Returns true if successful, false otherwise. There will also be a log of why the script failed.

Example:


-- Run the script list table
if not S2D_RunScript("scipts/script_list.lua") then 
	S2D_error("Failed to run lua script")
end

S2D_LoadScriptTable

FunctionDescription
S2D_LoadScriptTable(sol::table)This will attempt to run a lua table filled with the path of lua scripts to run. If running a script fails, it will log an error with the reason for failing.

Example:


-- Script List Example
ScriptList = 
{
	"game/scripts/assetDefs.lua",
	"game/scripts/utilities.lua",
	"game/scripts/player.lua",
	-- ... More scripts here
}

-- Run all the scripts in the table 
S2D_LoadScriptTable(ScriptList)

S2D_MeasureText

FunctionDescription
S2D_MeasureText(string text, string font)Takes in a string of text and the desired font and returns the width of the text as a float.

Example:


-- Script List Example

local transform = entity:get_component(Transform)
local text = entity:get_component(TextComponent)
local textWidth = S2D_MeasureText(text.textStr, text.fontName)

-- Center the entity to the window width 
transform.position = (S2D_WindowWidth() * 0.5) - (textWidth * 0.5)

Logging Functions

These logging functions use the common placeholder and formatting options:

PlaceholderDescription
%sPlaceholder for a string.
%cPlaceholder for a character.
%dPlaceholder for an integer number.
%fPlaceholder for a float number.
%.nfDesignates the number of decimal places. Where 'n' is the number of points.
%xPlaceholder for a hexidecimal number.

S2D_log

FunctionDescription
S2D_log(string, ...)The string to be formatted in the log followed by the variadic arguments.

Example:


-- Logging Example

S2D_log("My name is %s. I am %d years old", "Scion", 2)

S2D_log

S2D_warn

FunctionDescription
S2D_warn(string, ...)The string to be formatted in the log followed by the variadic arguments.

Example:


-- Warning Example

Names = 
{
	"Jack", "Jill", "Steve"
}

local nameToFind = "Scion"
local bFound = false 

for _, v in pairs(Names) do 
	if v == nameToFind then 
		bFound = true 
		break
	end 
end 

if not bFound then
	S2D_warn("This name [%s] could not be found.", nameToFind)
else
	S2D_log("This name [%s] was found successfully.", nameToFind)
end

S2D_warn

S2D_error

FunctionDescription
S2D_error(string, ...)The string to be formatted in the log followed by the variadic arguments.

Example:


Assets = 
{
	Textures = 
	{
		{ name = "hero", path = "./assets/textures/hero.png", bPixelArt = true },
		-- ... More Textures
	},
	Sounds = 
	{
		-- Add sounds
	}
}

-- Error Example

for k, v in pairs(Assets.Textures) do
	if AssetManager.add_texture(v.name, v.path, v.bPixelArt ) then 
		S2D_log("Added texture [%s] successfully", v.name)
	else
		S2D_error("Failed to add texture [%s] at path [%s].", v.name, v.path)
	end
end 

S2D_error

Physics Functions

S2D_EnablePhysics

FunctionDescription
S2D_EnablePhysics()Enables Box2D Physics.

Example:


-- Enable Box2D Physics

S2D_EnablePhysics()

S2D_DisablePhysics

FunctionDescription
S2D_DisablePhysics()Disables Box2D Physics.

Example:


-- Disable Box2D Physics

S2D_DisablePhysics()

S2D_IsPhysicsEnabled

FunctionDescription
S2D_IsPhysicsEnabled()Check to see if Box2D physics is enabled. Returns true if enabled, false otherwise.

Example:


-- Enable Box2D Physics

S2D_DisablePhysics()

Render Collider Functions

S2D_EnableCollisionRendering

FunctionDescription
S2D_EnableCollisionRendering()Enables the rendering off all colliders. Colliders are usually hidden in game. Enabling is usually for debugging purposes.

Example:


-- Show Box/Circle Colliders

S2D_EnableCollisionRendering()

S2D_DisableCollisionRendering

FunctionDescription
S2D_DisableCollisionRendering()Disable the rendering off all colliders. Colliders are usually hidden in game.

Example:


-- Hide Box/Circle Colliders

S2D_DisableCollisionRendering()

S2D_CollisionRenderingEnabled

FunctionDescription
S2D_CollisionRenderingEnabled()Checks to see if collision rendering is enabled.

Example:


-- Check to see if Collision rendering is enabled
if S2D_CollisionRenderingEnabled then 
	S2D_DisableCollisionRendering()
end 


Editor

Project Hub

The Project Hub is used for creating a new project or loading a previously created project.

S2D_PHLanding

Creating a New Project

In order to create a new project, you first want to press the New Project button. This will open up a new page that will allow you to name your project and set the projects path.

S2D_PHStartNewProject

By default, the editor will create a folder [ScionProjects] directly wherever the editor's executable is located for projects. This folder is just a suggestion and does not have to be used. If you would like to set a new location, press the Browse button to open the folder dialog and set the desired folder you wish to use.

S2D_PHSetProjectPath

Once the project path has been set, all you need to do now is add in the desired name of the project and then press the Create button.

S2D_PHCreateTheProject

When the project is created, there are different folders and files automatically created for you.

Generated Folders

Folder NameDescription
[Project_Name]This is the main folder that holds the project.
SCION_2DScion project separator.
SCION_2D/contentThe content folder will hold all of the games assets and scripts. Required by the editor.
SCION_2D/content/assetsThe assets folder is an easy way to organize you assets. Optional The editor does not directly look for this folder.
SCION_2D/content/assets/fontsThe fonts folder is another Optional generated folder used for organization.
SCION_2D/content/assets/musicThe music folder is another Optional generated folder used for organization.
SCION_2D/content/assets/scenesThe scenes folder is Required. When new scenes are created, all of the generated files go into the scenes folder and into a new folder [SceneName].
SCION_2D/content/assets/shadersThe shaders folder is another Optional generated folder used for organization.
SCION_2D/content/assets/soundfxThe soundfx folder is another Optional generated folder used for organization.
SCION_2D/content/assets/texturesThe textures folder is another Optional generated folder used for organization.
SCION_2D/content/scriptsThe scripts folder is Required The editor looks here to find the main.lua file. All other scripts should also be located here.

Generated Files

File NameDescription
[Project_Name].s2dprjThis is the main project file that is used for loading a saved project. This file is automatically generated and updated by the editor. Don't make changes to this file unless you know what you are doing!

The main project file or .s2dprj is just a JSON file underneath; however, the editor expects the file to contain specific content in order to work correctly. You should not have to make changes to this file because it should be created automatically upon project creation and updated every time the project is saved.

File NameDescription
main.luaThe main.lua file is the entry point for all scripts. The editor looks for specific functions located in the main.lua file in order to run the update and render functions.

--[[
	Main Lua script. This is needed to run all scripts in the editor
	GENERATED BY THE ENGINE ON PROJECT CREATION. DON'T CHANGE UNLESS 
	YOU KNOW WHAT YOU ARE DOING!
--]]

-- The engine looks for these two functions.
-- Please add your code inside of the update and render functions as needed.
main = { 
	[1] = { 
		update = function() 
			-- Add your code to be update here
		end
	}, 
	[2] = { 
		render = function() 
			-- Add any extra rendering code here
		end
	}, 
}

You do not have to write all of your code inside of the main.lua file. It can all be separate scripts. For instance, if you create different game states in different files, you would only want to call the Update function of the current state inside of this update function. The rest of the code can be written in the loaded scripts. In order to load separate scripts, see Lua Functions for an example.