Vehicle Tutorial

From Mod Wiki
Revision as of 17:05, 15 October 2007 by 192.168.0.129 (talk)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

This is a step-by-step tutorial on how to model, rig, export and script a custom vehicle. It should cover absolutely everything you need to do to get most types of wheeled vehicle working in Enemy Territory: QUAKE Wars. We will start out with the simplest form of vehicle and work from there to implement more complicated scripting and vehicle setup.

Part 1: Design

First off you need a good design. Find some reference photos or drawings that feature elements of what you'd like to include in your vehicle. Then you can create a quick series of sketches and thumbnail drawings to get a decent idea of what overall shape and size your vehicle will be. You don't have to be an amazing artist, just draw a variety of small sketches which explore the design direction you want to go in.

Vehicle design thumbnail sketches

Driving Considerations

It is important to design your vehicle so that it will function well. A vehicle may look really cool, but if it doesn't work well in-game then it's as good as not having a vehicle at all! Here we'll discuss the things you should take into account when designing a wheeled vehicle. These vehicles are the most difficult to get to function well.

ETQW has a rather exaggerated vertical scale. Whilst this makes for some spectacular scenery and epic feeling, it also means that bumps and potholes in the roads can be enormous - let alone the bumps to tackle when you go offroad! There are often steep slopes that players may wish to traverse, or sharp corners they want to skid through at extremely high G. For these reasons your vehicle should have:

  • As much ground clearance as possible (distance between the ground surface and the bottom of the vehicle). This helps prevent the vehicle "bottoming out" on the terrain and losing speed.
  • Large suspension travel (distance the suspension can move from top to bottom). This will allow the suspension tuning to be softer, providing a less jarring and bouncy ride, as it has more distance to absorb the bumps in the surface.
  • A wide track (distance between the two wheels of an axle). This will provide stability and help prevent the vehicle from tipping over.
  • Short front overhang, or upward-slanted front end (for example, see the Trojan). This helps prevent the front end of the vehicle "digging in" to a sudden slope, sapping all of your speed.


Part 2: Rough Model and Basic Vehicle Setup

I'm going to assume you already know how to make a model in LightWave, Maya, or 3D Studio Max. The following tutorial contains specific things to be aware of when modelling vehicles for ET:QW.

Roughing Out The Model

A rough vehicle model with GDF players for scale.

Firstly, and most importantly, don't rush into anything! Modelling a vehicle is a big project, and you need to consider a number of things before you get busy modelling. The last thing you want is to get your model all detailed out in high-poly, then realise that you've forgotten to allow the wheels space to move, or the player doesn't fit inside. This will result in wasted time and effort.

Instead, it makes sense to "rough out" your whole vehicle quickly, using basic shapes and without worrying about texturing or optimisation. Just model enough to make sure that your vehicle will drive well in-game, and be able to be used correctly by players. You can worry about fine details and texturing once it's sure that the design of the vehicle will cause no problems in-game.

Scaling

  • Import an existing ET:QW vehicle and player mesh into your scene to make sure your vehicle is scaled appropriately relative to them.
  • Choose a player animation that will suit your vehicle (eg. set up the Badger driver player animation in EditWorld and export it as an .OBJ file). You can use this to get the size of your seats and position of the steering wheel correct.

Texturing

  • For the time being your vehicle doesn't need a "proper" texture ... you just need a placeholder material to make sure the model will show up in game. Either create your own material and placeholder texture, or use something like textures/concrete/concrete10 for example.


A Basic Vehicle Rig

Once your model is roughed out, you will be ready to add bones (referred to in ETQW as "joints") and rig up your model to work in the game.

Joints

Initial vehicle rig showing the joint positions and names.

Here is a list of the major joints you will need, and what they do. This is the absolute minimum needed to get a 4-wheeled vehicle with no suspension working in game. Further info on adding suspension or a second passenger/gunner can be found in Part 3 of this tutorial. The joint names can be whatever you want, but bear in mind that you will have to refer to them by name in the VScript later, so naming them sensibly can save you a lot of time and avoids confusion.


  • With this setup, all joints should be parented to the origin joint.


Joint Name Description
origin The root joint of the vehicle, it should be at 0,0,0 in the scene.
cam1 Used to position the player's first-person view when sitting inside the vehicle.
driver Used to position the 3D player model within the vehicle.
exit1 The location where the player will try to get out of the vehicle.
front_left_wheel The point that the front left wheel will rotate around.
front_right_wheel The point that the front right wheel will rotate around.
rear_left_wheel The point that the rear left wheel will rotate around.
rear_right_wheel The point that the rear right wheel will rotate around.


  • Important note: all joints should be aligned so that they face forward along the world's X axis, and their local Z axis should point upwards. If not, you may find that your wheels or player view are rotated incorrectly in the game.
    • In 3DS Max, you can make sure that this is correct by creating Bones in the Top viewport and dragging them out to the right. Turn on Grid Snap to make sure the joints are perfectly straight.

Skinning


Skinning (also known as weighting, or binding) is the process of linking your model to the relevant joints. In the case of vehicles it is very straightforward, since nearly all parts of the model are only ever influenced by a single joint. So for example, you would need to bind the whole wheel mesh to the appropriate wheel joint. How you do this will depend on what modelling package you are using.

