├── models └── hands │ ├── HandRight.fbx │ ├── handleft.vmdl │ └── handright.vmdl ├── .gitignore ├── code ├── UI │ ├── ExampleHud.html │ ├── ExampleHud.scss │ ├── ExampleHudEntity.cs │ └── HudEntity.cs ├── Game.cs ├── Player │ ├── Hands │ │ ├── LeftHand.cs │ │ ├── RightHand.cs │ │ └── HandEntity.cs │ ├── PlayerAnimator.cs │ ├── Player.cs │ └── WalkController.cs └── Utils │ └── TransformExtensions.cs ├── .gitattributes ├── .addon ├── README.md ├── LICENSE └── animgraphs └── hands └── handright.vanmgrph /models/hands/HandRight.fbx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xezno/sbox-vr-minimal/HEAD/models/hands/HandRight.fbx -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | code/*.csproj 2 | code/obj 3 | code/Properties/launchSettings.json 4 | .intermediate 5 | tools_asset_info.bin 6 | _bakeresourcecache 7 | tools_thumbnail_cache.bin 8 | .vs 9 | *.*_c 10 | -------------------------------------------------------------------------------- /code/UI/ExampleHud.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | My VR Gamemode 5 | 6 | 7 |
-------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Allow .vfx & .fxc files to have hlsl syntax highlighting 5 | *.vfx linguist-language=HLSL 6 | *.fxc linguist-language=HLSL 7 | -------------------------------------------------------------------------------- /code/Game.cs: -------------------------------------------------------------------------------- 1 | using Sandbox; 2 | 3 | namespace VrExample; 4 | 5 | public partial class Game : Sandbox.Game 6 | { 7 | public Game() 8 | { 9 | if ( IsServer ) 10 | { 11 | _ = new ExampleHudEntity(); 12 | } 13 | } 14 | 15 | public override void ClientJoined( Client client ) 16 | { 17 | base.ClientJoined( client ); 18 | 19 | var player = new Player(); 20 | client.Pawn = player; 21 | 22 | player.Respawn(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /code/Player/Hands/LeftHand.cs: -------------------------------------------------------------------------------- 1 | using Sandbox; 2 | 3 | namespace VrExample; 4 | 5 | public class LeftHand : HandEntity 6 | { 7 | protected override string ModelPath => "models/hands/handleft.vmdl"; 8 | public override Input.VrHand InputHand => Input.VR.LeftHand; 9 | 10 | public override void Spawn() 11 | { 12 | base.Spawn(); 13 | Log.Info( "VR Controller Left Spawned" ); 14 | SetInteractsAs( CollisionLayer.LEFT_HAND ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /code/Player/Hands/RightHand.cs: -------------------------------------------------------------------------------- 1 | using Sandbox; 2 | 3 | namespace VrExample; 4 | 5 | public class RightHand : HandEntity 6 | { 7 | protected override string ModelPath => "models/hands/handright.vmdl"; 8 | public override Input.VrHand InputHand => Input.VR.RightHand; 9 | 10 | public override void Spawn() 11 | { 12 | base.Spawn(); 13 | Log.Info( "VR Controller Right Spawned" ); 14 | SetInteractsAs( CollisionLayer.RIGHT_HAND ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /code/Utils/TransformExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace VrExample; 2 | 3 | public static class TransformExtensions 4 | { 5 | public static Transform RotateAround( this Transform transform, Vector3 pivotPoint, Rotation rotation ) 6 | { 7 | var resultTransform = transform; 8 | 9 | resultTransform.Position = rotation * (transform.Position - pivotPoint) + pivotPoint; 10 | resultTransform.Rotation = rotation * transform.Rotation; 11 | 12 | return resultTransform; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /code/UI/ExampleHud.scss: -------------------------------------------------------------------------------- 1 | .main-panel { 2 | position: absolute; 3 | top: 0; 4 | left: 0; 5 | right: 0; 6 | bottom: 0; 7 | 8 | &.is-vr { 9 | border-radius: 100px; 10 | background-color: rgba( black, 0.1 ); 11 | backdrop-filter: blur( 50px ); 12 | } 13 | } 14 | 15 | text { 16 | font-family: Poppins; 17 | font-weight: 400; 18 | font-size: 64px; 19 | padding: 50px; 20 | color: white; 21 | text-shadow: 0 0 10px rgba( black, 0.25 ); 22 | } 23 | -------------------------------------------------------------------------------- /code/UI/ExampleHudEntity.cs: -------------------------------------------------------------------------------- 1 | using Sandbox; 2 | using Sandbox.UI; 3 | 4 | namespace VrExample; 5 | 6 | public class ExampleHudEntity : HudEntity 7 | { 8 | public ExampleHudEntity() 9 | { 10 | if ( IsClient ) 11 | { 12 | if ( Global.IsRunningInVR ) 13 | { 14 | // Use a world panel - we're in VR 15 | _ = new HudEntity(); 16 | } 17 | else 18 | { 19 | // Just display the HUD on-screen 20 | RootPanel.SetTemplate( "/Code/UI/ExampleHud.html" ); 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.addon: -------------------------------------------------------------------------------- 1 | { 2 | "Title": "VR Minimal", 3 | "Type": "game", 4 | "Org": "alex", 5 | "Ident": "vrminimal", 6 | "Schema": 1, 7 | "HasAssets": true, 8 | "AssetsPath": "", 9 | "ResourcePaths": [ 10 | "/code/UI/*" 11 | ], 12 | "HasCode": true, 13 | "CodePath": "code", 14 | "RootNamespace": "VrExample", 15 | "Metadata": { 16 | "ProjectTemplate": { 17 | "Icon": "panorama_photosphere" 18 | }, 19 | "MapList": [ 20 | "facepunch.construct" 21 | ], 22 | "PerMapRanking": false, 23 | "LeaderboardType": 0, 24 | "RankType": 0, 25 | "MapSelect": 0, 26 | "GameNetworkType": 0, 27 | "MaxPlayers": 16, 28 | "MinPlayers": 0 29 | } 30 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sbox-vr-minimal 2 | 3 | Basic minimal setup for VR in s&box. 4 | 5 | ## How-to 6 | 7 | ### Using the template system 8 | 9 | 1. `git clone https://github.com/xezno/sbox-vr-minimal` in `sbox/templates` 10 | 2. Launch s&box with the `-vr` launch argument 11 | 3. Create your addon using the template shown in the addon manager. 12 | 13 | ![image](https://user-images.githubusercontent.com/12881812/176691322-d4868b32-c43f-4165-b4fc-4b3ad24d3033.png) 14 | 15 | ### Manually cloning & adding 16 | 17 | 1. `git clone https://github.com/xezno/sbox-vr-minimal` in your s&box addons folder 18 | 2. Launch s&box with the `-vr` launch argument 19 | 3. Add the .addon file using the s&box addon manager 20 | 21 | ## Credits 22 | 23 | Hand models by [ShadowBrain](https://github.com/ShadowBrian) 24 | -------------------------------------------------------------------------------- /code/UI/HudEntity.cs: -------------------------------------------------------------------------------- 1 | using Sandbox; 2 | using Sandbox.UI; 3 | 4 | namespace VrExample; 5 | 6 | /// 7 | /// This will project the example HUD onto your left wrist 8 | /// 9 | public class HudEntity : WorldPanel 10 | { 11 | public HudEntity() 12 | { 13 | SetTemplate( "/Code/UI/ExampleHud.html" ); 14 | SetClass( "is-vr", true ); 15 | } 16 | 17 | public override void Tick() 18 | { 19 | base.Tick(); 20 | 21 | if ( Local.Pawn is Player player ) 22 | { 23 | Transform = player.LeftHand.Transform; 24 | 25 | // 26 | // Offsets 27 | // 28 | Rotation *= new Angles( -180, -90, 45 ).ToRotation(); 29 | Position += Rotation.Forward * 5 + Rotation.Up * 6 - Rotation.Left * 12; 30 | WorldScale = 0.1f; 31 | Scale = 2.0f; 32 | 33 | PanelBounds = new Rect( 0, 0, 1920, 1080 ); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Alex Guthrie 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /code/Player/Hands/HandEntity.cs: -------------------------------------------------------------------------------- 1 | using Sandbox; 2 | 3 | namespace VrExample; 4 | 5 | public partial class HandEntity : AnimatedEntity 6 | { 7 | protected virtual string ModelPath => ""; 8 | 9 | [Net] public HandEntity Other { get; set; } 10 | 11 | public bool GripPressed => InputHand.Grip > 0.5f; 12 | public bool TriggerPressed => InputHand.Trigger > 0.5f; 13 | 14 | public virtual Input.VrHand InputHand { get; } 15 | 16 | public override void Spawn() 17 | { 18 | SetModel( ModelPath ); 19 | 20 | Transmit = TransmitType.Always; 21 | } 22 | 23 | public override void FrameSimulate( Client cl ) 24 | { 25 | base.FrameSimulate( cl ); 26 | 27 | Transform = InputHand.Transform; 28 | } 29 | 30 | public override void Simulate( Client cl ) 31 | { 32 | base.Simulate( cl ); 33 | 34 | Transform = InputHand.Transform; 35 | Animate(); 36 | } 37 | 38 | private void Animate() 39 | { 40 | SetAnimParameter( "Index", InputHand.GetFingerCurl( 1 ) ); 41 | SetAnimParameter( "Middle", InputHand.GetFingerCurl( 2 ) ); 42 | SetAnimParameter( "Ring", InputHand.GetFingerCurl( 3 ) ); 43 | SetAnimParameter( "Thumb", InputHand.GetFingerCurl( 0 ) ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /code/Player/PlayerAnimator.cs: -------------------------------------------------------------------------------- 1 | using Sandbox; 2 | using System; 3 | 4 | namespace VrExample; 5 | 6 | public class PlayerAnimator : StandardPlayerAnimator 7 | { 8 | float duck; 9 | 10 | public override void Simulate() 11 | { 12 | DoWalk(); 13 | 14 | // 15 | // Let the animation graph know some shit 16 | // 17 | bool sitting = HasTag( "sitting" ); 18 | bool noclip = HasTag( "noclip" ) && !sitting; 19 | 20 | SetAnimParameter( "b_grounded", GroundEntity != null || noclip || sitting ); 21 | SetAnimParameter( "b_noclip", noclip ); 22 | SetAnimParameter( "b_sit", sitting ); 23 | SetAnimParameter( "b_swim", Pawn.WaterLevel > 0.5f && !sitting ); 24 | 25 | Vector3 aimPos = Pawn.EyePosition + Rotation.Forward * 256; 26 | Vector3 lookPos = Input.VR.Head.Position + Input.VR.Head.Rotation.Forward * 256; 27 | 28 | // 29 | // Look in the direction what the player's input is facing 30 | // 31 | SetLookAt( "lookat_pos", lookPos ); // old 32 | SetLookAt( "aimat_pos", aimPos ); // old 33 | 34 | SetLookAt( "aim_eyes", lookPos ); 35 | SetLookAt( "aim_head", lookPos ); 36 | SetLookAt( "aim_body", aimPos ); 37 | 38 | SetAnimParameter( "b_ducked", HasTag( "ducked" ) ); // old 39 | 40 | if ( HasTag( "ducked" ) ) duck = duck.LerpTo( 1.0f, Time.Delta * 10.0f ); 41 | else duck = duck.LerpTo( 0.0f, Time.Delta * 5.0f ); 42 | 43 | if ( (Pawn as Player)?.ActiveChild is BaseCarriable carry ) 44 | { 45 | carry.SimulateAnimator( this ); 46 | } 47 | else 48 | { 49 | SetAnimParameter( "holdtype", 0 ); 50 | SetAnimParameter( "aimat_weight", 0.5f ); // old 51 | SetAnimParameter( "aim_body_weight", 0.5f ); 52 | } 53 | } 54 | 55 | void DoWalk() 56 | { 57 | // Move Speed 58 | { 59 | var dir = Velocity; 60 | var forward = Rotation.Forward.Dot( dir ); 61 | var sideward = Rotation.Right.Dot( dir ); 62 | 63 | var angle = MathF.Atan2( sideward, forward ).RadianToDegree().NormalizeDegrees(); 64 | 65 | SetAnimParameter( "move_direction", angle ); 66 | SetAnimParameter( "move_speed", Velocity.Length ); 67 | SetAnimParameter( "move_groundspeed", Velocity.WithZ( 0 ).Length ); 68 | SetAnimParameter( "move_y", sideward ); 69 | SetAnimParameter( "move_x", forward ); 70 | SetAnimParameter( "move_z", Velocity.z ); 71 | } 72 | 73 | // Wish Speed 74 | { 75 | var dir = WishVelocity; 76 | var forward = Rotation.Forward.Dot( dir ); 77 | var sideward = Rotation.Right.Dot( dir ); 78 | 79 | var angle = MathF.Atan2( sideward, forward ).RadianToDegree().NormalizeDegrees(); 80 | 81 | SetAnimParameter( "wish_direction", angle ); 82 | SetAnimParameter( "wish_speed", WishVelocity.Length ); 83 | SetAnimParameter( "wish_groundspeed", WishVelocity.WithZ( 0 ).Length ); 84 | SetAnimParameter( "wish_y", sideward ); 85 | SetAnimParameter( "wish_x", forward ); 86 | SetAnimParameter( "wish_z", WishVelocity.z ); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /code/Player/Player.cs: -------------------------------------------------------------------------------- 1 | using Sandbox; 2 | 3 | namespace VrExample; 4 | 5 | partial class Player : Sandbox.Player 6 | { 7 | [Net, Local] public LeftHand LeftHand { get; set; } 8 | [Net, Local] public RightHand RightHand { get; set; } 9 | 10 | private void CreateHands() 11 | { 12 | DeleteHands(); 13 | 14 | LeftHand = new() { Owner = this }; 15 | RightHand = new() { Owner = this }; 16 | 17 | LeftHand.Other = RightHand; 18 | RightHand.Other = LeftHand; 19 | } 20 | 21 | private void DeleteHands() 22 | { 23 | LeftHand?.Delete(); 24 | RightHand?.Delete(); 25 | } 26 | 27 | public override void Respawn() 28 | { 29 | SetModel( "models/citizen/citizen.vmdl" ); 30 | 31 | if ( Client.IsUsingVr ) 32 | { 33 | Controller = new WalkController(); 34 | Animator = new PlayerAnimator(); 35 | CameraMode = new FirstPersonCamera(); 36 | } 37 | else 38 | { 39 | Controller = new Sandbox.WalkController(); 40 | Animator = new StandardPlayerAnimator(); 41 | CameraMode = new FirstPersonCamera(); 42 | } 43 | 44 | EnableAllCollisions = true; 45 | EnableDrawing = true; 46 | EnableHideInFirstPerson = true; 47 | EnableShadowInFirstPerson = true; 48 | 49 | CreateHands(); 50 | 51 | if ( Client.IsUsingVr ) 52 | SetBodyGroup( "Hands", 1 ); // Hide hands 53 | 54 | base.Respawn(); 55 | } 56 | 57 | public override void ClientSpawn() 58 | { 59 | base.ClientSpawn(); 60 | } 61 | 62 | public override void Simulate( Client cl ) 63 | { 64 | base.Simulate( cl ); 65 | SimulateActiveChild( cl, ActiveChild ); 66 | 67 | CheckRotate(); 68 | SetVrAnimProperties(); 69 | 70 | LeftHand?.Simulate( cl ); 71 | RightHand?.Simulate( cl ); 72 | } 73 | 74 | public override void FrameSimulate( Client cl ) 75 | { 76 | base.FrameSimulate( cl ); 77 | 78 | LeftHand?.FrameSimulate( cl ); 79 | RightHand?.FrameSimulate( cl ); 80 | } 81 | 82 | public void SetVrAnimProperties() 83 | { 84 | if ( LifeState != LifeState.Alive ) 85 | return; 86 | 87 | if ( !Input.VR.IsActive ) 88 | return; 89 | 90 | SetAnimParameter( "b_vr", true ); 91 | var leftHandLocal = Transform.ToLocal( LeftHand.GetBoneTransform( 0 ) ); 92 | var rightHandLocal = Transform.ToLocal( RightHand.GetBoneTransform( 0 ) ); 93 | 94 | var handOffset = Vector3.Zero; 95 | SetAnimParameter( "left_hand_ik.position", leftHandLocal.Position + (handOffset * leftHandLocal.Rotation) ); 96 | SetAnimParameter( "right_hand_ik.position", rightHandLocal.Position + (handOffset * rightHandLocal.Rotation) ); 97 | 98 | SetAnimParameter( "left_hand_ik.rotation", leftHandLocal.Rotation * Rotation.From( 0, 0, 180 ) ); 99 | SetAnimParameter( "right_hand_ik.rotation", rightHandLocal.Rotation ); 100 | 101 | float height = Input.VR.Head.Position.z - Position.z; 102 | SetAnimParameter( "duck", 1.0f - ((height - 32f) / 32f) ); // This will probably need tweaking depending on height 103 | } 104 | 105 | private TimeSince timeSinceLastRotation; 106 | private void CheckRotate() 107 | { 108 | if ( !IsServer ) 109 | return; 110 | 111 | const float deadzone = 0.2f; 112 | const float angle = 45f; 113 | const float delay = 0.25f; 114 | 115 | float rotate = Input.VR.RightHand.Joystick.Value.x; 116 | 117 | if ( timeSinceLastRotation > delay ) 118 | { 119 | if ( rotate > deadzone ) 120 | { 121 | Transform = Transform.RotateAround( 122 | Input.VR.Head.Position.WithZ( Position.z ), 123 | Rotation.FromAxis( Vector3.Up, -angle ) 124 | ); 125 | 126 | timeSinceLastRotation = 0; 127 | } 128 | else if ( rotate < -deadzone ) 129 | { 130 | Transform = Transform.RotateAround( 131 | Input.VR.Head.Position.WithZ( Position.z ), 132 | Rotation.FromAxis( Vector3.Up, angle ) 133 | ); 134 | 135 | timeSinceLastRotation = 0; 136 | } 137 | } 138 | 139 | if ( rotate > -deadzone && rotate < deadzone ) 140 | { 141 | timeSinceLastRotation = 10; 142 | } 143 | } 144 | 145 | public override void OnKilled() 146 | { 147 | base.OnKilled(); 148 | EnableDrawing = false; 149 | DeleteHands(); 150 | } 151 | 152 | public override void PostCameraSetup( ref CameraSetup setup ) 153 | { 154 | // You will probably need to tweak these depending on your use case 155 | setup.ZNear = 1; 156 | setup.ZFar = 25000; 157 | 158 | base.PostCameraSetup( ref setup ); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /models/hands/handleft.vmdl: -------------------------------------------------------------------------------- 1 | 2 | { 3 | rootNode = 4 | { 5 | _class = "RootNode" 6 | children = 7 | [ 8 | { 9 | _class = "MaterialGroupList" 10 | children = 11 | [ 12 | { 13 | _class = "DefaultMaterialGroup" 14 | remaps = [ ] 15 | use_global_default = true 16 | global_default_material = "models/citizen/skin/citizen_skin.vmat" 17 | }, 18 | ] 19 | }, 20 | { 21 | _class = "RenderMeshList" 22 | children = 23 | [ 24 | { 25 | _class = "RenderMeshFile" 26 | filename = "models/hands/HandRight.fbx" 27 | import_translation = [ 0.0, 0.0, 0.0 ] 28 | import_rotation = [ 0.0, 0.0, 0.0 ] 29 | import_scale = 1.0 30 | align_origin_x_type = "None" 31 | align_origin_y_type = "None" 32 | align_origin_z_type = "None" 33 | parent_bone = "" 34 | import_filter = 35 | { 36 | exclude_by_default = false 37 | exception_list = [ ] 38 | } 39 | }, 40 | ] 41 | }, 42 | { 43 | _class = "PhysicsShapeList" 44 | children = 45 | [ 46 | { 47 | _class = "PhysicsHullFromRender" 48 | parent_bone = "" 49 | surface_prop = "default" 50 | collision_group = "" 51 | collision_interact_as = "" 52 | collision_interact_with = "" 53 | collision_interact_exclude = "" 54 | faceMergeAngle = 5.0 55 | maxHullVertices = 53 56 | }, 57 | ] 58 | }, 59 | { 60 | _class = "AnimationList" 61 | children = 62 | [ 63 | { 64 | _class = "AnimFile" 65 | name = "HandRight" 66 | activity_name = "" 67 | activity_weight = 1 68 | weight_list_name = "" 69 | fade_in_time = 0.2 70 | fade_out_time = 0.2 71 | looping = false 72 | delta = false 73 | worldSpace = false 74 | hidden = false 75 | anim_markup_ordered = false 76 | disable_compression = false 77 | source_filename = "models/hands/HandRight.fbx" 78 | start_frame = -1 79 | end_frame = -1 80 | framerate = -1.0 81 | take = 0 82 | reverse = false 83 | }, 84 | ] 85 | default_root_bone_name = "" 86 | }, 87 | { 88 | _class = "ModelModifierList" 89 | children = 90 | [ 91 | { 92 | _class = "ModelModifier_ScaleAndMirror" 93 | scale = 0.7 94 | mirror_x = false 95 | mirror_y = true 96 | mirror_z = false 97 | flip_bone_forward = false 98 | swap_left_and_right_bones = false 99 | }, 100 | ] 101 | }, 102 | { 103 | _class = "WeightListList" 104 | children = 105 | [ 106 | { 107 | _class = "WeightList" 108 | name = "Index" 109 | default_weight = 0.0 110 | weights = 111 | [ 112 | { 113 | bone = "finger_index_meta_R" 114 | weight = 1.0 115 | }, 116 | ] 117 | master_morph_weight = -1.0 118 | morph_weights = [ ] 119 | }, 120 | { 121 | _class = "WeightList" 122 | name = "Middle" 123 | default_weight = 0.0 124 | weights = 125 | [ 126 | { 127 | bone = "finger_index_meta_R" 128 | weight = 0.0 129 | }, 130 | { 131 | bone = "finger_middle_meta_R" 132 | weight = 1.0 133 | }, 134 | ] 135 | master_morph_weight = -1.0 136 | morph_weights = [ ] 137 | }, 138 | { 139 | _class = "WeightList" 140 | name = "Ring" 141 | default_weight = 0.0 142 | weights = 143 | [ 144 | { 145 | bone = "finger_index_meta_R" 146 | weight = 0.0 147 | }, 148 | { 149 | bone = "finger_ring_meta_R" 150 | weight = 1.0 151 | }, 152 | ] 153 | master_morph_weight = -1.0 154 | morph_weights = [ ] 155 | }, 156 | { 157 | _class = "WeightList" 158 | name = "Thumb" 159 | default_weight = 0.0 160 | weights = 161 | [ 162 | { 163 | bone = "finger_index_meta_R" 164 | weight = 0.0 165 | }, 166 | { 167 | bone = "finger_thumb_0_R" 168 | weight = 1.0 169 | }, 170 | ] 171 | master_morph_weight = -1.0 172 | morph_weights = [ ] 173 | }, 174 | { 175 | _class = "WeightList" 176 | name = "Base" 177 | default_weight = 0.0 178 | weights = 179 | [ 180 | { 181 | bone = "finger_index_meta_R" 182 | weight = 0.0 183 | }, 184 | { 185 | bone = "finger_middle_meta_R" 186 | weight = 0.0 187 | }, 188 | { 189 | bone = "finger_ring_meta_R" 190 | weight = 0.0 191 | }, 192 | { 193 | bone = "finger_thumb_0_R" 194 | weight = 0.0 195 | }, 196 | { 197 | bone = "hand_R" 198 | weight = 1.0 199 | }, 200 | ] 201 | master_morph_weight = -1.0 202 | morph_weights = [ ] 203 | }, 204 | ] 205 | }, 206 | { 207 | _class = "ModelDataList" 208 | children = 209 | [ 210 | { 211 | _class = "Bounds View" 212 | mins = [ -9.14642, -3.673171, -6.486435 ] 213 | maxs = [ 1.0, 3.672426, 4.49463 ] 214 | }, 215 | ] 216 | }, 217 | ] 218 | model_archetype = "" 219 | primary_associated_entity = "" 220 | anim_graph_name = "animgraphs/hands/handright.vanmgrph" 221 | base_model_name = "" 222 | } 223 | } -------------------------------------------------------------------------------- /models/hands/handright.vmdl: -------------------------------------------------------------------------------- 1 | 2 | { 3 | rootNode = 4 | { 5 | _class = "RootNode" 6 | children = 7 | [ 8 | { 9 | _class = "MaterialGroupList" 10 | children = 11 | [ 12 | { 13 | _class = "DefaultMaterialGroup" 14 | remaps = [ ] 15 | use_global_default = true 16 | global_default_material = "models/citizen/skin/citizen_skin.vmat" 17 | }, 18 | ] 19 | }, 20 | { 21 | _class = "RenderMeshList" 22 | children = 23 | [ 24 | { 25 | _class = "RenderMeshFile" 26 | filename = "models/hands/HandRight.fbx" 27 | import_translation = [ 0.0, 0.0, 0.0 ] 28 | import_rotation = [ 0.0, 0.0, 0.0 ] 29 | import_scale = 1.0 30 | align_origin_x_type = "BoundsMax" 31 | align_origin_y_type = "BoundsCenter" 32 | align_origin_z_type = "BoundsCenter" 33 | parent_bone = "" 34 | import_filter = 35 | { 36 | exclude_by_default = false 37 | exception_list = [ ] 38 | } 39 | }, 40 | ] 41 | }, 42 | { 43 | _class = "PhysicsShapeList" 44 | children = 45 | [ 46 | { 47 | _class = "PhysicsHullFromRender" 48 | parent_bone = "" 49 | surface_prop = "default" 50 | collision_group = "" 51 | collision_interact_as = "" 52 | collision_interact_with = "" 53 | collision_interact_exclude = "" 54 | faceMergeAngle = 5.0 55 | maxHullVertices = 53 56 | }, 57 | ] 58 | }, 59 | { 60 | _class = "AnimationList" 61 | children = 62 | [ 63 | { 64 | _class = "AnimFile" 65 | name = "HandRight" 66 | activity_name = "" 67 | activity_weight = 1 68 | weight_list_name = "" 69 | fade_in_time = 0.2 70 | fade_out_time = 0.2 71 | looping = false 72 | delta = false 73 | worldSpace = false 74 | hidden = false 75 | anim_markup_ordered = false 76 | disable_compression = false 77 | source_filename = "models/hands/HandRight.fbx" 78 | start_frame = -1 79 | end_frame = -1 80 | framerate = -1.0 81 | take = 0 82 | reverse = false 83 | }, 84 | ] 85 | default_root_bone_name = "" 86 | }, 87 | { 88 | _class = "ModelDataList" 89 | children = 90 | [ 91 | { 92 | _class = "Bounds Hull" 93 | mins = [ -10.0, -4.0, -7.0 ] 94 | maxs = [ 2.0, 4.0, 5.0 ] 95 | }, 96 | { 97 | _class = "Bounds View" 98 | mins = [ -7.432799, -2.736166, -6.17193 ] 99 | maxs = [ 1.0, 3.972052, 5.079096 ] 100 | }, 101 | ] 102 | }, 103 | { 104 | _class = "WeightListList" 105 | children = 106 | [ 107 | { 108 | _class = "WeightList" 109 | name = "Index" 110 | default_weight = 0.0 111 | weights = 112 | [ 113 | { 114 | bone = "finger_index_meta_R" 115 | weight = 1.0 116 | }, 117 | ] 118 | master_morph_weight = -1.0 119 | morph_weights = [ ] 120 | }, 121 | { 122 | _class = "WeightList" 123 | name = "Middle" 124 | default_weight = 0.0 125 | weights = 126 | [ 127 | { 128 | bone = "finger_index_meta_R" 129 | weight = 0.0 130 | }, 131 | { 132 | bone = "finger_middle_meta_R" 133 | weight = 1.0 134 | }, 135 | ] 136 | master_morph_weight = -1.0 137 | morph_weights = [ ] 138 | }, 139 | { 140 | _class = "WeightList" 141 | name = "Ring" 142 | default_weight = 0.0 143 | weights = 144 | [ 145 | { 146 | bone = "finger_index_meta_R" 147 | weight = 0.0 148 | }, 149 | { 150 | bone = "finger_ring_meta_R" 151 | weight = 1.0 152 | }, 153 | ] 154 | master_morph_weight = -1.0 155 | morph_weights = [ ] 156 | }, 157 | { 158 | _class = "WeightList" 159 | name = "Thumb" 160 | default_weight = 0.0 161 | weights = 162 | [ 163 | { 164 | bone = "finger_index_meta_R" 165 | weight = 0.0 166 | }, 167 | { 168 | bone = "finger_thumb_0_R" 169 | weight = 1.0 170 | }, 171 | { 172 | bone = "hand_R" 173 | weight = 0.0 174 | }, 175 | ] 176 | master_morph_weight = -1.0 177 | morph_weights = [ ] 178 | }, 179 | { 180 | _class = "WeightList" 181 | name = "Base" 182 | default_weight = 0.0 183 | weights = 184 | [ 185 | { 186 | bone = "finger_index_meta_R" 187 | weight = 0.0 188 | }, 189 | { 190 | bone = "finger_middle_meta_R" 191 | weight = 0.0 192 | }, 193 | { 194 | bone = "finger_ring_meta_R" 195 | weight = 0.0 196 | }, 197 | { 198 | bone = "finger_thumb_0_R" 199 | weight = 0.0 200 | }, 201 | { 202 | bone = "hand_R" 203 | weight = 1.0 204 | }, 205 | ] 206 | master_morph_weight = -1.0 207 | morph_weights = [ ] 208 | }, 209 | ] 210 | }, 211 | { 212 | _class = "ModelModifierList" 213 | children = 214 | [ 215 | { 216 | _class = "ModelModifier_ScaleAndMirror" 217 | scale = 0.7 218 | mirror_x = false 219 | mirror_y = false 220 | mirror_z = false 221 | flip_bone_forward = false 222 | swap_left_and_right_bones = false 223 | }, 224 | ] 225 | }, 226 | ] 227 | model_archetype = "" 228 | primary_associated_entity = "" 229 | anim_graph_name = "animgraphs/hands/handright.vanmgrph" 230 | base_model_name = "" 231 | } 232 | } -------------------------------------------------------------------------------- /animgraphs/hands/handright.vanmgrph: -------------------------------------------------------------------------------- 1 | 2 | { 3 | _class = "CAnimationGraph" 4 | m_pParameterList = 5 | { 6 | _class = "CAnimParameterList" 7 | m_Parameters = 8 | [ 9 | { 10 | _class = "CBoolAnimParameter" 11 | m_name = "Grabbing" 12 | m_id = 13 | { 14 | m_id = 1548504135 15 | } 16 | m_previewButton = "ANIMPARAM_BUTTON_NONE" 17 | m_bNetwork = false 18 | m_bUseMostRecentValue = false 19 | m_bAutoReset = false 20 | m_bPredicted = false 21 | m_bDefaultValue = false 22 | }, 23 | { 24 | _class = "CFloatAnimParameter" 25 | m_name = "Index" 26 | m_id = 27 | { 28 | m_id = 640280030 29 | } 30 | m_previewButton = "ANIMPARAM_BUTTON_NONE" 31 | m_bNetwork = true 32 | m_bUseMostRecentValue = false 33 | m_bAutoReset = false 34 | m_bPredicted = false 35 | m_fDefaultValue = 0.0 36 | m_fMinValue = 0.0 37 | m_fMaxValue = 1.0 38 | m_bInterpolate = false 39 | }, 40 | { 41 | _class = "CFloatAnimParameter" 42 | m_name = "Middle" 43 | m_id = 44 | { 45 | m_id = 1812020517 46 | } 47 | m_previewButton = "ANIMPARAM_BUTTON_NONE" 48 | m_bNetwork = true 49 | m_bUseMostRecentValue = false 50 | m_bAutoReset = false 51 | m_bPredicted = false 52 | m_fDefaultValue = 0.0 53 | m_fMinValue = 0.0 54 | m_fMaxValue = 1.0 55 | m_bInterpolate = false 56 | }, 57 | { 58 | _class = "CFloatAnimParameter" 59 | m_name = "Ring" 60 | m_id = 61 | { 62 | m_id = 1168202039 63 | } 64 | m_previewButton = "ANIMPARAM_BUTTON_NONE" 65 | m_bNetwork = true 66 | m_bUseMostRecentValue = false 67 | m_bAutoReset = false 68 | m_bPredicted = false 69 | m_fDefaultValue = 0.0 70 | m_fMinValue = 0.0 71 | m_fMaxValue = 1.0 72 | m_bInterpolate = false 73 | }, 74 | { 75 | _class = "CFloatAnimParameter" 76 | m_name = "Thumb" 77 | m_id = 78 | { 79 | m_id = 1575258000 80 | } 81 | m_previewButton = "ANIMPARAM_BUTTON_NONE" 82 | m_bNetwork = true 83 | m_bUseMostRecentValue = false 84 | m_bAutoReset = false 85 | m_bPredicted = false 86 | m_fDefaultValue = 0.0 87 | m_fMinValue = 0.0 88 | m_fMaxValue = 1.0 89 | m_bInterpolate = false 90 | }, 91 | ] 92 | } 93 | m_pTagManager = 94 | { 95 | _class = "CAnimTagManager" 96 | m_tags = [ ] 97 | } 98 | m_pMovementManager = 99 | { 100 | _class = "CAnimMovementManager" 101 | m_MotorList = 102 | { 103 | _class = "CAnimMotorList" 104 | m_motors = [ ] 105 | } 106 | m_MovementSettings = 107 | { 108 | _class = "CAnimMovementSettings" 109 | m_bShouldCalculateSlope = false 110 | } 111 | } 112 | m_pSettingsManager = 113 | { 114 | _class = "CAnimGraphSettingsManager" 115 | m_settingsGroups = 116 | [ 117 | { 118 | _class = "CAnimGraphGeneralSettings" 119 | m_iGridSnap = 16 120 | }, 121 | { 122 | _class = "CAnimGraphNetworkSettings" 123 | m_bNetworkingEnabled = true 124 | }, 125 | ] 126 | } 127 | m_pActivityValuesList = 128 | { 129 | _class = "CActivityValueList" 130 | m_activities = [ ] 131 | } 132 | m_rootNodeID = 133 | { 134 | m_id = 333965117 135 | } 136 | m_previewModels = 137 | [ 138 | "models/hands/handleft.vmdl", 139 | ] 140 | m_nodes = 141 | [ 142 | { 143 | key = 144 | { 145 | m_id = 79048108 146 | } 147 | value = 148 | { 149 | _class = "CBoneMaskAnimNode" 150 | m_sName = "MaskMiddleIn" 151 | m_vecPosition = [ -493.0, -150.0 ] 152 | m_nNodeID = 153 | { 154 | m_id = 79048108 155 | } 156 | m_networkMode = "ClientPredicted" 157 | m_sNote = "" 158 | m_weightListName = "Middle" 159 | m_child1ID = 160 | { 161 | m_id = 272882607 162 | } 163 | m_child2ID = 164 | { 165 | m_id = 276102712 166 | } 167 | m_blendSpace = "BlendSpace_Parent" 168 | m_bUseBlendScale = false 169 | m_blendValueSource = "Parameter" 170 | m_blendParameter = 171 | { 172 | m_id = 4294967295 173 | } 174 | m_timingBehavior = "UseChild2" 175 | m_flTimingBlend = 0.5 176 | m_flRootMotionBlend = 1.0 177 | m_footMotionTiming = "Child1" 178 | m_bResetChild1 = true 179 | m_bResetChild2 = true 180 | } 181 | }, 182 | { 183 | key = 184 | { 185 | m_id = 194746901 186 | } 187 | value = 188 | { 189 | _class = "CBoneMaskAnimNode" 190 | m_sName = "MaskRingIn" 191 | m_vecPosition = [ -282.0, -29.0 ] 192 | m_nNodeID = 193 | { 194 | m_id = 194746901 195 | } 196 | m_networkMode = "ClientPredicted" 197 | m_sNote = "" 198 | m_weightListName = "Ring" 199 | m_child1ID = 200 | { 201 | m_id = 79048108 202 | } 203 | m_child2ID = 204 | { 205 | m_id = 1735913125 206 | } 207 | m_blendSpace = "BlendSpace_Parent" 208 | m_bUseBlendScale = false 209 | m_blendValueSource = "Parameter" 210 | m_blendParameter = 211 | { 212 | m_id = 4294967295 213 | } 214 | m_timingBehavior = "UseChild2" 215 | m_flTimingBlend = 0.5 216 | m_flRootMotionBlend = 1.0 217 | m_footMotionTiming = "Child1" 218 | m_bResetChild1 = true 219 | m_bResetChild2 = true 220 | } 221 | }, 222 | { 223 | key = 224 | { 225 | m_id = 272882607 226 | } 227 | value = 228 | { 229 | _class = "CBoneMaskAnimNode" 230 | m_sName = "MaskIndexIn" 231 | m_vecPosition = [ -711.0, -242.0 ] 232 | m_nNodeID = 233 | { 234 | m_id = 272882607 235 | } 236 | m_networkMode = "ClientPredicted" 237 | m_sNote = "" 238 | m_weightListName = "Index" 239 | m_child1ID = 240 | { 241 | m_id = 2020438561 242 | } 243 | m_child2ID = 244 | { 245 | m_id = 1682182037 246 | } 247 | m_blendSpace = "BlendSpace_Parent" 248 | m_bUseBlendScale = false 249 | m_blendValueSource = "Parameter" 250 | m_blendParameter = 251 | { 252 | m_id = 4294967295 253 | } 254 | m_timingBehavior = "UseChild2" 255 | m_flTimingBlend = 0.5 256 | m_flRootMotionBlend = 1.0 257 | m_footMotionTiming = "Child1" 258 | m_bResetChild1 = true 259 | m_bResetChild2 = true 260 | } 261 | }, 262 | { 263 | key = 264 | { 265 | m_id = 276102712 266 | } 267 | value = 268 | { 269 | _class = "CBlendAnimNode" 270 | m_sName = "BlendMiddle" 271 | m_vecPosition = [ -717.0, -133.0 ] 272 | m_nNodeID = 273 | { 274 | m_id = 276102712 275 | } 276 | m_networkMode = "ClientPredicted" 277 | m_sNote = "" 278 | m_children = 279 | [ 280 | { 281 | m_nodeID = 282 | { 283 | m_id = 640991500 284 | } 285 | m_name = "" 286 | m_blendValue = 0.0 287 | }, 288 | { 289 | m_nodeID = 290 | { 291 | m_id = 763054373 292 | } 293 | m_name = "" 294 | m_blendValue = 1.0 295 | }, 296 | ] 297 | m_blendValueSource = "Parameter" 298 | m_param = 299 | { 300 | m_id = 1812020517 301 | } 302 | m_blendKeyType = "BlendKey_UserValue" 303 | m_bLockBlendOnReset = false 304 | m_bSyncCycles = true 305 | m_bLoop = true 306 | m_bLockWhenWaning = true 307 | m_damping = 308 | { 309 | _class = "CAnimInputDamping" 310 | m_speedFunction = "NoDamping" 311 | m_fSpeedScale = 1.0 312 | m_fMinSpeed = 10.0 313 | m_fMaxTension = 1000.0 314 | } 315 | } 316 | }, 317 | { 318 | key = 319 | { 320 | m_id = 333965117 321 | } 322 | value = 323 | { 324 | _class = "CRootAnimNode" 325 | m_sName = "Unnamed" 326 | m_vecPosition = [ 155.0, 104.0 ] 327 | m_nNodeID = 328 | { 329 | m_id = 333965117 330 | } 331 | m_networkMode = "ClientPredicted" 332 | m_sNote = "" 333 | m_childID = 334 | { 335 | m_id = 571264672 336 | } 337 | } 338 | }, 339 | { 340 | key = 341 | { 342 | m_id = 571264672 343 | } 344 | value = 345 | { 346 | _class = "CBoneMaskAnimNode" 347 | m_sName = "MaskThumbIn" 348 | m_vecPosition = [ -78.0, 102.0 ] 349 | m_nNodeID = 350 | { 351 | m_id = 571264672 352 | } 353 | m_networkMode = "ClientPredicted" 354 | m_sNote = "" 355 | m_weightListName = "Thumb" 356 | m_child1ID = 357 | { 358 | m_id = 194746901 359 | } 360 | m_child2ID = 361 | { 362 | m_id = 876805543 363 | } 364 | m_blendSpace = "BlendSpace_Parent" 365 | m_bUseBlendScale = false 366 | m_blendValueSource = "Parameter" 367 | m_blendParameter = 368 | { 369 | m_id = 4294967295 370 | } 371 | m_timingBehavior = "UseChild2" 372 | m_flTimingBlend = 0.5 373 | m_flRootMotionBlend = 1.0 374 | m_footMotionTiming = "Child1" 375 | m_bResetChild1 = true 376 | m_bResetChild2 = true 377 | } 378 | }, 379 | { 380 | key = 381 | { 382 | m_id = 640991500 383 | } 384 | value = 385 | { 386 | _class = "CSingleFrameAnimNode" 387 | m_sName = "Unnamed" 388 | m_vecPosition = [ -916.0, -136.0 ] 389 | m_nNodeID = 390 | { 391 | m_id = 640991500 392 | } 393 | m_networkMode = "ClientPredicted" 394 | m_sNote = "" 395 | m_sequenceName = "HandRight" 396 | m_nFrameIndex = 0 397 | } 398 | }, 399 | { 400 | key = 401 | { 402 | m_id = 763054373 403 | } 404 | value = 405 | { 406 | _class = "CSingleFrameAnimNode" 407 | m_sName = "Unnamed" 408 | m_vecPosition = [ -916.0, -74.0 ] 409 | m_nNodeID = 410 | { 411 | m_id = 763054373 412 | } 413 | m_networkMode = "ClientPredicted" 414 | m_sNote = "" 415 | m_sequenceName = "HandRight" 416 | m_nFrameIndex = 20 417 | } 418 | }, 419 | { 420 | key = 421 | { 422 | m_id = 817800449 423 | } 424 | value = 425 | { 426 | _class = "CSingleFrameAnimNode" 427 | m_sName = "Unnamed" 428 | m_vecPosition = [ -491.0, 177.0 ] 429 | m_nNodeID = 430 | { 431 | m_id = 817800449 432 | } 433 | m_networkMode = "ClientPredicted" 434 | m_sNote = "" 435 | m_sequenceName = "HandRight" 436 | m_nFrameIndex = 20 437 | } 438 | }, 439 | { 440 | key = 441 | { 442 | m_id = 824680803 443 | } 444 | value = 445 | { 446 | _class = "CSingleFrameAnimNode" 447 | m_sName = "Unnamed" 448 | m_vecPosition = [ -717.0, -13.0 ] 449 | m_nNodeID = 450 | { 451 | m_id = 824680803 452 | } 453 | m_networkMode = "ClientPredicted" 454 | m_sNote = "" 455 | m_sequenceName = "HandRight" 456 | m_nFrameIndex = 0 457 | } 458 | }, 459 | { 460 | key = 461 | { 462 | m_id = 876805543 463 | } 464 | value = 465 | { 466 | _class = "CBlendAnimNode" 467 | m_sName = "BlendThumb" 468 | m_vecPosition = [ -279.0, 116.0 ] 469 | m_nNodeID = 470 | { 471 | m_id = 876805543 472 | } 473 | m_networkMode = "ClientPredicted" 474 | m_sNote = "" 475 | m_children = 476 | [ 477 | { 478 | m_nodeID = 479 | { 480 | m_id = 1627551740 481 | } 482 | m_name = "" 483 | m_blendValue = 0.0 484 | }, 485 | { 486 | m_nodeID = 487 | { 488 | m_id = 817800449 489 | } 490 | m_name = "" 491 | m_blendValue = 1.0 492 | }, 493 | ] 494 | m_blendValueSource = "Parameter" 495 | m_param = 496 | { 497 | m_id = 1575258000 498 | } 499 | m_blendKeyType = "BlendKey_UserValue" 500 | m_bLockBlendOnReset = false 501 | m_bSyncCycles = true 502 | m_bLoop = true 503 | m_bLockWhenWaning = true 504 | m_damping = 505 | { 506 | _class = "CAnimInputDamping" 507 | m_speedFunction = "NoDamping" 508 | m_fSpeedScale = 1.0 509 | m_fMinSpeed = 10.0 510 | m_fMaxTension = 1000.0 511 | } 512 | } 513 | }, 514 | { 515 | key = 516 | { 517 | m_id = 1373944808 518 | } 519 | value = 520 | { 521 | _class = "CSingleFrameAnimNode" 522 | m_sName = "Unnamed" 523 | m_vecPosition = [ -715.0, 49.0 ] 524 | m_nNodeID = 525 | { 526 | m_id = 1373944808 527 | } 528 | m_networkMode = "ClientPredicted" 529 | m_sNote = "" 530 | m_sequenceName = "HandRight" 531 | m_nFrameIndex = 20 532 | } 533 | }, 534 | { 535 | key = 536 | { 537 | m_id = 1499922691 538 | } 539 | value = 540 | { 541 | _class = "CSingleFrameAnimNode" 542 | m_sName = "Unnamed" 543 | m_vecPosition = [ -1113.0, -167.0 ] 544 | m_nNodeID = 545 | { 546 | m_id = 1499922691 547 | } 548 | m_networkMode = "ClientPredicted" 549 | m_sNote = "" 550 | m_sequenceName = "HandRight" 551 | m_nFrameIndex = 20 552 | } 553 | }, 554 | { 555 | key = 556 | { 557 | m_id = 1627551740 558 | } 559 | value = 560 | { 561 | _class = "CSingleFrameAnimNode" 562 | m_sName = "Unnamed" 563 | m_vecPosition = [ -493.0, 115.0 ] 564 | m_nNodeID = 565 | { 566 | m_id = 1627551740 567 | } 568 | m_networkMode = "ClientPredicted" 569 | m_sNote = "" 570 | m_sequenceName = "HandRight" 571 | m_nFrameIndex = 0 572 | } 573 | }, 574 | { 575 | key = 576 | { 577 | m_id = 1682182037 578 | } 579 | value = 580 | { 581 | _class = "CBlendAnimNode" 582 | m_sName = "BlendIndex" 583 | m_vecPosition = [ -916.0, -228.0 ] 584 | m_nNodeID = 585 | { 586 | m_id = 1682182037 587 | } 588 | m_networkMode = "ClientPredicted" 589 | m_sNote = "" 590 | m_children = 591 | [ 592 | { 593 | m_nodeID = 594 | { 595 | m_id = 1972884863 596 | } 597 | m_name = "" 598 | m_blendValue = 0.0 599 | }, 600 | { 601 | m_nodeID = 602 | { 603 | m_id = 1499922691 604 | } 605 | m_name = "" 606 | m_blendValue = 1.0 607 | }, 608 | ] 609 | m_blendValueSource = "Parameter" 610 | m_param = 611 | { 612 | m_id = 640280030 613 | } 614 | m_blendKeyType = "BlendKey_UserValue" 615 | m_bLockBlendOnReset = false 616 | m_bSyncCycles = true 617 | m_bLoop = true 618 | m_bLockWhenWaning = true 619 | m_damping = 620 | { 621 | _class = "CAnimInputDamping" 622 | m_speedFunction = "NoDamping" 623 | m_fSpeedScale = 1.0 624 | m_fMinSpeed = 10.0 625 | m_fMaxTension = 1000.0 626 | } 627 | } 628 | }, 629 | { 630 | key = 631 | { 632 | m_id = 1735913125 633 | } 634 | value = 635 | { 636 | _class = "CBlendAnimNode" 637 | m_sName = "BlendRing" 638 | m_vecPosition = [ -491.0, -12.0 ] 639 | m_nNodeID = 640 | { 641 | m_id = 1735913125 642 | } 643 | m_networkMode = "ClientPredicted" 644 | m_sNote = "" 645 | m_children = 646 | [ 647 | { 648 | m_nodeID = 649 | { 650 | m_id = 824680803 651 | } 652 | m_name = "" 653 | m_blendValue = 0.0 654 | }, 655 | { 656 | m_nodeID = 657 | { 658 | m_id = 1373944808 659 | } 660 | m_name = "" 661 | m_blendValue = 1.0 662 | }, 663 | ] 664 | m_blendValueSource = "Parameter" 665 | m_param = 666 | { 667 | m_id = 1168202039 668 | } 669 | m_blendKeyType = "BlendKey_UserValue" 670 | m_bLockBlendOnReset = false 671 | m_bSyncCycles = true 672 | m_bLoop = true 673 | m_bLockWhenWaning = true 674 | m_damping = 675 | { 676 | _class = "CAnimInputDamping" 677 | m_speedFunction = "NoDamping" 678 | m_fSpeedScale = 1.0 679 | m_fMinSpeed = 10.0 680 | m_fMaxTension = 1000.0 681 | } 682 | } 683 | }, 684 | { 685 | key = 686 | { 687 | m_id = 1972884863 688 | } 689 | value = 690 | { 691 | _class = "CSingleFrameAnimNode" 692 | m_sName = "Unnamed" 693 | m_vecPosition = [ -1111.0, -229.0 ] 694 | m_nNodeID = 695 | { 696 | m_id = 1972884863 697 | } 698 | m_networkMode = "ClientPredicted" 699 | m_sNote = "" 700 | m_sequenceName = "HandRight" 701 | m_nFrameIndex = 0 702 | } 703 | }, 704 | { 705 | key = 706 | { 707 | m_id = 2020438561 708 | } 709 | value = 710 | { 711 | _class = "CSingleFrameAnimNode" 712 | m_sName = "OpenHand" 713 | m_vecPosition = [ -914.0, -350.0 ] 714 | m_nNodeID = 715 | { 716 | m_id = 2020438561 717 | } 718 | m_networkMode = "ClientPredicted" 719 | m_sNote = "" 720 | m_sequenceName = "HandRight" 721 | m_nFrameIndex = 0 722 | } 723 | }, 724 | ] 725 | } -------------------------------------------------------------------------------- /code/Player/WalkController.cs: -------------------------------------------------------------------------------- 1 | using Sandbox; 2 | 3 | namespace VrExample; 4 | 5 | [Library] 6 | public class WalkController : BasePlayerController 7 | { 8 | public float Speed => 100.0f; 9 | public float Acceleration => 10.0f; 10 | public float AirAcceleration => 50.0f; 11 | public float FallSoundZ => -30.0f; 12 | public float GroundFriction => 4.0f; 13 | public float StopSpeed => 100.0f; 14 | public float Size => 20.0f; 15 | public float DistEpsilon => 0.03125f; 16 | public float GroundAngle => 46.0f; 17 | public float Bounce => 0.0f; 18 | public float MoveFriction => 1.0f; 19 | public float StepSize => 18.0f; 20 | public float MaxNonJumpVelocity => 140.0f; 21 | public float BodyGirth => 6.0f; 22 | public float BodyHeight => 72.0f; 23 | public float EyeHeight => 64.0f; 24 | public float Gravity => 800.0f; 25 | public float AirControl => 30.0f; 26 | 27 | public bool Swimming { get; set; } = false; 28 | public bool AutoJump { get; set; } = false; 29 | 30 | public Duck Duck; 31 | public Unstuck Unstuck; 32 | 33 | 34 | public WalkController() 35 | { 36 | Duck = new Duck( this ); 37 | Unstuck = new Unstuck( this ); 38 | } 39 | 40 | /// 41 | /// This is temporary, get the hull size for the player's collision 42 | /// 43 | public override BBox GetHull() 44 | { 45 | var girth = BodyGirth * 0.5f; 46 | var mins = new Vector3( -girth, -girth, 0 ); 47 | var maxs = new Vector3( +girth, +girth, BodyHeight ); 48 | 49 | return new BBox( mins, maxs ); 50 | } 51 | 52 | 53 | // Duck body height 32 54 | // Eye Height 64 55 | // Duck Eye Height 28 56 | 57 | protected Vector3 mins; 58 | protected Vector3 maxs; 59 | 60 | public virtual void SetBBox( Vector3 mins, Vector3 maxs ) 61 | { 62 | if ( this.mins == mins && this.maxs == maxs ) 63 | return; 64 | 65 | this.mins = mins; 66 | this.maxs = maxs; 67 | } 68 | 69 | /// 70 | /// Update the size of the bbox. We should really trigger some shit if this changes. 71 | /// 72 | public virtual void UpdateBBox() 73 | { 74 | Transform headLocal = Pawn.Transform.ToLocal( Input.VR.Head ); 75 | var girth = BodyGirth * 0.5f; 76 | 77 | var mins = (new Vector3( -girth, -girth, 0 ) + headLocal.Position.WithZ( 0 ) * Rotation) * Pawn.Scale; 78 | var maxs = (new Vector3( +girth, +girth, BodyHeight ) + headLocal.Position.WithZ( 0 ) * Rotation) * 79 | Pawn.Scale; 80 | 81 | Duck.UpdateBBox( ref mins, ref maxs, Pawn.Scale ); 82 | SetBBox( mins, maxs ); 83 | } 84 | 85 | protected float SurfaceFriction; 86 | 87 | public override void FrameSimulate() 88 | { 89 | base.FrameSimulate(); 90 | 91 | EyeRotation = Input.VR.Head.Rotation; 92 | } 93 | 94 | public override void Simulate() 95 | { 96 | EyeLocalPosition = Vector3.Up * (EyeHeight * Pawn.Scale); 97 | UpdateBBox(); 98 | 99 | EyeLocalPosition += TraceOffset; 100 | EyeRotation = Input.VR.Head.Rotation; 101 | 102 | RestoreGroundPos(); 103 | 104 | if ( Unstuck.TestAndFix() ) 105 | return; 106 | 107 | // RunLadderMode 108 | 109 | CheckLadder(); 110 | Swimming = Pawn.WaterLevel > 0.6f; 111 | 112 | // 113 | // Start Gravity 114 | // 115 | if ( !Swimming && !IsTouchingLadder ) 116 | { 117 | Velocity -= new Vector3( 0, 0, Gravity * 0.5f ) * Time.Delta; 118 | Velocity += new Vector3( 0, 0, BaseVelocity.z ) * Time.Delta; 119 | 120 | BaseVelocity = BaseVelocity.WithZ( 0 ); 121 | } 122 | 123 | if ( Input.VR.RightHand.JoystickPress.IsPressed ) 124 | { 125 | CheckJumpButton(); 126 | } 127 | 128 | // Fricion is handled before we add in any base velocity. That way, if we are on a conveyor, 129 | // we don't slow when standing still, relative to the conveyor. 130 | bool bStartOnGround = GroundEntity != null; 131 | 132 | if ( bStartOnGround ) 133 | { 134 | Velocity = Velocity.WithZ( 0 ); 135 | 136 | if ( GroundEntity != null ) 137 | { 138 | ApplyFriction( GroundFriction * SurfaceFriction ); 139 | } 140 | } 141 | 142 | // 143 | // Work out wish velocity.. just take input, rotate it to view, clamp to -1, 1 144 | // 145 | WishVelocity = Vector3.Zero; 146 | WishVelocity += Input.VR.LeftHand.Joystick.Value.y * Input.VR.Head.Rotation.Forward; 147 | WishVelocity += Input.VR.LeftHand.Joystick.Value.x * Input.VR.Head.Rotation.Right; 148 | 149 | var inSpeed = WishVelocity.Length.Clamp( 0, 1 ); 150 | 151 | if ( !Swimming && !IsTouchingLadder ) 152 | { 153 | WishVelocity = WishVelocity.WithZ( 0 ); 154 | } 155 | 156 | WishVelocity = WishVelocity.Normal * inSpeed; 157 | WishVelocity *= GetWishSpeed(); 158 | 159 | Duck.PreTick(); 160 | 161 | bool bStayOnGround = false; 162 | if ( Swimming ) 163 | { 164 | ApplyFriction( 1 ); 165 | WaterMove(); 166 | } 167 | else if ( IsTouchingLadder ) 168 | { 169 | LadderMove(); 170 | } 171 | else if ( GroundEntity != null ) 172 | { 173 | bStayOnGround = true; 174 | WalkMove(); 175 | } 176 | else 177 | { 178 | AirMove(); 179 | } 180 | 181 | CategorizePosition( bStayOnGround ); 182 | 183 | if ( !Swimming && !IsTouchingLadder ) 184 | Velocity -= new Vector3( 0, 0, Gravity * 0.5f ) * Time.Delta; 185 | 186 | if ( GroundEntity != null ) 187 | Velocity = Velocity.WithZ( 0 ); 188 | 189 | SaveGroundPos(); 190 | 191 | if ( Debug ) 192 | { 193 | DebugOverlay.Box( Position + TraceOffset, mins, maxs, Color.Red ); 194 | DebugOverlay.Box( Position, mins, maxs, Color.Blue ); 195 | 196 | var lineOffset = 0; 197 | if ( Host.IsServer ) lineOffset = 10; 198 | 199 | DebugOverlay.ScreenText( $" Position: {Position}", lineOffset + 0 ); 200 | DebugOverlay.ScreenText( $" Velocity: {Velocity}", lineOffset + 1 ); 201 | DebugOverlay.ScreenText( $" BaseVelocity: {BaseVelocity}", lineOffset + 2 ); 202 | DebugOverlay.ScreenText( $" GroundEntity: {GroundEntity} [{GroundEntity?.Velocity}]", lineOffset + 3 ); 203 | DebugOverlay.ScreenText( $" SurfaceFriction: {SurfaceFriction}", lineOffset + 4 ); 204 | DebugOverlay.ScreenText( $" WishVelocity: {WishVelocity}", lineOffset + 5 ); 205 | } 206 | } 207 | 208 | public virtual float GetWishSpeed() 209 | { 210 | var ws = Duck.GetWishSpeed(); 211 | if ( ws >= 0 ) return ws; 212 | 213 | return Speed; 214 | } 215 | 216 | public virtual void WalkMove() 217 | { 218 | var wishdir = WishVelocity.Normal; 219 | var wishspeed = WishVelocity.Length; 220 | 221 | WishVelocity = WishVelocity.WithZ( 0 ); 222 | WishVelocity = WishVelocity.Normal * wishspeed; 223 | 224 | Velocity = Velocity.WithZ( 0 ); 225 | Accelerate( wishdir, wishspeed, 0, Acceleration ); 226 | Velocity = Velocity.WithZ( 0 ); 227 | 228 | // Add in any base velocity to the current velocity. 229 | Velocity += BaseVelocity; 230 | 231 | try 232 | { 233 | if ( Velocity.Length < 1.0f ) 234 | { 235 | Velocity = Vector3.Zero; 236 | return; 237 | } 238 | 239 | // first try just moving to the destination 240 | var dest = (Position + Velocity * Time.Delta).WithZ( Position.z ); 241 | 242 | var pm = TraceBBox( Position, dest ); 243 | 244 | if ( pm.Fraction == 1 ) 245 | { 246 | Position = pm.EndPosition; 247 | StayOnGround(); 248 | return; 249 | } 250 | 251 | StepMove(); 252 | } 253 | finally 254 | { 255 | // Now pull the base velocity back out. Base velocity is set if you are on a moving object, like a conveyor (or maybe another monster?) 256 | Velocity -= BaseVelocity; 257 | } 258 | 259 | StayOnGround(); 260 | } 261 | 262 | public virtual void StepMove() 263 | { 264 | MoveHelper mover = new MoveHelper( Position, Velocity ); 265 | mover.Trace = mover.Trace.Size( mins, maxs ).Ignore( Pawn ); 266 | mover.MaxStandableAngle = GroundAngle; 267 | 268 | mover.TryMoveWithStep( Time.Delta, StepSize ); 269 | 270 | Position = mover.Position; 271 | Velocity = mover.Velocity; 272 | } 273 | 274 | public virtual void Move() 275 | { 276 | MoveHelper mover = new MoveHelper( Position, Velocity ); 277 | mover.Trace = mover.Trace.Size( mins, maxs ).Ignore( Pawn ); 278 | mover.MaxStandableAngle = GroundAngle; 279 | 280 | mover.TryMove( Time.Delta ); 281 | 282 | Position = mover.Position; 283 | Velocity = mover.Velocity; 284 | } 285 | 286 | /// 287 | /// Add our wish direction and speed onto our velocity 288 | /// 289 | public virtual void Accelerate( Vector3 wishdir, float wishspeed, float speedLimit, float acceleration ) 290 | { 291 | // This gets overridden because some games (CSPort) want to allow dead (observer) players 292 | // to be able to move around. 293 | 294 | if ( speedLimit > 0 && wishspeed > speedLimit ) 295 | wishspeed = speedLimit; 296 | 297 | // See if we are changing direction a bit 298 | var currentspeed = Velocity.Dot( wishdir ); 299 | 300 | // Reduce wishspeed by the amount of veer. 301 | var addspeed = wishspeed - currentspeed; 302 | 303 | // If not going to add any speed, done. 304 | if ( addspeed <= 0 ) 305 | return; 306 | 307 | // Determine amount of acceleration. 308 | var accelspeed = acceleration * Time.Delta * wishspeed * SurfaceFriction; 309 | 310 | // Cap at addspeed 311 | if ( accelspeed > addspeed ) 312 | accelspeed = addspeed; 313 | 314 | Velocity += wishdir * accelspeed; 315 | } 316 | 317 | /// 318 | /// Remove ground friction from velocity 319 | /// 320 | public virtual void ApplyFriction( float frictionAmount = 1.0f ) 321 | { 322 | // Calculate speed 323 | var speed = Velocity.Length; 324 | if ( speed < 0.1f ) return; 325 | 326 | // Bleed off some speed, but if we have less than the bleed 327 | // threshold, bleed the threshold amount. 328 | float control = (speed < StopSpeed) ? StopSpeed : speed; 329 | 330 | // Add the amount to the drop amount. 331 | var drop = control * Time.Delta * frictionAmount; 332 | 333 | // scale the velocity 334 | float newspeed = speed - drop; 335 | if ( newspeed < 0 ) newspeed = 0; 336 | 337 | if ( newspeed != speed ) 338 | { 339 | newspeed /= speed; 340 | Velocity *= newspeed; 341 | } 342 | } 343 | 344 | public virtual void CheckJumpButton() 345 | { 346 | // If we are in the water most of the way... 347 | if ( Swimming ) 348 | { 349 | ClearGroundEntity(); 350 | 351 | Velocity = Velocity.WithZ( 100 ); 352 | return; 353 | } 354 | 355 | if ( GroundEntity == null ) 356 | return; 357 | 358 | ClearGroundEntity(); 359 | 360 | float flGroundFactor = 1.0f; 361 | float flMul = 268.3281572999747f * 1.2f; 362 | float startz = Velocity.z; 363 | 364 | if ( Duck.IsActive ) 365 | flMul *= 0.8f; 366 | 367 | Velocity = Velocity.WithZ( startz + flMul * flGroundFactor ); 368 | 369 | Velocity -= new Vector3( 0, 0, Gravity * 0.5f ) * Time.Delta; 370 | 371 | AddEvent( "jump" ); 372 | } 373 | 374 | public virtual void AirMove() 375 | { 376 | var wishdir = WishVelocity.Normal; 377 | var wishspeed = WishVelocity.Length; 378 | 379 | Accelerate( wishdir, wishspeed, AirControl, AirAcceleration ); 380 | 381 | Velocity += BaseVelocity; 382 | 383 | Move(); 384 | 385 | Velocity -= BaseVelocity; 386 | } 387 | 388 | public virtual void WaterMove() 389 | { 390 | var wishdir = WishVelocity.Normal; 391 | var wishspeed = WishVelocity.Length; 392 | 393 | wishspeed *= 0.8f; 394 | 395 | Accelerate( wishdir, wishspeed, 100, Acceleration ); 396 | 397 | Velocity += BaseVelocity; 398 | 399 | Move(); 400 | 401 | Velocity -= BaseVelocity; 402 | } 403 | 404 | bool IsTouchingLadder = false; 405 | Vector3 LadderNormal; 406 | 407 | public virtual void CheckLadder() 408 | { 409 | if ( IsTouchingLadder && Input.VR.RightHand.JoystickPress.IsPressed ) 410 | { 411 | Velocity = LadderNormal * 100.0f; 412 | IsTouchingLadder = false; 413 | 414 | return; 415 | } 416 | 417 | const float ladderDistance = 1.0f; 418 | var start = Position; 419 | Vector3 end = start + (IsTouchingLadder ? (LadderNormal * -1.0f) : WishVelocity.Normal) * ladderDistance; 420 | 421 | var pm = Trace.Ray( start, end ) 422 | .Size( mins, maxs ) 423 | .HitLayer( CollisionLayer.All, false ) 424 | .HitLayer( CollisionLayer.LADDER, true ) 425 | .Ignore( Pawn ) 426 | .Run(); 427 | 428 | IsTouchingLadder = false; 429 | 430 | if ( pm.Hit ) 431 | { 432 | IsTouchingLadder = true; 433 | LadderNormal = pm.Normal; 434 | } 435 | } 436 | 437 | public virtual void LadderMove() 438 | { 439 | var velocity = WishVelocity; 440 | float normalDot = velocity.Dot( LadderNormal ); 441 | var cross = LadderNormal * normalDot; 442 | Velocity = (velocity - cross) + 443 | (-normalDot * LadderNormal.Cross( Vector3.Up.Cross( LadderNormal ).Normal )); 444 | 445 | Move(); 446 | } 447 | 448 | public virtual void CategorizePosition( bool bStayOnGround ) 449 | { 450 | SurfaceFriction = 1.0f; 451 | 452 | // Doing this before we move may introduce a potential latency in water detection, but 453 | // doing it after can get us stuck on the bottom in water if the amount we move up 454 | // is less than the 1 pixel 'threshold' we're about to snap to. Also, we'll call 455 | // this several times per frame, so we really need to avoid sticking to the bottom of 456 | // water on each call, and the converse case will correct itself if called twice. 457 | //CheckWater(); 458 | 459 | var point = Position - Vector3.Up * 2; 460 | var vBumpOrigin = Position; 461 | 462 | // 463 | // Shooting up really fast. Definitely not on ground trimed until ladder shit 464 | // 465 | bool bMovingUpRapidly = Velocity.z > MaxNonJumpVelocity; 466 | bool bMovingUp = Velocity.z > 0; 467 | 468 | bool bMoveToEndPos = false; 469 | 470 | if ( GroundEntity != null ) // and not underwater 471 | { 472 | bMoveToEndPos = true; 473 | point.z -= StepSize; 474 | } 475 | else if ( bStayOnGround ) 476 | { 477 | bMoveToEndPos = true; 478 | point.z -= StepSize; 479 | } 480 | 481 | if ( bMovingUpRapidly || Swimming ) // or ladder and moving up 482 | { 483 | ClearGroundEntity(); 484 | return; 485 | } 486 | 487 | var pm = TraceBBox( vBumpOrigin, point, 4.0f ); 488 | 489 | if ( pm.Entity == null || Vector3.GetAngle( Vector3.Up, pm.Normal ) > GroundAngle ) 490 | { 491 | ClearGroundEntity(); 492 | bMoveToEndPos = false; 493 | 494 | if ( Velocity.z > 0 ) 495 | SurfaceFriction = 0.25f; 496 | } 497 | else 498 | { 499 | UpdateGroundEntity( pm ); 500 | } 501 | 502 | if ( bMoveToEndPos && !pm.StartedSolid && pm.Fraction > 0.0f && pm.Fraction < 1.0f ) 503 | { 504 | Position = pm.EndPosition; 505 | } 506 | } 507 | 508 | /// 509 | /// We have a new ground entity 510 | /// 511 | public virtual void UpdateGroundEntity( TraceResult tr ) 512 | { 513 | GroundNormal = tr.Normal; 514 | 515 | // VALVE HACKHACK: Scale this to fudge the relationship between vphysics friction values and player friction values. 516 | // A value of 0.8f feels pretty normal for vphysics, whereas 1.0f is normal for players. 517 | // This scaling trivially makes them equivalent. REVISIT if this affects low friction surfaces too much. 518 | SurfaceFriction = tr.Surface.Friction * 1.25f; 519 | if ( SurfaceFriction > 1 ) SurfaceFriction = 1; 520 | 521 | GroundEntity = tr.Entity; 522 | 523 | if ( GroundEntity != null ) 524 | { 525 | BaseVelocity = GroundEntity.Velocity; 526 | } 527 | } 528 | 529 | /// 530 | /// We're no longer on the ground, remove it 531 | /// 532 | public virtual void ClearGroundEntity() 533 | { 534 | if ( GroundEntity == null ) return; 535 | 536 | Pawn.GroundEntity = null; 537 | GroundEntity = null; 538 | GroundNormal = Vector3.Up; 539 | SurfaceFriction = 1.0f; 540 | } 541 | 542 | /// 543 | /// Traces the current bbox and returns the result. 544 | /// liftFeet will move the start position up by this amount, while keeping the top of the bbox at the same 545 | /// position. This is good when tracing down because you won't be tracing through the ceiling above. 546 | /// 547 | public override TraceResult TraceBBox( Vector3 start, Vector3 end, float liftFeet = 0.0f ) 548 | { 549 | return TraceBBox( start, end, mins, maxs, liftFeet ); 550 | } 551 | 552 | /// 553 | /// Try to keep a walking player on the ground when running down slopes etc 554 | /// 555 | public virtual void StayOnGround() 556 | { 557 | var start = Position + Vector3.Up * 2; 558 | var end = Position + Vector3.Down * StepSize; 559 | 560 | // See how far up we can go without getting stuck 561 | var trace = TraceBBox( Position, start ); 562 | start = trace.EndPosition; 563 | 564 | // Now trace down from a known safe position 565 | trace = TraceBBox( start, end ); 566 | 567 | if ( trace.Fraction <= 0 ) return; 568 | if ( trace.Fraction >= 1 ) return; 569 | if ( trace.StartedSolid ) return; 570 | if ( Vector3.GetAngle( Vector3.Up, trace.Normal ) > GroundAngle ) return; 571 | 572 | Position = trace.EndPosition; 573 | } 574 | 575 | void RestoreGroundPos() 576 | { 577 | if ( GroundEntity == null || GroundEntity.IsWorld ) 578 | return; 579 | } 580 | 581 | void SaveGroundPos() 582 | { 583 | if ( GroundEntity == null || GroundEntity.IsWorld ) 584 | return; 585 | } 586 | } 587 | --------------------------------------------------------------------------------