refractored player behavior into not-monolithic code, better player handling, stair stepping going up and down, lua serialization/deserialization should be more optimal by virtue of not stringifying

This commit is contained in:
ecker 2026-06-16 21:41:18 -05:00
parent a834adca13
commit 2944335904
19 changed files with 1150 additions and 1242 deletions

View File

@ -354,7 +354,7 @@
"ngs": true,
"percent": 0.2,
"slop": 0.01, // 0.005
"max": 0.2 // 0.2
"max": 0.1 // 0.2
},
"debug draw": {
"static": false,

View File

@ -1,7 +1,7 @@
{
"type": "Object",
"name": "Burger",
"ignore": true,
"ignore": false,
"import": "/model.json",
"assets": [
// "/burger/burger.glb"
@ -11,7 +11,7 @@
],
"behaviors": [],
"transform": {
"position": [ -0.574743, 2.3547, -5.05161 ],
"position": [ 25.7525, 5.17746, 16.5508 ],
// "position": [ -4.66561, 0.0736207, -5.98057 ],
"rotation": {
"axis": [ 0, 1, 0 ],

View File

@ -1,26 +1,13 @@
{
"name": "Player",
"type": "Object",
"type": "Player",
"behaviors": [
"PlayerBehavior",
"AudioEmitterBehavior"
],
"ignore": false,
/*
"transform": {
"position": [ 0, 0, 0 ],
"rotation": {
"axis": [ 0, 1, 0 ],
"angle": 0
},
"scale": [ 1, 1, 1 ]
},
*/
"assets": [
{ "filename": "./playerModel.json", "delay": 1 },
// "./playerModel.json",
"./playerLight.json",
// "./playerHands.json",
"./gui/hud/hud.json",
"./scripts/player.lua"
],
@ -30,78 +17,37 @@
}
},
"metadata": {
"overlay": {
"transform": {
"position": [ 0, 0, -3 ],
"scale": [ 1.77778, -1, 1 ],
"orientation": [ 0, 0, 0, 1 ]
},
"floating": false,
"enabled": true,
"alpha": 1.0,
"cursor": {
"type": "mouse",
"radius": 0.05,
"color": [ 0.2, 0.2, 1.0, 1.0 ],
"enabled": false
}
},
"audio": {
"footstep": {
"volume": 0.5,
"list": [
"/footstep/1.ogg",
"/footstep/2.ogg"
]
}
},
"movement": {
"walk": 8,
"move": 8,
"run": 20,
// "rotate": 1.5,
"rotate": 6,
"walk": 6,
"move": 12,
"run": 18,
"rotate": 6.0,
"air": 0.1,
"crouch": 1,
"crouch": 0.9,
"jump": [ 0, 8, 0 ],
"look": 1,
"floored": {
"feet": [ 0, -1, 0 ],
// "floor": [ 0, -0.5, 0 ],
"floor": [ 0, -1, 0 ],
"print": false
"floor": [ 0, -1, 0 ]
},
"strafe": true
},
"physics": {
"gravity": [ 0, -9.81, 0 ],
"gravity": [ 0, -14.0, 0 ],
"inertia": false,
"offset": [ 0, 0, 0 ],
// "type": "sphere",
// "radius": 2,
"type": "capsule",
"radius": 1,
"height": 2,
"category": "player",
"mask": "player",
// "type": "bounding box",
// "min": [ -1, -1, -1 ],
// "max": [ 1, 1, 1 ],
"mass": 100,
"friction": 0.95,
"restitution": 0.0,
"shared": false
"friction": 0.90,
"restitution": 0.0
},
"camera": {
// "offset": [ 0, 10, 6 ],
// "orientation": [ 0, 0.894427, -0.447214, 0 ],
"position" : [ 0, 1.8, 0 ],
"scale": [ 1, 1, 1 ],
"invert": [ false, false, false ],

View File

@ -1,22 +1,19 @@
local ent = ent
local scene = entities.currentScene()
local graph = scene:getComponent("Graph")
local metadataJson = ent:getComponent("Metadata")
local transform = ent:getComponent("Transform")
local physicsBody = ent:getComponent("PhysicsBody")
local metadataJson = ent:getComponent("Metadata")
local camera = ent:getComponent("Camera")
local cameraTransform = camera:getTransform()
local metadata = ent:getComponent("PlayerBehavior::Metadata")
local fixedCamera = metadataJson["camera"]["settings"]["fixed"]
local cameraMetadata = ent:getComponent("PlayerCameraBehavior::Metadata")
local moveStates = ent:getComponent("PlayerMovementBehavior::Metadata")
local fixedCamera = cameraMetadata and cameraMetadata.fixed or false
-- setup all timers
local timers = {
use = Timer.new(),
holp = Timer.new(),
flashlight = Timer.new(),
physcannon = Timer.new()
}
if not timers.use:running() then timers.use:start(); end
if not timers.holp:running() then timers.holp:start(); end
if not timers.flashlight:running() then timers.flashlight:start(); end
if not timers.physcannon:running() then timers.physcannon:start(); end
@ -30,70 +27,42 @@ local heldObject = {
momentum = Vector3f(0,0,0),
rotate = false,
}
-- setup light locals
local light = {
entity = nil,
}
-- setup light
local light = { entity = nil }
for k, v in pairs(ent:getChildren()) do
if type(v) == "number" then
goto continue
end
if v:name() == "Light" then
light.entity = v
end
::continue::
if type(v) == "table" and v:name() == "Light" then light.entity = v end
end
if light.entity == nil then
light.entity = ent:loadChild("ent://playerLight.json",true)
end
if light.entity == nil then
light.entity = ent:loadChild("./playerLight.json",true)
end
light.metadata = light.entity:getComponent("LightBehavior::Metadata")
light.transform = light.entity:getComponent("Transform")
light.power = light.metadata.power
light.origin = Vector3f(light.transform.position)
light.metadata.power = 0
--light.entity:setComponent("Metadata", { light = { power = 0 } })
-- sound emitter
local playSound = function( path, loop )
if not loop then loop = false end
local uri = string.resolveURI(path)
ent:callHook("sound:Emit.%UID%", {
filename = uri,
spatial = true,
streamed = true,
volume = "sfx",
loop = loop
}, 0)
end
local stopSound = function( path )
ent:callHook("sound:Stop.%UID%", {
filename = string.resolveURI(path, metadataJson["system"]["root"])
}, 0)
end
-- UI sound helpers
local playUiSound = function( key, loop )
return playSound("/ui/" .. key .. ".ogg", loop)
end
local stopUiSound = function( key )
return stopSound("/ui/" .. key .. ".ogg", loop)
ent:callHook("sound:Emit.%UID%", {
filename = string.resolveURI("/ui/" .. key .. ".ogg"),
spatial = true, streamed = true, volume = "sfx", loop = loop or false
}, 0)
end
local useDistance = 6
local pullDistance = useDistance * 4
local pullDistance = 24
local function tickFlashlight( transform, axes, inputs )
-- update light position
local function tickFlashlight( transform, axes, inputState )
if light.enabled then
local center = transform.position
local direction = axes.forward * 8
local offset = 0.25
local _, depth = physicsBody:rayCast(center, direction)
depth = math.clamp(depth, 0, 0.5)
light.transform.position = center + direction * (depth - offset)
light.transform.position = center + direction * (depth - 0.25)
end
-- toggle
if timers.flashlight:elapsed() > 0.5 and inputs["F"] then
-- Flashlight uses 'F' key (we can keep this manual since it's game-specific, or add it to C++ input state)
if timers.flashlight:elapsed() > 0.5 and inputs.key("F") then
timers.flashlight:reset()
light.enabled = (light.metadata.power ~= light.power)
light.metadata.power = light.enabled and light.power or 0
@ -101,135 +70,47 @@ local function tickFlashlight( transform, axes, inputs )
end
end
local function onUse( payload )
local validUse = false
-- not currently holding anything, and hit something
if heldObject.uid == 0 and payload.depth > 0 then
local prop = entities.get( payload.uid )
local propMetadata = prop:getComponent("Metadata")
-- entity is holdable, pick it up
if propMetadata["holdable"] then
validUse = true
local heldObjectTransform = prop:getComponent("Transform")
local heldObjectFlattened = heldObjectTransform:flatten()
local offset = transform.position - heldObjectFlattened.position
local function tickGravGun( transform, axes, inputState )
local mouse1 = inputs.key("Mouse1") or inputs.key("L_TRIGGER")
local mouse2 = inputs.key("Mouse2") or inputs.key("R_TRIGGER")
local mouse3 = inputs.key("Mouse3")
local wheel = inputs.analog("MouseWheel")
heldObject.uid = payload.uid
heldObject.distance = offset:norm()
local heldObjectPhysicsBody = prop:getComponent("PhysicsBody")
heldObjectPhysicsBody:enableGravity(false)
else
validUse = not string.matched( prop:name(), "/^worldspawn/" )
end
-- currently holding something, drop it
elseif heldObject.uid ~= 0 then
validUse = true
local prop = entities.get( heldObject.uid )
local heldObjectPhysicsBody = prop:getComponent("PhysicsBody")
heldObjectPhysicsBody:enableGravity(true)
heldObjectPhysicsBody:applyImpulse( heldObject.momentum )
heldObject.uid = 0
heldObject.distance = 0
heldObject.momentum = Vector3f(0,0,0)
end
playUiSound(validUse and "select" or "deny")
end
local function tickUse( transform, axes, inputs )
-- trigger use
if timers.use:elapsed() > 0.5 and inputs["E"] then
timers.use:reset()
local center = transform.position
local direction = axes.forward * useDistance
local hit, depth = physicsBody:rayCast(center, direction)
local payload = {
user = ent:uid(),
uid = hit and hit:uid() or 0,
depth = depth,
}
if hit then hit:lazyCallHook("entity:Use.%UID%", payload) end
ent:lazyCallHook("entity:Use.%UID%", payload)
end
end
local function tickGravGun( transform, axes, inputs )
-- not holding anything
if heldObject.uid == 0 then
-- try and launch object in sights
if inputs["mouse2"] then
local center = transform.position
local direction = axes.forward * pullDistance
local prop, depth = physicsBody:rayCast( center, direction )
if mouse2 then
local prop, depth = physicsBody:rayCast( transform.position, axes.forward * pullDistance )
if depth >= 0 and prop and not string.matched( prop:name(), "/^worldspawn/" ) then
local heldObjectTransform = prop:getComponent("Transform")
local heldObjectPhysicsBody = prop:getComponent("PhysicsBody")
local propPhysics = prop:getComponent("PhysicsBody")
local distanceSquared = (prop:getComponent("Transform").position - transform.position):magnitude()
propPhysics:applyImpulse( axes.forward * -propPhysics:getMass() * 500 / distanceSquared )
local strength = 500
local distanceSquared = (heldObjectTransform.position - transform.position):magnitude()
heldObjectPhysicsBody:applyImpulse( axes.forward * -heldObjectPhysicsBody:getMass() * strength / distanceSquared )
if timers.physcannon:elapsed() > 1.0 then
timers.physcannon:reset()
playUiSound("phys_tooHeavy")
end
end
end
-- holding something
else
-- adjust hold distance
if inputs["wheel"] ~= 0 then
heldObject.distance = heldObject.distance + (inputs["wheel"] / 120 * heldObject.scrollSpeed) * time.delta()
end
-- update rotation mode
if inputs["mouse3"] then
heldObject.rotate = not heldObject.rotate
end
-- Held object logic (unchanged from your original, just cleaner scope)
if wheel ~= 0 then heldObject.distance = heldObject.distance + (wheel / 120 * heldObject.scrollSpeed) * time.delta() end
if mouse3 then heldObject.rotate = not heldObject.rotate end
local prop = entities.get( heldObject.uid )
local heldObjectTransform = prop:getComponent("Transform")
local heldObjectPhysicsBody = prop:getComponent("PhysicsBody")
local propTransform = prop:getComponent("Transform")
local propPhysics = prop:getComponent("PhysicsBody")
-- launch held object
if inputs["mouse1"] and timers.physcannon:elapsed() > 0.5 then
if mouse1 and timers.physcannon:elapsed() > 0.5 then
timers.physcannon:reset()
heldObject.uid = 0
heldObjectPhysicsBody:enableGravity(true)
heldObjectPhysicsBody:applyImpulse( axes.forward * heldObjectPhysicsBody:getMass() * 50 )
propPhysics:enableGravity(true)
propPhysics:applyImpulse( axes.forward * propPhysics:getMass() * 50 )
playUiSound("phys_launch"..math.random(1,4))
else
-- update rotation
if heldObject.rotate then
--heldObjectTransform.orientation = Quaternion.lookAt( (heldObjectTransform.position - transform.position):normalize(), axes.up )
heldObjectTransform.orientation = cameraTransform:flatten().orientation
end
-- move held object
local forward = axes.forward * heldObject.distance
if heldObject.smoothSpeed ~= 0 then
local heldObjectFlattened = heldObjectTransform:flatten()
if heldObject.rotate then propTransform.orientation = camera:getTransform():flatten().orientation end
local target = transform.position + forward
local offset = target - heldObjectFlattened.position
local stiffness = 15.0
local damping = 2.0
local currentVelocity = heldObjectPhysicsBody:getVelocity()
local mass = heldObjectPhysicsBody:getMass()
local springForce = offset * stiffness
local dampingForce = currentVelocity * -damping
heldObjectPhysicsBody:applyImpulse((springForce + dampingForce) * mass * time.delta())
else
heldObjectTransform.position = transform.position + forward
end
local target = transform.position + (axes.forward * heldObject.distance)
local offset = target - propTransform:flatten().position
propPhysics:applyImpulse((offset * 15.0 + propPhysics:getVelocity() * -2.0) * propPhysics:getMass() * time.delta())
end
end
end
@ -275,23 +156,7 @@ local playFootstepSound = function( surface, isCrouching )
}, 0)
end
local tickFootsteps = function()
local isWalking = metadata.states.walking
local isRunning = metadata.states.running
local isCrouching = metadata.states.crouching
local isFloored = metadata.states.floored
local isNoclipped = metadata.states.noclipped
if not isFloored or isNoclipped then return end
if not isWalking then
footstepTimer = 0.0
return
end
footstepTimer = footstepTimer - time.delta()
if footstepTimer > 0.0 then return end
local function getFloorSurface()
local surface = "concrete"
local collisionEvents = physicsBody:getCollisionEvents()
for i, event in ipairs( collisionEvents ) do
@ -305,20 +170,59 @@ local tickFootsteps = function()
local drawCommand = mesh:fetchDrawCommand( tri )
local instance = graph:getInstance( drawCommand.instanceID )
local materialName = string.lower( graph:getMaterialName( instance.materialID ) )
for _, key in ipairs(surfaceTypes) do
if string.find(materialName, key) then
surface = key
break
return key
end
end
break
end
break
end
end
return surface
end
playFootstepSound( surface, isCrouching )
local airTime = 0.0
local tickFootsteps = function( inputState )
local isWalking = moveStates.walking
local isRunning = moveStates.running
local isCrouching = moveStates.crouching
local isFloored = moveStates.floored
local isNoclipped = moveStates.noclipped
if not isNoclipped then
if not isFloored then
airTime = airTime + time.delta()
end
if isFloored and not wasFloored then
if airTime > 0.15 then
playFootstepSound( getFloorSurface(), isCrouching )
footstepTimer = isRunning and 0.3 or 0.45
end
airTime = 0.0
end
if not isFloored and wasFloored and inputState.jump then
playFootstepSound( getFloorSurface(), isCrouching )
end
end
wasFloored = isFloored
if not isWalking or isNoclipped then
footstepTimer = 0.0
return
end
footstepTimer = footstepTimer - time.delta()
if not isFloored then return end
if footstepTimer > 0.0 then return end
playFootstepSound( getFloorSurface(), isCrouching )
if isRunning then
footstepTimer = 0.3
@ -329,71 +233,43 @@ local tickFootsteps = function()
end
end
--[[
local tickCollisionEvents = function()
local collisionEvents = physicsBody:getCollisionEvents()
for i, event in ipairs(collisionEvents) do
-- print( event.state, event.a, event.b, event.point, event.normal, event.impulse, event.featureA )
local tri = event.featureA or event.featureB
local other = event.a == ent and event.a or event.b
local collider = other:getCollider()
-- technically will always return a triangle ID if there's a mesh collider
if tri ~= nil and collider.type == ShapeType.MESH then
local mesh = collider:asMesh()
local drawCommand = mesh:fetchDrawCommand( tri )
local instance = graph:getInstance( drawCommand.instanceID )
local material = graph:getMaterial( instance.materialID )
local materialName = graph:getMaterialName( instance.materialID )
if not materialName:find("tools/") and event.normal.y < -0.7 then
local soundKey = getSurfaceSound(materialName)
playSound("valve://sound/" .. soundKey .. ".wav", false)
end
ent:addHook( "entity:Use.%UID%", function( payload )
if payload.user ~= ent:uid() then return end
local validUse = false
if heldObject.uid == 0 and payload.depth > 0 then
local prop = entities.get( payload.uid )
if prop:getComponent("Metadata")["holdable"] then
validUse = true
heldObject.uid = payload.uid
heldObject.distance = (ent:getComponent("Transform").position - prop:getComponent("Transform"):flatten().position):norm()
prop:getComponent("PhysicsBody"):enableGravity(false)
else
validUse = not string.matched( prop:name(), "/^worldspawn/" )
end
elseif heldObject.uid ~= 0 then
validUse = true
local prop = entities.get( heldObject.uid )
prop:getComponent("PhysicsBody"):enableGravity(true)
heldObject.uid = 0
end
end
]]
playUiSound(validUse and "select" or "deny")
end )
-- on tick
ent:bind( "tick", function(self)
local inControl = scene:globalFindByName("Gui: Menu"):uid() == 0
local inputs = ent:getComponent("PlayerInputBehavior::Metadata")
if not inputs or not inputs.control then return end
local inputs = {
E = inputs.key("E") or inputs.key("R_Y"),
F = inputs.key("F"),
mouse1 = inputs.key("Mouse1") or inputs.key("L_TRIGGER"),
mouse2 = inputs.key("Mouse2") or inputs.key("R_TRIGGER"),
mouse3 = inputs.key("Mouse3"),
wheel = inputs.analog("MouseWheel"),
}
if not inControl then
inputs["E"] = false
inputs["F"] = false
inputs["mouse1"] = false
inputs["mouse2"] = false
inputs["mouse3"] = false
inputs["wheel"] = 0
end
-- eye transform
local flattenedTransform = fixedCamera and transform:flatten() or cameraTransform:flatten()
local flattenedTransform = fixedCamera and ent:getComponent("Transform"):flatten() or camera:getTransform():flatten()
local axes = flattenedTransform:axes()
-- update flashlight
tickFlashlight( flattenedTransform, axes, inputs )
-- update use
tickUse( flattenedTransform, axes, inputs )
-- update HOLP
tickGravGun( flattenedTransform, axes, inputs )
-- play footsteps
tickFootsteps()
end )
-- on use
ent:addHook( "entity:Use.%UID%", function( payload )
if payload.user ~= ent:uid() then return end
onUse( payload )
tickFootsteps( inputs )
end )

View File

@ -1,7 +1,8 @@
{
"import": "./base_sourceengine.json",
"assets": [
{ "filename": "./maps/mcdonalds-mds.bsp" }
// { "filename": "./maps/mcdonalds-mds/graph.json" }
// { "filename": "./maps/mcdonalds-mds.bsp" }
{ "filename": "./maps/mcdonalds-mds/graph.json" },
{ "filename": "ent://burger.json", "delay": 1 }
]
}

View File

@ -28,6 +28,7 @@
#include <uf/utils/memory/unordered_map.h>
#include <uf/utils/singletons/pre_main.h>
#include <uf/utils/string/ext.h>
#include <uf/ext/json/json.h>
namespace pod {
struct UF_API LuaScript {
@ -57,8 +58,8 @@ namespace ext {
sol::table createTable();
uf::stl::string sanitize( const uf::stl::string& dirty, int index = -1 );
std::optional<uf::stl::string> encode( sol::table table );
std::optional<sol::table> decode( const uf::stl::string& string );
std::optional<ext::json::Value> encode( sol::table table );
std::optional<sol::table> decode( const ext::json::Value& string );
}
}

View File

@ -1,828 +1,17 @@
#include "behavior.h"
#include <uf/utils/hook/hook.h>
#include <uf/utils/time/time.h>
#include <uf/utils/serialize/serializer.h>
#include <uf/utils/userdata/userdata.h>
#include <uf/utils/window/window.h>
#include <uf/utils/window/payloads.h>
#include <uf/utils/camera/camera.h>
#include <uf/utils/audio/audio.h>
#include <uf/ext/openvr/openvr.h>
#include <uf/engine/graph/graph.h>
#include <uf/utils/math/physics.h>
#include <uf/spec/controller/controller.h>
#include <uf/utils/io/inputs.h>
#include <sstream>
#include "../scene/behavior.h"
// #include "../../gui/manager/behavior.h"
#define ONE_OVER_SIXTY 0.016666f
UF_BEHAVIOR_REGISTER_CPP(ext::PlayerBehavior)
UF_BEHAVIOR_TRAITS_CPP(ext::PlayerBehavior, ticks = true, renders = false, thread = uf::thread::asyncThreadName)
#define this (&self)
void ext::PlayerBehavior::initialize( uf::Object& self ) {
auto& transform = this->getComponent<pod::Transform<>>();
auto& metadata = this->getComponent<ext::PlayerBehavior::Metadata>();
auto& metadataJson = this->getComponent<uf::Serializer>();
auto& camera = this->getComponent<uf::Camera>();
auto& cameraTransform = camera.getTransform();
auto& scene = uf::scene::getCurrentScene();
{
camera.setStereoscopic(true);
cameraTransform.position = uf::vector::decode( metadataJson["camera"]["position"], cameraTransform.position );
cameraTransform.scale = uf::vector::decode( metadataJson["camera"]["scale"], cameraTransform.scale );
cameraTransform.orientation = uf::vector::decode( metadataJson["camera"]["orientation"], cameraTransform.orientation );
cameraTransform.reference = metadata.camera.fixed ? NULL : &transform;
auto cameraSettingsJson = metadataJson["camera"]["settings"];
metadata.camera.offset = uf::vector::decode( metadataJson["camera"]["offset"], metadata.camera.offset );
if ( metadataJson["camera"]["ortho"].as<bool>() ) {
float l = cameraSettingsJson["left"].as<float>();
float r = cameraSettingsJson["right"].as<float>();
float b = cameraSettingsJson["bottom"].as<float>();
float t = cameraSettingsJson["top"].as<float>();
float n = cameraSettingsJson["near"].as<float>();
float f = cameraSettingsJson["far"].as<float>();
camera.setProjection( uf::matrix::orthographic( l, r, b, t, n, f ) );
} else {
float fov = cameraSettingsJson["fov"].as<float>(120) * (3.14159265358f / 180.0f);
pod::Vector2f range = uf::vector::decode( cameraSettingsJson["clip"], pod::Vector2f{ 0.1, 64.0f } );
pod::Vector2ui size = uf::vector::decode( cameraSettingsJson["size"], pod::Vector2ui{ uf::renderer::settings::width, uf::renderer::settings::height } );
float raidou = (float) size.x / (float) size.y;
if ( size.x == 0 || size.y == 0 ) {
size = uf::vector::decode( uf::config["window"]["size"], pod::Vector2ui{} );
raidou = (float) size.x / (float) size.y;
#if 0
this->addHook( "window:Resized", [&, fov, range](pod::payloads::windowResized& payload){
float width = uf::renderer::settings::
float raidou = (float) payload.window.size.x / (float) payload.window.size.y;
camera.setProjection( uf::matrix::perspective( fov, raidou, range.x, range.y ) );
} );
#endif
}
camera.setProjection( uf::matrix::perspective( fov, raidou, range.x, range.y ) );
}
camera.update();
}
// sloppy
metadata.mouse.sensitivity = uf::vector::decode( uf::config["window"]["mouse"]["sensitivity"], metadata.mouse.sensitivity );
metadata.mouse.smoothing = uf::vector::decode( uf::config["window"]["mouse"]["smoothing"], metadata.mouse.smoothing );
this->addHook( "window:Mouse.CursorVisibility", [&](pod::payloads::windowMouseCursorVisibility& payload){
metadata.system.control = !payload.mouse.visible;
});
this->addHook( "system:Control.%UID%", [&]( ext::json::Value& value ){
metadata.system.control = value["control"].as<bool>(!metadata.system.control);
});
// Rotate Camera
#if !UF_INPUT_USE_ENUM_MOUSE
#if !UF_ENV_DREAMCAST
this->addHook( "window:Mouse.Moved", [&](pod::payloads::windowMouseMoved& payload ){
const pod::Vector2ui deadZone{0, 0};
if ( (payload.mouse.delta.x == 0 && payload.mouse.delta.y == 0) || !metadata.system.control ) return;
if (abs(payload.mouse.delta.x) > deadZone.x) metadata.mouse.accum.x += payload.mouse.delta.x * (ONE_OVER_SIXTY)/* uf::physics::time::delta*/ / payload.window.size.x;
if (abs(payload.mouse.delta.y) > deadZone.y) metadata.mouse.accum.y += payload.mouse.delta.y * (ONE_OVER_SIXTY)/* uf::physics::time::delta*/ / payload.window.size.y;
});
#endif
#endif
#if UF_USE_DISCORD
// Discord Integration
this->addHook( "discord:Activity.Update.%UID%", [&](ext::json::Value& json){
uf::stl::string leaderId = metadataJson[""]["party"][0].as<uf::stl::string>();
uf::Serializer cardData = masterDataGet("Card", leaderId);
uf::Serializer charaData = masterDataGet("Chara", cardData["character_id"].as<uf::stl::string>());
uf::stl::string leader = charaData["name"].as<uf::stl::string>();
ext::json::Value payload = json;
payload["details"] = "Leader: " + leader;
uf::hooks.call( "discord:Activity.Update", payload );
});
this->queueHook("discord:Activity.Update.%UID%", ext::json::null(), 1.0);
#endif
{
ext::json::Value payload;
payload["uid"] = this->getUid();
this->queueHook("controller:Ready", payload, 0.0f );
}
UF_BEHAVIOR_METADATA_BIND_SERIALIZER_HOOKS(metadata, metadataJson);
}
void ext::PlayerBehavior::tick( uf::Object& self ) {
auto& transform = this->getComponent<pod::Transform<>>();
auto& camera = this->getComponent<uf::Camera>();
auto& cameraTransform = camera.getTransform();
auto cameraAxes = uf::transform::axes( cameraTransform );
auto axes = uf::transform::axes( transform );
auto& scene = uf::scene::getCurrentScene();
auto& graph = scene.getComponent<pod::Graph>();
auto& metadata = this->getComponent<ext::PlayerBehavior::Metadata>();
auto& metadataJson = this->getComponent<uf::Serializer>();
auto& physicsBody = this->getComponent<pod::PhysicsBody>();
#if UF_ENTITY_METADATA_USE_JSON
metadata.deserialize(self, metadataJson);
#endif
struct {
bool forward = false;
bool backwards = false;
bool left = false;
bool right = false;
bool lookLeft = false;
bool lookRight = false;
bool lookUp = false;
bool lookDown = false;
bool running = false;
bool walk = false;
bool jump = false;
bool crouch = false;
bool paused = false;
bool console = false;
bool vee = false;
bool use = false;
} keys;
ext::PlayerBehavior::Metadata::States stats;
struct {
float move = 4;
float walk = 1;
float run = 8;
float rotate = 1;
float friction = 0.8f;
float air = 1.0f;
} speed;
if ( uf::Window::focused ) {
keys = {
.forward = uf::inputs::kbm::states::W,
.backwards = uf::inputs::kbm::states::S,
.left = uf::inputs::kbm::states::A,
.right = uf::inputs::kbm::states::D,
.lookLeft = uf::inputs::kbm::states::Left,
.lookRight = uf::inputs::kbm::states::Right,
.lookUp = uf::inputs::kbm::states::Up,
.lookDown = uf::inputs::kbm::states::Down,
.running = uf::inputs::kbm::states::LShift,
.walk = uf::inputs::kbm::states::LAlt,
.jump = uf::inputs::kbm::states::Space,
.crouch = uf::inputs::kbm::states::LControl,
.paused = uf::inputs::kbm::states::Escape,
.console = uf::inputs::kbm::states::Tilde,
.vee = uf::inputs::kbm::states::V,
.use = uf::inputs::kbm::states::E,
};
if ( spec::controller::connected() ) {
#if UF_USE_OPENVR
if ( uf::inputs::controller::states::R_DPAD_UP ) keys.forward = true;
if ( uf::inputs::controller::states::R_DPAD_DOWN ) keys.backwards = true;
if ( uf::inputs::controller::states::R_DPAD_LEFT ) keys.lookLeft = true; // keys.left = true;
if ( uf::inputs::controller::states::R_DPAD_RIGHT ) keys.lookRight = true; // keys.right = true;
if ( uf::inputs::controller::states::R_JOYSTICK ) keys.running = true;
if ( uf::inputs::controller::states::R_A ) keys.jump = true;
if ( uf::inputs::controller::states::L_DPAD_UP ) keys.forward = true;
if ( uf::inputs::controller::states::L_DPAD_DOWN ) keys.backwards = true;
if ( uf::inputs::controller::states::L_DPAD_LEFT ) keys.lookLeft = true;
if ( uf::inputs::controller::states::L_DPAD_RIGHT ) keys.lookRight = true;
if ( uf::inputs::controller::states::L_JOYSTICK ) keys.crouch = true, keys.walk = true;
if ( uf::inputs::controller::states::L_A ) keys.paused = true;
#else
if ( uf::inputs::controller::states::L_DPAD_UP ) keys.forward = true;
if ( uf::inputs::controller::states::L_DPAD_DOWN ) keys.backwards = true;
if ( uf::inputs::controller::states::L_DPAD_LEFT ) keys.left = true;
if ( uf::inputs::controller::states::L_DPAD_RIGHT ) keys.right = true;
if ( uf::inputs::controller::states::A ) keys.jump = true;
if ( uf::inputs::controller::states::B ) keys.running = true;
if ( uf::inputs::controller::states::X ) keys.crouch = true, keys.walk = true;
// if ( uf::inputs::controller::states::Y ) keys.vee = true;
if ( uf::inputs::controller::states::Y ) keys.use = true;
if ( uf::inputs::controller::states::L_TRIGGER ) keys.lookLeft = true;
if ( uf::inputs::controller::states::R_TRIGGER ) keys.lookRight = true;
if ( uf::inputs::controller::states::START ) keys.paused = true;
#endif
float deadzone = 0.01f;
if ( uf::inputs::controller::states::L_JOYSTICK.x < -deadzone ) {
keys.left = true;
speed.move *= abs(uf::inputs::controller::states::L_JOYSTICK.x);
speed.run *= abs(uf::inputs::controller::states::L_JOYSTICK.x);
speed.walk *= abs(uf::inputs::controller::states::L_JOYSTICK.x);
} else if ( uf::inputs::controller::states::L_JOYSTICK.x > deadzone ) {
keys.right = true;
speed.move *= abs(uf::inputs::controller::states::L_JOYSTICK.x);
speed.run *= abs(uf::inputs::controller::states::L_JOYSTICK.x);
speed.walk *= abs(uf::inputs::controller::states::L_JOYSTICK.x);
}
if ( uf::inputs::controller::states::L_JOYSTICK.y < -deadzone ) {
keys.forward = true;
speed.move *= abs(uf::inputs::controller::states::L_JOYSTICK.y);
speed.run *= abs(uf::inputs::controller::states::L_JOYSTICK.y);
speed.walk *= abs(uf::inputs::controller::states::L_JOYSTICK.y);
} else if ( uf::inputs::controller::states::L_JOYSTICK.y > deadzone ) {
keys.backwards = true;
speed.move *= abs(uf::inputs::controller::states::L_JOYSTICK.y);
speed.run *= abs(uf::inputs::controller::states::L_JOYSTICK.y);
speed.walk *= abs(uf::inputs::controller::states::L_JOYSTICK.y);
}
}
}
#if 1
if ( uf::renderer::states::resized && uf::renderer::settings::width > 0 && uf::renderer::settings::height > 0 ) {
auto cameraSettingsJson = metadataJson["camera"]["settings"];
float fov = cameraSettingsJson["fov"].as<float>(120) * (3.14159265358f / 180.0f);
float raidou = (float) uf::renderer::settings::width / (float) uf::renderer::settings::height;
pod::Vector2f range = uf::vector::decode( cameraSettingsJson["clip"], pod::Vector2f{ 0.1, 64.0f } );
camera.setProjection( uf::matrix::perspective( fov, raidou, range.x, range.y ) );
}
#endif
bool wasFloored = stats.floored;
stats.menu = metadata.system.menu;
stats.noclipped = metadata.system.noclipped;
stats.floored = stats.noclipped;
if ( !stats.floored ) {
if ( physicsBody.activity.grounded ) {
stats.floored = true;
}
/*
pod::Vector3f origin = transform.position + metadata.movement.floored.feet;
pod::Vector3f direction = metadata.movement.floored.floor;
pod::RayQuery query = uf::physics::rayCast( pod::Ray{origin, direction}, physicsBody, 1.0f );
if ( query.hit ) {
if ( metadata.movement.floored.print ) UF_MSG_DEBUG("{}: {} | {}", query.contact.penetration, uf::string::toString(*query.body->object), uf::vector::toString(physicsBody.velocity));
stats.floored = true;
//if ( physicsBody.velocity.y < 0.0f ) physicsBody.velocity.y = 0.0f;
}
*/
}
#if 0
TIMER(0.25, keys.use ) {
size_t uid = 0;
float depth = -1;
uf::Object* pointer = NULL;
float length = metadata.use.length;
// pod::Vector3f center = transform.position + cameraTransform.position;
// pod::Vector3f direction = uf::vector::normalize( axes.forward + pod::Vector3f{ 0, cameraAxes.forward.y, 0 } ) * length;
auto flattened = uf::transform::flatten( cameraTransform );
pod::Vector3f center = flattened.position;
pod::Vector3f direction = flattened.forward * length;
pointer = uf::physics::rayCast( center, direction, this, depth );
ext::json::Value payload;
if ( pointer ) {
payload["user"] = this->getUid();
payload["uid"] = pointer->getUid();
payload["depth"] = uf::vector::norm(direction * depth);
pointer->lazyCallHook( "entity:Use.%UID%", payload );
} else {
payload["user"] = this->getUid();
payload["uid"] = 0;
payload["depth"] = -1;
}
this->lazyCallHook( "entity:Use.%UID%", payload );
/*
auto& emitter = this->getComponent<uf::AudioEmitter>();
uf::stl::string filename = pointer ? "./ui/select.ogg" : "./ui/deny.ogg";
uf::Audio& sfx = emitter.has(filename) ? emitter.get(filename) : emitter.load(filename);
bool playing = false;
if ( !sfx.playing() ) {
#if UF_AUDIO_MAPPED_VOLUMES
sfx.setVolume(uf::audio::volumes.count("sfx") > 0 ? uf::audio::volumes.at("sfx") : 1.0);
#else
sfx.setVolume(uf::audio::volumes::sfx);
#endif
sfx.setPosition( transform.position );
sfx.setTime( 0 );
sfx.play();
}
*/
}
#endif
if ( physicsBody.gravity == pod::Vector3f{0,0,0} ) stats.noclipped = true;
{
speed.rotate = metadata.movement.rotate * uf::physics::time::delta;
speed.move = metadata.movement.move;
speed.run = metadata.movement.run;
speed.walk = metadata.movement.walk;
speed.friction = metadata.movement.friction;
speed.air = metadata.movement.air;
if ( stats.noclipped ) {
speed.move *= 1.5;
speed.run *= 1.5;
}
if ( !stats.floored || stats.noclipped ) speed.friction = 1;
if ( stats.noclipped ) physicsBody.velocity = {};
}
if ( keys.running ) speed.move = speed.run;
else if ( keys.walk ) speed.move = speed.walk;
{
uf::Object* menu = (uf::Object*) scene.globalFindByName("Gui: Menu");
if ( !menu ) stats.menu = "";
// make assumptions
if ( !metadata.system.control ) {
} else if ( stats.menu == "" && keys.paused ) {
stats.menu = "paused";
metadata.system.control = false;
pod::payloads::menuOpen payload;
payload.name = "pause";
uf::hooks.call("menu:Open", payload);
/*
} else if ( stats.menu == "" && keys.dialogue ) {
stats.menu = "dialogue";
metadata.system.control = false;
pod::payloads::menuOpen payload;
payload.name = "dialogue";
payload.metadata["dialogue"]["name"] = "Test";
payload.metadata["dialogue"]["text"] = "The quick brown fox\njumped over the lazy dog.";
payload.metadata["dialogue"]["name color"] = uf::vector::encode( pod::Vector4f{ 0.8, 1.0, 0.8, 1.0 } );
payload.metadata["dialogue"]["text color"] = uf::vector::encode( pod::Vector4f{ 0.8, 1.0, 0.8, 1.0 } );
uf::hooks.call("menu:Open", payload);
// stats.menu = "menu";
*/
} else {
metadata.system.control = stats.menu == "";
}
metadata.system.menu = stats.menu;
}
pod::Axes translator = uf::transform::axes( transform );
#if UF_USE_OPENVR
// use the orientation of our controller to determine our target
/*if ( ext::openvr::context ) {
bool useController = true;
translator.orientation = uf::quaternion::multiply( transform.orientation, useController ? ext::openvr::controllerQuaternion( vr::Controller_Hand::Hand_Right ) : ext::openvr::hmdQuaternion() );
translator = uf::transform::reorient( translator );
// flatten if not noclipped
if ( !stats.noclipped ) {
translator.forward *= { 1, 0, 1 };
translator.right *= { 1, 0, 1 };
}
translator.forward = uf::vector::normalize( translator.forward );
translator.right = uf::vector::normalize( translator.right );
} else*/
#endif
// un-flatted if noclipped
if ( metadata.camera.fixed ) {
translator = uf::transform::axes( cameraTransform );
translator.forward.y = 0;
translator.forward = uf::vector::normalize( translator.forward );
}
else if ( stats.noclipped || physicsBody.gravity == pod::Vector3f{0,0,0} ){
translator.forward.y += cameraAxes.forward.y;
translator.forward = uf::vector::normalize( translator.forward );
}
if ( metadata.system.control ) {
// noclip handler
TIMER(0.25, keys.vee ) {
bool state = !stats.noclipped;
metadata.system.noclipped = state;
if ( !state ) {
uf::physics::setGravity( physicsBody );
uf::physics::setColliderCategory( physicsBody, "PLAYER");
uf::physics::setColliderMask( physicsBody, "PLAYER");
} else {
uf::physics::setGravity( physicsBody, pod::Vector3f{0,0,0});
uf::physics::setColliderCategory( physicsBody, "NONE");
uf::physics::setColliderMask( physicsBody, "NONE");
}
stats.noclipped = state;
UF_MSG_DEBUG( "{}abled noclip: {}", (state ? "En" : "Dis"), uf::vector::toString(transform.position));
}
// movement handler
// setup desired direction
pod::Vector3f target = {};
if ( keys.forward ^ keys.backwards ) target += translator.forward * (keys.forward ? 1 : -1);
if ( keys.left ^ keys.right ) target += translator.right * (keys.right ? 1 : -1);
target = uf::vector::normalize( target );
physicsBody.velocity *= { speed.friction, 1, speed.friction };
stats.walking = (keys.forward ^ keys.backwards) || (keys.left ^ keys.right);
stats.running = keys.running;
if ( stats.walking ) {
float factor = stats.floored ? 1.0f : speed.air;
if ( stats.noclipped ) {
physicsBody.velocity += target * speed.move * 50 * ONE_OVER_SIXTY;
} else {
physicsBody.velocity += target * std::clamp( speed.move * factor - uf::vector::dot( physicsBody.velocity, target ), 0.0f, speed.move * 10 * ONE_OVER_SIXTY /*uf::physics::time::delta*/ );
}
auto dot = uf::vector::dot( axes.forward, target );
if ( !metadata.movement.strafe && dot < 1.0f ) {
// auto cross = uf::vector::normalize( uf::vector::cross( axes.forward, target ) );
// auto axis = cross == pod::Vector3f{0, 0, 0} ? axes.up : cross;
auto axis = axes.up;
float angle = uf::vector::signedAngle( axes.forward, target, axis ) * ONE_OVER_SIXTY /*uf::physics::time::delta*/ * 4; // speed.rotate;
if ( physicsBody.object ) uf::physics::applyRotation( physicsBody, axis, angle ); else
uf::transform::rotate( transform, axis, angle );
}
}
if ( !stats.floored ) stats.walking = false;
}
TIMER(0.0625, stats.floored && keys.jump && !stats.noclipped ) {
physicsBody.velocity += translator.up * metadata.movement.jump;
}
if ( stats.floored && keys.jump && stats.noclipped ) transform.position += translator.up * metadata.movement.jump * uf::physics::time::delta * 4.0f;
if ( keys.crouch ) {
if ( stats.noclipped ) transform.position -= translator.up * metadata.movement.jump * uf::physics::time::delta * 4.0f;
else {
if ( !metadata.system.crouching ) stats.deltaCrouch = true;
metadata.system.crouching = true;
}
} else {
if ( metadata.system.crouching ) stats.deltaCrouch = true;
metadata.system.crouching = false;
}
//
#if UF_INPUT_USE_ENUM_MOUSE && !UF_ENV_DREAMCAST
{
const pod::Vector2ui deadZone{0, 0};
const auto& mouseDelta = uf::inputs::kbm::states::Mouse;
bool shouldnt = (mouseDelta.x == 0 && mouseDelta.y == 0) || !metadata.system.control || metadata.camera.fixed;
if ( !shouldnt ) {
if (abs(mouseDelta.x) > deadZone.x) metadata.mouse.accum.x += mouseDelta.x * (ONE_OVER_SIXTY)/* uf::physics::time::delta*/;
if (abs(mouseDelta.y) > deadZone.y) metadata.mouse.accum.y += mouseDelta.y * (ONE_OVER_SIXTY)/* uf::physics::time::delta*/;
}
}
#endif
if ( !metadata.camera.fixed ) {
if ( metadata.mouse.accum.x != 0 && metadata.mouse.accum.y != 0 ) {
metadata.camera.queued.x += metadata.mouse.accum.x * metadata.mouse.sensitivity.x;
metadata.camera.queued.y += metadata.mouse.accum.y * metadata.mouse.sensitivity.y;
metadata.mouse.accum = {};
}
if ( metadata.camera.queued.x != 0 || metadata.camera.queued.y != 0 ) {
auto lookDelta = metadata.camera.queued;
metadata.camera.queued -= lookDelta * metadata.mouse.smoothing;
//metadata.camera.queued = {};
if ( lookDelta.x != 0 ) {
if ( metadata.camera.invert.x ) lookDelta.x *= -1;
metadata.camera.limit.current.x += lookDelta.x;
if ( metadata.camera.limit.current.x != metadata.camera.limit.current.x || ( metadata.camera.limit.current.x < metadata.camera.limit.max.x && metadata.camera.limit.current.x > metadata.camera.limit.min.x ) ) {
if ( physicsBody.object ) uf::physics::applyRotation( physicsBody, axes.up, lookDelta.x ); else
uf::transform::rotate( transform, axes.up, lookDelta.x );
} else metadata.camera.limit.current.x -= lookDelta.x;
}
if ( lookDelta.y != 0 ) {
if ( metadata.camera.invert.y ) lookDelta.y *= -1;
metadata.camera.limit.current.y += lookDelta.y;
if ( metadata.camera.limit.current.y != metadata.camera.limit.current.y || ( metadata.camera.limit.current.y < metadata.camera.limit.max.y && metadata.camera.limit.current.y > metadata.camera.limit.min.y ) ) {
// if ( physicsBody.object && !physicsBody.shared ) uf::physics::applyRotation( physicsBody, cameraAxes.right, lookDelta.y ); else
uf::transform::rotate( cameraTransform, cameraAxes.right, lookDelta.y );
} else metadata.camera.limit.current.y -= lookDelta.y;
}
} else if ( metadata.system.control ) {
if ( keys.lookRight ^ keys.lookLeft ) {
if ( physicsBody.object ) uf::physics::applyRotation( physicsBody, axes.up, speed.rotate * (keys.lookRight ? 1 : -1) ); else
uf::transform::rotate( transform, axes.up, speed.rotate * (keys.lookRight ? 1 : -1) );
}
if ( keys.lookUp ^ keys.lookDown ) {
float direction = keys.lookUp ? 1 : -1;
if ( metadata.camera.invert.y ) direction *= -1;
uf::transform::rotate( cameraTransform, cameraAxes.right, speed.rotate * direction );
}
}
} else {
if ( keys.lookRight ^ keys.lookLeft ) {
// auto rotation = uf::quaternion::axisAngle( cameraAxes.up, uf::physics::time::delta * (keys.lookRight ? 1 : -1) );
// cameraTransform.position = uf::quaternion::rotate( rotation, cameraTransform.position - transform.position );
}
if ( keys.lookUp ^ keys.lookDown ) {
// if ( physicsBody.object && !physicsBody.shared ) uf::physics::applyRotation( physicsBody, cameraAxes.right, lookDelta.y ); else
float direction = keys.lookUp ? 1 : -1;
if ( metadata.camera.invert.y ) direction *= -1;
uf::transform::rotate( cameraTransform, cameraAxes.right, speed.rotate * direction );
}
}
{
if ( physicsBody.object ) uf::physics::setVelocity( physicsBody, physicsBody.velocity ); else
transform.position += physicsBody.velocity * ONE_OVER_SIXTY /*uf::physics::time::delta*/;
// if ( uf::vector::magnitude( physicsBody.velocity ) > 1.0e-6 ) UF_MSG_DEBUG("Velocity: {}", uf::vector::toString( physicsBody.velocity ));
}
if ( metadata.camera.fixed ) {
cameraTransform.reference = NULL;
cameraTransform.position = transform.position + metadata.camera.offset;
// cameraTransform.orientation = uf::vector::decode( metadataJson["camera"]["orientation"], uf::quaternion::identity() );
cameraAxes = uf::transform::axes( cameraTransform );
} else {
if ( metadata.camera.offset != pod::Vector3f{0,0,0} ) {
//auto flattened = uf::transform::flatten( cameraTransform );
auto& flattened = transform;
metadata.camera.intermediary.position = uf::quaternion::rotate( flattened.orientation, metadata.camera.offset );
metadata.camera.intermediary.reference = &transform;
cameraTransform.reference = &metadata.camera.intermediary;
}
if ( stats.deltaCrouch ) {
float delta = metadata.movement.crouch;
if ( metadata.system.crouching ) cameraTransform.position.y -= delta;
else cameraTransform.position.y += delta;
}
}
#if 0
{
int count = 0;
auto events = uf::physics::getCollisionEvents( physicsBody );
for ( const auto& event : events ) {
// do something
}
UF_MSG_DEBUG("count={}", events.size());
}
#endif
metadata.states = stats;
metadata.states.crouching = metadata.system.crouching;
#if 1 && UF_USE_OPENAL
if ( false && stats.floored && !stats.noclipped ) {
if ( stats.walking ) {
auto& emitter = this->getComponent<uf::AudioEmitter>();
metadata.audio.footstep.timer -= uf::physics::time::delta;
if ( metadata.audio.footstep.timer <= 0.0f ) {
// player/footsteps
static uf::stl::vector<uf::stl::string> surfaces = {
"chainlink",
"concrete",
"dirt",
"duct",
"grass",
"gravel",
"ladder",
"metal",
"metalgrate",
"mud",
"sand",
"slosh",
"tile",
"wade",
"wood",
"woodpanel",
};
uf::stl::string surface = "concrete";
auto events = uf::physics::getCollisionEvents( physicsBody );
for ( const auto& event : events ) {
if ( event.normal.y > -0.7f ) continue; // to-do: reorient?
auto materialName = uf::physics::getCollisionMaterialName( event );
for ( auto& key : surfaces ) {
if ( !uf::string::contains( materialName, key ) ) continue;
surface = key;
break;
}
break;
}
uf::stl::string filename = ::fmt::format("valve://sound/player/footsteps/{}{}.wav", surface, (rand() % 3) + 1 );
if ( !uf::asset::has(filename) ) {
auto payload = uf::asset::resolveToPayload(filename, "");
payload.type = uf::asset::Type::AUDIO;
uf::asset::load( payload );
}
pod::AudioClip* clip = &uf::asset::get<pod::AudioClip>( filename );
pod::AudioSource& footstep = emitter.emit(filename, clip, false);
float pitch = 0.95f + ((rand() % 11) / 100.0f);
float volume = metadata.audio.footstep.volume;
if ( metadata.system.crouching ) {
volume *= 0.5f;
} else if ( keys.running ) {
volume *= 1.0f;
}
uf::audio::pitch( footstep, pitch );
uf::audio::gain( footstep, volume );
uf::audio::position( footstep, transform.position );
uf::audio::play( footstep );
if ( keys.running ) {
metadata.audio.footstep.timer = 0.3f;
} else if ( metadata.system.crouching ) {
metadata.audio.footstep.timer = 0.6f;
} else {
metadata.audio.footstep.timer = 0.45f;
}
}
// set animation to walk
stats.targetAnimation = "walk";
} else if ( !keys.jump ) {
stats.targetAnimation = "idle";
metadata.audio.footstep.timer = 0;
}
}
#endif
#if !UF_ENV_DREAMCAST
// set animation to idle
if ( stats.targetAnimation != "" ) {
auto* playerModel = (uf::Object*) this->findByName("Player: Model");
if ( playerModel && playerModel->hasComponent<pod::Graph>() ) {
pod::payloads::QueueAnimationPayload payload;
payload.name = stats.targetAnimation;
playerModel->queueHook("animation:Set.%UID%", payload);
stats.targetAnimation = "";
}
/*
if ( playerModel && playerModel->hasComponent<pod::Graph>() ) {
auto& graph = playerModel->getComponent<pod::Graph>();
uf::graph::queueAnimation( graph, stats.targetAnimation );
}
*/
/*
if ( playerModel && playerModel->hasComponent<pod::Graph>() ) {
auto& graph = playerModel->getComponent<pod::Graph>();
bool should = true;
if ( graph.sequence.empty() && graph.sequence.front() == stats.targetAnimation ) should = false;
if ( should ) {
graph.settings.animations.loop = true;
uf::graph::animate( graph, stats.targetAnimation );
}
}
*/
}
#endif
#if 0 && UF_USE_LUA && !UF_ENV_DREAMCAST
#define TRACK_ORIENTATION(ORIENTATION) {\
static pod::Quaternion<> storedCameraOrientation = ORIENTATION;\
const pod::Quaternion<> prevCameraOrientation = storedCameraOrientation;\
const pod::Quaternion<> curCameraOrientation = ORIENTATION;\
const pod::Quaternion<> deltaOrientation = uf::quaternion::multiply( curCameraOrientation, uf::quaternion::inverse( prevCameraOrientation ) ) ;\
const pod::Vector3f deltaAngles = uf::quaternion::eulerAngles( deltaOrientation );\
combinedDeltaAngles = uf::vector::add( combinedDeltaAngles, deltaAngles );\
combinedDeltaOrientation = uf::quaternion::multiply( deltaOrientation, combinedDeltaOrientation );\
storedCameraOrientation = ORIENTATION;\
}
// this causes bigly memory leaks
{
pod::Quaternion<> combinedDeltaOrientation = {0,0,0,1};
pod::Vector3f combinedDeltaAngles = {};
TRACK_ORIENTATION(transform.orientation);
TRACK_ORIENTATION(camera.getTransform().orientation);
float magnitude = uf::quaternion::magnitude( combinedDeltaOrientation );
if ( magnitude > 0.0001f ) {
ext::json::Value payload;
payload["delta"] = uf::vector::encode( combinedDeltaOrientation );
payload["angle"]["pitch"] = combinedDeltaAngles.x;
payload["angle"]["yaw"] = combinedDeltaAngles.y;
payload["angle"]["roll"] = combinedDeltaAngles.z;
payload["magnitude"] = magnitude;
this->callHook("controller:Camera.Rotated", payload);
}
}
#endif
camera.update();
#if UF_ENTITY_METADATA_USE_JSON
metadata.serialize(self, metadataJson);
#endif
#include "./input/behavior.h"
#include "./movement/behavior.h"
#include "./interaction/behavior.h"
#include "./camera/behavior.h"
#include "./model/behavior.h"
namespace ext {
class Player : public uf::Object {
};
}
void ext::PlayerBehavior::render( uf::Object& self ){}
void ext::PlayerBehavior::destroy( uf::Object& self ){}
void ext::PlayerBehavior::Metadata::serialize( uf::Object& self, uf::Serializer& serializer ){
auto& serializerSystem = serializer["system"];
auto& serializerPhysics = serializer["physics"];
auto& serializerMovement = serializer["movement"];
auto& serializerAudioFootstep = serializer["audio"]["footstep"];
auto& serializerCamera = serializer["camera"];
auto& serializerCameraLimit = serializerCamera["limit"];
auto& serializerCameraSettings = serializerCamera["settings"];
serializerSystem["menu"] = /*this->*/system.menu;
serializerSystem["control"] = /*this->*/system.control;
serializerSystem["crouching"] = /*this->*/system.crouching;
serializerSystem["noclipped"] = /*this->*/system.noclipped;
serializerPhysics["friction"] = /*this->*/movement.friction;
serializerMovement["rotate"] = /*this->*/movement.rotate;
serializerMovement["move"] = /*this->*/movement.move;
serializerMovement["run"] = /*this->*/movement.run;
serializerMovement["walk"] = /*this->*/movement.walk;
serializerMovement["air"] = /*this->*/movement.air;
serializerMovement["strafe"] = /*this->*/movement.strafe;
serializerMovement["jump"] = uf::vector::encode(/*this->*/movement.jump);
serializerMovement["floored"]["feet"] = uf::vector::encode(/*this->*/movement.floored.feet);
serializerMovement["floored"]["floor"] = uf::vector::encode(/*this->*/movement.floored.floor);
serializerMovement["floored"]["print"] = /*this->*/movement.floored.print;
serializerMovement["crouch"] = /*this->*/movement.crouch;
// serializerMovement["look"] = /*this->*/movement.look;
serializerAudioFootstep["list"] = /*this->*/audio.footstep.list;
serializerAudioFootstep["volume"] = /*this->*/audio.footstep.volume;
serializerCamera["invert"] = uf::vector::encode(/*this->*/camera.invert);
serializerCameraLimit["current"] = uf::vector::encode(/*this->*/camera.limit.current);
serializerCameraLimit["minima"] = uf::vector::encode(/*this->*/camera.limit.min);
serializerCameraLimit["maxima"] = uf::vector::encode(/*this->*/camera.limit.max);
serializerCameraSettings["fixed"] = camera.fixed;
serializer["use"]["length"] = /*this->*/use.length;
}
void ext::PlayerBehavior::Metadata::deserialize( uf::Object& self, uf::Serializer& serializer ){
auto& serializerSystem = serializer["system"];
auto& serializerAudioFootstep = serializer["audio"]["footstep"];
auto& serializerPhysics = serializer["physics"];
auto& serializerMovement = serializer["movement"];
auto& serializerCamera = serializer["camera"];
auto& serializerCameraLimit = serializerCamera["limit"];
auto& serializerCameraSettings = serializerCamera["settings"];
/*this->*/system.menu = serializerSystem["menu"].as(/*this->*/system.menu);
/*this->*/system.control = serializerSystem["control"].as(/*this->*/system.control);
/*this->*/system.crouching = serializerSystem["crouching"].as(/*this->*/system.crouching);
/*this->*/system.noclipped = serializerSystem["noclipped"].as(/*this->*/system.noclipped);
/*this->*/movement.friction = serializerPhysics["friction"].as(/*this->*/movement.friction);
/*this->*/movement.rotate = serializerMovement["rotate"].as(/*this->*/movement.rotate);
/*this->*/movement.move = serializerMovement["move"].as(/*this->*/movement.move);
/*this->*/movement.run = serializerMovement["run"].as(/*this->*/movement.run);
/*this->*/movement.walk = serializerMovement["walk"].as(/*this->*/movement.walk);
/*this->*/movement.air = serializerMovement["air"].as(/*this->*/movement.air);
/*this->*/movement.strafe = serializerMovement["strafe"].as(/*this->*/movement.strafe);
/*this->*/movement.jump = uf::vector::decode(serializerMovement["jump"], /*this->*/movement.jump);
/*this->*/movement.floored.feet = uf::vector::decode(serializerMovement["floored"]["feet"], /*this->*/movement.floored.feet);
/*this->*/movement.floored.floor = uf::vector::decode(serializerMovement["floored"]["floor"], /*this->*/movement.floored.floor);
/*this->*/movement.floored.print = serializerMovement["floored"]["print"].as(/*this->*/movement.floored.print);
/*this->*/movement.crouch = serializerMovement["crouch"].as(/*this->*/movement.crouch);
// /*this->*/movement.look = serializerMovement["look"].as<float>(1.0f);
ext::json::forEach( serializerAudioFootstep["list"], [&]( const ext::json::Value& value ){
/*this->*/audio.footstep.list.emplace_back(value);
});
/*this->*/audio.footstep.volume = serializerAudioFootstep["volume"].as<float>();
/*this->*/camera.invert = uf::vector::decode( serializerCamera["invert"], /*this->*/camera.invert );
/*this->*/camera.limit.current = uf::vector::decode( serializerCameraLimit["current"], /*this->*/camera.limit.current );
/*this->*/camera.limit.min = uf::vector::decode( serializerCameraLimit["minima"], /*this->*/camera.limit.min );
/*this->*/camera.limit.max = uf::vector::decode( serializerCameraLimit["maxima"], /*this->*/camera.limit.max );
/*this->*/camera.fixed = serializerCameraSettings["fixed"].as( /*this->*/camera.fixed );
/*this->*/use.length = serializer["use"]["length"].as(/*this->*/use.length);
}
// yikes
#include <uf/ext/lua/component.h>
UF_LUA_REGISTER_USERTYPE(ext::PlayerBehavior::Metadata::States,
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerBehavior::Metadata::States::walking),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerBehavior::Metadata::States::running),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerBehavior::Metadata::States::crouching),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerBehavior::Metadata::States::floored),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerBehavior::Metadata::States::noclipped),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerBehavior::Metadata::States::deltaCrouch),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerBehavior::Metadata::States::menu),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerBehavior::Metadata::States::targetAnimation),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerBehavior::Metadata::States::previous)
)
UF_LUA_REGISTER_USERTYPE_AND_COMPONENT(ext::PlayerBehavior::Metadata,
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerBehavior::Metadata::states)
)
#undef this
UF_OBJECT_REGISTER_BEGIN(ext::Player)
UF_OBJECT_BIND_BEHAVIOR(ext::PlayerInputBehavior)
UF_OBJECT_BIND_BEHAVIOR(ext::PlayerMovementBehavior)
UF_OBJECT_BIND_BEHAVIOR(ext::PlayerInteractionBehavior)
UF_OBJECT_BIND_BEHAVIOR(ext::PlayerCameraBehavior)
UF_OBJECT_REGISTER_END()