3ds max

  • Apply a Skin modifier to each of the meshes you want to export (the main body and each of the 4 wheels, for example).
  • In the "Bones" list, click Add, and choose the joint you want the selected mesh to be linked to.
    • In this way, the main body of the vehicle should be linked to the origin joint.
    • Additionally, each wheel should only be linked to its related wheel joint, not anything else.

Maya

  • Bind your meshes to the relevant joints using the Smooth Bind option in the Skin menu.


Exporting


How you export your model and rig to .md5mesh and .md5anim files depends on what modelling package you are using.

3ds max

  • First of all you will need to get Jonathan "BeRSeRKeR" Garcia's maxscript MD5 Exporter, which you can download here.
  • Go to the Maxscript menu and select Run Script... then browse to the location you installed the MD5Exporter.mzp file.
  • A new window should pop up, from here you should set these options:
    • Base frame: 0
    • Start frame: 1
    • End frame: 1
    • Nodes to export: Select all of the mesh parts you want to export. You don't need to select the joints, just the meshes (the main body and 4 wheels, for example).
    • Export options should be set to Export both for now.
    • Ignore the Camera rollout, this is not needed for a vehicle.
  • Now click the Export button and choose a folder to save your MD5 files in - models/vehicles/gdf_buggy/buggy.md5mesh is where I'll put this file.
  • Let the exporter work and save the file (this can take several seconds), soon it will prompt you for a path to save the .md5anim file - just use the same folder and name as before.

That's it, you should now have a working MD5 file that can be read by ETQW!

Maya

That's it, you should now have a working MD5 file that can be read by ETQW!


Related Files


Getting It Into The Game

Now that you have a rough model and a simple rig you are ready to get the vehicle into the game.

Entity Definition

For your new vehicle to be spawned in the game it needs an entityDef describing it. Start out by creating a new file in the def/vehicles subdirectory of your mod. Create the directories if you don't have them already. For this tutorial I am calling the file buggy_tute.def.

The first thing needed is a model definition. This links together the model and any animations it may have into one named model to be referenced by the entity:

model vehicle_buggy_tute {
    mesh                       models/vehicles/gdf_buggy/buggy.md5mesh

    anim base                  models/vehicles/gdf_buggy/buggy.md5anim
    anim ik_pose               models/vehicles/gdf_buggy/buggy.md5anim
    anim initial               models/vehicles/gdf_buggy/buggy.md5anim
    anim idle                  models/vehicles/gdf_buggy/buggy.md5anim
}

Now onto the entityDef. The name you choose for this is the name you will use to spawn the entity. I am using vehicle_buggy_tute:

entityDef vehicle_buggy_tute {

There are base definitions set up for vehicles in base/def/vehicles/base.def, so rather than re-defining all the parameters you might as well just inherit from them. I'm going to use vehicle_base_gdf:

    "inherit"               "vehicle_base_gdf"

There are a large number of key/value pairs that can be set to adjust vehicle behaviour, so for starters I'll describe the bare minimums.

Key Description
spawnclass The code class to use - for most vehicles this should be set to sdVehicle_RigidBody
scriptObject The script to use. The essentials are all defined in vehicle_base, so we don't need to make a new script just yet.
vs_vehicleScript The vscript to point to. I'll make one of these later, called buggy_tute
model The model definition to use. This should be the one we created above.
health The amount of health the vehicle has. I'm using 400 to start with.
input_mode The joystick input mode to use. This selects which set of input CVARs are used to used to translate joystick input to vehicle actions. In this case it should be car
vehicle_control Selects the mode of vehicle control used - in this case it should be wheeled.
info_name This points to a localized string to present to the player to name the vehicle. We can create one of these later, so for now I'll just use game/vec/husky
steering_angle The maximum angle of steering. I'm using 40 to start with.
table_gearforces This points to a table defining the amount of force applied at the wheels at each "gear" speed. I'll be making one of these later, called bugg_tute_gear_forcetable
table_gearspeeds This points to a table defining the speeds at which "gear" forces are applied. I'll be making one of these later, called buggy_tute_gear_speedtable
overdrive_factor The multiplier applied to the wheel speed & force when overdrive (boost) is being used. I'm using 1.5 to start with.
power_curve_scale Adjusts the gear force between gears to act kind of like an engine's torque curve, to help the vehicle go up slopes or maintain speed when hitting a ramp. I'm using 2 to start with.

A few other keys are needed to select some options:

    "option_combat_model"           "1"
    "option_selection_combat_model" "1"
    "option_task_interface"         "1"

The first option sets up a combat model, which is used by bullets so they can hit the actual visible model. The second option sets it so it can be "selected" - this allows it to be locked onto by rockets etc. The final option allows the entity to have missions generated due to it being "spotted" by a radar. TODO: GORDON PLEASE CHECK THIS IS ACCURATE

This leaves us with a very simple, minimal vehicle entityDef. This does not use a lot of features, for example:

  • Command map icon
  • Collision damage
  • Control context CVARs
  • Vehicle HUD elements
  • Engine sounds
  • Climate-based skins
  • Horn sounds
  • Low health warning sounds
  • Tooltip on entering vehicle
  • Decoys

Many things (eg effects) will use the default values, which may not be the most suitable. Some other things won't function properly - for example bullets won't damage it yet. However, there is enough information in the entityDef to get us started.

The completed entityDef:

entityDef vehicle_buggy_tute {
    "inherit"                       "vehicle_base_gdf"

    "option_combat_model"           "1"
    "option_selection_combat_model" "1"
    "option_task_interface"         "1"

    "spawnclass"                    "sdVehicle_RigidBody"
    "scriptObject"                  "vehicle_base"
    "vs_vehicleScript"              "buggy_tute"
    "model"                         "vehicle_buggy_tute"

    "input_mode"                    "car"
    "vehicle_control"               "wheeled"

    "info_name"                     "game/vec/husky"
    "health"                        "400"

    "steering_angle"                "40"

    "table_gearforces"              "buggy_tute_gear_forcetable"
    "table_gearspeeds"              "buggy_tute_gear_speedtable"

    "overdrive_factor"              "1.5"
    "power_curve_scale"             "2"
}


Vehicle Script


The vehicle script file generally contains most of the information to define the behaviour of the vehicle. Start out by making a directory in your mod's directory called vehicles and create a text file in there with the .vscript extension. I named mine buggy_tute.vscript.

Start out by making the definitions we pointed to in the entityDef. The gear speed table and the gear force table are simple and don't take too much explaining so I'll start with those. While ETQW does not use gears in the true sense of the word, it roughly represents the amount of force that the vehicle can apply at the wheels for various speeds, and the speed the vehicle will aim for. In a real-world vehicle the force the vehicle can drive with reduces with each gear, so we generally set up these tables to ramp down the force as the speed ramps up, but you can mess with these to achieve various effects (eg lots of torque at high speed). Here are the tables I'll start out with:

table buggy_tute_gear_speedtable {
    clamp 
 
    {
        20, 30, 50, 80
    }
}
 
table buggy_tute_gear_forcetable {
    clamp

    {
        300000, 200000, 100000, 100000
    }
}

The clamp parameter means that it won't try to extrapolate beyond the range of the table when looking up values.

Now onto the vehicleDef. This does the bulk of the definition. First of all it needs to be named:

vehicleDef "buggy_tute" {


Collision Model


The vehicle needs some sort of physical representation in the world. I've decided that for my model it should weigh somewhere in between the Husky and the Armadillo, so I've chosen 2000kg. Give the vehicle a part to define the shape of it - you can measure your model in your modelling package to try to get the bounds perfect straight away, but I just added a part with a random size ("-100 -100 -100" & "100 100 100") and then tuned the size in-game. To do this you can use the console to turn on g_showCollisionModels and to extend g_maxShowDistance - 1024 should be far enough. You can then spawn your vehicle by typing spawn vehicle_buggy_tute at the console (or whatever you named the entityDef). Enter killTransports to remove all the vehicles in the map, and reloadDecls to load the changes you've made to the definitions. By putting ETQW into windowed mode you can have the vehicle script open in another window, edit the sizing, pop back to ETQW, reloadDecls and spawn a new vehicle. This way you can rapidly tweak the size of the collision. Make the bottom of the collision end at the underside of the body - this gives it some clearance over the ground. A simple box collision model will be enough to start with, you can experiment with compound collision models later to get the size and shape to your liking. This is what I ended up with:

    part {
        "mins"                      "-96 -55 24"
        "maxs"                      "78 55 94"
        "mass"                      "2000"
        "friction"                  "0.4 0.4 0.4"
    }

I also added some friction, so that if the vehicle flips over it doesn't slide forever.


Positions, Views, and Exits


If you spawn this vehicle in the game you'll notice that you can't get into it! This would be a bit of a problem even if it had wheels, so we had better set up a seat for our would-be-drivers. For this you'll need a positionDef. Here is the minimal positionDef I started with:

    positionDef {
        data {
            "joint_attach"  "driver"
            "show_player"   "1"
            "player_anim"   "VehicleBadgerDriver"
        }

        view {
            eyeJoint        "cam1"
            autoCenter

            clamp pitch {
                min         -70
                max         20
            }

            clamp yaw {
                min         -80
                max         80
            }
        }

        view {
            eyeJoint        "cam1"
            type            "smooth_locked"
            thirdPerson
            cameraDistance  240
            cameraHeight    50
        }
    }

This provides a very simple position. It attaches the player at the driver joint, shows the player, and uses the VehicleBadgerDriver animation state machine. It has two views - one is a first person view based on the cam1 joint, which auto-centers. It has pitch and yaw clamps to keep the view within a reasonable range. The other view is a third person view based around the same joint, set back and up from the joint. I haven't named the position or given it a stats name - these won't cause any problems just now, although a warning will be printed on the console. This is not a problem, as the stats only do anything in ranked matches. TODO: REMOVE THE WARNING?

If you spawn your vehicle now you will find that you can get in and look around, but you can't get out - we now need to add an exitDef (or multiple exitDefs) to give the player somewhere to get out of the vehicle. It will not allow the player to get out if there are no valid (unblocked) exits, so it is always a good idea to have several exits. In this case I have only one exit:

    exitDef {
        joint               "exit1"
    }


Wheels


Ok so now we have a vehicle that has physics, and you can get in and out of. Now we need to make it actually go places. In the case of a wheeled vehicle like this we'd obviously want, well, wheels! To start with I'm going to make all the wheels the same, with identical suspension parameters. Maintaining four copies of near-identical settings when tweaking is very error-prone, so I'm going to write a template to define all the common keys between all the four wheels, one for keys common between the front two wheels, and one for keys common between the rear two wheels. Using the parameters to the first template we can make it include the front/rear template too, and set up the names of the joints and surfaces to use. This makes it as simple as just using the template with appropriate front/rear & left/right parameters to define any one wheel. Now, suspension tuning is tricky and can take a lot of fiddling to get it just right. I'll outline some of the methods I use when tuning a new vehicle's suspension, but you'll largely have to experiment to get things the way you like them.

Here is my starting point:

template templates/vehicles/buggy_tute/wheel_behavior {
    parameters< FrontBackParm, LeftRightParm >
    text {
        "name"                         "FrontBackParm LeftRightParm Wheel"
        "surface1"                     "FrontBackParm_LeftRightParm_wheels"
        "joint"                        "FrontBackParm_LeftRightParm_wheel"

        "slowonLeftRightParm"          "1"
 
        "wheelSpinForceThreshhold"     "75000"

        "brakingForce"                 "250000"

        "drive"                        "1"

        "alternateSuspensionModel"     "1"
        "suspensionUpTrace"            "8"
        "suspensionDownTrace"          "20"
        "suspensionRange"              "28"

        "suspensionKCompress"          "100000"
        "suspensionDamping"            "0"

        "suspensionMaxRestVelocity"    "3.5"

        "maxSlip"                      "400"
        "contactFriction"              "0 0.7 0"

        useTemplate templates/vehicles/buggy_tute/wheel_FrontBackParm< "LeftRightParm" >
    }
}

template templates/vehicles/buggy_tute/wheel_front {
    parameters< LeftRightParm >
    text {
        "turn"                         "1"
        "radius"                       "18"
        "footprint"                    "15"
    }
}

template templates/vehicles/buggy_tute/wheel_rear {
    parameters< LeftRightParm >
    text {
        "hasHandbrake"                 "1"
 
        "radius"                       "20"
        "footprint"                    "15"
    }
}

These templates must be defined outside of the vehicleDef - I placed them at the top of the file. Note how it uses the template parameters to construct the joint, surface, and part names. I've set this one up to use the alternate suspension model, which behaves like a simple linear spring & dampener system, as it is somewhat easier to tune and can provide very entertaining results. Its set up for four wheel drive, with the front wheels steering and the rear wheels handbraking. To use the template to create wheels add the following into the vehicleDef:

wheel {
    useTemplate templates/vehicles/buggy_tute/wheel_behavior< "front", "left" >
}

wheel {
    useTemplate templates/vehicles/buggy_tute/wheel_behavior< "front", "right" >
}

wheel {
    useTemplate templates/vehicles/buggy_tute/wheel_behavior< "rear", "left" >
}

wheel {
    useTemplate templates/vehicles/buggy_tute/wheel_behavior< "rear", "right" >
}

If you now spawn your vehicle it should be drive-able! No doubt it will bounce up and down chronically and drive strangely, but its a start.


Wheel Geometry

Firstly you'll want to adjust the amount of suspension travel there is. Start by deciding how far up from the wheel joint you want the bottom of wheel to be able to move, and set suspensionUpTrace to this. It is OK for this to be negative, if you modelled your vehicle with the wheels a long way down. This is basically the vertical offset from the joint's original position that the wheel can go up. Similarly, suspensionDownTrace is the vertical offset from the joint's original position that the wheel can go down. suspensionRange is the distance from the bottom of the movement range over which the suspension will operate - this allows the wheel to still move beyond its range, but the equations used to generate the forces will act as if it had reached the end of its suspensionRange. It is fine to start by setting this to suspensionUpTrace + suspensionDownTrace.

Next you should out the wheel's size - the radius and footprint values set this. radius is pretty self explanatory - its best to measure this one in your modelling package. footprint is the width of the wheel, which should again be measured in your modelling package. In the model I am using the front & rear wheels are different sizes, so I have configured mine to reflect this.


Suspension Tuning

We're almost ready to roughly tune the suspension's spring and dampening constants, but first I'll give a brief explanation of what effect these two have on the vehicle.

The spring compression coefficient, suspensionKCompress, causes a force to be applied that opposes the compression/extension of the suspension. The force is proportional to the amount of compression on the spring. Setting a small value for this will result in "squishy" suspension that allows a lot of vertical movement. Setting a large value will result in "stiff" suspension where only a small amount of movement is possible before large forces are applied.

The damping coefficient, suspensionDamping, causes a force to be applied that opposes the change in compression/extension of the suspension - ie the velocity of the suspension. The force is proportional to the rate of change of the spring's compression. Setting a small value for this will result in bouncy suspension that only slowly resists suspension movement, allowing oscillation to occur. Setting a large value will result in very static feeling suspension that rapidly resists suspension movement, quickly absorbing bumps. Too large a value can result in an "over-damped" situation where it will tend to drastically overcompensate - an example is a vehicle being dropped to the ground and it bouncing up higher than the height it fell from.

Low coefficient values will typically result in a very bouncy, "squishy" ride. This will tend to handle rough terrain well, without bumping and jostling the vehicle too much, but handle smooth terrain badly - bouncing and wobbling too much. High coefficient values will typically result in a very stiff ride, handling smooth terrain well but having a hard time over rough terrain, giving a bumpy and nasty ride. Note that these are very general tips - there is no hard-and-fast rule, and you will need to experiment a lot to find values that satisfy you.

Given the above, I tend to start by making it handle the standing still case. I lower the damping coefficient to zero and spawn a vehicle on flat terrain to see how it sits. I adjust the compression coefficient until the vehicle's body tends to sit at about the height I had imagined. With the damping coefficient at zero it will tend to wobble indefinitely, so now I adjust the damping coefficient until it tends to stabilize after a few oscillations. I normally start with a small value, eg 100, and multiply by 10 until the oscillation disappears. Then I look for values in between that and the previous value until I'm satisfied. For my vehicle this ended up with suspensionKCompress being 300000, and suspensionKCompress being 15000.

I normally find that at this point the vehicle handles pretty decently. Not perfect, but good enough for this stage of development. Depending on the mass of your vehicle you will probably have to adjust the drive force in the force table to get a decent degree of acceleration.


Preventing it from rolling


If your vehicle is anything like mine, you'll probably find that it is all too easy to roll your shiny new car. If you turn on rb_showMass using the console you will see a yellow wireframe sphere somewhere in the middle of your vehicle - this shows where its center of mass (some call it the center of gravity) is. This is quite high, which makes it easier for the vehicle to roll over. There are a few ways to deal with this:

  • Use a point mass to lower the center of mass to a more reasonable place
  • Use several collision parts instead of just one, making the upper one lighter than the lower one - this represents the bulk of the mass of the vehicle being in the lower part of the vehicle (eg the engine, suspension, wheels, the driver, etc)
  • Use an antiroll constraint

I like to use a combination of these methods. The disadvantage of lowering the center of gravity is that it allows less dynamic behaviour, like body rolling and pitching. This behaviour looks really cool, so its a shame to lose it. Antiroll constraints make the vehicle tend to resist rolling over too much. These are very easy to use, but they can look a bit artificial if they are made too obvious. With a bit of tweaking a good combination of the methods can usually be achieved.

This is the combination I settled on:

part {
    "mins"                      "-96 -55 60"
    "maxs"                      "78 55 94"
    "mass"                      "500"
    "friction"                  "0.4 0.4 0.4"
}

part {
    "mins"                      "-96 -55 24"
    "maxs"                      "78 55 60"
    "mass"                      "1500"
    "friction"                  "0.4 0.4 0.4"
}

antiroll {
    "angle_start"               "5"
    "angle_end"                 "30"
    "strength"                  "2"
}


Running people over


At the moment you can probably go prone and crawl under your vehicle, and running prone people over will not kill them. To solve this there is a part called a hurtZone. These are made much the same as a part, but they don't have a mass or friction - they just have a shape. They prevent players from moving through them, and they will cause collision damage to players in their way. Make this big enough to cover the underside of the vehicle.

Here is mine:

    hurtZone {
        "mins"                  "-96 -55 0"
        "maxs"                  "78 55 24"
    }


Part 3: More Advanced Scripting and Setup

More Advanced Joints

More advanced vehicle rig showing the joint positions and names.

You can now add extra joints as needed for more passengers, gunners, suspension, effects locations and exit points.

  • Passengers - For each passenger you want to add, you will need a joint for the player location, and a joint for their first-person camera view.
  • Gunners - This can be set up like a passenger, but with more scripting needed, as described in the Scripting section below.
  • Suspension - These joints should be parented to the origin joint, and have the related wheel joint as a child.
  • Exit points - These joints define more places that players may appear when they exit the vehicle.
  • Effects locations - Used as an attachment location for effects such as smoke and fire when the vehicle is damaged.

Related Files


More Scripting

In Part 2 you learnt how to get your vehicle into the game in a very rough state. Here we are going to expand on what we developed last time to improve it


Suspension


Now that we have suspension joints we can hook them up so that we can see the wheels moving up and down, following the ground. To the text part of the wheel template add the following line:

    "suspension"                    "vehicle_buggy_tute_FrontBackParm_LeftRightParm_suspension"

This makes the wheel look for a stringMap to describe the suspension. The simplest type is vertical, which is the one we are going to use here:

stringMap vehicle_buggy_tute_front_right_suspension {
    "type"                          "vertical"
    "joint"                         "front_right_suspension"
}
stringMap vehicle_buggy_tute_front_left_suspension {
    "type"                          "vertical"
    "joint"                         "front_left_suspension"
}
stringMap vehicle_buggy_tute_rear_right_suspension {
    "type"                          "vertical"
    "joint"                         "rear_right_suspension"
}
stringMap vehicle_buggy_tute_rear_left_suspension {
    "type"                          "vertical"
    "joint"                         "rear_left_suspension"
}

These can go in the vscript file, outside the vehicleDef


Shooting it


At the moment your vehicle will not take damage when people shoot it - this is because it does not fit in any of the target lists used by the bullet damage definitions. To put it into these target lists we need to add the vehicle to some "collections". You'll need to add it to the following collections by adding these keys to the entityDef:

    "collection_antivehicle"       "antivehicle"
    "collection_vehicles_light"    "vehicles_light"
    "collection_vehicles_gdf"      "vehicles_gdf"

The first of these allows it to be targeted by anti-vehicle turrets. The second marks it as being a light vehicle - this means it will take more damage from bullets than if it were markes as a heavy vehicle. The last one allows it to be damaged by various map entities, for example Strogg energy walls.


Damage Effects


You'll probably now notice that your vehicle will have flames/smoke coming from the origin of the model when it gets significantly damaged. This is where your damage effect joint comes in - again, these keys go in the entityDef:

    "joint_damage_smoke"           "effects"
    "joint_damage_fire"            "effects"

This will make the damage effects play at the joint named effects. You can adjust the effects that play, and when they play, with the following keys:

   "damage_smoke"                  "70"
   "damage_level1"                 "50"
   "damage_level2"                 "30"
   "damage_level3"                 "10"
   "fx_damage_level_smoke"         "effects/vehicles/generic_smoke"
   "fx_damage_level1"              "effects/vehicles/vehicle_flames_small"
   "fx_damage_level2"              "effects/vehicles/vehicle_flames_medium"
   "fx_damage_level3"              "effects/vehicles/vehicle_flames_large"

The smoke effect will play at the same time as the damage level 1, 2, or 3 effects. When the vehicle's health drops below the value specified by "damage_smoke" etc the corresponding effect will be played. I have given the default values here.


Collision Damage


Your vehicle will not currently do any damage to anything else in a collision. For this you'll need to make a new damage type for the collision:

damageDef damage_buggy_tute_collide {
    damage                          "damage_buggy_tute_collide"

    team_kill_cvar                  "g_allowComplaint_vehicles"

    tt_obituary                     "tooltips/killmsgs/vehicles/buggy_tute"
    tt_obituary_unknown             "tooltips/killmsgs/vehicles/buggy_tute/empty"
    tt_obituary_team_kill           "tooltips/killmsgs/vehicles/buggy_tute/teamkill"
    tt_obituary_self                "tooltips/killmsgs/vehicles/driving"
}

damageFilter damage_buggy_tute_collide {
    type {
        target  "target_player_all"
        damage  100
    }
    type {
        target  "target_veh_all"
        damage  100
    }
    type {
        target  "target_supply_crate"
        damage  200
    }
    type {
        target  "target_deployables_all"
        damage  100
    }
}

I put these in the buggy_tute.def file, below the vehicle's entityDef. Then you'll need to add the following key to the vehicle's entityDef:

   "dmg_collide"                    "damage_buggy_tute_collide"

This sets up a damage filter listing the target sets that are damaged by the vehicle, and values that scale the damage caused - the damage caused varies with the speed of the collision. The damage definition also specifies a cvar that can be used to enable and disable complaints for team kills by this damage type, and obituary names. I'll describe how to set the obituaries up below.

Name


Anything to be displayed on the HUD needs to be a localized string. Its pretty easy to create new strings, but there are a few files involved. First of all you'll want to create a few directories under your mod directory: localization, localization/locstr, localization/english, and localization/english/strings. Now create a couple of files to put your new strings in: localization/locstr/buggy_tute.locstr, and localization/english/strings/buggy_tute.lang. Into the locstr files go locString definitions, which are named strings that can have arguments. Into lang files go tables of string values. This allows there to be lang files for each language, having different strings for each language but with them all looking the same from the rest of the game's code & data.

Into the locstr file add:

locString game/vec/buggy_tute {
    text "#str_10000100"
}

The number in the string is used to identify the number of the localized string in the lang file. All strings in retail ETQW are numbered less than 10000000, so values over that are safe. I like to set aside a range of values for a category of strings - in this case I'm setting aside the range 10000100 -> 10000200 for this vehicle. This helps prevent the situation where you have to search through all the files to find an unused number.

  • Note: You do not have to use this exact numbering scheme, for example to prevent conflicts you could call your string "#str_buggy_001" etc. This is slightly easier to remember and less potential string conflicts to worry about.


In the lang file all the strings must be between a pair of braces, so add:

{
    "#str_10000100" "Buggy!"
}

Any further strings I add for this car I'll be adding in between the two braces. You can now hook this up to the vehicle entity by changing the value of its "info_name" key to "game/vec/buggy_tute". When you look at the vehicle in the game you should now see "Buggy!" :)


Obituaries


There are three obituaries we need to fill in (we referenced them above when we made a collision damage definition). The strings for these are referenced via a tooltip definition, so we need to make those:

tooltip tooltips/killmsgs/vehicles/buggy_tute {
    text "game/obit/buggy_tute"
}

tooltip tooltips/killmsgs/vehicles/buggy_tute/empty {
    text "game/obit/buggy_tute_empty"
}

tooltip tooltips/killmsgs/vehicles/buggy_tute/teamkill {
    text "game/obit/buggy_tute_team"
}

I added these at the end of the buggy_tute.def file. Next we need to set up the localized string definitions for these:

locString game/obit/buggy_tute {
    text "#str_10000101"
}

locString game/obit/buggy_tute_empty {
    text "#str_10000102"
}

locString game/obit/buggy_tute_team {
    text "#str_10000103"
}

And finally the actual localized strings:

    "#str_10000101" "%b^0 &lbrBuggy&rbr %a"
    "#str_10000102" "&lbrBuggy&rbr %a"
    "#str_10000103" "%b^1 &lbrBuggy&rbr ^0%a"

These strings have some interesting escape sequences in them. %a and %b are replaced with the names of the name of the killer and the killed respectively, &lbr and &rbr are replaced by left square brackets and right square brackets respectively, and the caret ( ^ ) symbol allows the text colour to be changed.


Command Map


At the moment your vehicle won't be visible on the command map. This is easy fixed:

   "icon_size_cm"                  "16"
   "mtr_commandmap"                "guis/assets/commandmap/icon_vehicle"
   "mtr_commandmap_unknown"        "guis/assets/commandmap/icon_vehicle"

The first key sets the size of the icon and the other two set the material to be used for the icon. The unkown material isn't really important in this case, you should just set it to the same as the normal material as I have done here.


Control Binding Context


In ETQW bindings can be context sensitive, and by using a series of cvars (one for each vehicle type) you can customize the control layout for each vehicle. To set the name of the context cvar your vehicle uses you can add the following key to your vehicle's entityDef:

    "control_context"              "g_bind_context_buggy"

Note that you can call the cvar whatever you like. To create the cvar you will need to pull the console down and set it by typing something like set g_bind_context_buggy vehicle. This can be included in config files with your mod to ensure people have the cvars created.


Tuning the Steering


The default values for the steering tend to be pretty good for most cases. The steering works on the principle that there is a direction in which the player wants to go - by pressing left and right they alter the direction they want to go in. The game then tries to steer towards that direction. When driving a real car you automatically adjust the steering wheel as you go over bumps etc by feel - you do not get any of this feeling from a keyboard, normally by the time you see your car veering to one side from hitting a bump its already too late to correct it neatly. Keyboards are also an on/off input device, so making subtle corrections is very difficult. The game is essentially trying to compensate for your lack of this feeling of the road surface and make all the subtle adjustments you aren't able to make.

There are a number of keys that can be set on the vehicle's entityDef to adjust the behaviour of the steering:

Key Default Value Description
steering_angle n/a The maximum steering angle.
simplesteer_forward_speed 2 The speed at which the desired direction of movement will change due to player input while moving forward.
simplesteer_reverse_speed -4 The speed at which the desired direction of movement will change due to player input while moving in reverse.
simplesteer_centering_speed_min 2 The minimum speed at which the steering will tend to center back towards the current movement direction.
simplesteer_centering_speed_max 15 The maximum speed at which the steering will tend to center back towards the current movement direction.
simplesteer_centering_ramp_threshold 20 The vehicle speed at which the centering speed will drop to the minimum value (as the vehicle speeds up the steering becomes less twitchy).
simplesteer_centering_speed_air 0.5 The speed at which the steering will tend to center back towards the current movement direction while the vehicle is airborne (so it doesn't lose the direction of movement too fast when jumping).
simplesteer_reverse_angle_scale -0.5 Scales the amount/direction of steering when moving in reverse.

For example if you wish the player's input to result in the vehicle turning faster you can increase simplesteer_forward_speed.


Engine Sounds


One thing thats our vehicle lacks so far is sound. ETQW wheeled vehicles use three looping samples that are cross-faded and pitched with varying speeds and accelerator input values. In addition to this there is an engine start sound and an engine stop sound for when the driver gets in or out. All of these things are defined in the entityDef.

Firstly you'll need to set the sound control method:

    "sound_control"                 "wheeled"

You need to specify the samples to be used. We use an idle sample & a drive sample, where idle is cross-faded with the drive as speed increases, and a "hard acceleration" sample which is faded in and out with speed and input. This is meant to emulate the ferocious exhaust note you hear on a vehicle that is accelerating hard.

    "snd_engine_start"              "sounds/vehicles/badger/engine/start"
    "snd_engine_stop"               "sounds/vehicles/badger/engine/stop"
    "snd_engine_idle"               "sounds/vehicles/badger/engine/idle"
    "snd_engine_drive"              "sounds/vehicles/badger/engine/drive"
    "snd_engine_hardaccel"          "sounds/vehicles/husky/engine/hardaccel"

In my case I'm reusing most of the sounds of the badger, but mixing in the husky acceleration sound to hopefully make it sound a bit more raw.

The first few parameters control the change in pitch of the samples as the vehicle changes pitch:

    "engine_pitch_low"              "1.5"
    "engine_pitch_high"             "2"
    "engine_speed_low"              "10"
    "engine_speed_high"             "150"

This sets the high & low levels of the pitch and the speeds at which they occur. At engine_speed_low the pitch will be engine_pitch_low and similarly for high speed. The pitch is linearly ramped between those two. The original pitch in the sample is 1, so 1.5 is 50% faster, and 0.5% is 50% slower.

The next few parameters control engine "spooling" - this is meant to emulate the fact that an engine does not speed up or slow down instantly:

    "engine_accel_spool_time"       "0.033"
    "engine_decel_spool_time"       "0.033"

This is how long it takes for the "engine speed" to reach the actual speed of movement, in seconds. 0.033 is instant (1 / 30 - ie, one 30fps game frame).

Next are the volume settings for the idle and drive samples:

    "engine_idle_min_speed"         "5.0"
    "engine_idle_max_speed"         "50.0"
    "engine_idle_min_vol"           "0.0"
    "engine_idle_max_vol"           "-50.0"
    "engine_idle_power"             "1.0"
    "engine_idle_fade_time"         "0.066"
    "engine_drive_min_speed"        "20.0"
    "engine_drive_max_speed"        "80.0"
    "engine_drive_min_vol"          "-20.0"
    "engine_drive_max_vol"          "-5.0"
    "engine_drive_power"            "0.3"
    "engine_drive_fade_time"        "0.066"

At the min speed the sample will be at min volume - at max speed the sample will be at max volume. The sample will change between the two depending on the power value. This adjusts the linearity of the curve - 1 is linear, greater than 1 is exponential (slow change at first, fast change later), and less than 1 (but greater than zero) changes fast at first and slowly later. The fade time governs how long it takes the sound to fade away when the engine is shut off. All volume settings are measured in decibels, with zero being the original sample volume.

Next are the acceleration sound tuning parameters - these are much more complicated. First of all there are parameters to allow the pitch to be adjusted semi-independently of the idle and drive samples:

    "engine_accel_pitch_mult"       "1.0"
    "engine_accel_pitch_offset"     "0.5"

The pitch used for the other samples is subtracted from the minimum pitch, multiplied by the pitch multiplier and added to the offset. This effectively allows the pitch to change over a different range of values, given the same speed range, with the offset value giving the minimum. The pitch and the volume can also be adjusted based on the yaw speed of the vehicle (see the Titan/MCP for examples).

The volume of the acceleration sound has three key points - a min, mid and a max value. Each has a speed and a corresponding volume, and the transition between min & mid and mid & max each have their own power values to adjust the linearity of the transition. It is also increased when going up steep slopes and when the player is accelerating, to emulate the engine straining.

    "engine_accel_min_speed"        "1.0"
    "engine_accel_mid_speed"        "20.0"
    "engine_accel_max_speed"        "80.0"
    "engine_accel_min_vol"          "-5.0"
    "engine_accel_mid_vol"          "5.0"
    "engine_accel_max_vol"          "-3.0"
    "engine_accel_power_low"        "0.1"
    "engine_accel_power_high"       "5.0"
    "engine_accel_fade_time"        "0.0"


Overdrive Sounds

There are default overdrive sound settings, but you may want to tweak those. They support pitch shifting much like the engine sounds:

    "snd_overdrive"                 "sounds/vehicles/husky/overdrive"
    "snd_overdrive_stop"            "sounds/vehicles/husky/overdrive/stop"

    "overdrive_pitch_low"           "0.9"
    "overdrive_pitch_high"          "1.5"
    "overdrive_speed_low"           "20"
    "overdrive_speed_high"          "90"


Miscellaneous Sounds


There are a couple of other sounds used by vehicles - the horn, and the low health warning. These are dead easy to configure:

    "snd_horn_loop"                 "sounds/vehicles/husky/horn"
    "snd_horn_stop"                 "sounds/vehicles/husky/horn/stop"

    "snd_health_warn"               "sounds/vehicles/misc/warning/ground/gdf"

The keys used should be pretty self-explanatory for these!



Part 4: Advanced Scripting and Setup

Advanced Scripting

There are a plethora of other things you can do with your vehicle to set it up. I'm going to go into some of them here.

Vehicle HUD

When you are in a vehicle in ETQW a HUD section is added in the bottom left showing details about the vehicle - health, passengers, etc. These are quite simple to create, as they are all done through templates. The only new asset you need to create is an icon for it - it should be pretty easy from looking at the existing icons. They're made up from a border and a fill image, a couple of examples are guis/assets/icons/vehicles/gdf_top_husky_border.tga and guis/assets/icons/vehicles/gdf_top_husky_fill.tga.

To make the material you'll need to create a new .mtr file and place it in a materials directory in your mod directory. I'm calling mine buggy_tute.mtr. The material is created using a template, so we'll need to include the template file before we can create the material. Here is the result for mine. Note that I'm reusing the Husky's icon:

#include <materials/guis/hud/gdf.include>

material guis/assets/hud/vehicles/buggy_tute
{		
    _vehicle_icon( "guis/assets/icons/vehicles/gdf_top_husky" )
}

Now you can make the GUI. You'll need to make a .gui file and place this in a guis directory in your mod directory. I'm calling mine buggy_tute.gui. Again, we'll need to include a template file first:

#include <guis/game/vehicles/gdf/cockpits.include>

gui guis/vehicles/buggy_tute {
    properties {
        float flags = immediate( flags ) | GUI_FULLSCREEN;
    }
    materials {
        "icon"		"guis/assets/hud/vehicles/buggy_tute"
    }
    _class_icons
    _base_icon
    _hud_materials
    _position( 0, 1.5, 10 )
}

Next add the following key to the entityDef:

    "gui_vehicle"                   "guis/vehicles/buggy_tute"


Conclusion

You should now have a good understanding of all the steps required to make a new vehicle for ET:QW from scratch.