Car Physics with many cars

From GameStudio Wiki

Jump to: navigation, search

I modified the original scripts supplied in A7 demo project Car Level. The original source code contains three key-files: physics_car.c, physics_events.c and physics_init.c. Such scripts have two missing features:

  1. They can manage only a physic car
  2. You can create only a vehicle profile (sport-car, off-road, etc...)
  3. I made a general code clean-up (even if I left original commented code; I will eliminate it only in the final release).

I modified them in order to add such features, maintaining an high usability.

So I created the following scripts (modified version of the original ones). Filenames are the same, but its usage is quite different.

Here follows the source code. You can find how to use them at the end of the page.

NOTE: this is a beta-test code. What's missing? What should be completed?

  1. Lift was eliminated (sorry, but I really don't like it!)
  2. Particle system is used to create "smoke": currently, only a car at the same time can show the smoke. This is a bug: I'm working on it.

Contents

SOURCE CODE

physics_car.c

//////////////////////////////////////////////////////////////
// Program modified by Alessandro Manotti
// Original code taken from A7 buggy demo.
// Last-update: 24-Jan-2008
// History:
//   24-Jan-2008
//     * Original code
//////////////////////////////////////////////////////////////


//////////////////////////////////////////////////////////////
// These defines are needed to manage the vehicles and
// physics engine.
//////////////////////////////////////////////////////////////

#define MAX_CARS_COUNT 9		// Max number of cars available at the same time.
#define MAX_CARS_DETAILS_COUNT 9	// Max number of different car types (off-road/sport/etc...)

//////////////////////////////////////////////////////////////
// Car constants
// This struct maintain an array of different vehicle
// characteristics. Every vehicle characteristic, stored here,
// will be associated to a material vehicle using its
// "vehicle.properties" pointer.
//////////////////////////////////////////////////////////////

typedef struct {
	var maxTorque; 			// maximum torque applied to rear wheels
	var maxTorqueBrake; 	// this will be applied to rear wheels when hitting brakes or handbrake
	var maxAngSpeed; 		// max. angular speed of wheels; max linear speed depends on wheel radius
	var massWheel;			// mass per wheel
	var massChassis; 		// mass for chassis lighter than wheels so it doesn't tilt
	var fricWheel; 			// friction of wheels -god for "in the water"
	var fricChassis;		// friction of chassis (low to avoid getting stuck on ramps
	var dampChassis;		// damping lin/ang for chassis
	var dampWheel;			// damping ang for wheels
	var suspensionERP; 		// how quickly shocks are reduced to correct position (100,000 max, 0: never)
	var suspensionCFM; 		// how far shocks can overshoot (0: never, 100,000 max)
	var jumpRechargeDelay; 	// time before user is allowed to jump again in secs
	var jumpForce;	 		// force multiplier for jumps
	var bDentCar; 			// when set to 1 deform car on impact
} TVehicleProperties;

TVehicleProperties vehicleProperties[MAX_CARS_DETAILS_COUNT];

//////////////////////////////////////////////////////////////
// Steering constants
//////////////////////////////////////////////////////////////

var accelKeyboard = 2; 					// acceleration multiplier for key control
var accelSteering = 3; 					//  acceleration multiplier for steering key control
VECTOR* joyNullzone = { x=20; y=20; } 	// 0..255 x and y direction center range for joystick

//////////////////////////////////////////////////////////////
// these values depend on MDL and WMP where car is located
//////////////////////////////////////////////////////////////

var vecUp[3] = {0, 0, 1}; 					// up axis, usually +Z
var vecRight[3] = {0, -1, 0};				// chassis right axis
var vecDriverOffset[3] = {-35, 10, 34}; 	// where camera will be located relative to chassis center (cockpit)
var vecChaseOffset[3]  = {-150, 0, 60}; 	// where camera will be located relative to chassis center (chase)
ANGLE vecChaseAngOff;						// angle tweak of the chase camera

var gravity = 1800;							//2000;		// gravity along -Z axis

#define NUM_TRACKS 1 						//400;		// number of tire tracks
#define CRASH_SPEED 100 					// head-on impact above this speed causes wheels to come off
//////////////////////////////////////////////////////////////
// Controller setup
//////////////////////////////////////////////////////////////

#define KEY_FWD key_w			// Forward
#define KEY_BWD key_s			// Backward
#define KEY_LEFT key_a			// Left
#define KEY_RIGHT key_d			// Right
#define KEY_HORN key_h			// Push horn
#define KEY_JUMP key_e			// Turbo Boost ;-)
#define KEY_BRAKE key_space		// Pull Handbrake

var    USE_GAMEPAD= 0;				// Treat joystick as absolute device
#define JOY_JUMP joy_1			// Turbo Boost ;-)
#define JOY_BRAKE joy_2			// Pull Handbrake
#define JOY_HORN joy_5			// Push horn
#define JOYRAW_Y	joy_raw.y		// Forward/Backward axis on joystick
#define JOYRAW_X	joy_raw.x		// Left/right axis on joystick

//////////////////////////////////////////////////////////////
// physics global vars

var DoShutdown=0; // set to 1 and wait a while before reloading level
var DoSteering=1; // set to 1 to enable all controls 0 for pause

//////////////////////////////////////////////////////////////
// This structure defines every data related to a vehicle.
//////////////////////////////////////////////////////////////

typedef struct {
	ENTITY* pChassis;	// Chassis handle. Camera points this object.
	
	ENTITY* pFL; 		// Tires handles.
	ENTITY* pFR;			
	ENTITY* pRL; 
	ENTITY* pRR;	
	
	TVehicleProperties *properties;	// Pointer to an array element containing 
									// specific vehicle characteristics.	

	var wheelFL;		// constraint handles.
	var wheelFR; 
	var wheelRL; 
	var wheelRR;

	var jumping;  		// time left in jump
	var hornPlaying;	// on or off
	var brakePlaying; 	// on or off

	var lastPos[3];				// position of chassis in last frame

	var arrpTracks[NUM_TRACKS];	// cyclic buffer of track entities (handles)
	var trackLen;				// length of treadmark in quants
	var currTrack;				// current track number
	var timerSeconds;		    // a simple stopwatch, can be removed

	var linSpeed;		// car speed in XY
	var downSpeed;		// car speed in Z direction
	var angSpeed;		// current wheel angular speed along y axis of wheel
	var targetSpeed;	// desired angular speed (if torque permits)
	var targetSteer;  	// desired steering angle
	
	var commonWheelHeight;
} TVehicle;
 
// This variable store every detail regarding vehicle physics.
TVehicle vehicle[MAX_CARS_COUNT];



//////////////////////////////////////////////////////////////////////////////////////////
// Resources
//////////////////////////////////////////////////////////////////////////////////////////

SOUND* engine_wav	= "engine.wav";		// player's engine sound
SOUND* horn_wav 	= "horn.wav"; 		// player's horn
SOUND* crash_wav 	= "crash.wav"; 		// player's crashing sound
SOUND* tires_wav 	= "tires.wav"; 		// tires squealing
SOUND* flap_wav 	= "lp_flap.wav"; 	// tires squealing

var hTireSnd_n;			// handle to sound playing

BMAP* bmpSmoke= "smoke.tga";

//////////////////////////////////////////////////////////////////////////////////////////
// Includes
#include "physics_init.c" // all car init functions
#include "physics_events.c" // all car event & particle functions

//////////////////////////////////////////////////////////////////////////////////////////
//  Some status data
var noTorque=0;

//////////////////////////////////////////////////////////////////////////////////////////
// Startup initialization.
//////////////////////////////////////////////////////////////////////////////////////////

#define VEHICLE_PROPS_BUGGY 0

function game_startup() {
	int i;
	
	//_/Init dati della struct contnenti le info dei veicoli\__________________
	//_________________________________________________________________________
	
	for(i=0; i<MAX_CARS_COUNT; i++) {

		//_/Vehicle characteristics\_______________________________________________
		//_________________________________________________________________________
		
		//
		// BUGGY
		//
		
		vehicleProperties[i].maxTorque = 800; 			//1200	// maximum torque applied to rear wheels
		vehicleProperties[i].maxTorqueBrake = 3000; 		// this will be applied to rear wheels when hitting brakes or handbrake
		vehicleProperties[i].maxAngSpeed = 80; 			//70  		// max. angular speed of wheels; max linear speed depends on wheel radius
		vehicleProperties[i].massWheel = 20;				// mass per wheel
		vehicleProperties[i].massChassis = 70; 			// mass for chassis lighter than wheels so it doesn't tilt
		vehicleProperties[i].fricWheel = 80; 				//100	// friction of wheels -god for "in the water"
		vehicleProperties[i].fricChassis = 50;			// friction of chassis (low to avoid getting stuck on ramps
		vehicleProperties[i].dampChassis = 50;			//5;		// damping lin/ang for chassis
		vehicleProperties[i].dampWheel =	50;				//15; //15		// damping ang for wheels
		vehicleProperties[i].suspensionERP = 20000; 		//90000;	// how quickly shocks are reduced to correct position (100,000 max, 0: never)
		vehicleProperties[i].suspensionCFM = 150; 		//500	// how far shocks can overshoot (0: never, 100,000 max)
		vehicleProperties[i].jumpRechargeDelay = 1.0; 	//1.5 	// time before user is allowed to jump again in secs
		vehicleProperties[i].jumpForce = 3000;	 		//3000;	// force multiplier for jumps
		vehicleProperties[i].bDentCar = 1; 				// when set to 1 deform car on impact	
	
		vehicle[i].jumping = 0;  		// time left in jump
		vehicle[i].hornPlaying = 0;		// on or off
		vehicle[i].brakePlaying = 0; 	// on or off
		vehicle[i].trackLen = 20;		// length of treadmark in quants
		vehicle[i].currTrack = 0;		// current track number
		vehicle[i].targetSpeed=0;		// desired angular speed (if torque permits)
		vehicle[i].targetSteer=0;  		// desired steering angle
		vehicle[i].commonWheelHeight = -100000;
		
		vehicle[i].properties = &vehicleProperties[i];
	}	
}

//////////////////////////////////////////////////////////////////////////////////////////
// SpeedControl
//////////////////////////////////////////////////////////////////////////////////////////

function SpeedControl()
{
	ENTITY* myEntity = my;
	int myID = (int)my.skill1;
	
	var locDirection;
	var locUseJoy = 0;

	while (1)
	{
		if (DoShutdown) { return; } // quitting?
		if (!DoSteering) // currently pausing?
		{
			wait(1); continue;
		}

		if (JOYRAW_Y<-joyNullzone.y || JOYRAW_Y>joyNullzone.y)
		{
			// treat joystick as absolute device which is more
			// like a keyboard than a joystick
			if (USE_GAMEPAD) {
				locUseJoy=0; locDirection= -(JOYRAW_Y / 255);
			} else {
				locUseJoy=1; locDirection= -(JOYRAW_Y-joyNullzone.y)/(255-joyNullzone.y);
			}
		} else {
			locUseJoy=0; locDirection= (KEY_FWD-KEY_BWD) + joy_force.y;
		}
			
		// if we drive in one locDirection and want to change locDirection, apply brakes
		// (angSpeed>0 checks if rear wheel is turning forward
		// locDirection<0 checks if user hits reverse key
		if ((vehicle[myID].angSpeed>5 && locDirection<0) || (vehicle[myID].angSpeed<-5 && locDirection>0))
		{
			vehicle[myID].targetSpeed=0; // brake
			
			// press brakes quarter down when rolling at high speed
			if (abs(vehicle[myID].angSpeed)>0.25 * vehicle[myID].properties.maxAngSpeed)
			{
				phcon_setmotor(vehicle[myID].wheelRL, nullvector, vector(vehicle[myID].angSpeed*0.5, 0.5 * vehicle[myID].properties.maxTorqueBrake,0), nullvector);
				phcon_setmotor(vehicle[myID].wheelRR, nullvector, vector(vehicle[myID].angSpeed*0.5, 0.5 * vehicle[myID].properties.maxTorqueBrake,0), nullvector);
				if (abs(vehicle[myID].angSpeed)> (0.50 * vehicle[myID].properties.maxAngSpeed) )
				{
					EventBrake(myID); // braking sound
				}
			} else {
				// we're already slow- apply max. units brake torque
				phcon_setmotor(vehicle[myID].wheelRL, nullvector, vector(0, vehicle[myID].properties.maxTorqueBrake,0), nullvector);
				phcon_setmotor(vehicle[myID].wheelRR, nullvector, vector(0, vehicle[myID].properties.maxTorqueBrake,0), nullvector);
			}
			
		} else {
			var oldSpeed; oldSpeed= vehicle[myID].targetSpeed;
			
			// regular driving
			if (locUseJoy) {
				vehicle[myID].targetSpeed = vehicle[myID].properties.maxAngSpeed * locDirection; //absolute value for stick
			} else {
				// speed up over time for keyboard (relative acceleration)
				vehicle[myID].targetSpeed += locDirection*accelKeyboard* time_step;
			}

			var locTorque = vehicle[myID].properties.maxTorque;
			
			// damp down speed over time if no key pressed
			if (!locUseJoy && locDirection==0) {
				vehicle[myID].targetSpeed *= 1-(time_step * 0.02);
				locTorque = vehicle[myID].properties.maxTorque / 2;
				
				// switch off opposing locTorque so we can roll down hills
				if (abs(vehicle[myID].linSpeed) < 20) {
					locTorque=0; 
					vehicle[myID].targetSpeed *= 0.5;
				}
				
			} else {
				// use less locTorque to slow down or else we'll spin
				if (locUseJoy && abs(vehicle[myID].targetSpeed) < abs(vehicle[myID].angSpeed)) {
					locTorque = vehicle[myID].properties.maxTorque / 2;
					//targetSpeed *=0.5;
				}
			}
			
			// max. reverse is 1/4 of forward speed
			vehicle[myID].targetSpeed= clamp(vehicle[myID].targetSpeed, -vehicle[myID].properties.maxAngSpeed*0.25, vehicle[myID].properties.maxAngSpeed);
			phcon_setmotor(vehicle[myID].wheelRL, nullvector, vector(vehicle[myID].targetSpeed, locTorque,0), nullvector);
			phcon_setmotor(vehicle[myID].wheelRR, nullvector, vector(vehicle[myID].targetSpeed, locTorque,0), nullvector);
		}
		
		// handbrake
		if (KEY_BRAKE|| JOY_BRAKE) { 
			phcon_setmotor(vehicle[myID].wheelRL, nullvector, vector(0, vehicle[myID].properties.maxTorqueBrake,0), nullvector);
			phcon_setmotor(vehicle[myID].wheelRR, nullvector, vector(0, vehicle[myID].properties.maxTorqueBrake,0), nullvector);
			
			if (abs(vehicle[myID].linSpeed) > 20) { 
				EventBrake(myID); // squeaky brakes
			}
		}

		EventJump(myEntity); // check for jump
		wait(1);
	}
}

//////////////////////////////////////////////////////////////////////////////////////////
// Continuously calculate wheel rotational speed and store in global var
//////////////////////////////////////////////////////////////////////////////////////////

function UpdateSpeed()
{ 
	int myID = (int)my.skill1;
	var locAngRL; var locAngRR;
	VECTOR locVecLin;
	var	locRotSign;
	var locPlayers_engine; //handle for soundloop
	
	while( (vehicle[myID].pRL == NULL) || (vehicle[myID].pRR == NULL) ) { wait(1); }

	locPlayers_engine = snd_loop(engine_wav, 100, 0); // play the engine sound in a loop
	snd_tune(locPlayers_engine, 80, 80, 0);  //80% of real frequency

	// check the rotation of the rear tires
	if(vehicle[myID].pRL.pan != vehicle[myID].pRR.pan) {
		// assume that the wheels are twisted 180 deg from each other
		locRotSign = -1;
	} else {
		locRotSign = 1;
	}

	while(1) {
		if (DoShutdown) { 	// quitting?
			snd_stop (locPlayers_engine);
			return;
		}

		// get linear speed and tune engine sound
		phent_getvelocity(vehicle[myID].pChassis, locVecLin, nullvector); //linear speed
		
		vehicle[myID].downSpeed = locVecLin.z * 0.091;
		locVecLin.z = 0; // store Z separately
		
		vehicle[myID].linSpeed= vec_length(locVecLin) * 0.091; // convert from quant/sec to kmh
		
		if (vehicle[myID].properties.maxAngSpeed > 0)
		{
			var locFreq = 300 * abs(vehicle[myID].angSpeed) / vehicle[myID].properties.maxAngSpeed;
			snd_tune(locPlayers_engine, 5, locFreq + 80, 0);  //80% of real frequency
		}

		// grab angular velocity vector from rear wheels
		
		phent_getangvelocity(vehicle[myID].pRL, temp); locAngRL = temp[1];
		phent_getangvelocity(vehicle[myID].pRR, temp); locAngRR = temp[1];

		// store largest one
		if (abs(locAngRL) > abs(locAngRR)) { 
			vehicle[myID].angSpeed = locAngRL; 
		} else {
			vehicle[myID].angSpeed = locRotSign * locAngRR; 
		}

    	wait(1);
	}
}

//////////////////////////////////////////////////////////////////////
// the faster we go the more sensitive the car gets to steering. to make it easier for user
// scale down his steering inputs the faster we go
//////////////////////////////////////////////////////////////////////

function CalcSteeringScale(int myID)
{
	return (1 - (abs(vehicle[myID].angSpeed) / (vehicle[myID].properties.maxAngSpeed+60)));
}

function SteerControl()
{ 
	ENTITY* myEntity = my;
	int myID = (int)my.skill1;	
	
	var locDirection = 0;		//delta change
	var locUseJoy;				//using joystick for input?
	
	while (1) {
		if (DoShutdown) { return; } // quitting?
		if (!DoSteering) // currently pausing?
		{
			wait(1); continue;
		}

		if (JOYRAW_X<-joyNullzone.x || JOYRAW_X>joyNullzone.x)
		{
			// treat gamepad as absolute device which is more
			// like a keyboard than a joystick
			if (USE_GAMEPAD) {
				locUseJoy = 0; 
				locDirection = (JOYRAW_X / 255);
				locDirection *= CalcSteeringScale(myID);
			} else {
				// using joystick
				locUseJoy = 1;
				locDirection = (JOYRAW_X - joyNullzone.x) / (255 - joyNullzone.x);
				locDirection *= CalcSteeringScale(myID);
			}
			
		} else {
			locUseJoy = 0;
			locDirection = (KEY_RIGHT-KEY_LEFT) + joy_force.x;
			locDirection *= CalcSteeringScale(myID);
		}

		// steering control
		if (locUseJoy == 0) {
			vehicle[myID].targetSteer += locDirection * accelSteering * time_step;
		} else {
			vehicle[myID].targetSteer = 30 * locDirection;
		}
		
		vehicle[myID].targetSteer = clamp(vehicle[myID].targetSteer, -30, 30);

		if ((vehicle[myID].angSpeed > vehicle[myID].properties.maxAngSpeed * 0.95) && abs(vehicle[myID].targetSteer) > 7) {
			EventBrake(myID); // play squeaky tires
		}

		if (locDirection != 0) {
			phcon_setparams2(vehicle[myID].wheelFL, vector(vehicle[myID].targetSteer, vehicle[myID].targetSteer, 0), nullvector, vector(vehicle[myID].properties.suspensionERP, vehicle[myID].properties.suspensionCFM, 0));
			phcon_setparams2(vehicle[myID].wheelFR, vector(vehicle[myID].targetSteer, vehicle[myID].targetSteer ,0), nullvector, vector(vehicle[myID].properties.suspensionERP, vehicle[myID].properties.suspensionCFM, 0));
		} else {
			vehicle[myID].targetSteer *= 1 - (time_step * 0.25);
			if (abs(vehicle[myID].targetSteer) < 1) { 
				vehicle[myID].targetSteer = 0; 
			} // force to 0
			
			phcon_setparams2(vehicle[myID].wheelFL, vector(vehicle[myID].targetSteer, vehicle[myID].targetSteer, 0), nullvector, vector(vehicle[myID].properties.suspensionERP, vehicle[myID].properties.suspensionCFM, 0));
			phcon_setparams2(vehicle[myID].wheelFR, vector(vehicle[myID].targetSteer, vehicle[myID].targetSteer, 0), nullvector, vector(vehicle[myID].properties.suspensionERP, vehicle[myID].properties.suspensionCFM, 0));
		}

		if (KEY_HORN || JOY_HORN)
		{
			EventHorn(); //sound horn
		}
		wait(1);
	}
}

///////////////////////////////////////////////////////////////////////////////
// CarControl: Assigning this to your car's chassis is all you have to do
///////////////////////////////////////////////////////////////////////////////

// skill1: Vehicle_ID 1
// skill2: Vehicle_type 1
action CarInit()
{
	int myID = my.skill1;
	 
	// build a car and wait til all wheels are ready
	ChassisInit();
	my.material = mat_metal;

	while( (vehicle[myID].pRL == NULL) || (vehicle[myID].pRR == NULL) ) { wait(1); }
	while( (vehicle[myID].pFL == NULL) || (vehicle[myID].pFR == NULL) ) { wait(1); }

	// Setup vehicle characteristics.
	
	
	UpdateSpeed(); // update rear wheel speed (continuous)
	SpeedControl();// change speed (continuous)
	SteerControl();// change heading (continuous)
}

physics_events.c

//////////////////////////////////////////////////////////////
// Program modified by Alessandro Manotti
// Original code taken from A7 buggy demo.
// Last-update: 24-Jan-2008
// History:
//   24-Jan-2008
//     * Original code
//////////////////////////////////////////////////////////////


//////////////////////////////////////////////////////////////////////////////////////////
// Particle and event function


///////////////////////////////////////////////////////////////////
// helper function: sets the vector to random direction   and length
function vec_randomize (var* vec, range) {
   vec[0] = random(1) - 0.5;
   vec[1] = random(1) - 0.5;
   vec[2] = random(1) - 0.5;
   vec_normalize(vec,random(range));
}

// helper function: fades out a particle
function part_alphafade(PARTICLE* p) {
	p.alpha -= time_step;
	p.size  += time_step;

	if (p.alpha <= 0) {
		p.lifespan = 0;
	} else {
		p.lifespan=5; 
	}
}

// particle function: generates a fading explosion into vel direction
function smokeAction(PARTICLE* p) {
   var locSpeed; 
   
   vec_randomize(temp, 1.5);
   
   // extract passed values
   locSpeed = p.vel_x;
   p.vel_x = 0;
   vec_add (p.vel_x, temp);
   p.bmap = bmpSmoke;
   set(p, MOVE);
   p.event = part_alphafade; // change to a shorter, faster function

   if (random(2) > 1) {
		 // every other particle is white
		set(p, BRIGHT);
   		p.alpha = 10 + random(10);
   		p.size = 1 + locSpeed * random(10);
   } else {
   		p.alpha = 5 + random(25);
   		p.size = 5 + locSpeed * random(30);
   }
}

//////////////////////////////////////////////////////////////////////////////////////////
//
function CreateTrack(ENTITY* myEntity) {
return(-1); // only for WMB blocks.

//	int myID; myID = (int)myEntity.skill1;
//	
//	// only create track if we have moved a bit
//	vec_set(temp, vehicle[myID].pChassis.x);
//	vec_sub(temp, vehicle[myID].lastPos);
//	if (vec_length(temp)<=(trackLen*0.2))
//	{
//		return ;
//	} else {
//		vec_set(lastPos, pChassis[whoami].x);
//	}
//
//	VECTOR pos;
//	vec_set(temp, vector(trackLen*0.5,0,0));
//	vec_rotate(temp, pChassis[whoami].pan);
//	vec_set(pos, temp);
//	vec_add(pos, pRL[whoami].x);
//	c_trace(pos, vector(pos.x, pos.y, -5),
//		IGNORE_PASSABLE+IGNORE_PASSENTS+IGNORE_MODELS+IGNORE_SPRITES
//	);
//	if (trace_hit)
//	{
//		target.z+=1; //raise above track
//		you=ptr_for_handle(arrpTracks[currTrack]);
//		vec_set(you.x, target); reset(you,INVISIBLE);
//		vec_set(you.pan, pChassis[whoami].pan); 	you.tilt+=90;
//		currTrack+=1; if (currTrack>=NUM_TRACKS) { currTrack=0; }
//	}
//	vec_set(pos, temp);
//	vec_add(pos, pRR[whoami].x);
//	c_trace(pos, vector(pos.x, pos.y, -5),
//		IGNORE_PASSABLE+IGNORE_PASSENTS+IGNORE_MODELS+IGNORE_SPRITES
//	);
//	if (trace_hit)
//	{
//		target.z+=1; //raise above track
//		you=ptr_for_handle(arrpTracks[currTrack]);
//		vec_set(you.x, target); reset(you,INVISIBLE);
//		vec_set(you.pan, pChassis[whoami].pan); 	you.tilt+=90;
//		currTrack+=1; if (currTrack>=NUM_TRACKS) { currTrack=0; }
//	}
}


//////////////////////////////////////////////////////////////////////////////////////////
//
function EventBrake(int myID) {
	var locWheelDir[3];
	var locSpeed; 
	
	if (!snd_playing(hTireSnd_n)) {
		hTireSnd_n = snd_play (tires_wav, 55, 0);
		
		while (snd_playing(hTireSnd_n)) {
			vec_set(locWheelDir, vector(-15, random(16)-8, -15));
			
			locSpeed = (abs(vehicle[myID].angSpeed) / (vehicle[myID].properties.maxAngSpeed));
			
			vec_rotate(locWheelDir, vehicle[myID].pChassis.pan);
			vec_set(temp, vehicle[myID].pRL.x); 
			vec_add(temp, locWheelDir);
			
			effect(smokeAction, 5, temp, vector(locSpeed, 0, 0));
			
			vec_set(temp, vehicle[myID].pRR.x);
			vec_add(temp, locWheelDir);
			
			effect(smokeAction, 5, temp, vector(locSpeed, 0, 0));
			//CreateTrack(myID);
			
			wait(random(4));
		}
	}
}

//////////////////////////////////////////////////////////////////////////////////////////
//
function EventJump(ENTITY* myEntity) {
	int myID = (int)myEntity.skill1;
	
	// jump!
	if ((KEY_JUMP || JOY_JUMP) && !vehicle[myID].jumping)
	{
		var locJumpTimer = 1;
	
		vehicle[myID].jumping = 1;
	
		snd_play (crash_wav, 70, 0);
		while (locJumpTimer > 0)
		{
			locJumpTimer -= time_step;
			vec_set(temp, vector(0, 0, (vehicle[myID].properties.massWheel * 4 + vehicle[myID].properties.massChassis) * vehicle[myID].properties.jumpForce));
			vec_rotate(temp, vehicle[myID].pChassis.pan);
			phent_addcentralforce(vehicle[myID].pFL, temp);
			phent_addcentralforce(vehicle[myID].pFR, temp);
			phent_addcentralforce(vehicle[myID].pRL, temp);
			phent_addcentralforce(vehicle[myID].pRR, temp);
			
			wait(1);
			if (DoShutdown) { return; } // quitting?
		}
		wait(-vehicle[myID].properties.jumpRechargeDelay); // wait a while before jumping again
		vehicle[myID].jumping = 0;
	}
}

//////////////////////////////////////////////////////////////////////////////////////////
//
//////////////////////////////////////////////////////////////////////////////////////////

var Horn_handle; //cg

function EventHorn() {
//	if (!hornPlaying) { cg
	if (!snd_playing (Horn_handle)) {
		//hornPlaying=1; cg
		Horn_handle = snd_play (horn_wav, 100, 0);
		//sleep(0.5); cg
		//hornPlaying=0; cg
    }
}

//////////////////////////////////////////////////////////////////////////////////////////
// check if we crashed at high speed, then disassemble car
//////////////////////////////////////////////////////////////////////////////////////////

var falp_handle; //cg

function EventImpact() {
	int myID = (int)my.skill1;
	
	if (!snd_playing (falp_handle)) {
		falp_handle=snd_play (flap_wav, 60, 0);
	} //cg
	
	if (!DoSteering || DoShutdown) { 
		EventHorn(); 
		return;
	}

	if (vehicle[myID].properties.bDentCar!=0 && vehicle[myID].linSpeed>(CRASH_SPEED*0.7) ) {

		//_/find closest vertex and dent the car\__________________
		//_________________________________________________________
		
		var i = 0; 
		var k = 0; 
		var temp99[3];
		
		var min_val=500; 
		var min_i;
		
		while (i < ent_vertices(my)) { 
			vec_for_vertex(temp99, my, i);
			//k=vec_dist(temp99, you.x);
			k=vec_dist(temp99, target.x);
			
			if(k<min_val) {
				min_val = k; 
				min_i = i;
			}
			i += 1;
		}
		
		vec_for_mesh(temp, my, min_i);
		vec_scale(temp, 0.9);
		vec_to_mesh(temp, my, min_i);
		ent_fixnormals(my, 0);
		//////////////////////////// 
	}

	if(vehicle[myID].linSpeed>CRASH_SPEED && abs(normal.z)<0.1) {// heads on into wall
		DoSteering = 0;
		snd_play(crash_wav, 100, 0);
		wait(1);
		
		phcon_remove(vehicle[myID].wheelFL); 
		phcon_remove(vehicle[myID].wheelFR);
		phcon_remove(vehicle[myID].wheelRL); 
		phcon_remove(vehicle[myID].wheelRR);
		phent_setdamping(vehicle[myID].pFL, 80, 50); 	
		phent_setdamping(vehicle[myID].pFR, 80, 50);
		phent_setdamping(vehicle[myID].pRL, 80, 50); 	
		phent_setdamping(vehicle[myID].pRR, 80, 50);
		phent_setdamping(vehicle[myID].pChassis, 80, 50);

		var locTimer = 80;
		while (locTimer > 0) {
			locTimer -= 1;
			vec_set(temp, vehicle[myID].pChassis.x); 
			//vec_add(temp, wheelDir);
			effect(smokeAction, 10, temp, vector(1, 0, 0));
			wait(-0.1);
		}
	}
}

physics_init.c

//////////////////////////////////////////////////////////////
// Program modified by Alessandro Manotti
// Original code taken from A7 buggy demo.
// Last-update: 24-Jan-2008
// History:
//   24-Jan-2008
//     * Original code
//////////////////////////////////////////////////////////////



//////////////////////////////////////////////////////////////
// INIT

STRING* dummy_pcx = "dummy.pcx";

//////////////////////////////////////////////////////////////
// setup all 4 wheels
function InitWheel() {
	int myID = (int)my.skill1;
	
//    set(my, SHADOW);
    
	if (vehicle[myID].commonWheelHeight <= -100000) {
		vehicle[myID].commonWheelHeight = my.z; // store z for other wheels to come
	} else {
		my.z = vehicle[myID].commonWheelHeight; // Set all wheels to same height
	}
	
	vec_set(my.skill51, my.x); 		// store pos/orientation for reset
	vec_set(my.skill54, my.pan);
	
	phent_settype(my, PH_RIGID, PH_SPHERE);
	phent_setmass(my, vehicle[myID].properties.massWheel, PH_SPHERE);
	phent_setgroup(my, 2);
	phent_setfriction(my, vehicle[myID].properties.fricWheel);
	phent_setdamping(my, 15, vehicle[myID].properties.dampWheel);
	phent_setelasticity(my, 0, 100); // bounciness
}

// skill1: Vehicle_ID 1
action FLInit() {
	int myID = my.skill1;	

	set(my,PASSABLE);
	
	while(!vehicle[myID].pChassis) { wait(1); }
	
	vehicle[myID].pFL = my;
	InitWheel();
	
	// make wheel constraint pointing up and towards center
	vehicle[myID].wheelFL = phcon_add(PH_WHEEL, vehicle[myID].pChassis, my);

	phcon_setparams1(vehicle[myID].wheelFL, my.x, vecUp, vecRight);
	phcon_setparams2(vehicle[myID].wheelFL, vector(0, 0, 0), nullvector, vector(vehicle[myID].properties.suspensionERP, vehicle[myID].properties.suspensionCFM, 0));
	reset(my,PASSABLE);
}

// skill1: Vehicle_ID 1
action FRInit() { 
	int myID = my.skill1;	

	set(my,PASSABLE);

	while (!vehicle[myID].pChassis) { wait(1); }
	
	vehicle[myID].pFR = my;
	InitWheel();
	
	// make wheel constraint pointing up and towards center
	vehicle[myID].wheelFR = phcon_add(PH_WHEEL, vehicle[myID].pChassis, my);

	phcon_setparams1(vehicle[myID].wheelFR, my.x, vecUp, vecRight);
	phcon_setparams2(vehicle[myID].wheelFR, vector(0,0,0), nullvector, vector(vehicle[myID].properties.suspensionERP, vehicle[myID].properties.suspensionCFM,0));
	reset(my,PASSABLE);
}

// skill1: Vehicle_ID 1
action RLInit() {  
	int myID = my.skill1;	

	set(my,PASSABLE);

	while (!vehicle[myID].pChassis) { wait(1); }

	vehicle[myID].pRL = my;
	InitWheel();
	
	// make wheel constraint pointing up and towards center
	vehicle[myID].wheelRL = phcon_add(PH_WHEEL, vehicle[myID].pChassis, my);

	phcon_setparams1(vehicle[myID].wheelRL, my.x, vecUp, vecRight);
	phcon_setparams2(vehicle[myID].wheelRL, vector(0,0,0), nullvector, vector(vehicle[myID].properties.suspensionERP, vehicle[myID].properties.suspensionCFM,0));
	reset(my,PASSABLE);
}

// skill1: Vehicle_ID 1
action RRInit() {
	int myID = my.skill1;	
	
	set(my,PASSABLE);
	
	while (!vehicle[myID].pChassis) { wait(1); }
	
	vehicle[myID].pRR = my;
	InitWheel();
	
	// make wheel constraint pointing up
	vehicle[myID].wheelRR = phcon_add(PH_WHEEL, vehicle[myID].pChassis, my);
	
	phcon_setparams1(vehicle[myID].wheelRR, my.x, vecUp, vecRight);
	phcon_setparams2(vehicle[myID].wheelRR, vector(0,0,0), nullvector, vector(vehicle[myID].properties.suspensionERP, vehicle[myID].properties.suspensionCFM,0));
	reset(my,PASSABLE);
}

//////////////////////////////////////////////////////////////
// create a whole bunch of track marks at startup
function InitMarks(ENTITY* myEntity) {
	int myID = (int)myEntity.skill1;
	
	while (!vehicle[myID].pChassis) { wait(1); }
	
	var locCounter = NUM_TRACKS - 1;
	
	while (locCounter >= 0) {
		you = ent_createlocal(dummy_pcx, vector(-5000, -900, 20), 0);
	
		vehicle[myID].arrpTracks[locCounter] = handle(you);
		locCounter -= 1 ;	//you.overlay= OFF;
		you.scale_y = vehicle[myID].trackLen; 
		you.scale_x = 10;
		set(you, INVISIBLE|PASSABLE|TRANSLUCENT|UNLIT); 
		you.pan = 0.1;
		you.alpha = 90;
	}
}

//////////////////////////////////////////////////////////////
// construct car
function EventImpact();

function ChassisInit() {
		int myID = (int)my.skill1;

		DoShutdown = 0;
		DoSteering = 1;
		
		vec_set(my.skill51, my.x); 		// store pos/orientation for reset
		vec_set(my.skill54, my.pan);
		
		pFocus = my;
		
		set(my, SHADOW|PASSABLE);
		
		vec_set(vecRight, vector(0, -1, 0));		// reset right vector (in case of level restart)
		vec_rotate(vecRight, my.pan); 				// rotate right vector to correspond to car orientation
		phent_settype(my, PH_RIGID, PH_BOX);

		phent_setmass(my, vehicle[myID].properties.massChassis, PH_BOX);

		phent_setgroup(my, 2); 						// all car parts in group2 ->no Collisions
		phent_setfriction(my, vehicle[myID].properties.fricChassis);
		phent_setdamping(my, vehicle[myID].properties.dampChassis, vehicle[myID].properties.dampChassis);
		phent_setelasticity(my, 10, 100); 			// little bouncing

	 	vehicle[myID].pChassis = my;
	 	
		// set event handler if we hit something
		vehicle[myID].pChassis.event = EventImpact; 
		vehicle[myID].pChassis.emask = ENABLE_FRICTION;

		while( (vehicle[myID].pRL == NULL) || (vehicle[myID].pRR == NULL) ) { wait(1); }
		while( (vehicle[myID].pFL == NULL) || (vehicle[myID].pFR == NULL) ) { wait(1); }

		reset(my,PASSABLE);
		
		ph_setgravity(vector(0, 0, -gravity));
		
		InitMarks(my);

		// wait for shutdown and remove everything
		while (1) {
			if (DoShutdown) {
				var i;
				
				vehicle[myID].targetSpeed = 0; 
				vehicle[myID].angSpeed = 0;
				
				ph_setgravity(vector(0, 0, 0));
				phcon_remove(vehicle[myID].wheelFL); 
				phcon_remove(vehicle[myID].wheelFR);
				phcon_remove(vehicle[myID].wheelRL);
				phcon_remove(vehicle[myID].wheelRR);
				
				phent_settype(vehicle[myID].pChassis, 0, 0);
				phent_settype(vehicle[myID].pFL, 0, 0); 
				phent_settype(vehicle[myID].pFR, 0, 0);
				phent_settype(vehicle[myID].pRL, 0, 0); 
				phent_settype(vehicle[myID].pRR, 0, 0);
				
				return;
			}
			wait(-0.5);
		}
}

HOW TO USE IT?

Its usage is very easy.

STEP 1

Decide how many cars at the same time, maximum, you will have in your application. You should not indicate the maximum number of cars in total in your program, but only the ones that will work at the same time. Modify the #define in the source code physics_car.c:

#define MAX_CARS_COUNT 12		// Max number of cars available at the same time.
#define MAX_CARS_DETAILS_COUNT 3	// Max number of different car types (off-road/sport/etc...)

In this case you will be able to use 12 cars at the same time, and 3 different cars setup (e.g.: a sport-car, a jeep, a big-foot).

STEP 2

Pleas check two key-structs, called TVehicle and TVehicleProperties. The first struct contains all info regarding a vehicle (you will not need to "interact" with it; this struct contains internal runtime vehicle parameters). The second struct is very important for you, in-fact you need to fill-in it in order to define physic information of a specific car type. Fill the data regarding the vehicle details using the code contained in function game_startup() (physics_car.c).

STEP 3

Now you can simply create the vehicles in WED, and assigning the actions the the chassis and tires. Finally assign a number to skill1 - Vehicle ID, starting from 0 (zero). NOTE: The same id will identify the same vehicle. E.g.: a car could have a chassis with code "1", then four tires with the same code "1".


Traction: Rear or 4WD!

Next step is creating a car with a rear traction, front traction, or 4WD. Well, it is very easy. In the source code "physics_car.c" look for "// max. reverse is 1/4 of forward speed" (without apex!). You will find the following code:

// max. reverse is 1/4 of forward speed
vehicle[myID].targetSpeed= clamp(vehicle[myID].targetSpeed, -vehicle[myID].properties.maxAngSpeed*0.25, vehicle[myID].properties.maxAngSpeed);
phcon_setmotor(vehicle[myID].wheelRL, nullvector, vector(vehicle[myID].targetSpeed, locTorque,0), nullvector);
phcon_setmotor(vehicle[myID].wheelRR, nullvector, vector(vehicle[myID].targetSpeed, locTorque,0), nullvector);

If you want to add 4WD system, add the following lines:

if(activate4Tires == 1) {
   //FRONT
   phcon_setmotor(vehicle[myID].wheelFL, nullvector, vector(vehicle[myID].targetSpeed, locTorque,0), nullvector);
   phcon_setmotor(vehicle[myID].wheelFR, nullvector, vector(vehicle[myID].targetSpeed, locTorque,0), nullvector);
}

In this way you can use the variable "activate4Tires" to insert/deactivate 4WD system! It's great!



Thank you.

Author: Alessandro Manotti


Original post: [1]

Personal tools