View File

@ -1,81 +0,0 @@
#pragma once
#include <uf/config.h>
#include <uf/ext/ext.h>
#include <uf/engine/entity/entity.h>
#include <uf/engine/scene/scene.h>
#include <uf/utils/math/vector.h>
namespace ext {
namespace PlayerBehavior {
UF_BEHAVIOR_DEFINE_TYPE();
EXT_BEHAVIOR_DEFINE_TRAITS();
EXT_BEHAVIOR_DEFINE_FUNCTIONS();
UF_BEHAVIOR_DEFINE_METADATA(
struct {
bool control = true;
uf::stl::string menu = "";
bool crouching = false;
bool noclipped = false;
} system;
struct {
float crouch = -1.0f;
float rotate = 1.0f;
float move = 1.0f;
float run = 1.0f;
float walk = 1.0f;
float friction = 0.8f;
float air = 1.0f;
bool strafe = true;
pod::Vector3f jump = {0,8,0};
struct {
pod::Vector3f feet = {0,-1.5,0};
pod::Vector3f floor = {0,-1,0};
bool print = false;
} floored;
} movement;
struct {
struct {
pod::Vector3f current = {NAN, NAN, NAN};
pod::Vector3f min = {NAN, NAN, NAN};
pod::Vector3f max = {NAN, NAN, NAN};
} limit;
pod::Vector3t<bool> invert;
pod::Vector2f queued;
pod::Vector3f offset;
pod::Transform<> intermediary;
bool fixed = false;
} camera;
struct {
pod::Vector2f sensitivity = {1,1};
pod::Vector2f smoothing = {0,0};
pod::Vector2f accum = {};
} mouse;
struct {
struct {
uf::stl::vector<uf::stl::string> list;
float volume = 1;
float timer = 0;
} footstep;
} audio;
struct {
float length = 4.0f;
} use;
struct States {
bool walking = false;
bool running = false;
bool crouching = false;
bool floored = true;
bool noclipped = false;
bool deltaCrouch = false;
uf::stl::string menu = "";
uf::stl::string targetAnimation = "";
pod::Matrix4f previous;
} states;
);
}
}

