46_RaycastVehicleDemo.as 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  1. // Vehicle example.
  2. // This sample demonstrates:
  3. // - Creating a heightmap terrain with collision
  4. // - Constructing a physical vehicle with rigid bodies for the hull and the wheels, joined with constraints
  5. // - Saving and loading the variables of a script object, including node & component references
  6. #include "Scripts/Utilities/Sample.as"
  7. const int CTRL_FORWARD = 1;
  8. const int CTRL_BACK = 2;
  9. const int CTRL_LEFT = 4;
  10. const int CTRL_RIGHT = 8;
  11. const int CTRL_BRAKE = 16;
  12. const float CAMERA_DISTANCE = 10.0f;
  13. const float YAW_SENSITIVITY = 0.1f;
  14. const float ENGINE_FORCE = 2500.0f;
  15. const float DOWN_FORCE = 100.0f;
  16. const float MAX_WHEEL_ANGLE = 22.5f;
  17. const float CHASSIS_WIDTH = 2.6f;
  18. Node@ vehicleNode;
  19. void Start()
  20. {
  21. // Execute the common startup for samples
  22. SampleStart();
  23. // Create static scene content
  24. CreateScene();
  25. // Create the controllable vehicle
  26. CreateVehicle();
  27. // Create the UI content
  28. CreateInstructions();
  29. // Set the mouse mode to use in the sample
  30. SampleInitMouseMode(MM_RELATIVE);
  31. // Subscribe to necessary events
  32. SubscribeToEvents();
  33. }
  34. void CreateScene()
  35. {
  36. scene_ = Scene();
  37. // Create scene subsystem components
  38. scene_.CreateComponent("Octree");
  39. scene_.CreateComponent("PhysicsWorld");
  40. // Create camera and define viewport. Camera does not necessarily have to belong to the scene
  41. cameraNode = Node();
  42. Camera@ camera = cameraNode.CreateComponent("Camera");
  43. camera.farClip = 500.0f;
  44. renderer.viewports[0] = Viewport(scene_, camera);
  45. // Create static scene content. First create a zone for ambient lighting and fog control
  46. Node@ zoneNode = scene_.CreateChild("Zone");
  47. Zone@ zone = zoneNode.CreateComponent("Zone");
  48. zone.ambientColor = Color(0.15f, 0.15f, 0.15f);
  49. zone.fogColor = Color(0.5f, 0.5f, 0.7f);
  50. zone.fogStart = 300.0f;
  51. zone.fogEnd = 500.0f;
  52. zone.boundingBox = BoundingBox(-2000.0f, 2000.0f);
  53. // Create a directional light to the world. Enable cascaded shadows on it
  54. Node@ lightNode = scene_.CreateChild("DirectionalLight");
  55. lightNode.direction = Vector3(0.3f, -0.5f, 0.425f);
  56. Light@ light = lightNode.CreateComponent("Light");
  57. light.lightType = LIGHT_DIRECTIONAL;
  58. light.castShadows = true;
  59. light.shadowBias = BiasParameters(0.00025f, 0.5f);
  60. light.shadowCascade = CascadeParameters(10.0f, 50.0f, 200.0f, 0.0f, 0.8f);
  61. light.specularIntensity = 0.5f;
  62. // Create heightmap terrain with collision
  63. Node@ terrainNode = scene_.CreateChild("Terrain");
  64. terrainNode.position = Vector3(0.0f, 0.0f, 0.0f);
  65. Terrain@ terrain = terrainNode.CreateComponent("Terrain");
  66. terrain.patchSize = 64;
  67. terrain.spacing = Vector3(2.0f, 0.1f, 2.0f); // Spacing between vertices and vertical resolution of the height map
  68. terrain.smoothing = true;
  69. terrain.heightMap = cache.GetResource("Image", "Textures/HeightMap.png");
  70. terrain.material = cache.GetResource("Material", "Materials/Terrain.xml");
  71. // The terrain consists of large triangles, which fits well for occlusion rendering, as a hill can occlude all
  72. // terrain patches and other objects behind it
  73. terrain.occluder = true;
  74. RigidBody@ body = terrainNode.CreateComponent("RigidBody");
  75. body.collisionLayer = 2; // Use layer bitmask 2 for static geometry
  76. CollisionShape@ shape = terrainNode.CreateComponent("CollisionShape");
  77. shape.SetTerrain();
  78. // Create 1000 mushrooms in the terrain. Always face outward along the terrain normal
  79. const uint NUM_MUSHROOMS = 1000;
  80. for (uint i = 0; i < NUM_MUSHROOMS; ++i)
  81. {
  82. Node@ objectNode = scene_.CreateChild("Mushroom");
  83. Vector3 position(Random(2000.0f) - 1000.0f, 0.0f, Random(2000.0f) - 1000.0f);
  84. position.y = terrain.GetHeight(position) - 0.1f;
  85. objectNode.position = position;
  86. // Create a rotation quaternion from up vector to terrain normal
  87. objectNode.rotation = Quaternion(Vector3(0.0f, 1.0f, 0.0), terrain.GetNormal(position));
  88. objectNode.SetScale(3.0f);
  89. StaticModel@ object = objectNode.CreateComponent("StaticModel");
  90. object.model = cache.GetResource("Model", "Models/Mushroom.mdl");
  91. object.material = cache.GetResource("Material", "Materials/Mushroom.xml");
  92. object.castShadows = true;
  93. RigidBody@ mushroomBody = objectNode.CreateComponent("RigidBody");
  94. mushroomBody.collisionLayer = 2;
  95. CollisionShape@ mushroomShape = objectNode.CreateComponent("CollisionShape");
  96. mushroomShape.SetTriangleMesh(object.model, 0);
  97. }
  98. }
  99. void
  100. CreateVehicle()
  101. {
  102. vehicleNode = scene_.CreateChild("Vehicle");
  103. vehicleNode.position = Vector3(0.0f, 5.0f, 0.0f);
  104. // First createing player-controlled vehicle
  105. // Create the vehicle logic script object
  106. Vehicle@ vehicle = cast<Vehicle>(vehicleNode.CreateScriptObject(scriptFile, "Vehicle"));
  107. // Initialize vehicle component
  108. vehicle.Init();
  109. vehicleNode.AddTag("vehicle");
  110. // Set RigidBody physics parameters
  111. // (The RigidBody was created by vehicle.Init()
  112. RigidBody@ hullBody = vehicleNode.GetComponent("RigidBody");
  113. hullBody.mass = 800.0f;
  114. hullBody.linearDamping = 0.2f; // Some air resistance
  115. hullBody.angularDamping = 0.5f;
  116. hullBody.collisionLayer = 1;
  117. }
  118. void
  119. CreateInstructions()
  120. {
  121. // Construct new Text object, set string to display and font to use
  122. Text@ instructionText = ui.root.CreateChild("Text");
  123. instructionText.text =
  124. "Use WASD keys to drive, F to brake, mouse/touch to rotate camera\n"
  125. "F5 to save scene, F7 to load";
  126. instructionText.
  127. SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
  128. // The text has multiple rows. Center them in relation to each other
  129. instructionText.textAlignment = HA_CENTER;
  130. // Position the text relative to the screen center
  131. instructionText.horizontalAlignment = HA_CENTER;
  132. instructionText.verticalAlignment = VA_CENTER;
  133. instructionText.SetPosition(0, ui.root.height / 4);
  134. }
  135. void
  136. SubscribeToEvents()
  137. {
  138. // Subscribe to Update event for setting the vehicle controls before physics simulation
  139. SubscribeToEvent("Update", "HandleUpdate");
  140. // Subscribe to PostUpdate event for updating the camera position after physics simulation
  141. SubscribeToEvent("PostUpdate", "HandlePostUpdate");
  142. // Unsubscribe the SceneUpdate event from base class as the camera node is being controlled in HandlePostUpdate() in this sample
  143. UnsubscribeFromEvent("SceneUpdate");
  144. }
  145. void
  146. HandleUpdate(StringHash eventType, VariantMap& eventData)
  147. {
  148. if (vehicleNode is null)
  149. return;
  150. Vehicle@ vehicle = cast < Vehicle > (vehicleNode.scriptObject);
  151. if (vehicle is null)
  152. return;
  153. // Get movement controls and assign them to the vehicle component. If UI has a focused element, clear controls
  154. if (ui.focusElement is null)
  155. {
  156. vehicle.controls.Set(CTRL_FORWARD, input.keyDown[KEY_W]);
  157. vehicle.controls.Set(CTRL_BACK, input.keyDown[KEY_S]);
  158. vehicle.controls.Set(CTRL_LEFT, input.keyDown[KEY_A]);
  159. vehicle.controls.Set(CTRL_RIGHT, input.keyDown[KEY_D]);
  160. vehicle.controls.Set(CTRL_BRAKE, input.keyDown[KEY_F]);
  161. // Add yaw & pitch from the mouse motion. Used only for the camera, does not affect motion
  162. if (touchEnabled)
  163. {
  164. for (uint i = 0; i < input.numTouches; ++i)
  165. {
  166. TouchState@ state = input.touches[i];
  167. if (state.touchedElement is null) // Touch on empty space
  168. {
  169. Camera@ camera = cameraNode.GetComponent("Camera");
  170. if (camera is null)
  171. return;
  172. vehicle.controls.yaw += TOUCH_SENSITIVITY * camera.fov / graphics.height * state.delta.x;
  173. vehicle.controls.pitch += TOUCH_SENSITIVITY * camera.fov / graphics.height * state.delta.y;
  174. }
  175. }
  176. }
  177. else
  178. {
  179. vehicle.controls.yaw += input.mouseMoveX * YAW_SENSITIVITY;
  180. vehicle.controls.pitch += input.mouseMoveY * YAW_SENSITIVITY;
  181. }
  182. // Limit pitch
  183. vehicle.controls.pitch = Clamp(vehicle.controls.pitch, 0.0f, 80.0f);
  184. // Check for loading / saving the scene
  185. if (input.keyPress[KEY_F5])
  186. {
  187. File saveFile(fileSystem.programDir + "Data/Scenes/RaycastScriptVehicleDemo.xml", FILE_WRITE);
  188. scene_.SaveXML(saveFile);
  189. }
  190. if (input.keyPress[KEY_F7])
  191. {
  192. File loadFile(fileSystem.programDir + "Data/Scenes/RaycastScriptVehicleDemo.xml", FILE_READ);
  193. scene_.LoadXML(loadFile);
  194. // After loading we have to reacquire the vehicle scene node, as it has been recreated
  195. // Simply find by name as there's only one of them
  196. Array<Node@>@ vehicles = scene_.GetChildrenWithTag("vehicle");
  197. for (int i = 0; i < vehicles.length; i++)
  198. {
  199. Vehicle@ vehicleData = cast<Vehicle>(vehicles[i].scriptObject);
  200. vehicleData.CreateEmitters();
  201. }
  202. vehicleNode = scene_.GetChild("Vehicle", true);
  203. }
  204. }
  205. else
  206. vehicle.controls.Set(CTRL_FORWARD | CTRL_BACK | CTRL_LEFT | CTRL_RIGHT | CTRL_BRAKE, false);
  207. }
  208. void
  209. HandlePostUpdate(StringHash eventType, VariantMap& eventData)
  210. {
  211. if (vehicleNode is null)
  212. return;
  213. Vehicle@ vehicle = cast < Vehicle > (vehicleNode.scriptObject);
  214. if (vehicle is null)
  215. return;
  216. // Physics update has completed. Position camera behind vehicle
  217. Quaternion dir(vehicleNode.rotation.yaw, Vector3(0.0f, 1.0f, 0.0f));
  218. dir = dir * Quaternion(vehicle.controls.yaw, Vector3(0.0f, 1.0f, 0.0f));
  219. dir = dir * Quaternion(vehicle.controls.pitch, Vector3(1.0f, 0.0f, 0.0f));
  220. Vector3 cameraTargetPos = vehicleNode.position - dir * Vector3(0.0f, 0.0f, CAMERA_DISTANCE);
  221. Vector3 cameraStartPos = vehicleNode.position;
  222. // Raycast camera against static objects (physics collision mask 2)
  223. // and move it closer to the vehicle if something in between
  224. Ray cameraRay(cameraStartPos, (cameraTargetPos - cameraStartPos).Normalized());
  225. float cameraRayLength = (cameraTargetPos - cameraStartPos).length;
  226. PhysicsRaycastResult result = scene_.physicsWorld.RaycastSingle(cameraRay, cameraRayLength, 2);
  227. if (result.body !is null)
  228. cameraTargetPos = cameraStartPos + cameraRay.direction * (result.distance - 0.5f);
  229. cameraNode.position = cameraTargetPos;
  230. cameraNode.rotation = dir;
  231. }
  232. // Vehicle script object class
  233. //
  234. // When saving, the node and component handles are automatically converted into nodeID or componentID attributes
  235. // and are acquired from the scene when loading. The steering member variable will likewise be saved automatically.
  236. // The Controls object can not be automatically saved, so handle it manually in the Load() and Save() methods
  237. class Vehicle:ScriptObject
  238. {
  239. RigidBody@ hullBody;
  240. // Current left/right steering amount (-1 to 1.)
  241. float steering = 0.0f;
  242. // Vehicle controls.
  243. Controls controls;
  244. float suspensionRestLength = 0.6f;
  245. float suspensionStiffness = 14.0f;
  246. float suspensionDamping = 2.0f;
  247. float suspensionCompression = 4.0f;
  248. float wheelFriction = 1000.0f;
  249. float rollInfluence = 0.12f;
  250. float maxEngineForce = ENGINE_FORCE;
  251. float wheelWidth = 0.4f;
  252. float wheelRadius = 0.5f;
  253. float brakingForce = 50.0f;
  254. Array<Vector3> connectionPoints;
  255. Array<Node@> particleEmitterNodeList;
  256. protected Vector3 prevVelocity;
  257. void Load(Deserializer& deserializer)
  258. {
  259. controls.yaw = deserializer.ReadFloat();
  260. controls.pitch = deserializer.ReadFloat();
  261. }
  262. void Save(Serializer& serializer)
  263. {
  264. serializer.WriteFloat(controls.yaw);
  265. serializer.WriteFloat(controls.pitch);
  266. }
  267. void Init()
  268. {
  269. // This function is called only from the main program when initially creating the vehicle, not on scene load
  270. StaticModel@ hullObject = node.CreateComponent("StaticModel");
  271. hullBody = node.CreateComponent("RigidBody");
  272. CollisionShape@ hullShape = node.CreateComponent("CollisionShape");
  273. node.scale = Vector3(2.3f, 1.0f, 4.0f);
  274. hullObject.model = cache.GetResource("Model", "Models/Box.mdl");
  275. hullObject.material = cache.GetResource("Material", "Materials/Stone.xml");
  276. hullObject.castShadows = true;
  277. hullShape.SetBox(Vector3(1.0f, 1.0f, 1.0f));
  278. hullBody.mass = 800.0f;
  279. hullBody.linearDamping = 0.2f; // Some air resistance
  280. hullBody.angularDamping = 0.5f;
  281. hullBody.collisionLayer = 1;
  282. RaycastVehicle@ raycastVehicle = node.CreateComponent("RaycastVehicle");
  283. raycastVehicle.Init();
  284. connectionPoints.Reserve(4);
  285. float connectionHeight = -0.4f; //1.2f;
  286. bool isFrontWheel = true;
  287. Vector3 wheelDirection(0, -1, 0);
  288. Vector3 wheelAxle(-1, 0, 0);
  289. float wheelX = CHASSIS_WIDTH / 2.0 - wheelWidth;
  290. // Front left
  291. connectionPoints.Push(Vector3(-wheelX, connectionHeight, 2.5f - wheelRadius * 2.0f));
  292. // Front right
  293. connectionPoints.Push(Vector3(wheelX, connectionHeight, 2.5f - wheelRadius * 2.0f));
  294. // Back left
  295. connectionPoints.Push(Vector3(-wheelX, connectionHeight, -2.5f + wheelRadius * 2.0f));
  296. // Back right
  297. connectionPoints.Push(Vector3(wheelX, connectionHeight, -2.5f + wheelRadius * 2.0f));
  298. const Color LtBrown(0.972f, 0.780f, 0.412f);
  299. for (int id = 0; id < connectionPoints.length; id++)
  300. {
  301. Node@ wheelNode = scene_.CreateChild();
  302. Vector3 connectionPoint = connectionPoints[id];
  303. // Front wheels are at front (z > 0)
  304. // back wheels are at z < 0
  305. // Setting rotation according to wheel position
  306. bool isFrontWheel = connectionPoints[id].z > 0.0f;
  307. wheelNode.rotation = (connectionPoint.x >= 0.0 ? Quaternion(0.0f, 0.0f, -90.0f) : Quaternion(0.0f, 0.0f, 90.0f));
  308. wheelNode.worldPosition = (node.worldPosition + node.worldRotation * connectionPoints[id]);
  309. wheelNode.scale = Vector3(1.0f, 0.65f, 1.0f);
  310. raycastVehicle.AddWheel(wheelNode, wheelDirection, wheelAxle, suspensionRestLength, wheelRadius, isFrontWheel);
  311. raycastVehicle.SetWheelSuspensionStiffness(id, suspensionStiffness);
  312. raycastVehicle.SetWheelDampingRelaxation(id, suspensionDamping);
  313. raycastVehicle.SetWheelDampingCompression(id, suspensionCompression);
  314. raycastVehicle.SetWheelFrictionSlip(id, wheelFriction);
  315. raycastVehicle.SetWheelRollInfluence(id, rollInfluence);
  316. StaticModel@ pWheel = wheelNode.CreateComponent("StaticModel");
  317. pWheel.model = cache.GetResource("Model", "Models/Cylinder.mdl");
  318. pWheel.material = cache.GetResource("Material", "Materials/Stone.xml");
  319. pWheel.castShadows = true;
  320. }
  321. CreateEmitters();
  322. raycastVehicle.ResetWheels();
  323. }
  324. void CreateEmitter(Vector3 place)
  325. {
  326. Node@ emitter = scene_.CreateChild();
  327. emitter.worldPosition = node.worldPosition + node.worldRotation * place + Vector3(0, -wheelRadius, 0);
  328. ParticleEmitter@ particleEmitter = emitter.CreateComponent("ParticleEmitter");
  329. particleEmitter.effect = cache.GetResource("ParticleEffect", "Particle/Dust.xml");
  330. particleEmitter.emitting = false;
  331. particleEmitterNodeList.Push(emitter);
  332. emitter.temporary = true;
  333. }
  334. void CreateEmitters()
  335. {
  336. particleEmitterNodeList.Clear();
  337. RaycastVehicle@ raycastVehicle = node.GetComponent("RaycastVehicle");
  338. for (int id = 0; id < raycastVehicle.numWheels; id++)
  339. {
  340. Vector3 connectionPoint = raycastVehicle.GetWheelConnectionPoint(id);
  341. CreateEmitter(connectionPoint);
  342. }
  343. }
  344. void FixedUpdate(float timeStep)
  345. {
  346. float newSteering = 0.0f;
  347. float accelerator = 0.0f;
  348. bool brake = false;
  349. RaycastVehicle@ raycastVehicle = node.GetComponent("RaycastVehicle");
  350. if (controls.IsDown(CTRL_LEFT))
  351. newSteering = -1.0f;
  352. if (controls.IsDown(CTRL_RIGHT))
  353. newSteering = 1.0f;
  354. if (controls.IsDown(CTRL_FORWARD))
  355. accelerator = 1.0f;
  356. if (controls.IsDown(CTRL_BACK))
  357. accelerator = -0.5f;
  358. if (controls.IsDown(CTRL_BRAKE))
  359. brake = true;
  360. // When steering, wake up the wheel rigidbodies so that their orientation is updated
  361. if (newSteering != 0.0f)
  362. {
  363. steering = steering * 0.95f + newSteering * 0.05f;
  364. }
  365. else
  366. steering = steering * 0.8f + newSteering * 0.2f;
  367. Quaternion steeringRot(0.0f, steering * MAX_WHEEL_ANGLE, 0.0f);
  368. raycastVehicle.SetSteeringValue(0, steering);
  369. raycastVehicle.SetSteeringValue(1, steering);
  370. raycastVehicle.SetEngineForce(2, maxEngineForce * accelerator);
  371. raycastVehicle.SetEngineForce(3, maxEngineForce * accelerator);
  372. for (int i = 0; i < raycastVehicle.numWheels; i++)
  373. {
  374. if (brake)
  375. {
  376. raycastVehicle.SetBrake(i, brakingForce);
  377. }
  378. else
  379. {
  380. raycastVehicle.SetBrake(i, 0.0f);
  381. }
  382. }
  383. // Apply downforce proportional to velocity
  384. Vector3 localVelocity = hullBody.rotation.Inverse() * hullBody.linearVelocity;
  385. hullBody.ApplyForce(hullBody.rotation * Vector3(0.0f, -1.0f, 0.0f) * Abs(localVelocity.z) * DOWN_FORCE);
  386. }
  387. void PostUpdate(float timeStep)
  388. {
  389. if (particleEmitterNodeList.length == 0)
  390. return;
  391. RaycastVehicle@ raycastVehicle = node.GetComponent("RaycastVehicle");
  392. RigidBody@ vehicleBody = node.GetComponent("RigidBody");
  393. Vector3 velocity = hullBody.linearVelocity;
  394. Vector3 accel = (velocity - prevVelocity) / timeStep;
  395. float planeAccel = Vector3(accel.x, 0.0f, accel.z).length;
  396. for (int i = 0; i < raycastVehicle.numWheels; i++)
  397. {
  398. Node@ emitter = particleEmitterNodeList[i];
  399. ParticleEmitter@ particleEmitter = emitter.GetComponent("ParticleEmitter");
  400. if (raycastVehicle.WheelIsGrounded(i) && (raycastVehicle.GetWheelSkidInfoCumulative(i) < 0.9f || raycastVehicle.GetBrake(i) > 2.0f ||
  401. planeAccel > 15.0f))
  402. {
  403. emitter.worldPosition = raycastVehicle.GetContactPosition(i);
  404. if (!particleEmitter.emitting)
  405. particleEmitter.emitting = true;
  406. }
  407. else if (particleEmitter.emitting)
  408. particleEmitter.emitting = false;
  409. }
  410. prevVelocity = velocity;
  411. }
  412. }
  413. // Create XML patch instructions for screen joystick layout specific to this sample app
  414. String patchInstructions = "";