Vehicles (modeling)

From Valve Developer Community
Jump to navigation Jump to search
English (en)日本語 (ja)Translate (Translate)


This page describes how to set up a model for use as a functional vehicle, with basic animations, and optionally, gauge animations.

SMD prerequisites

Reference model

Your reference model at minimum needs the following bones in order to work:

  • suspension front left -> steering front left -> wheel front left
  • suspension front right -> steering front right -> wheel front right
  • suspension rear left -> wheel rear left
  • suspension rear right -> wheel rear right
  • view, located where the driver's view should be

Optionally:

  • steering wheel
  • gauge needle(s)

All of which should (probably) be children of a static root bone, and should follow the above hierarchy. You can name them anything, as long they're the uniquely named, and you edit the following code examples accordingly.

Your respective meshes need to be rigged to their respective bones.

Collision model

All vehicles need physical models, so you need to set up a collision model, with a weight value close to what it would be in real life. See $collisionmodel.

To get started, you can simply use a copy of the reference model of your vehicle, but make sure to remove the wheels from the model, as wheel collisions aren't defined in the collision model itself, but at runtime, based on the entire compiled model's wheel attachments. Note that doing this will give your model a convex collision model.

If you have the time for it, modelling a low-poly version of the vehicle specifically for use as a collision model can be useful, both for optimization, and because it can be concave. See Collision mesh.

Fundamental animations

Vehicles require a few animations with basics movements set up as $poseparameters in order to work properly and interact with suspension simulation, see Pose parameters on this page

  • An animation SMD with only the suspension bones animated, starting compressed, going to neutral, then going to fully extended. This can be 3 frames. This animation influences the suspension travel each wheel has in a vehicle's physics simulation.
  • An animation SMD with only the wheel bones individually spinning one full revolution. This can be 6 frames configured like in the QC code example on this page. It could probably use a linear animation curve in how the animation is exported, and if your QC code is changed to compensate you could probably use something like 3 frames as long as the animation is still configured for 360 degrees distributed across all frames
  • An animation SMD with the steering of the wheels, with the steering bones (and the steering wheel bone if it exists) first turning left, then centering, then turning right, animating only the steering bones and not their child wheel bones. This animation can be just 3 frames like with this page's main QC code example.

Be sure to note the exact angle left and right that the wheels turn and make it consistent. You can put that angle into the vehicle’s script file so that the physics match the animation more closely while steering.


You might want to export all of these animations with linear animation curves.


Note that with just 3 animation frames, if you animate a steering wheel's bone (or basically any bone), it can't turn further than 180 degrees one way without its rotation getting effectively wrongly inverted when exported to animations configured like in the main QC example below. See Making the steering wheel turn further than 180 degrees.

QC setup

Attachments

Wheels

You need to set up attachments for all four wheels, to define where they are located in the model.

$attachment "wheel_fl" "<wheel front left bone>" 0 0 8
$attachment "wheel_fr" "<wheel front right bone>" 0 0 -8
$attachment "wheel_rl" "<wheel rear left bone>" 0 0 8
$attachment "wheel_rr" "<wheel rear right bone>" 0 0 –8

You must name the attachments as above. You can however name their respective bones anything, and you can place the $attachment lines anywhere in your .QC.

Only four wheels can be simulated in one vehicle. Their attachments are named with two letter codes:

  • fl "front left"
  • fr "front right"
  • rl "rear left"
  • rr "rear right"

One important detail is that the wheels are simulated as spheres. For this reason, you might want to pull the attachments slightly inwards. The origin of the wheel bones is actually the center of the wheel graphically, but in order to avoid having the spherical wheel collisions poke out from the sides of the vehicle (thus brushing against things next to it), you might want to pull the attachments in by a bit.

For example, this would place the wheel sphere exactly over its graphical representation:

$attachment "wheel_fl" "<wheel front left bone>" 0 0 0

While this adjusts it inwards by 8 inches:

$attachment "wheel_fl" "<wheel front left bone>" 0 0 8

The amount to pull them in depends on the size of the wheels and the collision model of the vehicle. In this case, the wheels have a radius of about 22.5 inches and I’ve pulled them in by 8 inches. Using this ratio is a good starting point.