View File

@ -0,0 +1,224 @@
#include "behavior.h"
#include "../input/behavior.h"
#include "../movement/behavior.h"
#include <uf/utils/hook/hook.h>
#include <uf/utils/time/time.h>
#include <uf/utils/serialize/serializer.h>
#include <uf/utils/userdata/userdata.h>
#include <uf/utils/window/window.h>
#include <uf/utils/window/payloads.h>
#include <uf/utils/camera/camera.h>
#include <uf/utils/audio/audio.h>
#include <uf/ext/openvr/openvr.h>
#include <uf/engine/graph/graph.h>
#include <uf/utils/math/physics.h>
#include <uf/spec/controller/controller.h>
#include <uf/utils/io/inputs.h>
#include "../../scene/behavior.h"
#define ONE_OVER_SIXTY 0.016666f
UF_BEHAVIOR_REGISTER_CPP(ext::PlayerCameraBehavior)
UF_BEHAVIOR_TRAITS_CPP(ext::PlayerCameraBehavior, ticks = true, renders = false, thread = uf::thread::asyncThreadName)
#define this (&self)
void ext::PlayerCameraBehavior::initialize( uf::Object& self ) {
auto& transform = this->getComponent<pod::Transform<>>();
auto& metadata = this->getComponent<ext::PlayerCameraBehavior::Metadata>();
auto& metadataJson = this->getComponent<uf::Serializer>();
auto& camera = this->getComponent<uf::Camera>();
auto& cameraTransform = camera.getTransform();
camera.setStereoscopic(true);
cameraTransform.position = uf::vector::decode(metadataJson["camera"]["position"], cameraTransform.position);
cameraTransform.scale = uf::vector::decode(metadataJson["camera"]["scale"], cameraTransform.scale);
cameraTransform.orientation = uf::vector::decode(metadataJson["camera"]["orientation"], cameraTransform.orientation);
cameraTransform.reference = metadata.fixed ? NULL : &transform;
auto cameraSettingsJson = metadataJson["camera"]["settings"];
if ( metadataJson["camera"]["ortho"].as<bool>() ) {
float l = cameraSettingsJson["left"].as<float>();
float r = cameraSettingsJson["right"].as<float>();
float b = cameraSettingsJson["bottom"].as<float>();
float t = cameraSettingsJson["top"].as<float>();
float n = cameraSettingsJson["near"].as<float>();
float f = cameraSettingsJson["far"].as<float>();
camera.setProjection( uf::matrix::orthographic( l, r, b, t, n, f ) );
} else {
float fov = cameraSettingsJson["fov"].as<float>(120) * (3.14159265358f / 180.0f);
pod::Vector2f range = uf::vector::decode(cameraSettingsJson["clip"], pod::Vector2f{0.1, 64.0f});
pod::Vector2ui size = uf::vector::decode(cameraSettingsJson["size"], pod::Vector2ui{uf::renderer::settings::width, uf::renderer::settings::height});
float raidou = (float) size.x / (float) size.y;
if ( size.x == 0 || size.y == 0 ) {
size = uf::vector::decode(uf::config["window"]["size"], pod::Vector2ui{});
raidou = (float) size.x / (float) size.y;
}
camera.setProjection( uf::matrix::perspective( fov, raidou, range.x, range.y ) );
}
camera.update();
metadata.mouse.sensitivity = uf::vector::decode(uf::config["window"]["mouse"]["sensitivity"], metadata.mouse.sensitivity);
metadata.mouse.smoothing = uf::vector::decode(uf::config["window"]["mouse"]["smoothing"], metadata.mouse.smoothing);
UF_BEHAVIOR_METADATA_BIND_SERIALIZER_HOOKS(metadata, metadataJson);
}
void ext::PlayerCameraBehavior::tick( uf::Object& self ) {
auto& metadata = this->getComponent<ext::PlayerCameraBehavior::Metadata>();
auto& transform = this->getComponent<pod::Transform<>>();
auto& camera = this->getComponent<uf::Camera>();
auto& cameraTransform = camera.getTransform();
auto axes = uf::transform::axes(transform);
auto cameraAxes = uf::transform::axes(cameraTransform);
if ( uf::renderer::states::resized && uf::renderer::settings::width > 0 && uf::renderer::settings::height > 0 ) {
auto& metadataJson = this->getComponent<uf::Serializer>();
auto cameraSettingsJson = metadataJson["camera"]["settings"];
float fov = cameraSettingsJson["fov"].as<float>(120) * (3.14159265358f / 180.0f);
float raidou = (float) uf::renderer::settings::width / (float) uf::renderer::settings::height;
pod::Vector2f range = uf::vector::decode(cameraSettingsJson["clip"], pod::Vector2f{0.1, 64.0f});
camera.setProjection(uf::matrix::perspective(fov, raidou, range.x, range.y));
}
if ( this->hasComponent<ext::PlayerInputBehavior::Metadata>() ) {
auto& input = this->getComponent<ext::PlayerInputBehavior::Metadata>();
if ( input.look.x != 0 && input.look.y != 0 ) {
metadata.queued.x += input.look.x * metadata.mouse.sensitivity.x;
metadata.queued.y += input.look.y * metadata.mouse.sensitivity.y;
// Note: ensure you reset input.look = {0,0} at the start of PlayerInputBehavior::tick!
input.look = {0, 0};
}
if ( metadata.queued.x != 0 || metadata.queued.y != 0 ) {
auto lookDelta = metadata.queued;
metadata.queued -= lookDelta * metadata.mouse.smoothing;
if ( lookDelta.x != 0 ) {
if ( metadata.invert.x ) lookDelta.x *= -1;
metadata.limit.current.x += lookDelta.x;
if ( metadata.limit.current.x != metadata.limit.current.x || (metadata.limit.current.x < metadata.limit.max.x && metadata.limit.current.x > metadata.limit.min.x) ) {
if ( this->hasComponent<pod::PhysicsBody>() ) {
auto& physicsBody = this->getComponent<pod::PhysicsBody>();
if ( physicsBody.object ) uf::physics::applyRotation( physicsBody, axes.up, lookDelta.x );
else uf::transform::rotate( transform, axes.up, lookDelta.x );
} else {
uf::transform::rotate(transform, axes.up, lookDelta.x);
}
} else metadata.limit.current.x -= lookDelta.x;
}
if ( lookDelta.y != 0 ) {
if ( metadata.invert.y ) lookDelta.y *= -1;
metadata.limit.current.y += lookDelta.y;
if ( metadata.limit.current.y != metadata.limit.current.y || (metadata.limit.current.y < metadata.limit.max.y && metadata.limit.current.y > metadata.limit.min.y) ) {
uf::transform::rotate( cameraTransform, cameraAxes.right, lookDelta.y );
} else metadata.limit.current.y -= lookDelta.y;
}
}
}
if ( metadata.fixed ) {
cameraTransform.reference = NULL;
cameraTransform.position = transform.position + metadata.offset;
} else {
if ( metadata.offset != pod::Vector3f{0,0,0} ) {
metadata.intermediary.position = uf::quaternion::rotate( transform.orientation, metadata.offset );
metadata.intermediary.reference = &transform;
cameraTransform.reference = &metadata.intermediary;
}
if ( this->hasComponent<ext::PlayerInputBehavior::Metadata>() && this->hasComponent<ext::PlayerMovementBehavior::Metadata>() ) {
auto& input = this->getComponent<ext::PlayerInputBehavior::Metadata>();
auto& movement = this->getComponent<ext::PlayerMovementBehavior::Metadata>();
float targetRoll = -input.movement.x * 0.02f;
metadata.viewRoll = std::lerp(metadata.viewRoll, targetRoll, 8.0f * ONE_OVER_SIXTY);
float rollDelta = metadata.viewRoll - metadata.previousRoll;
metadata.previousRoll = metadata.viewRoll;
uf::transform::rotate(cameraTransform, cameraAxes.forward, rollDelta);
if ( this->hasComponent<pod::PhysicsBody>() ) {
auto& physicsBody = this->getComponent<pod::PhysicsBody>();
float currentYVel = physicsBody.velocity.y;
if ( movement.floored && !metadata.wasFloored ) {
if ( metadata.lastYVelocity < -4.0f ) {
metadata.punchVelocity += metadata.lastYVelocity * 0.025f;
}
}
metadata.punchVelocity += (0.0f - metadata.viewPunch) * 0.2f;
metadata.punchVelocity *= 0.7f;
metadata.viewPunch += metadata.punchVelocity;
metadata.viewPunch = std::clamp(metadata.viewPunch, -0.5f, 0.0f);
float punchDelta = metadata.viewPunch - metadata.previousPunch;
metadata.previousPunch = metadata.viewPunch;
float lerpSpeed = (metadata.stairOffset > 0.0f) ? 5.0f : 15.0f;
metadata.stairOffset = std::lerp(metadata.stairOffset, 0.0f, lerpSpeed * ONE_OVER_SIXTY);
if ( std::abs(metadata.stairOffset) < 0.0001f ) metadata.stairOffset = 0.0f;
float stairDelta = metadata.stairOffset - metadata.previousStairOffset;
metadata.previousStairOffset = metadata.stairOffset;
cameraTransform.position.y += punchDelta + stairDelta;
metadata.wasFloored = movement.floored;
metadata.lastYVelocity = currentYVel;
}
}
}
camera.update();
}
void ext::PlayerCameraBehavior::render( uf::Object& self ) {}
void ext::PlayerCameraBehavior::destroy( uf::Object& self ) {}
void ext::PlayerCameraBehavior::Metadata::serialize( uf::Object& self, uf::Serializer& serializer ){
serializer["camera"]["invert"] = uf::vector::encode(invert);
serializer["camera"]["limit"]["current"] = uf::vector::encode(limit.current);
serializer["camera"]["limit"]["minima"] = uf::vector::encode(limit.min);
serializer["camera"]["limit"]["maxima"] = uf::vector::encode(limit.max);
serializer["camera"]["settings"]["fixed"] = fixed;
serializer["camera"]["offset"] = uf::vector::encode(offset);
}
void ext::PlayerCameraBehavior::Metadata::deserialize( uf::Object& self, uf::Serializer& serializer ){
invert = uf::vector::decode(serializer["camera"]["invert"], invert);
limit.current = uf::vector::decode(serializer["camera"]["limit"]["current"], limit.current);
limit.min = uf::vector::decode(serializer["camera"]["limit"]["minima"], limit.min);
limit.max = uf::vector::decode(serializer["camera"]["limit"]["maxima"], limit.max);
fixed = serializer["camera"]["settings"]["fixed"].as(fixed);
offset = uf::vector::decode(serializer["camera"]["offset"], offset);
}
#undef this
#include <uf/ext/lua/component.h>
UF_LUA_REGISTER_USERTYPE_AND_COMPONENT(ext::PlayerCameraBehavior::Metadata,
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerCameraBehavior::Metadata::fixed),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerCameraBehavior::Metadata::offset),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerCameraBehavior::Metadata::viewRoll),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerCameraBehavior::Metadata::viewPunch),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerCameraBehavior::Metadata::punchVelocity),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerCameraBehavior::Metadata::lastYVelocity),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerCameraBehavior::Metadata::wasFloored),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerCameraBehavior::Metadata::stairOffset)
)

