|
|
@@ -0,0 +1,534 @@
|
|
|
+// Vehicle example.
|
|
|
+// This sample demonstrates:
|
|
|
+// - Creating a heightmap terrain with collision
|
|
|
+// - Constructing a physical vehicle with rigid bodies for the hull and the wheels, joined with constraints
|
|
|
+// - Saving and loading the variables of a script object, including node & component references
|
|
|
+
|
|
|
+#include "Scripts/Utilities/Sample.as"
|
|
|
+
|
|
|
+const int CTRL_FORWARD = 1;
|
|
|
+const int CTRL_BACK = 2;
|
|
|
+const int CTRL_LEFT = 4;
|
|
|
+const int CTRL_RIGHT = 8;
|
|
|
+const int CTRL_BRAKE = 16;
|
|
|
+
|
|
|
+const float CAMERA_DISTANCE = 10.0f;
|
|
|
+const float YAW_SENSITIVITY = 0.1f;
|
|
|
+const float ENGINE_FORCE = 2500.0f;
|
|
|
+const float DOWN_FORCE = 100.0f;
|
|
|
+const float MAX_WHEEL_ANGLE = 22.5f;
|
|
|
+const float CHASSIS_WIDTH = 2.6f;
|
|
|
+
|
|
|
+Node@ vehicleNode;
|
|
|
+
|
|
|
+void Start()
|
|
|
+{
|
|
|
+ // Execute the common startup for samples
|
|
|
+ SampleStart();
|
|
|
+ // Create static scene content
|
|
|
+ CreateScene();
|
|
|
+ // Create the controllable vehicle
|
|
|
+ CreateVehicle();
|
|
|
+ // Create the UI content
|
|
|
+ CreateInstructions();
|
|
|
+ // Set the mouse mode to use in the sample
|
|
|
+ SampleInitMouseMode(MM_RELATIVE);
|
|
|
+ // Subscribe to necessary events
|
|
|
+ SubscribeToEvents();
|
|
|
+}
|
|
|
+
|
|
|
+void CreateScene()
|
|
|
+{
|
|
|
+ scene_ = Scene();
|
|
|
+ // Create scene subsystem components
|
|
|
+ scene_.CreateComponent("Octree");
|
|
|
+ scene_.CreateComponent("PhysicsWorld");
|
|
|
+ // Create camera and define viewport. Camera does not necessarily have to belong to the scene
|
|
|
+ cameraNode = Node();
|
|
|
+ Camera@ camera = cameraNode.CreateComponent("Camera");
|
|
|
+ camera.farClip = 500.0f;
|
|
|
+ renderer.viewports[0] = Viewport(scene_, camera);
|
|
|
+ // Create static scene content. First create a zone for ambient lighting and fog control
|
|
|
+ Node@ zoneNode = scene_.CreateChild("Zone");
|
|
|
+ Zone@ zone = zoneNode.CreateComponent("Zone");
|
|
|
+ zone.ambientColor = Color(0.15f, 0.15f, 0.15f);
|
|
|
+ zone.fogColor = Color(0.5f, 0.5f, 0.7f);
|
|
|
+ zone.fogStart = 300.0f;
|
|
|
+ zone.fogEnd = 500.0f;
|
|
|
+ zone.boundingBox = BoundingBox(-2000.0f, 2000.0f);
|
|
|
+ // Create a directional light to the world. Enable cascaded shadows on it
|
|
|
+ Node@ lightNode = scene_.CreateChild("DirectionalLight");
|
|
|
+ lightNode.direction = Vector3(0.3f, -0.5f, 0.425f);
|
|
|
+ Light@ light = lightNode.CreateComponent("Light");
|
|
|
+ light.lightType = LIGHT_DIRECTIONAL;
|
|
|
+ light.castShadows = true;
|
|
|
+ light.shadowBias = BiasParameters(0.00025f, 0.5f);
|
|
|
+ light.shadowCascade =
|
|
|
+ CascadeParameters(10.0f, 50.0f, 200.0f, 0.0f, 0.8f);
|
|
|
+ light.specularIntensity = 0.5f;
|
|
|
+ // Create heightmap terrain with collision
|
|
|
+ Node@ terrainNode = scene_.CreateChild("Terrain");
|
|
|
+ terrainNode.position = Vector3(0.0f, 0.0f, 0.0f);
|
|
|
+ Terrain@ terrain = terrainNode.CreateComponent("Terrain");
|
|
|
+ terrain.patchSize = 64;
|
|
|
+ terrain.spacing = Vector3(2.0f, 0.1f, 2.0f); // Spacing between vertices and vertical resolution of the height map
|
|
|
+ terrain.smoothing = true;
|
|
|
+ terrain.heightMap =
|
|
|
+ cache.GetResource("Image", "Textures/HeightMap.png");
|
|
|
+ terrain.material =
|
|
|
+ cache.GetResource("Material", "Materials/Terrain.xml");
|
|
|
+ // The terrain consists of large triangles, which fits well for occlusion rendering, as a hill can occlude all
|
|
|
+ // terrain patches and other objects behind it
|
|
|
+ terrain.occluder = true;
|
|
|
+ RigidBody@ body = terrainNode.CreateComponent("RigidBody");
|
|
|
+ body.collisionLayer = 2; // Use layer bitmask 2 for static geometry
|
|
|
+ CollisionShape@ shape = terrainNode.CreateComponent("CollisionShape");
|
|
|
+ shape.SetTerrain();
|
|
|
+ // Create 1000 mushrooms in the terrain. Always face outward along the terrain normal
|
|
|
+ const uint NUM_MUSHROOMS = 1000;
|
|
|
+ for (uint i = 0; i < NUM_MUSHROOMS; ++i)
|
|
|
+ {
|
|
|
+ Node@ objectNode = scene_.CreateChild("Mushroom");
|
|
|
+ Vector3 position(Random(2000.0f) - 1000.0f, 0.0f,
|
|
|
+ Random(2000.0f) - 1000.0f);
|
|
|
+ position.y = terrain.GetHeight(position) - 0.1f;
|
|
|
+ objectNode.position = position;
|
|
|
+ // Create a rotation quaternion from up vector to terrain normal
|
|
|
+ objectNode.rotation =
|
|
|
+ Quaternion(Vector3(0.0f, 1.0f, 0.0),
|
|
|
+ terrain.GetNormal(position));
|
|
|
+ objectNode.SetScale(3.0f);
|
|
|
+ StaticModel@ object = objectNode.CreateComponent("StaticModel");
|
|
|
+ object.model = cache.GetResource("Model", "Models/Mushroom.mdl");
|
|
|
+ object.material =
|
|
|
+ cache.GetResource("Material", "Materials/Mushroom.xml");
|
|
|
+ object.castShadows = true;
|
|
|
+ RigidBody@ mushroomBody =
|
|
|
+ objectNode.CreateComponent("RigidBody");
|
|
|
+ mushroomBody.collisionLayer = 2;
|
|
|
+ CollisionShape@ mushroomShape =
|
|
|
+ objectNode.CreateComponent("CollisionShape");
|
|
|
+ mushroomShape.SetTriangleMesh(object.model, 0);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+void
|
|
|
+CreateVehicle()
|
|
|
+{
|
|
|
+ vehicleNode = scene_.CreateChild("Vehicle");
|
|
|
+ vehicleNode.position = Vector3(0.0f, 5.0f, 0.0f);
|
|
|
+ // First createing player-controlled vehicle
|
|
|
+ // Create the vehicle logic script object
|
|
|
+ Vehicle@ vehicle =
|
|
|
+ cast < Vehicle >
|
|
|
+ (vehicleNode.CreateScriptObject(scriptFile, "Vehicle"));
|
|
|
+ // Initialize vehicle component
|
|
|
+ vehicle.Init();
|
|
|
+ vehicleNode.AddTag("vehicle");
|
|
|
+ // Set RigidBody physics parameters
|
|
|
+ // (The RigidBody was created by vehicle.Init()
|
|
|
+ RigidBody@ hullBody = vehicleNode.GetComponent("RigidBody");
|
|
|
+ hullBody.mass = 800.0f;
|
|
|
+ hullBody.linearDamping = 0.2f; // Some air resistance
|
|
|
+ hullBody.angularDamping = 0.5f;
|
|
|
+ hullBody.collisionLayer = 1;
|
|
|
+}
|
|
|
+
|
|
|
+void
|
|
|
+
|
|
|
+CreateInstructions()
|
|
|
+{
|
|
|
+ // Construct new Text object, set string to display and font to use
|
|
|
+ Text@ instructionText = ui.root.CreateChild("Text");
|
|
|
+ instructionText.text =
|
|
|
+ "Use WASD keys to drive, F to brake, mouse/touch to rotate camera\n"
|
|
|
+ "F5 to save scene, F7 to load";
|
|
|
+ instructionText.
|
|
|
+ SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
|
|
|
+ // The text has multiple rows. Center them in relation to each other
|
|
|
+ instructionText.textAlignment = HA_CENTER;
|
|
|
+ // Position the text relative to the screen center
|
|
|
+ instructionText.horizontalAlignment = HA_CENTER;
|
|
|
+ instructionText.verticalAlignment = VA_CENTER;
|
|
|
+ instructionText.SetPosition(0, ui.root.height / 4);
|
|
|
+}
|
|
|
+
|
|
|
+void
|
|
|
+
|
|
|
+SubscribeToEvents()
|
|
|
+{
|
|
|
+ // Subscribe to Update event for setting the vehicle controls before physics simulation
|
|
|
+ SubscribeToEvent("Update", "HandleUpdate");
|
|
|
+ // Subscribe to PostUpdate event for updating the camera position after physics simulation
|
|
|
+ SubscribeToEvent("PostUpdate", "HandlePostUpdate");
|
|
|
+ // Unsubscribe the SceneUpdate event from base class as the camera node is being controlled in HandlePostUpdate() in this sample
|
|
|
+ UnsubscribeFromEvent("SceneUpdate");
|
|
|
+}
|
|
|
+
|
|
|
+void
|
|
|
+
|
|
|
+HandleUpdate(StringHash eventType, VariantMap& eventData)
|
|
|
+{
|
|
|
+ if (vehicleNode is null)
|
|
|
+ return;
|
|
|
+ Vehicle@ vehicle = cast < Vehicle > (vehicleNode.scriptObject);
|
|
|
+ if (vehicle is null)
|
|
|
+ return;
|
|
|
+ // Get movement controls and assign them to the vehicle component. If UI has a focused element, clear controls
|
|
|
+ if (ui.focusElement is null)
|
|
|
+ {
|
|
|
+ vehicle.controls.Set(CTRL_FORWARD, input.keyDown[KEY_W]);
|
|
|
+ vehicle.controls.Set(CTRL_BACK, input.keyDown[KEY_S]);
|
|
|
+ vehicle.controls.Set(CTRL_LEFT, input.keyDown[KEY_A]);
|
|
|
+ vehicle.controls.Set(CTRL_RIGHT, input.keyDown[KEY_D]);
|
|
|
+ vehicle.controls.Set(CTRL_BRAKE, input.keyDown[KEY_F]);
|
|
|
+ // Add yaw & pitch from the mouse motion. Used only for the camera, does not affect motion
|
|
|
+ if (touchEnabled)
|
|
|
+ {
|
|
|
+ for (uint i = 0; i < input.numTouches; ++i)
|
|
|
+ {
|
|
|
+ TouchState@ state = input.touches[i];
|
|
|
+ if (state.touchedElement is null) // Touch on empty space
|
|
|
+ {
|
|
|
+ Camera@ camera =
|
|
|
+ cameraNode.GetComponent("Camera");
|
|
|
+ if (camera is null)
|
|
|
+ return;
|
|
|
+ vehicle.controls.yaw +=
|
|
|
+ TOUCH_SENSITIVITY * camera.fov /
|
|
|
+ graphics.height * state.delta.x;
|
|
|
+ vehicle.controls.pitch +=
|
|
|
+ TOUCH_SENSITIVITY * camera.fov /
|
|
|
+ graphics.height * state.delta.y;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ vehicle.controls.yaw += input.mouseMoveX * YAW_SENSITIVITY;
|
|
|
+ vehicle.controls.pitch += input.mouseMoveY * YAW_SENSITIVITY;
|
|
|
+ }
|
|
|
+ // Limit pitch
|
|
|
+ vehicle.controls.pitch =
|
|
|
+ Clamp(vehicle.controls.pitch, 0.0f, 80.0f);
|
|
|
+ // Check for loading / saving the scene
|
|
|
+ if (input.keyPress[KEY_F5])
|
|
|
+ {
|
|
|
+ File saveFile(fileSystem.programDir +
|
|
|
+ "Data/Scenes/RaycastScriptVehicleDemo.xml",
|
|
|
+ FILE_WRITE);
|
|
|
+ scene_.SaveXML(saveFile);
|
|
|
+ }
|
|
|
+ if (input.keyPress[KEY_F7])
|
|
|
+ {
|
|
|
+ File loadFile(fileSystem.programDir +
|
|
|
+ "Data/Scenes/RaycastScriptVehicleDemo.xml",
|
|
|
+ FILE_READ);
|
|
|
+ scene_.LoadXML(loadFile);
|
|
|
+ // After loading we have to reacquire the vehicle scene node, as it has been recreated
|
|
|
+ // Simply find by name as there's only one of them
|
|
|
+ Array < Node@ >@vehicles =
|
|
|
+ scene_.GetChildrenWithTag("vehicle");
|
|
|
+ for (int i = 0; i < vehicles.length; i++)
|
|
|
+ {
|
|
|
+ Vehicle@ vehicleData =
|
|
|
+ cast < Vehicle > (vehicles[i].scriptObject);
|
|
|
+ vehicleData.CreateEmitters();
|
|
|
+ }
|
|
|
+ vehicleNode = scene_.GetChild("Vehicle", true);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ vehicle.
|
|
|
+ controls.Set(CTRL_FORWARD | CTRL_BACK | CTRL_LEFT | CTRL_RIGHT |
|
|
|
+ CTRL_BRAKE, false);
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+void
|
|
|
+HandlePostUpdate(StringHash eventType, VariantMap& eventData)
|
|
|
+{
|
|
|
+ if (vehicleNode is null)
|
|
|
+ return;
|
|
|
+ Vehicle@ vehicle = cast < Vehicle > (vehicleNode.scriptObject);
|
|
|
+ if (vehicle is null)
|
|
|
+ return;
|
|
|
+ // Physics update has completed. Position camera behind vehicle
|
|
|
+ Quaternion dir(vehicleNode.rotation.yaw, Vector3(0.0f, 1.0f, 0.0f));
|
|
|
+ dir =
|
|
|
+ dir * Quaternion(vehicle.controls.yaw, Vector3(0.0f, 1.0f, 0.0f));
|
|
|
+ dir =
|
|
|
+ dir * Quaternion(vehicle.controls.pitch, Vector3(1.0f, 0.0f, 0.0f));
|
|
|
+ Vector3 cameraTargetPos =
|
|
|
+ vehicleNode.position - dir * Vector3(0.0f, 0.0f, CAMERA_DISTANCE);
|
|
|
+ Vector3 cameraStartPos = vehicleNode.position;
|
|
|
+ // Raycast camera against static objects (physics collision mask 2)
|
|
|
+ // and move it closer to the vehicle if something in between
|
|
|
+ Ray cameraRay(cameraStartPos,
|
|
|
+ (cameraTargetPos - cameraStartPos).Normalized());
|
|
|
+ float cameraRayLength = (cameraTargetPos - cameraStartPos).length;
|
|
|
+ PhysicsRaycastResult result =
|
|
|
+ scene_.physicsWorld.RaycastSingle(cameraRay, cameraRayLength, 2);
|
|
|
+ if (result.body ! is null)
|
|
|
+ cameraTargetPos =
|
|
|
+ cameraStartPos + cameraRay.direction * (result.distance - 0.5f);
|
|
|
+ cameraNode.position = cameraTargetPos;
|
|
|
+ cameraNode.rotation = dir;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+// Vehicle script object class
|
|
|
+//
|
|
|
+// When saving, the node and component handles are automatically converted into nodeID or componentID attributes
|
|
|
+// and are acquired from the scene when loading. The steering member variable will likewise be saved automatically.
|
|
|
+// The Controls object can not be automatically saved, so handle it manually in the Load() and Save() methods
|
|
|
+
|
|
|
+class Vehicle:ScriptObject
|
|
|
+{
|
|
|
+
|
|
|
+ RigidBody@ hullBody;
|
|
|
+
|
|
|
+
|
|
|
+ // Current left/right steering amount (-1 to 1.)
|
|
|
+ float steering = 0.0f;
|
|
|
+
|
|
|
+ // Vehicle controls.
|
|
|
+ Controls controls;
|
|
|
+
|
|
|
+ float m_fsuspensionRestLength = 0.6f;
|
|
|
+
|
|
|
+ float m_fsuspensionStiffness = 14.0f;
|
|
|
+
|
|
|
+ float m_fsuspensionDamping = 2.0f;
|
|
|
+
|
|
|
+ float m_fsuspensionCompression = 4.0f;
|
|
|
+
|
|
|
+ float m_fwheelFriction = 1000.0f;
|
|
|
+
|
|
|
+ float m_frollInfluence = 0.12f;
|
|
|
+
|
|
|
+ float maxEngineForce = ENGINE_FORCE;
|
|
|
+
|
|
|
+ float wheelWidth = 0.4f;
|
|
|
+
|
|
|
+ float wheelRadius = 0.5f;
|
|
|
+
|
|
|
+ float brakingForce = 50.0f;
|
|
|
+
|
|
|
+ Array < Vector3 > connectionPoints;
|
|
|
+
|
|
|
+ Array < Node@ >particleEmitterNodeList;
|
|
|
+
|
|
|
+ protected Vector3 prevVelocity;
|
|
|
+
|
|
|
+
|
|
|
+ void Load(Deserializer& deserializer)
|
|
|
+ {
|
|
|
+ controls.yaw = deserializer.ReadFloat();
|
|
|
+ controls.pitch = deserializer.ReadFloat();
|
|
|
+ }
|
|
|
+
|
|
|
+ void Save(Serializer& serializer)
|
|
|
+ {
|
|
|
+ serializer.WriteFloat(controls.yaw);
|
|
|
+ serializer.WriteFloat(controls.pitch);
|
|
|
+ }
|
|
|
+
|
|
|
+ void Init()
|
|
|
+ {
|
|
|
+ // This function is called only from the main program when initially creating the vehicle, not on scene load
|
|
|
+ StaticModel@ hullObject = node.CreateComponent("StaticModel");
|
|
|
+ hullBody = node.CreateComponent("RigidBody");
|
|
|
+ CollisionShape@ hullShape = node.CreateComponent("CollisionShape");
|
|
|
+ node.scale = Vector3(2.3f, 1.0f, 4.0f);
|
|
|
+ hullObject.model = cache.GetResource("Model", "Models/Box.mdl");
|
|
|
+ hullObject.material =
|
|
|
+ cache.GetResource("Material", "Materials/Stone.xml");
|
|
|
+ hullObject.castShadows = true;
|
|
|
+ hullShape.SetBox(Vector3(1.0f, 1.0f, 1.0f));
|
|
|
+ hullBody.mass = 800.0f;
|
|
|
+ hullBody.linearDamping = 0.2f; // Some air resistance
|
|
|
+ hullBody.angularDamping = 0.5f;
|
|
|
+ hullBody.collisionLayer = 1;
|
|
|
+ RaycastVehicle@ raycastVehicle =
|
|
|
+ node.CreateComponent("RaycastVehicle");
|
|
|
+ raycastVehicle.Init();
|
|
|
+ connectionPoints.Reserve(4);
|
|
|
+ float connectionHeight = -0.4f; //1.2f;
|
|
|
+ bool isFrontWheel = true;
|
|
|
+ Vector3 wheelDirection(0, -1, 0);
|
|
|
+ Vector3 wheelAxle(-1, 0, 0);
|
|
|
+ float wheelX = CHASSIS_WIDTH / 2.0 - wheelWidth;
|
|
|
+ // Front left
|
|
|
+ connectionPoints.Push(Vector3
|
|
|
+ (-wheelX, connectionHeight,
|
|
|
+ 2.5f - wheelRadius * 2.0f));
|
|
|
+ // Front right
|
|
|
+ connectionPoints.Push(Vector3
|
|
|
+ (wheelX, connectionHeight,
|
|
|
+ 2.5f - wheelRadius * 2.0f));
|
|
|
+ // Back left
|
|
|
+ connectionPoints.Push(Vector3
|
|
|
+ (-wheelX, connectionHeight,
|
|
|
+ -2.5f + wheelRadius * 2.0f));
|
|
|
+ // Back right
|
|
|
+ connectionPoints.Push(Vector3
|
|
|
+ (wheelX, connectionHeight,
|
|
|
+ -2.5f + wheelRadius * 2.0f));
|
|
|
+ const Color LtBrown(0.972f, 0.780f, 0.412f);
|
|
|
+ for (int id = 0; id < connectionPoints.length; id++)
|
|
|
+ {
|
|
|
+ Node@ wheelNode = scene_.CreateChild();
|
|
|
+ Vector3 connectionPoint = connectionPoints[id];
|
|
|
+ // Front wheels are at front (z > 0)
|
|
|
+ // back wheels are at z < 0
|
|
|
+ // Setting rotation according to wheel position
|
|
|
+ bool isFrontWheel = connectionPoints[id].z > 0.0f;
|
|
|
+ wheelNode.rotation =
|
|
|
+ (connectionPoint.x >=
|
|
|
+ 0.0 ? Quaternion(0.0f, 0.0f, -90.0f) : Quaternion(0.0f,
|
|
|
+ 0.0f,
|
|
|
+ 90.0f));
|
|
|
+ wheelNode.worldPosition =
|
|
|
+ (node.worldPosition +
|
|
|
+ node.worldRotation * connectionPoints[id]);
|
|
|
+ wheelNode.scale = Vector3(1.0f, 0.65f, 1.0f);
|
|
|
+ raycastVehicle.AddWheel(wheelNode, wheelDirection, wheelAxle,
|
|
|
+ m_fsuspensionRestLength, wheelRadius,
|
|
|
+ isFrontWheel);
|
|
|
+ raycastVehicle.SetWheelSuspensionStiffness(id,
|
|
|
+ m_fsuspensionStiffness);
|
|
|
+ raycastVehicle.SetWheelDampingRelaxation(id,
|
|
|
+ m_fsuspensionDamping);
|
|
|
+ raycastVehicle.SetWheelDampingCompression(id,
|
|
|
+ m_fsuspensionCompression);
|
|
|
+ raycastVehicle.SetWheelFrictionSlip(id, m_fwheelFriction);
|
|
|
+ raycastVehicle.SetWheelRollInfluence(id, m_frollInfluence);
|
|
|
+ StaticModel@ pWheel =
|
|
|
+ wheelNode.CreateComponent("StaticModel");
|
|
|
+ pWheel.model =
|
|
|
+ cache.GetResource("Model", "Models/Cylinder.mdl");
|
|
|
+ pWheel.material =
|
|
|
+ cache.GetResource("Material", "Materials/Stone.xml");
|
|
|
+ pWheel.castShadows = true;
|
|
|
+ }
|
|
|
+ CreateEmitters();
|
|
|
+ raycastVehicle.ResetWheels();
|
|
|
+ }
|
|
|
+
|
|
|
+ void CreateEmitter(Vector3 place)
|
|
|
+ {
|
|
|
+ Node@ emitter = scene_.CreateChild();
|
|
|
+ emitter.worldPosition =
|
|
|
+ node.worldPosition + node.worldRotation * place + Vector3(0,
|
|
|
+ -wheelRadius,
|
|
|
+ 0);
|
|
|
+ ParticleEmitter@ particleEmitter =
|
|
|
+ emitter.CreateComponent("ParticleEmitter");
|
|
|
+ particleEmitter.effect =
|
|
|
+ cache.GetResource("ParticleEffect", "Particle/Dust.xml");
|
|
|
+ particleEmitter.emitting = false;
|
|
|
+ particleEmitterNodeList.Push(emitter);
|
|
|
+ emitter.temporary = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ void CreateEmitters()
|
|
|
+ {
|
|
|
+ particleEmitterNodeList.Clear();
|
|
|
+ RaycastVehicle@ raycastVehicle =
|
|
|
+ node.GetComponent("RaycastVehicle");
|
|
|
+ for (int id = 0; id < raycastVehicle.numWheels; id++)
|
|
|
+ {
|
|
|
+ Vector3 connectionPoint =
|
|
|
+ raycastVehicle.GetWheelConnectionPoint(id);
|
|
|
+ CreateEmitter(connectionPoint);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ void FixedUpdate(float timeStep)
|
|
|
+ {
|
|
|
+ float newSteering = 0.0f;
|
|
|
+ float accelerator = 0.0f;
|
|
|
+ bool brake = false;
|
|
|
+ RaycastVehicle@ raycastVehicle =
|
|
|
+ node.GetComponent("RaycastVehicle");
|
|
|
+ if (controls.IsDown(CTRL_LEFT))
|
|
|
+ newSteering = -1.0f;
|
|
|
+ if (controls.IsDown(CTRL_RIGHT))
|
|
|
+ newSteering = 1.0f;
|
|
|
+ if (controls.IsDown(CTRL_FORWARD))
|
|
|
+ accelerator = 1.0f;
|
|
|
+ if (controls.IsDown(CTRL_BACK))
|
|
|
+ accelerator = -0.5f;
|
|
|
+ if (controls.IsDown(CTRL_BRAKE))
|
|
|
+ brake = true;
|
|
|
+ // When steering, wake up the wheel rigidbodies so that their orientation is updated
|
|
|
+ if (newSteering != 0.0f)
|
|
|
+ {
|
|
|
+ steering = steering * 0.95f + newSteering * 0.05f;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ steering = steering * 0.8f + newSteering * 0.2f;
|
|
|
+ Quaternion steeringRot(0.0f, steering * MAX_WHEEL_ANGLE, 0.0f);
|
|
|
+ raycastVehicle.SetSteeringValue(0, steering);
|
|
|
+ raycastVehicle.SetSteeringValue(1, steering);
|
|
|
+ raycastVehicle.SetEngineForce(2, maxEngineForce * accelerator);
|
|
|
+ raycastVehicle.SetEngineForce(3, maxEngineForce * accelerator);
|
|
|
+ for (int i = 0; i < raycastVehicle.numWheels; i++)
|
|
|
+ if (brake)
|
|
|
+ {
|
|
|
+ raycastVehicle.SetBrake(i, brakingForce);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ raycastVehicle.SetBrake(i, 0.0f);
|
|
|
+ }
|
|
|
+ // Apply downforce proportional to velocity
|
|
|
+ Vector3 localVelocity =
|
|
|
+ hullBody.rotation.Inverse() * hullBody.linearVelocity;
|
|
|
+ hullBody.ApplyForce(hullBody.rotation *
|
|
|
+ Vector3(0.0f, -1.0f,
|
|
|
+ 0.0f) * Abs(localVelocity.z) *
|
|
|
+ DOWN_FORCE);
|
|
|
+ }
|
|
|
+
|
|
|
+ void PostUpdate(float timeStep)
|
|
|
+ {
|
|
|
+ if (particleEmitterNodeList.length == 0)
|
|
|
+ return;
|
|
|
+ RaycastVehicle@ raycastVehicle =
|
|
|
+ node.GetComponent("RaycastVehicle");
|
|
|
+ RigidBody@ vehicleBody = node.GetComponent("RigidBody");
|
|
|
+ Vector3 velocity = hullBody.linearVelocity;
|
|
|
+ Vector3 accel = (velocity - prevVelocity) / timeStep;
|
|
|
+ float planeAccel = Vector3(accel.x, 0.0f, accel.z).length;
|
|
|
+ for (int i = 0; i < raycastVehicle.numWheels; i++)
|
|
|
+ {
|
|
|
+ Node@ emitter = particleEmitterNodeList[i];
|
|
|
+ ParticleEmitter@ particleEmitter =
|
|
|
+ emitter.GetComponent("ParticleEmitter");
|
|
|
+ if (raycastVehicle.WheelIsGrounded(i)
|
|
|
+ && (
|
|
|
+ raycastVehicle.GetWheelSkidInfoCumulative(i) < 0.9f
|
|
|
+ ||
|
|
|
+ raycastVehicle.GetBrake(i) > 2.0f
|
|
|
+ ||
|
|
|
+ planeAccel > 15.0f))
|
|
|
+ {
|
|
|
+ emitter.worldPosition =
|
|
|
+ raycastVehicle.GetContactPosition(i);
|
|
|
+ if (!particleEmitter.emitting)
|
|
|
+ particleEmitter.emitting = true;
|
|
|
+ }
|
|
|
+ else if (particleEmitter.emitting)
|
|
|
+ particleEmitter.emitting = false;
|
|
|
+ }
|
|
|
+ prevVelocity = velocity;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+// Create XML patch instructions for screen joystick layout specific to this sample app
|
|
|
+String patchInstructions = "";
|
|
|
+
|