Driver

This line defines where your view is going to be when driving.

$attachment "vehicle_driver_eyes" "<view bone>" 0 0 0

As with the wheel attachments, this attachment must be named vehicle_driver_eyes.

This line defines where

Miscellaneous attachments

These are some miscellaneous attachments that might be required, with approximate positions that you might have to tweak.

$attachment "vehicle_feet_passenger0" "<view bone>" 0 0.00 -27.00
$attachment "vehicle_engine" "<root bone>" 40.00 0.00 17.00

The vehicle_feet_passenger0 attachment determines where the driver's third person player model is located.

Both attachments can use their own unique bones.

Pose parameters

You need the following $poseparameter lines to allow the physics to interact with your model.

This line is for the steering. –1 is left, +1 is right.

$poseparameter "vehicle_steer" -1 1

These lines are for the suspension states of each wheel. 0 is compressed, 1 is extended.

$poseparameter "vehicle_wheel_fl_height" 0 1
$poseparameter "vehicle_wheel_fr_height" 0 1
$poseparameter "vehicle_wheel_rl_height" 0 1
$poseparameter "vehicle_wheel_rr_height" 0 1

These lines are for the rotation of each wheel. +/- 180 degrees from base rotation.

$poseparameter "vehicle_wheel_fl_spin" -180 180 wrap
$poseparameter "vehicle_wheel_fr_spin" -180 180 wrap
$poseparameter "vehicle_wheel_rl_spin" -180 180 wrap
$poseparameter "vehicle_wheel_rr_spin" -180 180 wrap

All of the above should not be renamed.

Animations

Here’s an example .QC snippet that sets up animations for the pose parameters, including a steering wheel in the steering animation.

$sequence "idle" "<reference model SMD>"  activity ACT_IDLE -1 loop fps 30
$animation neutral "<reference model SMD>" frames 0 0

// steering
$weightlist front_wheels { "<steering front left bone>" 1.0 "<steering front right bone>" 1.0 "<steering wheel bone>" 1.0 }
$animation turn_left "<steering animation SMD>" frame 0 0 subtract neutral 0 weightlist front_wheels
$animation turn_middle "<steering animation SMD>" frame 1 1 subtract neutral 0 weightlist front_wheels
$animation turn_right "<steering animation SMD>" frame 2 2 subtract neutral 0 weightlist front_wheels
$sequence turning { turn_left turn_middle turn_right blend vehicle_steer -1 1 } weightlist front_wheels delta autoplay


// front left
$weightlist wheel_fl { "<steering front left bone>" 1.0  "<suspension front left bone>" 1.0 "<wheel front left bone>" 0.0 }
$animation wheel_fl_low "<suspension animation SMD>" frame 0 0 subtract neutral 0 weightlist wheel_fl 
$animation wheel_fl_high "<suspension animation SMD>" frame 2 2 subtract neutral 0 weightlist wheel_fl 
$sequence wheel_fl_suspension { wheel_fl_low wheel_fl_high blend "vehicle_wheel_fl_height" 0 1.0 } weightlist wheel_fl delta autoplay

$weightlist wheel_fl_spin { "<wheel front left bone>" 1.0 } 
$animation sp_fl_1 "<wheelspin animation SMD>" frame 0 0 subtract neutral 0 weightlist wheel_fl_spin 
$animation sp_fl_2 "<wheelspin animation SMD>" frame 1 1 subtract neutral 0 weightlist wheel_fl_spin 
$animation sp_fl_3 "<wheelspin animation SMD>" frame 2 2 subtract neutral 0 weightlist wheel_fl_spin 
$animation sp_fl_4 "<wheelspin animation SMD>" frame 3 3 subtract neutral 0 weightlist wheel_fl_spin 
$animation sp_fl_5 "<wheelspin animation SMD>" frame 4 4 subtract neutral 0 weightlist wheel_fl_spin 
$animation sp_fl_6 "<wheelspin animation SMD>" frame 5 6 subtract neutral 0 weightlist wheel_fl_spin 
$sequence wheel_fl_spin { sp_fl_1 sp_fl_2 sp_fl_3 sp_fl_4 sp_fl_5 sp_fl_6 blendwidth 6 blend "vehicle_wheel_fl_spin" -180 180 } weightlist wheel_fl_spin delta autoplay



