Manual Shift Car
Breakdown
This is a convertion of ONSWheeledCraft. This class allows for a stick shift or otherwise known as a manual transmission car. Why stick shift?
- It doesn't cost much in terms of fps
- Lets you rev it in neutral
- Fun to drive
- Will allow rear/front/all wheel drive
- Holding the brake and the rear wheels keep spinning (no rear brakes)
- I hope you can use what I (CIpen) have created
The Goods
To start the class off
class WheeledClutch_Brake extends ONSVehicle;
Critical variables
var() float WheelSoftness; var() float WheelPenScale; var() float WheelPenOffset; var() float WheelRestitution; var() float WheelAdhesion; var() float WheelInertia; // I believe this is how much to resist change in motion //I(CIpen) made these an array so we could have different slips on each surface var() array<float> WheelLongSlip; // I think this is so we can have the wheels slip when you take off var() array<InterpCurve> WheelLongFrictionFunc;//this is the curve for how slippery the wheels are allong the X axis(forwards slip) var() array<InterpCurve> WheelLatSlipFunc;//this is the curve for how slippery the wheels are allong the Y axis(left/right slip) var() array<float> WheelLongFrictionScale; // quick way to change the ammount of friction without having to draw out var() array<float> WheelLatFrictionScale; // a new friction curve var() float WheelHandbrakeSlip; var() float WheelHandbrakeFriction; var() float WheelSuspensionTravel; var() float WheelSuspensionOffset; var() float WheelSuspensionMaxRenderTravel; var() float FTScale; var() float ChassisTorqueScale; var() float MinBrakeFriction; var() InterpCurve MaxSteerAngleCurve; // degrees, this makes the wheels turn like real car wheels* var() float GearRatios[8]; // 0 is reverse, 1-4 f var() int NumForwardGears; var() float TransRatio; // Other(constant)gears i.e. so we can have driveshaft ratio(if one applies) var() float ChangeUpPoint; //EngineRPM that signals gear up change var() float ChangeDownPoint; //EngineRPM that signals gear down change var int bGearUp; var int bGearDown; var() float LSDFactor; var() float EngineBrakeFactor; var() float EngineBrakeRPMScale; var() float MaxBrakeTorque; var() float SteerSpeed; // How fast it turns var() float TurnDamping; var() float StopThreshold; var() float HandbrakeThresh; var() float EngineInertia; // Pre-gear box engine (piston mass) var bool bClutching; // If holding the clutch var bool bOldClutching; var() float IdleRPM; //RPM to idle at var() float EngineRPMSoundRange; //RPM sound range var() name SteerBoneName; var() EAxis SteerBoneAxis; var() float SteerBoneMaxAngle; // degrees var float OutputBrake; //How much we are braking var float OutputGas; //How much are we giving gas var float OutputPitch; var bool OutputHandbrake; var int Gear; //What gear we are in var float ForwardVel; var bool bIsInverted; var bool bIsDriving; var float NumPoweredWheels; var float NeutralRPM; var InterpCurve RPMtoGas; // This is what I use to convert the current in gear RPM to the amount of gas so the RPM doesn't go to zero when we hit the clutch var() InterpCurve TorqueCurve; // Engine output torque var() InterpCurve EngineS; var() InterpCurve BrakeCurve; //This simulates more lifelike braking, where you apply braking var float Gas, Gas2, hBrake; var float RPM2; //Neutral RPM var float RPM; //Neutral RPM var float NRPM; var bool bBraking; //if we are braking var bool bThrot, bNThrot, bNoGas; var bool bInGear; //if we are completly in gear var bool bRadians; var float TotalSpinVel; var float EngineRPM; var float WheelRPM;// used to get the wheel RPM at anytime(so we don't change EngineRPM which is what the engine sound is tied to) var float CarMPH; var float ETorque; var float ActualSteering; var float SteerBoneAngle; var float EnginePitch; var Vector worldForward; var Vector worldRight; var Vector worldUp; var Matrix carTM; var Rotator SteerRot; var bool bCurrentOnGround; var float DeltaPitch; var float DeltaHeading; var float DeltaRoll; var float VRate; var vector one, two, three; var vector Dist1, Dist2; // unused but this was for doing the daredevil stuff var float Dist3; var bool bBrakeFrontWheelsOnly, bBrakeFrontWheels; var Vector UDForce, UDTorque; var Vector WForce, WTorque; var Coords WheelCoords; var Coords OldCoords; var Coords Coords; var Vector ForwardsInOldPlane; var Pawn OldDriver; struct native SCarState { var vector ChassisPosition; var Quat ChassisQuaternion; var vector ChassisLinVel; var vector ChassisAngVel; var byte ServerHandbrake; var byte ServerBrake; var byte ServerGas; var byte ServerGear; var byte ServerSteering; var int ServerViewPitch; var int ServerViewYaw; }; var byte FudgeByte; var SCarState CarState; var SCarState OldCarState; var KRigidBodyState ChassisState; var bool bNewCarState; var bool bOldVehicleOnGround; var float TheDeltaTime;
Not so critical variables
var class<CameraEffect> myBlur; var myMotionBlur ClientMotionBlur; var array<ONSDirtSlipEffect> Dust; // FL, FR, RL, RR var() float DustSlipRate; var() float DustSlipThresh; var() float RevMeterPosX; var() float RevMeterPosY; var() float RevMeterScale; var() float RevMeterSizeY; var() bool bDoStuntInfo; var() bool bAllowAirControl; var() bool bAllowChargingJump; var bool bAllowBigWheels; var bool bPushDown; //jump is being charged var bool bOldPushDown; var bool bAllWheelsOnGround; var() float MaxJumpForce; var float JumpForce; var() float MaxJumpSpin; var float JumpSpin; var() float JumpChargeTime; var float DesiredJumpForce; //used by AI var string JumpFeedbackForce; var sound JumpSound; var() float JumpMeterOriginX; var() float JumpMeterOriginY; var() float JumpMeterWidth; var() float JumpMeterHeight; var() float JumpMeterSpacing; var() color JumpMeterColor; var() color SpinMeterColor; var Texture JumpMeterTexture; var() float AirTurnTorque; var() float AirPitchTorque; var() float AirPitchDamping; var() float AirRollTorque; var() float AirRollDamping; var() float MinAirControlDamping; var float FenderBenderSpeed; var() bool bMakeBrakeLights; var() vector BrakeLightOffset[2]; var ONSBrakelightCorona BrakeLight[2]; var() Material BrakeLightMaterial; var Rotator OldRotation; var Vector LastOnGroundLocation; var float LastOnGroundTime; var float InAirSpin; // Degrees var float InAirPitch; // Degrees var float InAirRoll; // Degrees var float InAirTime; // Second var float InAirDistance; // Meters var int DaredevilPoints; var config int IntSteerBoneAngle; var() float DaredevilThreshInAirSpin; var() float DaredevilThreshInAirPitch; var() float DaredevilThreshInAirRoll; var() float DaredevilThreshInAirTime; var() float DaredevilThreshInAirDistance; var() class<LocalMessage> DaredevilMessageClass;
Replication
replication { reliable if (Role == ROLE_Authority) CarState, FudgeByte; // make sure we get what we need to the client reliable if (bNetInitial && Role == ROLE_Authority) bAllowAirControl, bAllowChargingJump; // showoff stuff reliable if (bNetInitial && bDoStuntInfo && Role == ROLE_Authority) DaredevilThreshInAirDistance, DaredevilThreshInAirTime, DaredevilThreshInAirSpin, DaredevilThreshInAirPitch, DaredevilThreshInAirRoll; }
Functions
- PostNetBeginPlay
simulated function PostNetBeginPlay() { local int i; NumPoweredWheels = 0.0; for(i=0; i<=1; i++) { NumPoweredWheels += 1.0; } SVehicleUpdateParams(); Super.PostNetBeginPlay(); }
- PostNetReceive
- Here is were we deal with stuff coming from the server.
simulated function PostNetReceive() { Super.PostNetReceive(); if(OldCarState.ChassisPosition == CarState.ChassisPosition && OldCarState.ChassisQuaternion.X == CarState.ChassisQuaternion.X && OldCarState.ChassisQuaternion.Y == CarState.ChassisQuaternion.Y && OldCarState.ChassisQuaternion.Z == CarState.ChassisQuaternion.Z && OldCarState.ChassisQuaternion.W == CarState.ChassisQuaternion.W && OldCarState.ChassisLinVel == CarState.ChassisLinVel && OldCarState.ChassisAngVel == CarState.ChassisAngVel && OldCarState.ServerHandbrake == CarState.ServerHandbrake && OldCarState.ServerBrake == CarState.ServerBrake && OldCarState.ServerGas == CarState.ServerGas && OldCarState.ServerGear == CarState.ServerGear && OldCarState.ServerSteering == CarState.ServerSteering && OldCarState.ServerViewPitch == CarState.ServerViewPitch && OldCarState.ServerViewYaw == CarState.ServerViewYaw) return; ChassisState.Position.X = CarState.ChassisPosition.X; ChassisState.Position.Y = CarState.ChassisPosition.Y; ChassisState.Position.Z = CarState.ChassisPosition.Z; ChassisState.Quaternion = CarState.ChassisQuaternion; ChassisState.LinVel.X = 0.1f * CarState.ChassisLinVel.X; ChassisState.LinVel.Y = 0.1f * CarState.ChassisLinVel.Y; ChassisState.LinVel.Z = 0.1f * CarState.ChassisLinVel.Z; ChassisState.AngVel.X = 0.001f * CarState.ChassisAngVel.X; ChassisState.AngVel.Y = 0.001f * CarState.ChassisAngVel.Y; ChassisState.AngVel.Z = 0.001f * CarState.ChassisAngVel.Z; bNewCarState = true; OldCarState.ChassisPosition = CarState.ChassisPosition; OldCarState.ChassisQuaternion = CarState.ChassisQuaternion; OldCarState.ChassisLinVel = CarState.ChassisLinVel; OldCarState.ChassisAngVel = CarState.ChassisAngVel; OldCarState.ServerHandbrake = CarState.ServerHandbrake; OldCarState.ServerBrake = CarState.ServerBrake; OldCarState.ServerGas = CarState.ServerGas; OldCarState.ServerGear = CarState.ServerGear; OldCarState.ServerSteering = CarState.ServerSteering; OldCarState.ServerViewPitch = CarState.ServerViewPitch; OldCarState.ServerViewYaw = CarState.ServerViewYaw; OutputPitch = RangeByteToFloat(CarState.ServerHandbrake); OutputHandbrake = (OutputPitch > 0.01); OutputBrake = RangeByteToFloat(CarState.ServerBrake); OutputGas = RangeByteToFloat(CarState.ServerGas); Gear = CarState.ServerGear; Steering = RangeByteToFloat(CarState.ServerSteering); DriverViewPitch = CarState.ServerViewPitch; DriverViewYaw = CarState.ServerViewYaw; }
- PrecacheAnnouncer
simulated function PrecacheAnnouncer(AnnouncerVoice V, bool bRewardSounds) { if (bRewardSounds && !bSoundsPrecached) V.PrecacheSound('fender_bender'); Super.PrecacheAnnouncer(V, bRewardSounds); }
- KUpdateState
- This function tells when we should update.
event bool KUpdateState(out KRigidBodyState newState) { if(Role == ROLE_Authority || !bNewCarState) return false; newState = ChassisState; bNewCarState = false; return true; }
- SVehicleUpdateParams
event SVehicleUpdateParams() { local int i; Super.SVehicleUpdateParams(); for(i=0; i<Wheels.Length; i++) { Wheels[i].Softness = WheelSoftness; Wheels[i].PenScale = WheelPenScale; Wheels[i].PenOffset = WheelPenOffset; Wheels[i].LongSlip = WheelLongSlip[0]; Wheels[i].LatSlipFunc = WheelLatSlipFunc[0]; Wheels[i].Restitution = WheelRestitution; Wheels[i].Adhesion = WheelAdhesion; Wheels[i].WheelInertia = WheelInertia; Wheels[i].LongFrictionFunc = WheelLongFrictionFunc[0]; Wheels[i].HandbrakeFrictionFactor = WheelHandbrakeFriction; Wheels[i].HandbrakeSlipFactor = WheelHandbrakeSlip; Wheels[i].SuspensionTravel = WheelSuspensionTravel; Wheels[i].SuspensionOffset = WheelSuspensionOffset; Wheels[i].SuspensionMaxRenderTravel = WheelSuspensionMaxRenderTravel; } if(Level.NetMode != NM_DedicatedServer && bMakeBrakeLights) { for(i=0; i<2; i++) { if (BrakeLight[i] != None) { BrakeLight[i].SetBase(None); BrakeLight[i].SetLocation( Location + (BrakelightOffset[i] >> Rotation) ); BrakeLight[i].SetBase(self); BrakeLight[i].SetRelativeRotation( rot(0,32768,0) ); BrakeLight[i].Skins[0] = BrakeLightMaterial; } } } }
- UpdateVehicle
- This is the function were everything happens
Also see Manual Shift Car/C++ Version
function UpdateVehicle(float DeltaTime) { local Matrix carTM; local Vector worldUp, worldRight, worldForward; local KarmaParams KP; local KRigidBodyState rbState; local int i; local float maxSteerAngle, maxSteer, deltaSteer; local float DriveTorque, GripTorque, EngineTorque, EngineBraking, EngineWheelRatio; local float BrakeTorque; local float NewTotalSpinVel, LSDSplit, EvenSplit, UseSplit; local float TransInertia; local float LimitBrakeTorque; local float WheelTorque, VehicleForce, TransAcc; local float TurnAngVel, DampingScale, TurnDampingMag; local float PitchAngVel, RollAngVel, PitchDampingMag, RollDampingMag; local Vector AirControlTorque, AngVel; local float VRate; local Vector Force, Torque; local int z; WForce = vect(0, 0, 0); WTorque = vect(0, 0, 0); if(!KIsAwake()) return; KP = KarmaParams(KParams); if(KP == None) return; if(Controller != None) { //Calc up (z), right(y) and forward (x) vectors GetAxes(Rotation, worldRight, worldForward, worldUp); /////////// STEERING /////////// maxSteerAngle = InterpCurveEval(MaxSteerAngleCurve, VRate); if(maxSteerAngle==0) maxSteer = DeltaTime * SteerSpeed * maxSteerAngle; else maxSteer = DeltaTime * SteerSpeed; deltaSteer = (-Steering * maxSteerAngle) - ActualSteering; // Amount we want to move (target - current) deltaSteer = Clamp(deltaSteer, -maxSteer, maxSteer); ActualSteering += deltaSteer; if(bAuto==True) { EngineTorque = OutputGas * InterpCurveEval(TorqueCurve, EngineRPM); EngineBraking = (1.0f - OutputGas) * (EngineBrakeRPMScale*EngineRPM * EngineBrakeRPMScale*EngineRPM * EngineBrakeFactor); EngineTorque -= EngineBraking; EngineWheelRatio = GearRatios[Gear] * TransRatio; NewTotalSpinVel=0.0; EngineRPM = 0.0; for(i=0; i<Wheels.length; i++) { EvenSplit = 1/NumPoweredWheels; if(TotalSpinVel > 0.1) LSDSplit = (TotalSpinVel - Wheels[i].SpinVel)/((NumPoweredWheels-1) * TotalSpinVel); else LSDSplit = EvenSplit; UseSplit = ((1-LSDFactor) * EvenSplit) + (LSDFactor * LSDSplit); DriveTorque = UseSplit * (EngineTorque / EngineWheelRatio); GripTorque = FTScale * Wheels[i].WheelRadius * Wheels[i].TireLoad * WheelLongFrictionScale[0] * InterpCurveEval(Wheels[i].LongFrictionFunc, Abs(Wheels[i].SlipVel)); if(Wheels[i].SlipVel < 0.0) GripTorque *= -1.0; TransInertia = (EngineInertia / Abs(GearRatios[Gear] * TransRatio)) + Wheels[i].WheelInertia; if(Wheels[i].SpinVel > 0.0) BrakeTorque = -OutputBrake * MaxBrakeTorque; else BrakeTorque = OutputBrake * MaxBrakeTorque; LimitBrakeTorque = ( Abs(Wheels[i].SpinVel) * TransInertia ) / TheDeltaTime; // Size of torque needed to completely stop wheel spinning. BrakeTorque = Clamp(BrakeTorque, -LimitBrakeTorque, LimitBrakeTorque); // Never apply more than this! WheelTorque = DriveTorque + BrakeTorque - GripTorque; VehicleForce = GripTorque / (FTScale * Wheels[i].WheelRadius); if( OutputBrake > 0.0 || (DriveTorque + BrakeTorque) * Wheels[i].SpinVel < 0.0) { Wheels[i].DriveForce = 0.0; Wheels[i].LongFriction = Abs(VehicleForce) + (OutputBrake * MinBrakeFriction); } else { Wheels[i].DriveForce = VehicleForce; Wheels[i].LongFriction = 0.0; } if (Wheels[i].bWheelOnGround) Wheels[i].ChassisTorque = -1.0 * (DriveTorque + BrakeTorque) * ChassisTorqueScale; else Wheels[i].ChassisTorque = 0.0; TransAcc = WheelTorque / TransInertia; Wheels[i].SpinVel += TransAcc * DeltaTime; if(Gear == 0 && Wheels[i].SpinVel > 0.0) Wheels[i].SpinVel = 0.0; else if(Gear > 0 && Wheels[i].SpinVel < 0.0) Wheels[i].SpinVel = 0.0; NewTotalSpinVel += Wheels[i].SpinVel; EngineRPM += Wheels[i].SpinVel / EngineWheelRatio; Wheels[i].LatFriction = WheelLatFrictionScale[0] * Wheels[i].TireLoad; Wheels[i].LatSlip = InterpCurveEval(Wheels[i].LatSlipFunc, Wheels[i].SlipAngle); if(OutputHandbrake && Wheels[i].bHandbrakeWheel) { Wheels[i].LatFriction *= Wheels[i].HandbrakeFrictionFactor; Wheels[i].LatSlip *= Wheels[i].HandbrakeSlipFactor; } if(Wheels[i].SteerType == VST_Steered) Wheels[i].Steer = ActualSteering; else if(Wheels[i].SteerType == VST_Inverted) Wheels[i].Steer = -ActualSteering; else Wheels[i].Steer = 0.0; } EngineRPM /= NumPoweredWheels; EngineRPM = Max(EngineRPM, 0.01); // ensure always positive! TotalSpinVel = NewTotalSpinVel; } ///////////////STICK SHIFT/////////////////////////////// else { if(bClutching) { EngineTorque = InterpCurveEval(TorqueCurve, EngineRPM); } else { EngineTorque = InterpCurveEval(TorqueCurve, EngineRPM); if(EngineTorque >= 0) { // Calculate torque at output of engine. Combination of throttle, current RPM and engine braking. EngineTorque = OutputGas * InterpCurveEval(TorqueCurve, EngineRPM); } else if(EngineTorque < 0) { EngineTorque = InterpCurveEval(TorqueCurve, EngineRPM); } EngineBraking = (1.0f - OutputGas) * (EngineBrakeRPMScale*EngineRPM * EngineBrakeRPMScale*EngineRPM * EngineBrakeFactor); } // EngineRPM = OutputGas * InterpCurveEval(RPMCurve, EngineTorque); EngineTorque -= EngineBraking; ETorque = EngineTorque; //DebugInfo = FString::Printf(TEXT("OutputBrake: %f EngineRPM: %f EngineTorque: %f"), OutputBrake, EngineRPM, EngineTorque); // Total gear ratio between engine and differential (ie before being split between wheels). // A higher gear ratio and the torque at the wheels is reduced. EngineWheelRatio = GearRatios[Gear] * TransRatio; if(bClutching) { //EngineRPM = 0.0; WheelRPM = 0.0; } else { if(Wheels[i].bPoweredWheel) { // Reset engine RPM. We calculate this by adding the component of each wheel spinning. NewTotalSpinVel=0.0; if(bInGear) { WheelRPM = 0.0; EngineRPM = 0.0; } else if(!bInGear) WheelRPM = 0.0; } } // Do model for each wheel. // Okay this needs to be fixed because the way this is, all the wheels get power for(i=0;i<Wheels.length; i++) { //Wheels[i]; /////////// DRIVE /////////// // Heuristic to divide torque up so that the wheels that are spinning slower get more of it. // Sum of LSDFactor across all wheels should be 1. // JTODO: Do we need to handle the case of vehicles with different size wheels? EvenSplit = 1/NumPoweredWheels; // If no wheels are spinning, just do an even split. if(TotalSpinVel > 0.1) LSDSplit = (TotalSpinVel - Wheels[i].SpinVel)/((NumPoweredWheels-1) * TotalSpinVel); else LSDSplit = EvenSplit; UseSplit = ((1-LSDFactor) * EvenSplit) + (LSDFactor * LSDSplit); if(bClutching) { EngineRPM = RPM; //DriveTorque = UseSplit * (EngineTorque / EngineWheelRatio); } else { if(Wheels[i].bPoweredWheel) { //EngineRPM = RPM; // Calculate Drive Torque : applied at wheels (ie after gearbox and differential) // This is an 'open differential' ie. equal torque to each wheel DriveTorque = UseSplit * (EngineTorque / EngineWheelRatio); } } /////////// LONGITUDINAL /////////// // Calculate Grip Torque : longitudinal force against ground * distance of action (radius of tyre) // LongFrictionFunc is assumed to be reflected for negative Slip Ratio GripTorque = FTScale * Wheels[i].WheelRadius * Wheels[i].TireLoad * WheelLongFrictionScale[0] * InterpCurveEval(Wheels[i].LongFrictionFunc, Abs(Wheels[i].SlipVel)); if(Wheels[i].SlipVel < 0.0) GripTorque *= -1.0; if(Wheels[i].bPoweredWheel) { // GripTorque can't be more than the torque needed to invert slip ratio. TransInertia = (EngineInertia / Abs(GearRatios[Gear] * TransRatio)) + Wheels[i].WheelInertia; } //FLOAT SlipAngVel = Wheels[i].SlipVel/Wheels[i].WheelRadius; if(bBraking) { // Brake torque acts to stop wheels (ie against direction of motion) BrakeTorque = 0.0; if(Wheels[i].SpinVel > 0.0) BrakeTorque = -OutputBrake * MaxBrakeTorque; else BrakeTorque = OutputBrake * MaxBrakeTorque; LimitBrakeTorque = ( Abs(Wheels[i].SpinVel) * TransInertia ) / TheDeltaTime; // Size of torque needed to completely stop wheel spinning. BrakeTorque = Clamp(BrakeTorque, -LimitBrakeTorque, LimitBrakeTorque); // Never apply more than this! mBrakeTorque = BrakeTorque; } else { BrakeTorque = 0; mBrakeTorque = BrakeTorque; } //_____________________________________________ if(bClutching) { WheelTorque = GripTorque; //Log("GripTorque"); //Log(GripTorque); } else // we are in full gear { if(!Wheels[i].bPoweredWheel) { //Don't do this if 4 wheel drive!!!! WheelTorque = GripTorque * 2; } else { // Resultant torque at wheel : torque applied from engine + brakes + equal-and-opposite from tire-road interaction. WheelTorque = DriveTorque + BrakeTorque - GripTorque; /* Log("WheelTorque"); Log(WheelTorque); Log("GripTorque"); Log(GripTorque); */ } } //____________________________________________ // Resultant linear force applied to car. (GripTorque applied at road) VehicleForce = GripTorque / (FTScale * Wheels[i].WheelRadius); // If the wheel torque is opposing the direction of spin (ie braking) we use friction to apply it. if( OutputBrake > 0 && bBraking)// || (DriveTorque + BrakeTorque) * Wheels[i].SpinVel < 0.0 { Wheels[i].DriveForce = 0.0; Wheels[i].LongFriction = Abs(VehicleForce) + (OutputBrake * MinBrakeFriction); } else if(bClutching) { Wheels[i].DriveForce = 0.0; Wheels[i].LongFriction = 0.0; } else { if(!Wheels[i].bPoweredWheel) { Wheels[i].DriveForce = 0.0; Wheels[i].LongFriction = 0.0; } else { Wheels[i].DriveForce = VehicleForce; Wheels[i].LongFriction = 0.0; //Wheels[i].LongFriction = 0.0; } } if(Wheels[i].bPoweredWheel) { // Calculate torque applied back to chassis if wheel is on the ground if (Wheels[i].bWheelOnGround) Wheels[i].ChassisTorque = -1.0 * (DriveTorque + BrakeTorque) * ChassisTorqueScale; else Wheels[i].ChassisTorque = 0.0; } // Calculate new wheel speed. // The lower the gear ratio, the harder it is to accelerate the engine. if(bClutching) { TransAcc = WheelTorque / -1; Wheels[i].SpinVel += TransAcc * DeltaTime; } else { if(!Wheels[i].bPoweredWheel) { TransAcc = WheelTorque / -1; Wheels[i].SpinVel += TransAcc * DeltaTime; } else { TransAcc = WheelTorque / TransInertia; Wheels[i].SpinVel += TransAcc * DeltaTime; } } // -----this is were the engine needs to be slowly put into gear----- if(bClutching) { WheelRPM += (Wheels[i].SpinVel / EngineWheelRatio); //TorqueConverter(); } else // we are in full gear { if(!Wheels[i].bPoweredWheel) { WheelRPM += (Wheels[i].SpinVel / EngineWheelRatio); } else { // Accumulate wheel spin speeds to find engine RPM. // The lower the gear ratio, the faster the engine spins for a given wheel speed. NewTotalSpinVel += Wheels[i].SpinVel; if(!bInGear) { // EngineRPM += (Wheels[i].SpinVel / EngineWheelRatio); WheelRPM += (Wheels[i].SpinVel / EngineWheelRatio); TorqueConverter(); } if(bInGear) { WheelRPM += (Wheels[i].SpinVel / EngineWheelRatio); EngineRPM += (Wheels[i].SpinVel / EngineWheelRatio); } } } // --------------------------------------------------------------- // Make sure the wheel can't spin in the wrong direction for the current gear. if(bClutching) { } else { if(Wheels[i].bPoweredWheel) { if(Gear == 0 && Wheels[i].SpinVel > 0.0) Wheels[i].SpinVel = 0.0; else if(Gear > 0 && Wheels[i].SpinVel < 0.0) Wheels[i].SpinVel = 0.0; } } /////////// LATERAL /////////// if(!Wheels[i].bPoweredWheel) { Wheels[i].LatFriction = (WheelLatFrictionScale[0] * 1.5) * Wheels[i].TireLoad ; Wheels[i].LatSlip = InterpCurveEval(Wheels[i].LatSlipFunc, Wheels[i].SlipAngle); } else { Wheels[i].LatFriction = WheelLatFrictionScale[0] * Wheels[i].TireLoad; Wheels[i].LatSlip = InterpCurveEval(Wheels[i].LatSlipFunc, Wheels[i].SlipAngle); } if(OutputHandbrake && Wheels[i].bHandbrakeWheel) { Wheels[i].LatFriction *= Wheels[i].HandbrakeFrictionFactor; Wheels[i].LatSlip *= Wheels[i].HandbrakeSlipFactor; } /////////// STEERING /////////// // Pass on steering to wheels that want it. if(Wheels[i].SteerType == VST_Steered) Wheels[i].Steer = ActualSteering; else if(Wheels[i].SteerType == VST_Inverted) Wheels[i].Steer = -ActualSteering; else Wheels[i].Steer = 0.0; } } if(bClutching) { } else { // EngineRPM is in radians per second, want in revolutions per minute EngineRPM /= 4; // EngineRPM /= 2.0 * PI; // revs per sec // EngineRPM *= 60; EngineRPM = Max(EngineRPM, 0.00); // ensure always positive! RPM /= NumPoweredWheels; // RPM /= 2.0 * PI; // revs per sec // RPM *= 60; RPM = Max(EngineRPM, 0.00); // ensure always positive! } if(!bClutching) { // Update total wheel spin vel TotalSpinVel = NewTotalSpinVel; } // Turn (yaw) damping. carTM = CachedLocalToWorld; // was carTM = LocalToWorld() //worldUp(carTM.M[2][0], carTM.M[2][1], carTM.M[2][2]); //worldRight(carTM.M[1][0], carTM.M[1][1], carTM.M[1][2]); //worldForward(carTM.M[0][0], carTM.M[0][1], carTM.M[0][2]); // KGetRigidBodyState(rbState); // AngVel = KRBVecToVector(rbState.AngVel); AngVel.X = rbState.AngVel.X; AngVel.Y = rbState.AngVel.Y; AngVel.Z = rbState.AngVel.Z; TurnAngVel = AngVel dot worldUp; DampingScale = 1.0 - MinAirControlDamping; if(bAllowAirControl && !bVehicleOnGround) { // Log("bVehicleOnGround"); // Log(bVehicleOnGround); TurnDampingMag = (1.0 - DampingScale*Abs(Steering)) * TurnDamping * TurnAngVel; //KAddImpulse( WForce, -TurnDampingMag * worldUp ); Force += (WForce + (-1 * TurnDampingMag) * worldUp); } else { TurnDampingMag = (1.0 - Abs(ActualSteering)) * TurnDamping * TurnAngVel; //KAddImpulse( WForce, -TurnDampingMag * worldUp ); Force += (WForce + (-1 + TurnDampingMag) * worldUp); } // If vehicle is in the air and we are allowing air control... if(!bVehicleOnGround) { PitchAngVel = AngVel dot worldRight; RollAngVel = AngVel dot worldForward; if(bAllowAirControl) { AirControlTorque = worldRight * OutputPitch * -AirPitchTorque; if(bIsWalking) { // Log("bIsWalking"); // Log(bIsWalking); AirControlTorque += (worldForward * Steering * -AirRollTorque); } else { AirControlTorque += (worldUp * Steering * -AirTurnTorque); } //KAddImpulse( WForce, AirControlTorque ); Force += AirControlTorque; // Damping forces PitchDampingMag = (1.0 - DampingScale*Abs(OutputPitch)) * AirPitchDamping * PitchAngVel; RollDampingMag = (1.0 - DampingScale*Abs(Steering)) * AirRollDamping * RollAngVel; //KAddImpulse( WForce, (-PitchDampingMag * worldRight) + (-RollDampingMag * worldForward) ); Force += (WForce + ((-1 + PitchDampingMag) * worldRight) + ((-1 + RollDampingMag) * worldForward)); UDForce = Force; UDTorque = Torque; } else { PitchDampingMag = AirPitchDamping * PitchAngVel; //KAddImpulse( WForce, -PitchDampingMag * worldRight ); Force += (WForce + (-1 + PitchDampingMag) * worldRight); } } else { UDForce = Force; UDTorque = Torque; } } }
- Engine
function Engine() { Gas = Gas2; RPM = InterpCurveEval(EngineS, Gas); }
- StoptheCar
- This function allows for slower, more real life like breaking
function StoptheCar() { if(bBraking) { if(hBrake<=21 && hBrake>=0) { hBrake += 0.5; } if(hBrake>21) hBrake = 21; } if(!bBraking) { if(hBrake<=21 && hBrake>=0) { hBrake -= 0.5; } if(hBrake<0) hBrake = 0; } }
- TorqueConverter
- This function should prolly have a different name, I named it this thinking I would need to accually have a torque converter for the tranny but I turned out making it a function that eases into gear and fixes the rpm to equal that of the engine rpm when the clutch is pressed.
function TorqueConverter() { local float rpmDifference; local float myRPM; rpmDifference = 0.0; myRPM = WheelRPM; myRPM *= 6.28; // convert to radians per sec myRPM /= 60; if(EngineRPM == myRPM) bInGear = True; else if(EngineRPM > myRPM) { if(myRPM < 0) rpmDifference = EngineRPM - (2 * Abs(myRPM)); else if(myRPM >= 0) rpmDifference = EngineRPM - myRPM; if(rpmDifference <= 200.0) { bInGear = True; } else if(rpmDifference > 200.0) { bInGear = False; rpmDifference = rpmDifference * 0.2; EngineRPM -= rpmDifference; bRadians = True; } } else if(EngineRPM < myRPM) { rpmDifference = myRPM - EngineRPM; if(rpmDifference <= 200.0) { bInGear = True; } else if(rpmDifference > 200.0) { bInGear = False; rpmDifference = rpmDifference * 0.2; EngineRPM -= rpmDifference; bRadians = True; } } }
- KApplyForce
function KApplyForce(out vector Force, out vector Torque) { Super.KApplyForce(Force, Torque); Force += UDForce; Torque += UDTorque; if (bBoosting == true && bVehicleOnGround == true) Force += vector(Rotation) * SpeedBoost; }
- KImpact
event KImpact(Actor Other, vector Pos, vector ImpactVel, vector ImpactNorm) { if (Role == ROLE_Authority) { ImpactInfo.Other = Other; ImpactInfo.Pos = Pos; ImpactInfo.ImpactVel = ImpactVel; ImpactInfo.ImpactNorm = ImpactNorm; ImpactInfo.ImpactAccel = KParams.KAcceleration; ImpactTicksLeft = ImpactDamageTicks; } }
- DrivingStatusChanged
event DrivingStatusChanged() { local int i; Super.DrivingStatusChanged(); if (bDriving && Level.NetMode != NM_DedicatedServer && !bDropDetail) { Dust.length = Wheels.length; for(i=0; i<Wheels.Length; i++) if (Dust[i] == None) { // Create wheel dust emitters. WheelCoords = GetBoneCoords(Wheels[i].BoneName); Dust[i] = spawn(class'ONSDirtSlipEffect', self,, WheelCoords.Origin + ((vect(0,0,-1) * Wheels[i].WheelRadius) >> Rotation)); Dust[i].SetBase(self); Dust[i].SetDirtColor( Level.DustColor ); } if(bMakeBrakeLights) { for(i=0; i<2; i++) if (BrakeLight[i] == None) { BrakeLight[i] = spawn(class'ONSBrakelightCorona', self,, Location + (BrakeLightOffset[i] >> Rotation) ); BrakeLight[i].SetBase(self); BrakeLight[i].SetRelativeRotation( rot(0,32768,0) ); // Point lights backwards. BrakeLight[i].Skins[0] = BrakeLightMaterial; } } } else { if (Level.NetMode != NM_DedicatedServer) { for(i=0; i<Dust.Length; i++) Dust[i].Destroy(); Dust.Length = 0; if(bMakeBrakeLights) { for(i=0; i<2; i++) if (BrakeLight[i] != None) BrakeLight[i].Destroy(); } } TurnDamping = 0.0; } }
- KDriverEnter
function KDriverEnter(Pawn P) { Super.KDriverEnter(P); }
- ClientKDriverLeave
simulated function ClientKDriverLeave(PlayerController PC) { Super.ClientKDriverLeave(PC); bWeaponIsAltFiring = false; PC.EndZoom(); }
- SpecialCalcFirstPersonView
simulated function SpecialCalcFirstPersonView(PlayerController PC, out actor ViewActor, out vector CameraLocation, out rotator CameraRotation ) { ViewActor = self; CameraLocation = Location + (FPCamPos >> Rotation); }
- Fire
function Fire(optional float F) { VehicleFire(False); }
- AltFire
function AltFire(optional float F) { VehicleFire(true); }
- VehicleFire
function VehicleFire(bool bWasAltFire) { if(!bWasAltFire) { bGearUp = 1; ChangeGear(bGearUp); } else if(bWasAltFire) { bGearDown = -1; ChangeGear(bGearDown); } }
- VehicleCeaseFire
function VehicleCeaseFire(bool bWasAltFire) { if(!bWasAltFire) { bGearUp = 0; ChangeGear(bGearUp); } else if(bWasAltFire) { bGearDown = 0; ChangeGear(bGearDown); } Super.VehicleCeaseFire(bWasAltFire); }
- ClientVehicleCeaseFire
simulated function ClientVehicleCeaseFire(bool bWasAltFire) { Super.ClientVehicleCeaseFire(bWasAltFire); }
- Tick
function Tick(float DT) { local int i, x; local bool lostTraction; local KarmaParams KP; local KRigidBodyState BodyState; TheDeltaTime = DT; Super.Tick(DT); VRate = VSize(Velocity); KP = KarmaParams(KParams); KGetRigidBodyState(BodyState); SteerBoneAxis = AXIS_Z; KWake(); if(Role == ROLE_Authority) { if(bDriving) { // Update ForwardVel, CarMPH and bIsInverted on both server and client. GetAxes(Rotation, worldForward, worldRight, worldUp); ForwardVel = Velocity dot worldForward; CarMPH = Abs((ForwardVel * 3600.0) / 140800.0); // Convert from units per sec to miles per hour. bIsInverted = worldUp.Z < 0.2; // Update engine sound pitch EnginePitch = 255.0 * ((EngineRPM+IdleRPM)/EngineRPMSoundRange); EnginePitch = Clamp(EnginePitch, 0.0, 255.0); SoundPitch = EnginePitch; SteerBoneAngle = (ActualSteering/InterpCurveEval(MaxSteerAngleCurve, VRate)) * SteerBoneMaxAngle * (65535.0/360.0); one.X = 0; one.Y = 0; one.Z = SteerBoneAngle; two.X = SteerBoneAngle; two.Y = 0; two.Z = 0; three.X = 0; three.Y = SteerBoneAngle; three.Z = 0; if(SteerBoneAxis == AXIS_X) //SteerRot = Rotator(0, 0, SteerBoneAngle); SteerRot = Rotator(one); else if(SteerBoneAxis == AXIS_Y) //SteerRot = Rotator(SteerBoneAngle, 0, 0); SteerRot = Rotator(two); else //SteerRot = Rotator(0, SteerBoneAngle, 0); SteerRot = Rotator(three); SetBoneDirection(SteerBoneName, SteerRot, WForce, 1, 0); } } // Update any stunt variables. if(bDoStuntInfo) { ForwardsInOldPlane = Coords.XAxis - (Coords.XAxis dot OldCoords.ZAxis) * OldCoords.ZAxis; //ForwardsInOldPlane = SafeNormal(ForwardsInOldPlane); DeltaHeading = Acos( Clamp( ForwardsInOldPlane dot OldCoords.XAxis, -1.0, 1.0 ) ); if( (ForwardsInOldPlane dot OldCoords.YAxis) < 0.0) DeltaHeading *= -1.0; DeltaPitch = Asin( Clamp(Coords.XAxis dot OldCoords.ZAxis, -1.0f, 1.0) ); DeltaRoll = Asin( Clamp(Coords.YAxis dot OldCoords.ZAxis, -1.0f, 1.0) ); //debugf( TEXT("DR:%f DP:%f"), DeltaRoll, DeltaPitch ); bCurrentOnGround = (bVehicleOnGround || KP.bContactingLevel); DaredevilPoints = 0; if(bCurrentOnGround) { if(!bOldVehicleOnGround) { // We just landed - see if we should display Daredevil 'message' InAirTime = Level.TimeSeconds - LastOnGroundTime; Dist1 = Location; Dist2 = LastOnGroundLocation; Vect = Dist1 - Dist2; Dist3 = Vect.X * Vect.X - Vect.Y * Vect.Y; Dist3 = Sqrt(Dist3); DaredevilPoints += Max( Ceil( Abs(InAirSpin)/(0.5*DaredevilThreshInAirSpin) ) - 1, 0 ); DaredevilPoints += Max( Ceil( Abs(InAirPitch)/(0.5*DaredevilThreshInAirPitch) ) - 1, 0 ); DaredevilPoints += Max( Ceil( Abs(InAirRoll)/(0.5*DaredevilThreshInAirRoll) ) - 1, 0 ); DaredevilPoints += Max( Ceil( InAirTime/(0.5*DaredevilThreshInAirTime) ) - 1, 0 ); DaredevilPoints += Max( Ceil( InAirDistance/(0.5f*DaredevilThreshInAirDistance) ) - 1, 0 ); DaredevilPoints *= 10; // A wheel must be touching the ground on landing to get a daredevil if( bVehicleOnGround && DaredevilPoints > 0 ) { OnDaredevil(); } } LastOnGroundLocation = Location; LastOnGroundTime = Level.TimeSeconds; InAirSpin = 0.0; InAirPitch = 0.0; InAirRoll = 0.0; } else { InAirSpin += (180.0/PI) * DeltaHeading; InAirPitch += (180.0/PI) * DeltaPitch; InAirRoll += (180.0/PI) * DeltaRoll; } OldRotation = Rotation; bOldVehicleOnGround = bCurrentOnGround; } if(!bDriving) { bOldVehicleOnGround = true; // If no-one is in the vehicle, dont consider it for daredevil status. } if(bAllowChargingJump) { // Check if all wheels are on the ground. if(bAllWheelsOnGround == true) { for(x = 0; x<Wheels.Length; x++) { if (Wheels[x].bWheelOnGround != false) bAllWheelsOnGround = false; } } else { // If any wheels are off the ground - we can just reset everything. if(!bVehicleOnGround) { JumpForce = 0.0; JumpSpin = 0.0; // bPushDown = false; } } } //Engine(Throttle); // If on the server - we work out OutputGas, OutputBrake etc, and pack them to be sent to the client. if(!bClutching) Gas2 = InterpCurveEval(RPMtoGas, EngineRPM); ProcessCarInput(); PackState(); }
- ProcessCarInput
function ProcessCarInput() { local bool bReverse; bReverse = false; if(Driver == none) { OutputBrake = 1.0; OutputGas = 0.0; } else { if(Throttle >= 0) // pressing forwards { OutputGas = Abs(Throttle); OutputBrake = 0.0; if(bClutching) { if(Throttle == 0) { if(Gas2<=21 && Gas2>=0) { Gas2 -= 0.5; } if(Gas2<0) Gas2 = 0; } else { if(Gas2<=21 && Gas2>=0) { Gas2 += 0.5; } if(Gas2>21) Gas2 = 21; } if(Gas2 >=21) Gas2 = 21; } if(!bClutching) } StoptheCar(); OutputBrake = Abs(InterpCurveEval(BrakeCurve, hBrake)); OutputPitch = Rise; } Engine(); KWake(); }
- Clutch
exec function Clutch(bool bEnabled) { if(!bEnabled) { bClutching = False; bInGear = False; } else if(bEnabled) { bClutching = True; RPM = EngineRPM; } }
- Brake
exec function Brake(bool bEnabled) { if(bEnabled) { bBraking = True; OutputHandbrake = true; } if(!bEnabled) { bBraking = false; OutputHandbrake = false; } }
- ChangeGear
function ChangeGear(int GearChoice) { if(bClutching){ if(Gear >= 1 && Gear <= 7) { if(GearChoice == 1) { Gear++; bGearUp = 0; } else if(GearChoice == -1) { Gear--; bGearDown = 0; } else if(GearChoice == 0) { } } else if(Gear == 0 && GearChoice == 1) { Gear++; bGearUp = 0; } else if(Gear == 8 && GearChoice == -1) { Gear--; bGearDown = 0; } else { bGearUp = 0; bGearDown = 0; } } }
- PackState
function PackState() { local KRigidBodyState RBState; local rotator ViewRot; if(!KIsAwake()) return; KGetRigidBodyState(RBState); CActiveWeapon=SActiveWeapon; CarState.ChassisPosition.X = RBState.Position.X; CarState.ChassisPosition.Y = RBState.Position.Y; CarState.ChassisPosition.Z = RBState.Position.Z; CarState.ChassisQuaternion = RBState.Quaternion; CarState.ChassisLinVel.X = 10.0 * RBState.LinVel.X; CarState.ChassisLinVel.Y = 10.0 * RBState.LinVel.Y; CarState.ChassisLinVel.Z = 10.0 * RBState.LinVel.Z; CarState.ChassisAngVel.X = 1000.0 * RBState.AngVel.X; CarState.ChassisAngVel.Y = 1000.0 * RBState.AngVel.Y; CarState.ChassisAngVel.Z = 1000.0 * RBState.AngVel.Z; CarState.ServerHandbrake = FloatToRangeByte(OutputPitch); CarState.ServerBrake = FloatToRangeByte(OutputBrake); CarState.ServerGas = FloatToRangeByte(OutputGas); CarState.ServerGear = Gear; CarState.ServerSteering = FloatToRangeByte(Steering); if(Controller != None) { if(IsHumanControlled()) { DriverViewPitch = Controller.Rotation.Pitch; DriverViewYaw = Controller.Rotation.Yaw; } else { ViewRot = rotator(Controller.FocalPoint - Location); DriverViewPitch = ViewRot.Pitch; DriverViewYaw = ViewRot.Yaw; } } else { DriverViewPitch = Rotation.Pitch; DriverViewYaw = Rotation.Yaw; } CarState.ServerViewPitch = DriverViewPitch; CarState.ServerViewYaw = DriverViewYaw; FudgeByte++; }
- Motion Blur (still in the works)
function addblur() { local PlayerController PC; PC = PlayerController(Controller); PC.AddCameraEffect(new myBlur); }
- LimitPitch
function int LimitPitch(int pitch) { if (bAllowAirControl && !bVehicleOnGround) return pitch; return Super.LimitPitch(pitch); }
- Destroyed
simulated function Destroyed() { if (LeftTrail != None) LeftTrail.Destroy(); if (RightTrail != None) RightTrail.Destroy(); Super.Destroyed(); }
- TakeImpactDamage
event TakeImpactDamage(float AccelMag) { local int Damage; Damage = int(VSize(ImpactInfo.Other.Velocity) * 20 * ImpactDamageModifier()); if (Vehicle(ImpactInfo.Other) != None) TakeDamage(Damage, Vehicle(ImpactInfo.Other), ImpactInfo.Pos, vect(0,0,0), class'DamType'); else TakeDamage(int(AccelMag * ImpactDamageModifier())/ WallCollisionResistance , Self, ImpactInfo.Pos, vect(0,0,0), class'DamType'); //FIXME - scale sound volume to damage amount if (ImpactDamageSounds.Length > 0) PlaySound(ImpactDamageSounds[Rand(ImpactDamageSounds.Length-1)],,TransientSoundVolume*2.5); if (Health < 0 && (Level.TimeSeconds - LastImpactExplosionTime) > TimeBetweenImpactExplosions) { VehicleExplosion(Normal(ImpactInfo.ImpactNorm), 0.5); LastImpactExplosionTime = Level.TimeSeconds; } }
- Died
function Died(Controller Killer, class<DamageType> damageType, vector HitLocation) { if (damageType == none) return; if (Killer != None) PlayerController(Killer).ReceiveLocalizedMessage(class'VehicleKillMessage', 7); Super.Died(Killer, damageType, HitLocation); }
- FloatToRangeByte
simulated function byte FloatToRangeByte(float f) { f= FClamp(f, 0, 1); f = Round(f * 255); return byte(f); }
- RangeByteToFloat
//The opposite of the above function. simulated function float RangeByteToFloat(byte b) { local float f; f = b; f /= 255; return f; }
Defaults
defaultproperties { myBlur=Class'WhatEverPackage.myMotionBlur' NumForwardGears=7 DustSlipRate=2.800000 DustSlipThresh=50.000000 DaredevilThreshInAirSpin=100.000000 DaredevilThreshInAirPitch=300.000000 DaredevilThreshInAirRoll=300.000000 DaredevilThreshInAirTime=1.500000 DaredevilThreshInAirDistance=17.000000 DaredevilMessageClass=Class'Onslaught.ONSDaredevilMessage' bOldVehicleOnGround=True MaxJumpSpin=30000.000000 JumpChargeTime=1.000000 JumpFeedbackForce="HoverBikeJump" JumpSound=Sound'ONSVehicleSounds-S.Hydraulics.Hydraulic10' JumpMeterOriginX=0.275000 JumpMeterOriginY=0.943000 JumpMeterWidth=0.136000 JumpMeterHeight=0.057000 JumpMeterSpacing=0.010000 JumpMeterColor=(R=215,A=255) SpinMeterColor=(B=215,G=100,A=255) JumpMeterTexture=Texture'Engine.WhiteTexture' MinAirControlDamping=0.100000 bBrakeFrontWheelsOnly=False ChangeUpPoint=2800 StopThreshold=0.2 CrosshairHitFeedbackTex=Texture'ONSInterface-TX.tankBarrelAligned' DefaultCrosshair=Material'InterfaceContent.Hud.fbBombFocus' CrosshairScale=0.125 bTeamLocked=false bGearUp=True bInGear=True bEjectPassengersWhenFlipped=False bCanFlip=true DisintegrationHealth=0.0 bShowChargingBar = false; // Damages WallCollisionResistance = 5 // Collision resistance: the bigger it is, the less damage taken ExplosionDamage=0.0 DriverDamageMult=0 CenterSpringRangePitch=5000 CenterSpringRangeRoll=5000 StolenAnnouncement="Play" bNetNotify=true }
- Global variables that would need to be added to the above uscript:
enum ETextAnchor { TA_Left, TA_Right, TA_Middle }; var float ScaleX, ScaleY;
CIpen: Here is some simple code you can add to print data on the screen to avoid logging 24/7. This is what I used to check numbers such as RPM, Gas, EngineRPM, and whatever else I could fit on 1024x768 resolution. I've found it to be most handy. I'm sure you can find this elsewhere but I'm saving you the effort of looking.
- And here is the DrawHUD function that we must have to draw on the screen when a player has possesion of the vehicle:
simulated function DrawHUD(Canvas C) { local PlayerController PC; PC = PlayerController(Controller); ScaleX = C.ClipX/1024; ScaleY = C.ClipY/768; C.Style = ERenderStyle.STY_Additive; C.Font = PC.MyHUD.GetFontSizeIndex(C, -2); C.DrawColor = C.MakeColor(255,255,255); DrawEngineStuff(C); }
- Here is the DrawEngineStuff(C) which is isn't a required function but is there to organize:
simulated function DrawEngineStuff(Canvas C) { C.Font = C.TinyFont; C.SetPos(40*ScaleX, 150*ScaleY); C.DrawText("Engine stuff"); C.SetPos(40*ScaleX, 160*ScaleY); C.DrawText("Engine RPM: " @ EngineRPM); }
40*ScaleX sets the vertical starting position, and 150*ScaleY sets the horizontal starting position.
Notes
If you are to subclass, you need to know that the engine rpm is in radians per min. This means that if you were to create a torque curve and wanted the rpm range to be from 500 to 7000, you would need to convert all the points from revolutions per min to radians per sec. Take the rpm(real life engine rpm) of 7000, divide by 60, then multiply by 2 PI. Like so:
(7000/60)*6.2831853
this yields 733.038285 as the correct rpm
bNetNotify=true is prolly the most critical thing needed for this class to work online/lan
I've got a lot of changes that need to be made to this class, so be patient. Additions include, properly calculating wheel torque if the wheel is powered or not, better collisions with walls(currently if you hit a wall flat at 300 mph the vehicle just stops and takes no damage), available auto transmision, ...
Related Topics
- Vehicles – Main topics for Vehicles.
- Vehicle – Parent class for all vehicles and turrets.
- Making Mods
Discussion
Graphik: Very nice idea, but a bit unsuited for an FPS, more for a racing game like UnWheel. It seems to me that Epic made sure that the vehicles were as "dumbed down" as possible to keep the focus on fragging people with a vehicle rather than the minutia of driving one. I recommend sending this in to [UnWheel] when you're done. If they don't actually use it I'm sure they'd at the very least find it interesting.
CIpen: Accually I got a thumbs up on that at the same time I created this page. The bounds of this script is almost limitless, as it would be easy to have say the tire blow out when shot. As soon as I get a free day I'll update this page with all the new additions (infact that might be something good to do tonight). The big change is that I added the option for auto or stick control and the the power is ditributed over the wheels that are powered.
Foxpaw: Why do you divide by 60 when converting rotations per minute to radians per minute? The radians per minute should be greater than the roatations per minute, as there's more than one radian per rotation. Or is that supposed to say, radians per second?
CIpen: Yes, you pointed out a typo . Yes, it should say radians per second. Yet your own message has typos, it's not rotations per minute, but revolutions per minute.
Foxpaw: Err, ehm, well, I guess that depends on where you measure it from. I'd measure it from the flywheel which definately rotates. I suppose some parts of the crankshaft do revolve though, so either could be correct.
Zxanphorian: WOW, that is a lot of scriptage there! I have an idea, to make it randomly stall :B
CIpen: Ya, there are a lot of cool possibilities with this class. You could attach a model to the wheel bone, add another bone for disk brakes (to attach a disk brake mesh to that turns when the wheels turn), make the engine blow up if you downgear and the engine rpm goes to high, have an axle break, have more wheel slip functions for different traction of different surfaces... any number of things....
OlympusMons: I would have to agree CIpen. Has anyone given any thought to making a proper automatic car you know the manual automatic ones. :S Like what mafia did. I havent really had time to look through all this code although I'd like to, I was just wondering if all of this is actually needed to make a manual shift car or if there are other features which have been added through all of this script. Actually another cool thing to do would be to make a proper 4wd manual shift. Hmm!
CIpen: Good! Lets get to the meat! The first thing you need to know is that the function UpdateVehicle() is somehow tied to native code. To see this all you need to do is log the DeltaTime from inside the Tick() function and from inside UpdateVehicle() function. You should see that UpdateVehicle() always has the same DeltaTime so matter what your FPS is (at least this is what I remember it being when I tested it).
The next serious thing is that, unless you do your own wheel calculations, you'll have to build around the SVehicleWheel variables that are calculated each frame. It's both good and bad I guess. Take a minute to thing about this: below is the line that calculates the GripTorque.
GripTorque = FTScale * Wheels[i].WheelRadius * Wheels[i].TireLoad * WheelLongFrictionScale[0] * InterpCurveEval(Wheels[i].LongFrictionFunc, Abs(Wheels[i].SlipVel));
Now, lets say we want to be the biggest uscript hot shot around, you might change this line to look like this:
if(bPlayerIsOnHood) GripTorque = FTScale * Wheels[i].WheelRadius * (Wheels[i].TireLoad + PlayerMass) * WheelLongFrictionScale[0] * InterpCurveEval(Wheels[i].LongFrictionFunc, Abs(Wheels[i].SlipVel)); else GripTorque = FTScale * Wheels[i].WheelRadius * Wheels[i].TireLoad * WheelLongFrictionScale[0] * InterpCurveEval(Wheels[i].LongFrictionFunc, Abs(Wheels[i].SlipVel));
Thus simulating player weight on the car(more or less). That is after you do some stuff to get if the player is on the vehicle or not.
btw I just updated it UpdateVehicle() because it was far behind.
Tarquin: I moved this back to its original name, as the class name didn't seem very descriptive.
WinterHummer: Can someong give me a good scorpion subclass of this for me and other lazy people?