View File

@ -0,0 +1,44 @@
#pragma once
#include <uf/config.h>
#include <uf/ext/ext.h>
#include <uf/engine/entity/entity.h>
#include <uf/engine/scene/scene.h>
#include <uf/utils/math/vector.h>
namespace ext {
namespace PlayerCameraBehavior {
UF_BEHAVIOR_DEFINE_TYPE();
EXT_BEHAVIOR_DEFINE_TRAITS();
EXT_BEHAVIOR_DEFINE_FUNCTIONS();
UF_BEHAVIOR_DEFINE_METADATA(
struct {
pod::Vector3f current = {NAN, NAN, NAN};
pod::Vector3f min = {NAN, NAN, NAN};
pod::Vector3f max = {NAN, NAN, NAN};
} limit;
pod::Vector3t<bool> invert;
pod::Vector2f queued = {};
pod::Vector3f offset = {};
pod::Transform<> intermediary;
bool fixed = false;
struct {
pod::Vector2f sensitivity = {1, 1};
pod::Vector2f smoothing = {0, 0};
} mouse;
float viewRoll = 0.0f;
float previousRoll = 0.0f;
float viewPunch = 0.0f;
float previousPunch = 0.0f;
float punchVelocity = 0.0f;
float lastYVelocity = 0.0f;
bool wasFloored = true;
float stairOffset = 0.0f;
float previousStairOffset = 0.0f;
);
}
}

