Skip to main content

Character Quick Start

Overview

This documentation will introduce how to create Anim Trees and State Machines, transit the state of State Machines, bind inputs, and finally write scripts to control the character's actions in the Level.

Creating a New Project

Create a new empty project, please see Project for details

Importing Art Assets

Import skeleton meshes and animations. Please see Model Resources in the Resources Workflow for details.

Assembling Actors

Please see Assembling Actors for details.

Adding State Machine

Create an Anim Tree tp_tree in the Animation Editor.

image-20220902154842336

image-20220902154854384

Add a State Machine Node to the Anim Tree and connect it to Final Animation Pose.

image-20230131134913981

Double-click the State Machine Node to enter its editing interface. Add two state nodes, idle and run, and connect them to form a cycle.

Double-click the idle state node and add a stand pose to it. And then add a Sequence Playback Node and connect it to Final Animation Pose.

Similarly, add a run action to the run state.

Add a Speed Parameter of the Float type.

image-20220909152055538

Set the state transition condition of idle → run as Speed is greater than 0.1.

image-20230131135404636

Set the state transition condition of run → idle as Speed is less than 0.1.

image-20230131135532751

Click Save.

image-20230131135737370

Editing Input Settings

Click Config (Menu Bar) -> Project Settings to open the Project Settings window in the Component Editor.

image-20230131140714361

In the Axis Mapping List of Input Settings, add 7 keys similar to the ones in the picture below.

image-20230131141641589

image-20230131142043798

Character Settings

Creating a Character

Click Create Game Object -> Character to create a player character in the new level.

image-20230131142240082

Property Settings

Modify the property Skeletal Mesh Config File of the CharacterMesh0(Inherited) Component to the previously created .actor file.

image-20230131142824083

Then modify the property Default Action Override to the previously created tp_tree as well.

image-20230131143134752

In the Hierarchy panel, select CollisionCylinder(Inherited) and adjust the position of the capsule. Then select CharacterMesh0(Inherited) and adjust the position of the character mesh, so that the feet of the character skin is close to the ground in the premise that the capsule wraps the character as much as possible.

image-20230330104903739

Add a Spring Arm Component under the CollisionCylinder(Inherited) Component.

image-20230131144413413

image-20230131144538828

Then add a Camera Component under the Spring Arm Component.

image-20220916145200994

image-20230131144717514

Adjust the Camera Component to the appropriate position.

image-20230131144921019

Select the LCharacter and set its property Auto Possess Player to Player0. At this time, PIE will automatically use the camera of the character.

image-20230131145142029

Set (Uncheck) the property Use Controller Angle Yaw of the character object to false.

image-20230131145232078

Set (Check) the properties of the Spring Arm Component including Use Pawn Control Rotation to true, Do Collision Test to false (Uncheck), and Enable Camera Lag to true respectively.

Set (Check) the property Orient Rotation To Movement of the CharMoveComp(Inherited) to true.

image-20230131145547564

Scripting Character Control

Add a controller.lua file in the script folder of the project, and scripting as follows.


--Animation Resource Type
AT_UNKNOWN = 0
AT_SKELETON = 1
AT_SKELETON_AS_ANIMSEQUENCE = 2
AT_ANIMSEQUENCE = 3
AT_MONTAGE = 4
AT_BLENDSPACE = 5
AT_BLENDSPACE1D = 6
AT_AIMOFFSETBLENDSPACE = 7
AT_ANIMTREE = 8

local IE_Pressed = 0 --Press the Key
local IE_Released = 1 --Release the Key
local IE_Repeat = 2
local IE_DoubleClick = 3
local IE_Axis = 4
local IE_MAX = 5

local CAMERA_DISTANCE_CHANGE_SPEED = 0.5
local CAMERA_DISTANCE_MIN = -1
local CAMERA_DISTANCE_MAX = 50


--use this for initialization
function on_begin_play(component)
local owner = component.ActorOwner

if nx_is_valid(owner) then
local input_comp = owner.InputComponent

if nx_is_valid(input_comp) then
nx_bind_script(owner, nx_current())

--The first parameter corresponds to the Axis Name in the input settings
--The last parameter is the custom Event Name
input_comp:AddAxisBinding("MoveForward", true, false, true, "InputAxisEvent_MoveForward")
input_comp:AddAxisBinding("MoveRight", true, false, true, "InputAxisEvent_MoveRight")
input_comp:AddAxisBinding("Turn", true, false, true, "InputAxisEvent_Turn")
input_comp:AddAxisBinding("LookUp", true, false, true, "InputAxisEvent_LookUp")
input_comp:AddAxisBinding("AdjustCameraDis", true, false, true, "InputAxisEvent_AdjustCameraDis")