// front right
$weightlist wheel_fr { "<steering front right bone>" 1.0  "<suspension front right bone>" 1.0 "<wheel front right bone>" 0.0 }
$animation wheel_fr_low "<suspension animation SMD>" frame 0 0 subtract neutral 0 weightlist wheel_fr 
$animation wheel_fr_high "<suspension animation SMD>" frame 2 2 subtract neutral 0 weightlist wheel_fr 
$sequence wheel_fr_suspension { wheel_fr_low wheel_fr_high blend "vehicle_wheel_fr_height" 0 1.0 } weightlist wheel_fr delta autoplay

$weightlist wheel_fr_spin { "<wheel front right bone>" 1.0 } 
$animation sp_fr_1 "<wheelspin animation SMD>" frame 0 0 subtract neutral 0 weightlist wheel_fr_spin 
$animation sp_fr_2 "<wheelspin animation SMD>" frame 1 1 subtract neutral 0 weightlist wheel_fr_spin 
$animation sp_fr_3 "<wheelspin animation SMD>" frame 2 2 subtract neutral 0 weightlist wheel_fr_spin 
$animation sp_fr_4 "<wheelspin animation SMD>" frame 3 3 subtract neutral 0 weightlist wheel_fr_spin 
$animation sp_fr_5 "<wheelspin animation SMD>" frame 4 4 subtract neutral 0 weightlist wheel_fr_spin 
$animation sp_fr_6 "<wheelspin animation SMD>" frame 5 6 subtract neutral 0 weightlist wheel_fr_spin 
$sequence wheel_fr_spin { sp_fr_1 sp_fr_2 sp_fr_3 sp_fr_4 sp_fr_5 sp_fr_6 blendwidth 6 blend "vehicle_wheel_fr_spin" -180 180 } weightlist wheel_fr_spin delta autoplay


// rear left
$weightlist wheel_rl { "<suspension rear left bone>" 1.0 "<wheel rear left bone>" 0.0 }
$animation wheel_rl_low "<suspension animation SMD>" frame 0 0 subtract neutral 0 weightlist wheel_rl 
$animation wheel_rl_high "<suspension animation SMD>" frame 2 2 subtract neutral 0 weightlist wheel_rl 
$sequence wheel_rl_suspension { wheel_rl_low wheel_rl_high blend "vehicle_wheel_rl_height" 0 1.0 } weightlist wheel_rl delta autoplay

$weightlist wheel_rl_spin { "<wheel rear left bone>" 1.0 } 
$animation sp_rl_1 "<wheelspin animation SMD>" frame 0 0 subtract neutral 0 weightlist wheel_rl_spin 
$animation sp_rl_2 "<wheelspin animation SMD>" frame 1 1 subtract neutral 0 weightlist wheel_rl_spin 
$animation sp_rl_3 "<wheelspin animation SMD>" frame 2 2 subtract neutral 0 weightlist wheel_rl_spin 
$animation sp_rl_4 "<wheelspin animation SMD>" frame 3 3 subtract neutral 0 weightlist wheel_rl_spin 
$animation sp_rl_5 "<wheelspin animation SMD>" frame 4 4 subtract neutral 0 weightlist wheel_rl_spin 
$animation sp_rl_6 "<wheelspin animation SMD>" frame 5 6 subtract neutral 0 weightlist wheel_rl_spin 
$sequence wheel_rl_spin { sp_rl_1 sp_rl_2 sp_rl_3 sp_rl_4 sp_rl_5 sp_rl_6 blendwidth 6 blend "vehicle_wheel_rl_spin" -180 180 } weightlist wheel_rl_spin delta autoplay


// rear right
$weightlist wheel_rr { "<suspension rear right bone>" 1.0 "<wheel rear right bone>" 0.0 }
$animation wheel_rr_low "<suspension animation SMD>" frame 0 0 subtract neutral 0 weightlist wheel_rr 
$animation wheel_rr_high "<suspension animation SMD>" frame 2 2 subtract neutral 0 weightlist wheel_rr 
$sequence wheel_rr_suspension { wheel_rr_low wheel_rr_high blend "vehicle_wheel_rr_height" 0 1.0 } weightlist wheel_rr delta autoplay