View File

@ -0,0 +1,145 @@
#include "behavior.h"
#include <uf/utils/hook/hook.h>
#include <uf/utils/time/time.h>
#include <uf/utils/serialize/serializer.h>
#include <uf/utils/userdata/userdata.h>
#include <uf/utils/window/window.h>
#include <uf/utils/window/payloads.h>
#include <uf/utils/camera/camera.h>
#include <uf/utils/audio/audio.h>
#include <uf/ext/openvr/openvr.h>
#include <uf/engine/graph/graph.h>
#include <uf/utils/math/physics.h>
#include <uf/spec/controller/controller.h>
#include <uf/utils/io/inputs.h>
#define ONE_OVER_SIXTY 0.016666f
UF_BEHAVIOR_REGISTER_CPP(ext::PlayerInputBehavior)
UF_BEHAVIOR_TRAITS_CPP(ext::PlayerInputBehavior, ticks = true, renders = false, thread = uf::thread::asyncThreadName)
#define this (&self)
void ext::PlayerInputBehavior::initialize(uf::Object& self) {
auto& transform = this->getComponent<pod::Transform<>>();
auto& state = this->getComponent<ext::PlayerInputBehavior::Metadata>();
auto& metadataJson = this->getComponent<uf::Serializer>();
auto& scene = uf::scene::getCurrentScene();
this->addHook( "window:Mouse.CursorVisibility", [&](pod::payloads::windowMouseCursorVisibility& payload){
state.control = !payload.mouse.visible;
});
this->addHook( "system:Control.%UID%", [&]( ext::json::Value& value ){
state.control = value["control"].as<bool>(!state.control);
});
ext::json::Value payload;
payload["uid"] = this->getUid();
this->queueHook("controller:Ready", payload, 0.0f );
UF_BEHAVIOR_METADATA_BIND_SERIALIZER_HOOKS(state, metadataJson);
}
void ext::PlayerInputBehavior::tick(uf::Object& self) {
auto& state = this->getComponent<ext::PlayerInputBehavior::Metadata>();
// reset state
state.movement = {};
state.look = {};
state.magnitude = 1.0f;
state.jump = false;
state.crouch = false;
state.run = false;
state.walk = false;
state.use = false;
state.noclipToggle = false;
state.menuToggle = false;
// not in control
if ( !state.control ) return;
if ( uf::Window::focused) {
if ( uf::inputs::kbm::states::W ) state.movement.y += 1.0f;
if ( uf::inputs::kbm::states::S ) state.movement.y -= 1.0f;
if ( uf::inputs::kbm::states::D ) state.movement.x += 1.0f;
if ( uf::inputs::kbm::states::A ) state.movement.x -= 1.0f;
state.run = uf::inputs::kbm::states::LShift;
state.walk = uf::inputs::kbm::states::LAlt;
state.jump = uf::inputs::kbm::states::Space;
state.crouch = uf::inputs::kbm::states::LControl;
state.menuToggle = uf::inputs::kbm::states::Escape;
state.noclipToggle = uf::inputs::kbm::states::V;
state.use = uf::inputs::kbm::states::E;
}
if ( spec::controller::connected() ) {
float deadzone = 0.01f;
auto stick = uf::inputs::controller::states::L_JOYSTICK;
if ( abs(stick.x) > deadzone || abs(stick.y) > deadzone ) {
state.movement.x = stick.x;
state.movement.y = stick.y;
state.magnitude = uf::vector::norm( stick );
}
if ( uf::inputs::controller::states::A ) state.jump = true;
if ( uf::inputs::controller::states::B ) state.run = true;
if ( uf::inputs::controller::states::X ) { state.crouch = true; state.walk = true; }
if ( uf::inputs::controller::states::Y ) state.use = true;
if ( uf::inputs::controller::states::START ) state.menuToggle = true;
}
if ( state.movement.x != 0 && state.movement.y != 0 && state.magnitude == 1.0f ) {
state.movement = uf::vector::normalize( state.movement );
}
{
auto& scene = uf::scene::getCurrentScene();
auto* menu = scene.globalFindByName("Gui: Menu");
if ( !menu ) state.menu = "";
if ( state.menu == "" && state.menuToggle ) {
state.menu = "paused";
state.control = false;
pod::payloads::menuOpen payload;
payload.name = "pause";
uf::hooks.call("menu:Open", payload);
} else {
state.control = state.menu == "";
}
}
#if UF_INPUT_USE_ENUM_MOUSE && !UF_ENV_DREAMCAST
const auto& mouseDelta = uf::inputs::kbm::states::Mouse;
state.look.x += mouseDelta.x * ONE_OVER_SIXTY;
state.look.y += mouseDelta.y * ONE_OVER_SIXTY;
#endif
}
void ext::PlayerInputBehavior::render(uf::Object& self) {}
void ext::PlayerInputBehavior::destroy(uf::Object& self) {}
void ext::PlayerInputBehavior::Metadata::serialize( uf::Object& self, uf::Serializer& serializer ){
serializer["system"]["control"] = control;
serializer["system"]["menu"] = menu;
}
void ext::PlayerInputBehavior::Metadata::deserialize( uf::Object& self, uf::Serializer& serializer ){
control = serializer["system"]["control"].as(control);
menu = serializer["system"]["menu"].as(menu);
}
#undef this
#include <uf/ext/lua/component.h>
UF_LUA_REGISTER_USERTYPE_AND_COMPONENT(ext::PlayerInputBehavior::Metadata,
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerInputBehavior::Metadata::control),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerInputBehavior::Metadata::menu),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerInputBehavior::Metadata::movement),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerInputBehavior::Metadata::look),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerInputBehavior::Metadata::jump),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerInputBehavior::Metadata::crouch),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerInputBehavior::Metadata::run),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerInputBehavior::Metadata::walk),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerInputBehavior::Metadata::use),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerInputBehavior::Metadata::magnitude)
)