--The second parameter is the last parameter in AddAxisBinding, which represents the function name in the script
nx_callback(owner, "InputAxisEvent_MoveForward", "on_moveforward")
nx_callback(owner, "InputAxisEvent_MoveRight", "on_moveright")
nx_callback(owner, "InputAxisEvent_Turn", "on_turn")
nx_callback(owner, "InputAxisEvent_LookUp", "on_lookup")
nx_callback(owner, "InputAxisEvent_AdjustCameraDis", "on_adjust_camera_dis")

nx_callback(owner, "on_velocity_changed", "on_velocity_changed")

end
end

nx_callback(component, "on_tick", "tick")
end

--use this for release
function on_end_play(component)
end

--Get actor
function character_get_actor(character)
if nx_is_valid(character) then
local mesh_component = character.MeshComponent

if nx_is_valid(mesh_component) then
local vis_base = mesh_component.VisBase

if nx_is_valid(vis_base) and nx_is_kind(vis_base, "Actor") then
return vis_base
end
end
end

return nx_null()
end

--Get spring_arm
function character_get_spring_arm(character)
if nx_is_valid(character) then
local spring_arm_component = character:FindComponentByClassName("LSpringArmComponent")

return spring_arm_component
end

return nx_null()
end

--Move forward callback
function on_moveforward(owner, axis_value)
if not nx_is_valid(owner) then
return 0
end

--Get controller Y-axis angle
local controller = owner.Controller

if nx_is_valid(controller) then
local yaw = controller.AngleY
--Get forward direction
local x, y, z = nx_function("ext_angle_get_forward_vector", 0.0, yaw, 0.0)

--Add Movement value

owner:AddMovementInput(x, y, z, axis_value)
end

return 1
end

--Move right callback
function on_moveright(owner, axis_value)
if not nx_is_valid(owner) then
return 0
end

--Get controller Y-axis angle
local controller = owner.Controller

if nx_is_valid(controller) then
local yaw = controller.AngleY
--Get right direction
local x, y, z = nx_function("ext_angle_get_right_vector", 0.0, yaw, 0.0)

--Add Movement value
owner:AddMovementInput(x, y, z, axis_value)
end
return 1
end

--Rotate left and right (along Y-axis) callback
function on_turn(owner, axis_value)
if not nx_is_valid(owner) then
return 0
end

owner:AddControllerYawInput(axis_value)

return 1
end

--Rotate up and down (along X-axis) callback
function on_lookup(owner, axis_value)
if not nx_is_valid(owner) then
return 0
end

owner:AddControllerPitchInput(-axis_value)

return 1
end

--Mouse wheel callback
function on_adjust_camera_dis(owner, axis_value)
if not nx_is_valid(owner) then
return 0
end

local spring_arm_component = character_get_spring_arm(owner)

if nx_is_valid(spring_arm_component) then
local delta = axis_value * CAMERA_DISTANCE_CHANGE_SPEED

local cur_value = spring_arm_component.TargetArmLength
local new_value = cur_value + delta

if delta > 0 then
if new_value > CAMERA_DISTANCE_MAX then
new_value = CAMERA_DISTANCE_MAX
end

spring_arm_component.TargetArmLength = new_value
else
if new_value < CAMERA_DISTANCE_MIN then
new_value = CAMERA_DISTANCE_MIN
end
spring_arm_component.TargetArmLength = new_value
end
end

return 1
end


--Velocity change callback
function on_velocity_changed(owner, component_id, new_x, new_y, new_z, old_x, old_y, old_z)
local actor = character_get_actor(owner)

if nx_is_valid(actor) then
if actor:GetBlendActionCount() > 0 then
local action_name = actor:GetBlendActionName(0)
local action_type = actor:GetActionType(action_name)
local action_index = actor:GetActionIndex(action_name)

if action_type == AT_ANIMTREE then
local speed = math.sqrt(new_x * new_x + new_y * new_y + new_z * new_z)
actor:SetAnimTreeValue(action_index, "Speed", speed)

nx_set_custom(actor,"CurSpeed",speed);
end
end
end

return 1
end

Binding Character Control Script

Add a Script Component to the character object.

image-20230131145718225

image-20230131145813385

Set the property Script File of the Script Component to the controller.lua as written previously.

image-20220909153047489

Click Save Current Level (Ctrl+S).

image-20230131151941603

PIE

Playing in the Editor as shown below.

image-20230131150200053