$weightlist wheel_rr_spin { "<wheel rear right bone>" 1.0 }  
$animation sp_rr_1 "<wheelspin animation SMD>" frame 0 0 subtract neutral 0 weightlist wheel_rr_spin 
$animation sp_rr_2 "<wheelspin animation SMD>" frame 1 1 subtract neutral 0 weightlist wheel_rr_spin 
$animation sp_rr_3 "<wheelspin animation SMD>" frame 2 2 subtract neutral 0 weightlist wheel_rr_spin 
$animation sp_rr_4 "<wheelspin animation SMD>" frame 3 3 subtract neutral 0 weightlist wheel_rr_spin 
$animation sp_rr_5 "<wheelspin animation SMD>" frame 4 4 subtract neutral 0 weightlist wheel_rr_spin 
$animation sp_rr_6 "<wheelspin animation SMD>" frame 5 6 subtract neutral 0 weightlist wheel_rr_spin 
$sequence wheel_rr_spin { sp_rr_1 sp_rr_2 sp_rr_3 sp_rr_4 sp_rr_5 sp_rr_6 blendwidth 6 blend "vehicle_wheel_rr_spin" -180 180 } weightlist wheel_rr_spin delta autoplay

Miscellaneous

Model origin note

If your model is rotated by 90 to 180 degrees relative to the direction it actually drives, try using an $origin line like this:

$origin 0 0 0 90

Gauge animations

The gauge animations are bound to vehicle speed. The faster the vehicle, the further the pose parameter is pushed.
This means the gauge animation can not only be for a speed gauge, but anything that should move when the car drives fast, such as:

  • Speedometer
  • Tachometer
  • Gear Display
  • Active Spoilers
  • Suspension which pushes the car down the faster you go.
  • Moving camera closer to seat to simulate speed.


For either method, use this $poseparameter line:

$poseparameter "vehicle_guage" 0 1
Note.pngNote:"guage" isn't a typo, it's the actual necessary spelling
Tip.pngTip:Use linear Interpolation on keyframes, as opposed to the default keyframes which ease in and out.

Simple

For a simple animation with a single gauge needle, like in the Half-Life 2 buggy:

$weightlist speedo { "<gauge needle bone>" 1.0 }
$animation slow "<gauge animation SMD>" frame 0 0 subtract neutral 0 weightlist speedo
$animation mid "<gauge animation SMD>" frame 1 1 subtract neutral 0 weightlist speedo
$animation fast "<gauge animation SMD>" frame 2 2 subtract neutral 0 weightlist speedo
$sequence speedometer { slow mid fast blend vehicle_guage 0 1 } weightlist speedo delta autoplay

Advanced

For an animation with two or more moving gauge needles (or other animated things like a temperature gauge), like in a regular car's gauge cluster:

Note.pngNote:The gauges will not move independently. i.e: 100MPH will always show whatever RPM the tachometer needle is at during your animation.
$weightlist speedo { "<gauge needle speedo bone>" 1.0 "<gauge needle tacho bone>" 1.0 }
$animation speedo1 "<gauge animation SMD>" frame 0 0 subtract neutral 0 weightlist speedo
$animation speedo2 "<gauge animation SMD>" frame 1 1 subtract neutral 0 weightlist speedo
$animation speedo3 "<gauge animation SMD>" frame 2 2 subtract neutral 0 weightlist speedo
$animation speedo4 "<gauge animation SMD>" frame 3 3 subtract neutral 0 weightlist speedo
$animation speedo5 "<gauge animation SMD>" frame 4 4 subtract neutral 0 weightlist speedo
$animation speedo6 "<gauge animation SMD>" frame 5 5 subtract neutral 0 weightlist speedo
$animation speedo7 "<gauge animation SMD>" frame 6 6 subtract neutral 0 weightlist speedo
$animation speedo8 "<gauge animation SMD>" frame 7 7 subtract neutral 0 weightlist speedo
$animation speedo9 "<gauge animation SMD>" frame 8 8 subtract neutral 0 weightlist speedo
$animation speedo10 "<gauge animation SMD>" frame 9 9 subtract neutral 0 weightlist speedo
$animation speedo11 "<gauge animation SMD>" frame 10 10 subtract neutral 0 weightlist speedo
$animation speedo12 "<gauge animation SMD>" frame 11 11 subtract neutral 0 weightlist speedo
$animation speedo13 "<gauge animation SMD>" frame 12 12 subtract neutral 0 weightlist speedo
$animation speedo14 "<gauge animation SMD>" frame 13 13 subtract neutral 0 weightlist speedo
$animation speedo15 "<gauge animation SMD>" frame 14 14 subtract neutral 0 weightlist speedo
$animation speedo16 "<gauge animation SMD>" frame 15 15 subtract neutral 0 weightlist speedo
$animation speedo17 "<gauge animation SMD>" frame 16 16 subtract neutral 0 weightlist speedo
$animation speedo18 "<gauge animation SMD>" frame 17 17 subtract neutral 0 weightlist speedo
$animation speedo19 "<gauge animation SMD>" frame 18 18 subtract neutral 0 weightlist speedo
$animation speedo20 "<gauge animation SMD>" frame 19 19 subtract neutral 0 weightlist speedo
$sequence speedometer { speedo1 speedo2 speedo3 speedo4 speedo5 speedo6 speedo7 speedo8 speedo9 speedo10 speedo11 speedo12 speedo13 speedo14 speedo15 speedo16 speedo17 speedo18 speedo19 speedo20 blend vehicle_guage 0 1 } blendwidth 20 weightlist speedo delta autoplay

In this example, 20 frames were animated, but you can use as many as you want, as long as you change the code to account for differences in frame amounts.
This animation is basically just the speedometer rising from idle to full throttle, while the tachometer goes through the RPM and falls down again when gear shifting. We are simply using every single frame as a separate $animation to combine it to a blended sequence.

Making the steering wheel turn further than 180 degrees

Because of how animation SMDs work, if a bone rotates over 180 degrees over one frame, its rotation direction will appear to be the opposite of what you might've intended. To avoid this, you can animate in a way so that there's no more than 180 degrees of rotation in individual frames.

Make sure your animation has a linear curve.

This setup allows for about 2.5 proper revolutions of the steering wheel, when turning one way:

$weightlist front_wheels { "<steering front left bone>" 1.0 "<steering front right bone>" 1.0 "<steering wheel bone>" 1.0 }
$animation steer1 "<steering animation SMD>" frame 0 0 subtract neutral 0 weightlist front_wheels
$animation steer2 "<steering animation SMD>" frame 1 1 subtract neutral 0 weightlist front_wheels
$animation steer3 "<steering animation SMD>" frame 2 2 subtract neutral 0 weightlist front_wheels
$animation steer4 "<steering animation SMD>" frame 3 3 subtract neutral 0 weightlist front_wheels
$animation steer5 "<steering animation SMD>" frame 4 4 subtract neutral 0 weightlist front_wheels
$animation steer6 "<steering animation SMD>" frame 5 5 subtract neutral 0 weightlist front_wheels // center frame
$animation steer7 "<steering animation SMD>" frame 6 6 subtract neutral 0 weightlist front_wheels
$animation steer8 "<steering animation SMD>" frame 7 7 subtract neutral 0 weightlist front_wheels
$animation steer9 "<steering animation SMD>" frame 8 8 subtract neutral 0 weightlist front_wheels
$animation steer10 "<steering animation SMD>" frame 9 9 subtract neutral 0 weightlist front_wheels
$animation steer11 "<steering animation SMD>" frame 10 10 subtract neutral 0 weightlist front_wheels
$sequence turning { steer1 steer2 steer3 steer4 steer5 steer6 steer7 steer8 steer9 steer10 steer11 blend vehicle_steer -1 1 } blendwidth 11 weightlist front_wheels delta autoplay

When creating your own animation for such a setup, make sure to have an even amount of frames for both turning directions, with a center frame in the middle.

As with the above advanced gauge setup, it's possible to have more or less than 10 frames.

See also