View File

@ -0,0 +1,32 @@
#pragma once
#include <uf/config.h>
#include <uf/ext/ext.h>
#include <uf/engine/entity/entity.h>
#include <uf/engine/scene/scene.h>
#include <uf/utils/math/vector.h>
namespace ext {
namespace PlayerInputBehavior {
UF_BEHAVIOR_DEFINE_TYPE();
EXT_BEHAVIOR_DEFINE_TRAITS();
EXT_BEHAVIOR_DEFINE_FUNCTIONS();
UF_BEHAVIOR_DEFINE_METADATA(
pod::Vector2f movement = {};
pod::Vector2f look = {};
bool control = true;
uf::stl::string menu = "";
bool jump = false;
bool crouch = false;
bool run = false;
bool walk = false;
bool use = false;
bool noclipToggle = false;
bool menuToggle = false;
float magnitude = 1.0f;
);
};
}

View File

@ -0,0 +1,72 @@
#include "behavior.h"
#include "../input/behavior.h"
#include <uf/utils/hook/hook.h>
#include <uf/utils/time/time.h>
#include <uf/utils/serialize/serializer.h>
#include <uf/utils/userdata/userdata.h>
#include <uf/utils/window/window.h>
#include <uf/utils/window/payloads.h>
#include <uf/utils/camera/camera.h>
#include <uf/utils/audio/audio.h>
#include <uf/ext/openvr/openvr.h>
#include <uf/engine/graph/graph.h>
#include <uf/utils/math/physics.h>
#include <uf/spec/controller/controller.h>
#include <uf/utils/io/inputs.h>
UF_BEHAVIOR_REGISTER_CPP(ext::PlayerInteractionBehavior)
UF_BEHAVIOR_TRAITS_CPP(ext::PlayerInteractionBehavior, ticks = true, renders = false, thread = uf::thread::asyncThreadName)
#define this (&self)
void ext::PlayerInteractionBehavior::initialize(uf::Object& self) {
auto& metadata = this->getComponent<ext::PlayerInteractionBehavior::Metadata>();
auto& metadataJson = this->getComponent<uf::Serializer>();
UF_BEHAVIOR_METADATA_BIND_SERIALIZER_HOOKS( metadata, metadataJson );
}
void ext::PlayerInteractionBehavior::tick(uf::Object& self) {
if (!this->hasComponent<ext::PlayerInputBehavior::Metadata>()) return;
if (!this->hasComponent<pod::PhysicsBody>()) return; // We need a physics body for the raycast!
auto& input = this->getComponent<ext::PlayerInputBehavior::Metadata>();
auto& metadata = this->getComponent<ext::PlayerInteractionBehavior::Metadata>();
auto& physicsBody = this->getComponent<pod::PhysicsBody>();
TIMER(0.25, input.use) {
auto& camera = this->getComponent<uf::Camera>();
auto cameraTransform = camera.getTransform();
auto flattened = uf::transform::flatten(cameraTransform);
auto axes = uf::transform::axes( flattened );
pod::Vector3f center = flattened.position;
pod::Vector3f direction = axes.forward;
pod::RayQuery query = uf::physics::rayCast( pod::Ray{center, direction}, physicsBody, metadata.length );
uf::Object* pointer = query.hit ? query.body->object : NULL;
float depth = query.hit ? query.contact.penetration : -1;
ext::json::Value payload;
payload["user"] = this->getUid();
payload["uid"] = pointer ? pointer->getUid() : 0;
payload["depth"] = depth;
if ( pointer ) {
pointer->lazyCallHook("entity:Use.%UID%", payload);
}
this->lazyCallHook("entity:Use.%UID%", payload);
}
}
void ext::PlayerInteractionBehavior::render(uf::Object& self) {}
void ext::PlayerInteractionBehavior::destroy(uf::Object& self) {}
void ext::PlayerInteractionBehavior::Metadata::serialize(uf::Object& self, uf::Serializer& serializer) {
serializer["use"]["length"] = length;
}
void ext::PlayerInteractionBehavior::Metadata::deserialize(uf::Object& self, uf::Serializer& serializer) {
length = serializer["use"]["length"].as(length);
}
#undef this

