Actors and Components

This article refers to PFM v0.4.3 and newer and may not be representative of older versions.

pfm_actor_editor.pngActors in PFM are entities (i.e. game objects) that are part of your animation, not including map entities or other entities that exist independently of your project. An actor can be anything from a camera to a light source, prop or particle system. An articulated actor is an actor with a skeletal rig or morph targets (i.e. flexes/flex controllers).

In the PFM interface you can find a list of all actors in the currently selected film clip in the "Actor Editor" window.

 

 

 

Creating an actor

To create a new actor, click the gear icon at the top of the actor editor:

pragma_2021-09-23_16-35-56.png

Choosing "New Actor" will create an empty actor without any components, the other options will automatically add the required components for the chosen option. Components are explained in the following section:

Components

Contrary to SFM, there are no concrete actor types in PFM. Instead, every actor has a list of components associated with it which define the behavior/function of that actor. For instance, if you create a camera in SFM, that actor is a camera entity. If you create a camera in PFM, that actor is an entity with a camera component. The key difference is that components in PFM can be freely added or removed to/from an actor.

Example: You create an actor X and add a camera component to it, which turns that actor into a camera. If you add another component to actor X, for instance a light source component, then actor X is both a camera and a light source at the same time. If you then remove the camera component from the actor, it ceases to be a camera, but remains to be a light source (This should ring a bell if you're familiar with entity-component-systems.). This allows you to flexibly change or enhance the behavior of an actor. Some other examples:

  • You can add a "retarget" component to an actor to change its model to another, even if the new model has different proportions or a different rig, while keeping the animation intact.
  • You can add a "physics" component to make an actor move in a physically accurate manner (e.g. dropping on the ground).
    • You can then add a "gravity" component to change the actor's gravity (e.g. make it fall upwards instead).
  • etc.

If you have some programming experience, you can also create custom components with Lua, and then add those components to your actors. Custom components can also add new animatable properties. See here for details.

 

You can find a list of components an actor has in the "Actor Editor":

actor_components.png

To avoid cluttering the interface, not all of the actor's components are listed by default. Right-click on "Components" and choose "Add Component" to see which ones of the actor's current components are hidden. By clicking one of the components it will be added to the interface (along with its properties, if it has any). You should only do this if you intend to modify the properties of one of the components.

actor_add_component.png

The option "Create new Component" allows you to add an entirely new component to the actor:

actor_create_new_component.png

This list includes all components known by the engine, many of which may not be suited for use in PFM!

Custom Lua-based components will also appear in this list automatically.

If you select one or more components from the actor component list, you will get a set of properties on the right-hand side of the editor:

pragma_2021-09-23_16-19-05.png

Most of these properties can be animated (with the exception of strings and a few other types).

 

Creating custom components

To create a new Lua-based component for use in PFM, follow these steps:

  1. Create a new addon in the "addons" directory
  2. Within that directory create the path and file "lua/entities/components/<component_name>/client/client.lua"
    • "component_name" has to be lower-case and must not contain any special characters (aside from '_') or spaces!
    • If the component is specific to PFM only, you should use the "pfm_" prefix for the component name.
  3. Open the "client.lua" in a text-editor and paste the following contents into it (and make sure to follow the comments):
-- Replace "CustomComponent" with a name of your choice (make sure it doesn't exist yet)
local Component = util.register_class("ents.CustomComponent",BaseEntityComponent)

-- Register properties for this component. These will be editable (and animatable) in PFM
Component:RegisterMember(
	"ExampleProperty", -- Property name
	udm.TYPE_FLOAT, -- Property type
	1.0, -- Default value
	{
		min = 0.0, -- Min value (hint for the UI)
		max = 10.0 -- Max value (hint for the UI)
	}
)

function Component:Initialize()
	BaseEntityComponent.Initialize(self)
	print("Initializing component of type '" .. self:GetComponentName() .. "'...")
	-- Do something when this component has been added to an entity/actor
end
function Component:OnRemove()
	print("Removing component of type '" .. self:GetComponentName() .. "'...")
	-- Do some cleanup?
end

-- Register the component. "component_name" has to match the name of the directory in "entities/components/"!
-- "COMPONENT_CUSTOM" should also reflect this name!
-- e.g. if your component name is "apple_tree", "COMPONENT_CUSTOM" should be "COMPONENT_APPLE_TREE"
ents.COMPONENT_CUSTOM = ents.register_component("component_name",Component)

The next time you start PFM, your component should appear in the component list automatically and you can add it to your actors.

 

Below is an example of the pfm_sky component, which enables animating the sky orientation and strength when rendering with raytracing:

util.register_class("ents.PFMSky",BaseEntityComponent)

local Component = ents.PFMSky
Component:RegisterMember("Strength",udm.TYPE_FLOAT,1.0,{
	min = 0.0,
	max = 10.0
})
Component:RegisterMember("Transparent",udm.TYPE_BOOLEAN,false)
Component:RegisterMember("SkyTexture",udm.TYPE_STRING,"",{
	specializationType = ents.ComponentInfo.MemberInfo.SPECIALIZATION_TYPE_FILE,
	metaData = {
		rootPath = "materials/skies/",
		basePath = "skies/",
		extensions = {"hdr"},
		stripExtension = true
	}
})

function Component:Initialize()
	BaseEntityComponent.Initialize(self)

	self.m_callbacks = {}
	table.insert(self.m_callbacks,ents.add_component_creation_listener("unirender",function(c)
		table.insert(self.m_callbacks,c:AddEventCallback(ents.UnirenderComponent.EVENT_INITIALIZE_SCENE,function(scene,renderSettings)
			scene:SetSkyAngles(self:GetEntity():GetAngles())
			scene:SetSkyStrength(self:GetStrength())
			local tex = self:GetSkyTexture()
			if(#tex > 0) then scene:SetSky(tex) end
			scene:SetSkyTransparent(self:GetTransparent())
		end))
	end))
end
function Component:OnRemove()
	util.remove(self.m_callbacks)
end
ents.COMPONENT_PFM_SKY = ents.register_component("pfm_sky",Component)