View File

@ -0,0 +1,18 @@
#pragma once
#include <uf/config.h>
#include <uf/ext/ext.h>
#include <uf/engine/entity/entity.h>
#include <uf/engine/scene/scene.h>
#include <uf/utils/math/vector.h>
namespace ext {
namespace PlayerInteractionBehavior {
UF_BEHAVIOR_DEFINE_TYPE();
EXT_BEHAVIOR_DEFINE_TRAITS();
EXT_BEHAVIOR_DEFINE_FUNCTIONS();
UF_BEHAVIOR_DEFINE_METADATA(
float length = 4.0f;
);
}
}

View File

@ -0,0 +1,339 @@
#include "behavior.h"
#include "../input/behavior.h"
#include "../camera/behavior.h"
#include <uf/utils/hook/hook.h>
#include <uf/utils/time/time.h>
#include <uf/utils/serialize/serializer.h>
#include <uf/utils/userdata/userdata.h>
#include <uf/utils/window/window.h>
#include <uf/utils/window/payloads.h>
#include <uf/utils/camera/camera.h>
#include <uf/utils/audio/audio.h>
#include <uf/ext/openvr/openvr.h>
#include <uf/engine/graph/graph.h>
#include <uf/utils/math/physics.h>
#include <uf/spec/controller/controller.h>
#include <uf/utils/io/inputs.h>
#define ONE_OVER_SIXTY 0.016666f
UF_BEHAVIOR_REGISTER_CPP(ext::PlayerMovementBehavior)
UF_BEHAVIOR_TRAITS_CPP(ext::PlayerMovementBehavior, ticks = true, renders = false, thread = uf::thread::asyncThreadName)
#define this (&self)
void ext::PlayerMovementBehavior::initialize(uf::Object& self) {
auto& metadata = this->getComponent<ext::PlayerMovementBehavior::Metadata>();
auto& metadataJson = this->getComponent<uf::Serializer>();
UF_BEHAVIOR_METADATA_BIND_SERIALIZER_HOOKS(metadata, metadataJson);
}
void ext::PlayerMovementBehavior::tick(uf::Object& self) {
if ( !this->hasComponent<ext::PlayerInputBehavior::Metadata>() ) return;
auto& input = this->getComponent<ext::PlayerInputBehavior::Metadata>();
auto& metadata = this->getComponent<ext::PlayerMovementBehavior::Metadata>();
auto& transform = this->getComponent<pod::Transform<>>();
auto& physicsBody = this->getComponent<pod::PhysicsBody>();
auto& camera = this->getComponent<uf::Camera>();
auto& cameraTransform = camera.getTransform();
auto cameraAxes = uf::transform::axes( cameraTransform );
auto axes = uf::transform::axes( transform );
/*if ( metadata.camera.fixed ) {
axes = uf::transform::axes( cameraTransform );
axes.forward.y = 0;
axes.forward = uf::vector::normalize( axes.forward );
}
else*/ if ( metadata.noclipped || physicsBody.gravity == pod::Vector3f{0,0,0} ){
axes.forward.y += cameraAxes.forward.y;
axes.forward = uf::vector::normalize( axes.forward );
}
bool wasFloored = metadata.floored;
metadata.deltaCrouch = false;
metadata.floored = metadata.noclipped;
if ( !metadata.floored ) {
if ( physicsBody.activity.grounded ) {
metadata.floored = true;
}
}
if ( physicsBody.gravity == pod::Vector3f{0,0,0} ) metadata.noclipped = true;
{
TIMER(0.25, input.noclipToggle) {
bool state = !metadata.noclipped;
metadata.noclipped = state;
if (!state) {
uf::physics::setGravity(physicsBody);
uf::physics::setColliderCategory(physicsBody, "PLAYER");
uf::physics::setColliderMask(physicsBody, "PLAYER");
} else {
uf::physics::setGravity(physicsBody, pod::Vector3f{0,0,0});
uf::physics::setColliderCategory(physicsBody, "NONE");
uf::physics::setColliderMask(physicsBody, "NONE");
}
UF_MSG_DEBUG("{}abled noclip: {}", (state ? "En" : "Dis"), uf::vector::toString(transform.position));
}
}
float currentSpeed = metadata.settings.move;
if ( input.run ) currentSpeed = metadata.settings.run;
else if ( input.walk ) currentSpeed = metadata.settings.walk;
currentSpeed *= input.magnitude;
float currentFriction = metadata.settings.friction;
if ( metadata.noclipped ) currentSpeed *= 1.5f;
if ( !metadata.floored || metadata.noclipped ) currentFriction = 1.0f;
if ( metadata.noclipped ) physicsBody.velocity = {};
pod::Vector3f target = (axes.forward * input.movement.y) + (axes.right * input.movement.x);
if (uf::vector::norm(target) > 0) {
target = uf::vector::normalize(target);
}
physicsBody.velocity *= { currentFriction, 1.0f, currentFriction };
metadata.walking = (input.movement.x != 0 || input.movement.y != 0);
metadata.running = input.run;
if ( metadata.walking && !metadata.noclipped && physicsBody.object ) {
float stepHeight = 0.65f;
float lookAhead = 0.15f;
float radius = 0.5f;
float cylHalfHeight = 1.0f;
if ( physicsBody.collider.type == pod::ShapeType::CAPSULE ) {
radius = physicsBody.collider.capsule.radius;
cylHalfHeight = uf::vector::norm(physicsBody.collider.capsule.up);
}
pod::Vector3f centerPos = transform.position + physicsBody.offsetPosition;
float feetY = centerPos.y - (cylHalfHeight + radius);
pod::Vector3f checkDir = target;
checkDir.y = 0;
if ( uf::vector::norm(checkDir) > 0.0f ) {
checkDir = uf::vector::normalize(checkDir);
}
bool steppedThisFrame = false;
if ( uf::vector::norm(checkDir) > 0.0f ) {
pod::Ray highFwdRay;
highFwdRay.origin = centerPos;
highFwdRay.origin.y = feetY + stepHeight + 0.1f;
highFwdRay.direction = checkDir;
auto highFwdHit = uf::physics::rayCast( highFwdRay, physicsBody, radius + lookAhead + 0.1f );
if ( highFwdHit.contact.penetration > (radius + lookAhead) ) {
pod::Ray downRay;
downRay.origin = centerPos + (checkDir * (radius + lookAhead));
downRay.origin.y = feetY + stepHeight + 0.1f;
downRay.direction = -axes.up;
auto downHit = uf::physics::rayCast( downRay, physicsBody, stepHeight * 1.5f );
if ( downHit.contact.penetration <= stepHeight * 1.5f ) {
float floorDot = uf::vector::dot(downHit.contact.normal, axes.up);
float stairYDifference = downHit.contact.point.y - feetY;
if ( floorDot > uf::physics::settings.groundedThreshold ) {
if ( stairYDifference > 0.05f && stairYDifference <= stepHeight ) {
transform.position.y += stairYDifference;
if ( this->hasComponent<ext::PlayerCameraBehavior::Metadata>() ) {
this->getComponent<ext::PlayerCameraBehavior::Metadata>().stairOffset -= stairYDifference;
}
if ( physicsBody.velocity.y < 0.0f ) physicsBody.velocity.y = 0.0f;
metadata.floored = true;
physicsBody.activity.grounded = true;
steppedThisFrame = true;
}
}
}
}
}
// commenting this out makes the camera smooth
bool allowedToSnap = wasFloored || (physicsBody.velocity.y > -3.0f && physicsBody.velocity.y < 0.0f);
if ( !steppedThisFrame && allowedToSnap && !physicsBody.activity.grounded && physicsBody.velocity.y <= 0.1f ) {
pod::Vector3f rayOffsets[3] = {
pod::Vector3f{0, 0, 0},
checkDir * (radius * 0.4f),
checkDir * -(radius * 0.4f)
};
bool snappedThisFrame = false;
for ( int i = 0; i < 3; ++i ) {
if ( snappedThisFrame ) break;
pod::Ray stickRay;
stickRay.origin = centerPos + rayOffsets[i];
stickRay.direction = -axes.up;
float castDist = (cylHalfHeight + radius) + stepHeight + 0.1f;
auto stickHit = uf::physics::rayCast( stickRay, physicsBody, castDist );
if ( stickHit.contact.penetration > 0.0f && stickHit.contact.penetration <= castDist ) {
float floorDot = uf::vector::dot(stickHit.contact.normal, axes.up);
float floorY = stickHit.contact.point.y;
float dropDist = feetY - floorY;
if ( floorDot > uf::physics::settings.groundedThreshold ) {
if ( dropDist > 0.01f && dropDist <= stepHeight ) {
bool shouldSnapDown = true;
if ( uf::vector::norm(checkDir) > 0.0f ) {
pod::Ray fwdDownRay;
fwdDownRay.origin = centerPos + (checkDir * (radius + 0.1f));
fwdDownRay.origin.y = feetY + stepHeight + 0.1f;
fwdDownRay.direction = -axes.up;
float fwdCastDist = stepHeight * 1.5f;
auto fwdHit = uf::physics::rayCast(fwdDownRay, physicsBody, fwdCastDist);
if ( fwdHit.contact.penetration > 0.0f && fwdHit.contact.penetration <= fwdCastDist ) {
float fwdDropDist = feetY - fwdHit.contact.point.y;
if ( fwdDropDist >= -0.25f && fwdDropDist <= 0.05f ) {
shouldSnapDown = false;
}
}
}
if ( shouldSnapDown ) {
float hSpeed = uf::vector::norm(pod::Vector3f{physicsBody.velocity.x, 0.0f, physicsBody.velocity.z});
if ( hSpeed < 1.0f ) hSpeed = currentSpeed;
physicsBody.velocity.y = -hSpeed * 1.5f;
if ( uf::vector::norm(target) > 0.0f ) {
physicsBody.velocity.x = target.x * currentSpeed;
physicsBody.velocity.z = target.z * currentSpeed;
}
metadata.floored = true;
physicsBody.activity.grounded = true;
snappedThisFrame = true;
}
}
}
}
}
}
}
if ( metadata.walking ) {
float factor = metadata.floored ? 1.0f : metadata.settings.air;
if ( metadata.noclipped ) {
physicsBody.velocity += target * currentSpeed * 50.0f * ONE_OVER_SIXTY;
} else {
physicsBody.velocity += target * std::clamp(
currentSpeed * factor - uf::vector::dot(physicsBody.velocity, target),
0.0f,
currentSpeed * 10.0f * ONE_OVER_SIXTY
);
}
auto dot = uf::vector::dot( axes.forward, target );
if ( !metadata.settings.strafe && dot < 1.0f ) {
auto axis = axes.up;
float angle = uf::vector::signedAngle( axes.forward, target, axis ) * ONE_OVER_SIXTY /*uf::physics::time::delta*/ * 4;
if ( physicsBody.object ) uf::physics::applyRotation( physicsBody, axis, angle ); else
uf::transform::rotate( transform, axis, angle );
}
}
{
TIMER( 0.0625, metadata.floored && input.jump && !metadata.noclipped ) {
physicsBody.velocity += axes.up * metadata.settings.jump;
}
}
if ( metadata.floored && input.jump && metadata.noclipped ) {
transform.position += axes.up * metadata.settings.jump * uf::physics::time::delta * 4.0f;
}
if ( input.crouch ) {
if ( metadata.noclipped ) transform.position -= axes.up * metadata.settings.jump * uf::physics::time::delta * 4.0f;
else {
if ( !metadata.crouching ) metadata.deltaCrouch = true;
metadata.crouching = true;
}
} else {
if ( metadata.crouching ) metadata.deltaCrouch = true;
metadata.crouching = false;
}
if ( metadata.deltaCrouch && !metadata.noclipped && physicsBody.object ) {
float halfCrouch = metadata.settings.crouch * 0.5f;
if ( physicsBody.collider.type == pod::ShapeType::CAPSULE ) {
if ( metadata.crouching ) {
physicsBody.collider.capsule.up.y -= halfCrouch;
physicsBody.offsetPosition.y += halfCrouch;
if ( metadata.floored ) {
transform.position.y -= metadata.settings.crouch;
}
} else {
physicsBody.collider.capsule.up.y += halfCrouch;
physicsBody.offsetPosition.y -= halfCrouch;
}
}
}
if ( physicsBody.object ) uf::physics::setVelocity(physicsBody, physicsBody.velocity);
else transform.position += physicsBody.velocity * ONE_OVER_SIXTY;
}
void ext::PlayerMovementBehavior::render(uf::Object& self) {}
void ext::PlayerMovementBehavior::destroy(uf::Object& self) {}
void ext::PlayerMovementBehavior::Metadata::serialize( uf::Object& self, uf::Serializer& serializer ){
serializer["system"]["crouching"] = crouching;
serializer["system"]["noclipped"] = noclipped;
serializer["physics"]["friction"] = settings.friction;
serializer["movement"]["rotate"] = settings.rotate;
serializer["movement"]["move"] = settings.move;
serializer["movement"]["run"] = settings.run;
serializer["movement"]["walk"] = settings.walk;
serializer["movement"]["air"] = settings.air;
serializer["movement"]["strafe"] = settings.strafe;
serializer["movement"]["crouch"] = settings.crouch;
serializer["movement"]["jump"] = uf::vector::encode(settings.jump);
}
void ext::PlayerMovementBehavior::Metadata::deserialize( uf::Object& self, uf::Serializer& serializer ){
crouching = serializer["system"]["crouching"].as(crouching);
noclipped = serializer["system"]["noclipped"].as(noclipped);
settings.friction = serializer["physics"]["friction"].as(settings.friction);
settings.rotate = serializer["movement"]["rotate"].as(settings.rotate);
settings.move = serializer["movement"]["move"].as(settings.move);
settings.run = serializer["movement"]["run"].as(settings.run);
settings.walk = serializer["movement"]["walk"].as(settings.walk);
settings.air = serializer["movement"]["air"].as(settings.air);
settings.strafe = serializer["movement"]["strafe"].as(settings.strafe);
settings.crouch = serializer["movement"]["crouch"].as(settings.crouch);
settings.jump = uf::vector::decode(serializer["movement"]["jump"], settings.jump);
}
#undef this
#include <uf/ext/lua/component.h>
UF_LUA_REGISTER_USERTYPE_AND_COMPONENT(ext::PlayerMovementBehavior::Metadata,
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerMovementBehavior::Metadata::walking),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerMovementBehavior::Metadata::running),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerMovementBehavior::Metadata::crouching),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerMovementBehavior::Metadata::floored),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerMovementBehavior::Metadata::noclipped),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerMovementBehavior::Metadata::deltaCrouch)
)

View File

@ -0,0 +1,36 @@
#pragma once
#include <uf/config.h>
#include <uf/ext/ext.h>
#include <uf/engine/entity/entity.h>
#include <uf/engine/scene/scene.h>
#include <uf/utils/math/vector.h>
namespace ext {
namespace PlayerMovementBehavior {
UF_BEHAVIOR_DEFINE_TYPE();
EXT_BEHAVIOR_DEFINE_TRAITS();
EXT_BEHAVIOR_DEFINE_FUNCTIONS();
UF_BEHAVIOR_DEFINE_METADATA(
struct Settings {
float crouch = -1.0f;
float rotate = 1.0f;
float move = 4.0f;
float run = 8.0f;
float walk = 1.0f;
float friction = 0.8f;
float air = 1.0f;
float stepHeight = 0.35f;
bool strafe = true;
pod::Vector3f jump = {0, 8, 0};
} settings;
bool walking = false;
bool running = false;
bool crouching = false;
bool floored = true;
bool noclipped = false;
bool deltaCrouch = false;
);
}
}

View File

@ -20,30 +20,85 @@ uf::stl::unordered_map<uf::stl::string, uf::stl::string> ext::lua::modules;
#include <uf/utils/io/inputs.h>
#include <uf/utils/io/fmt.h>
struct vfs_reader {
uf::stl::vector<uint8_t> buffer;
bool read_once;
namespace {
struct vfs_reader {
uf::stl::vector<uint8_t> buffer;
bool read_once;
vfs_reader(const uf::stl::string& filename) : read_once(false) {
// Use your VFS abstraction to load the entire file into the buffer
uf::io::readAsBuffer(buffer, filename);
}
static const char* read(lua_State*, void* data, size_t* size) {
vfs_reader* reader = static_cast<vfs_reader*>(data);
// If we haven't yielded the buffer to Lua yet, do it now.
if (!reader->read_once && !reader->buffer.empty()) {
*size = reader->buffer.size();
reader->read_once = true;
return reinterpret_cast<const char*>(reader->buffer.data());
vfs_reader(const uf::stl::string& filename) : read_once(false) {
uf::io::readAsBuffer(buffer, filename);
}
// Yield 0 to signify EOF
*size = 0;
return nullptr;
static const char* read(lua_State*, void* data, size_t* size) {
vfs_reader* reader = static_cast<vfs_reader*>(data);
if (!reader->read_once && !reader->buffer.empty()) {
*size = reader->buffer.size();
reader->read_once = true;
return reinterpret_cast<const char*>(reader->buffer.data());
}
*size = 0;
return nullptr;
}
};
ext::json::Value encodeNode( sol::object obj ) {
if ( obj.get_type() == sol::type::boolean ) return ext::json::Value(obj.as<bool>());
if ( obj.get_type() == sol::type::number ) return ext::json::Value(obj.as<double>());
if ( obj.get_type() == sol::type::string ) return ext::json::Value(obj.as<uf::stl::string>());
if ( obj.get_type() == sol::type::table ) {
sol::table t = obj.as<sol::table>();
ext::json::Value json;
bool isArray = true;
size_t expectedIndex = 1;
for (auto& kv : t) {
if (kv.first.get_type() != sol::type::number || kv.first.as<size_t>() != expectedIndex++) {
isArray = false;
break;
}
}
if ( isArray ) {
for ( auto& kv : t ) json.emplace_back( encodeNode(kv.second) );
} else {
for ( auto& kv : t ) {
if ( kv.first.get_type() == sol::type::string ) {
json[kv.first.as<uf::stl::string>()] = encodeNode( kv.second );
}
}
}
return json;
}
return ext::json::Value();
}
};
sol::object decodeNode(const ext::json::Value& json) {
if ( json.is_null() ) return sol::lua_nil;
if ( json.is_boolean() ) return sol::make_object(ext::lua::state, json.as<bool>());
if ( json.is_number() ) return sol::make_object(ext::lua::state, json.as<double>());
if ( json.is_string() ) return sol::make_object(ext::lua::state, json.as<uf::stl::string>());
if ( json.is_array() ) {
sol::table t = ext::lua::state.create_table();
ext::json::forEach(json, [&](size_t i, const ext::json::Value& val) {
t[i + 1] = decodeNode(val);
});
return t;
}
if ( json.is_object() ) {
sol::table t = ext::lua::state.create_table();
ext::json::forEach(json, [&](const uf::stl::string& key, const ext::json::Value& val) {
t[key] = decodeNode(val);
});
return t;
}
return sol::lua_nil;
}
}
sol::table ext::lua::createTable() {
return sol::table(ext::lua::state, sol::create);
@ -55,6 +110,8 @@ uf::stl::string ext::lua::sanitize( const uf::stl::string& dirty, int index ) {
part = uf::string::replace( part, "<>", "" );
return part;
}
/*
std::optional<uf::stl::string> ext::lua::encode( sol::table table ) {
LUA_FUN fun = ext::lua::state["json"]["encode"];
auto result = fun( table );
@ -79,6 +136,16 @@ std::optional<sol::table> ext::lua::decode( const uf::stl::string& string ) {
#endif
return result;
}
*/
std::optional<ext::json::Value> ext::lua::encode(sol::table table) {
return ::encodeNode(table);
}
std::optional<sol::table> ext::lua::decode(const ext::json::Value& json) {
sol::object obj = ::decodeNode(json);
if ( obj.is<sol::table>() ) return obj.as<sol::table>();
return ext::lua::state.create_table();
}
uf::stl::vector<std::function<void()>>* ext::lua::onInitializationFunctions = NULL;
void ext::lua::onInitialization( const std::function<void()>& function ) {
@ -94,7 +161,7 @@ namespace binds {
namespace hook {
void add( const uf::stl::string& name, LUA_FUN function ) {
uf::hooks.addHook( name, [function](ext::json::Value& json){
sol::table table = ext::lua::state["json"]["decode"]( json.dump() );
sol::table table = ext::lua::decode(json).value_or(ext::lua::state.create_table());
auto result = function( table );
// ???
#if UF_LUA_PCALLS
@ -188,8 +255,7 @@ namespace binds {
sol::table readFromFile( const uf::stl::string& filename ){
uf::Serializer serializer;
serializer.readFromFile( filename );
uf::stl::string string = serializer.serialize();
auto decoded = ext::lua::decode( string );
auto decoded = ext::lua::decode( serializer );
return decoded ? decoded.value() : ext::lua::createTable();
};
bool writeToFile( sol::table table, const uf::stl::string& path ) {

View File

@ -155,22 +155,21 @@ namespace binds {
#if UF_LUA_PCALLS
if ( !result.valid() ) {
sol::error err = result;
uf::iostream << err.what() << "\n";
UF_MSG_ERROR("{}", err.what());
return;
}
#endif
return;
}
uf::stl::string payload = json.dump();
auto decoded = ext::lua::decode( payload );
auto decoded = ext::lua::decode( json );
if ( !decoded ) return;
sol::table table = decoded.value();
auto result = fun( table );
#if UF_LUA_PCALLS
if ( !result.valid() ) {
sol::error err = result;
uf::iostream << err.what() << "\n";
UF_MSG_ERROR("{}", err.what());
return;
}
#endif

View File

@ -223,7 +223,8 @@ void uf::physics::step( pod::World& world, float dt ) {
if ( a.activity.awake && !b.activity.awake ) impl::wakeBody( b );
if ( b.activity.awake && !a.activity.awake ) impl::wakeBody( a );
// mark as grounded
for ( auto& c : manifold.points ) {
bool isTrigger = (a.collider.category & pod::Collider::CATEGORY_TRIGGER) || (b.collider.category & pod::Collider::CATEGORY_TRIGGER);
if ( !isTrigger ) for ( auto& c : manifold.points ) {
if ( std::fabs(uf::vector::dot(c.normal, pod::Vector3f{0,1,0})) > uf::physics::settings.groundedThreshold ) {
// only mark if contact point is below body
if ( c.point.y < impl::getPosition(a).y ) a.activity.grounded = true;