AvatarController.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. // designate component
  2. "atomic component";
  3. //import gl-matrix library
  4. //https://github.com/toji/gl-matrix for more information
  5. import {vec3, quat} from "gl-matrix";
  6. //define constans
  7. const MOVE_FORCE = 1.8;
  8. const INAIR_MOVE_FORCE = 0.02;
  9. const BRAKE_FORCE = 0.2;
  10. const JUMP_FORCE = 7.0;
  11. const YAW_SENSITIVITY = 0.1;
  12. const INAIR_THRESHOLD_TIME = 0.1;
  13. //define a component AvatarController
  14. class AvatarController extends Atomic.JSComponent {
  15. //define an inspectorFields to make variables visible in editor
  16. inspectorFields = {
  17. //needs default value to make editor understand type of that value
  18. speed: 1.0
  19. };
  20. speed = 1.0;
  21. cameraNode: Atomic.Node;
  22. onGround = true;
  23. okToJump = true;
  24. inAirTime = 0.0;
  25. softGrounded = true;
  26. cameraMode = 0;
  27. yaw = 0;
  28. pitch = 0;
  29. moveForward = false;
  30. moveBackwards = false;
  31. moveLeft = false;
  32. moveRight = false;
  33. mouseMoveX = 0.0;
  34. mouseMoveY = 0.0;
  35. button0 = false;
  36. button1 = false;
  37. lastButton0 = false;
  38. lastButton1 = false;
  39. idle = true;
  40. start() {
  41. //get main camera and set its node to cameraNode
  42. const camera = this.node.scene.getMainCamera();
  43. this.cameraNode = camera.node;
  44. // Create rigidbody, and set non-zero mass so that the body becomes dynamic
  45. const body = <Atomic.RigidBody>this.node.createComponent("RigidBody");
  46. body.mass = 1.0;
  47. // Set zero angular factor so that physics doesn't turn the character on its own.
  48. // Instead we will control the character yaw manually
  49. body.angularFactor = [0, 0, 0];
  50. // Set the rigidbody to signal collision also when in rest, so that we get ground collisions properly
  51. body.collisionEventMode = Atomic.COLLISION_ALWAYS;
  52. // Set a capsule shape for collision
  53. const shape = <Atomic.CollisionShape>this.node.createComponent("CollisionShape");
  54. shape.setCapsule(2, 4, [0, 2, 0]);
  55. }
  56. fixedUpdate(timeStep: number) {
  57. //get a RigidBody component from the current node
  58. const body = <Atomic.RigidBody>this.node.getComponent("RigidBody");
  59. // Update the in air timer. Reset if grounded
  60. if (!this.onGround) {
  61. this.inAirTime += timeStep;
  62. }
  63. else {
  64. this.inAirTime = 0.0;
  65. }
  66. // When character has been in air less than 1/10 second, it's still interpreted as being on ground
  67. const softGrounded = this.inAirTime < INAIR_THRESHOLD_TIME;
  68. // Get rotation of the current node
  69. const rot = this.node.getRotation();
  70. let moveDir = [0, 0, 0];
  71. // Update movement & animation
  72. const velocity = body.getLinearVelocity();
  73. // Velocity on the XZ plane
  74. const planeVelocity = [velocity[0], 0.0, velocity[2]];
  75. if (this.cameraMode != 2) {
  76. if (this.moveForward) {
  77. vec3.add(moveDir, moveDir, [0, 0, 1]);
  78. }
  79. if (this.moveBackwards) {
  80. vec3.add(moveDir, moveDir, [0, 0, -1]);
  81. }
  82. if (this.moveLeft) {
  83. vec3.add(moveDir, moveDir, [-1, 0, 0]);
  84. }
  85. if (this.moveRight) {
  86. vec3.add(moveDir, moveDir, [1, 0, 0]);
  87. }
  88. }
  89. if (vec3.length(moveDir) > 0.0) {
  90. vec3.normalize(moveDir, moveDir);
  91. }
  92. vec3.transformQuat(moveDir, moveDir, [rot[1], rot[2], rot[3], rot[0]]);
  93. vec3.scale(moveDir, moveDir, (softGrounded ? MOVE_FORCE : INAIR_MOVE_FORCE));
  94. if (this.softGrounded) {
  95. vec3.scale(moveDir, moveDir, this.speed);
  96. }
  97. body.applyImpulse(moveDir);
  98. if (this.softGrounded) {
  99. // When on ground, apply a braking force to limit maximum ground velocity
  100. vec3.negate(planeVelocity, planeVelocity);
  101. vec3.scale(planeVelocity, planeVelocity, BRAKE_FORCE);
  102. body.applyImpulse(planeVelocity);
  103. // Jump. Must release jump control inbetween jumps
  104. if (this.button1) {
  105. if (this.okToJump) {
  106. let jumpforce = [0, 1, 0];
  107. vec3.scale(jumpforce, jumpforce, JUMP_FORCE);
  108. //Apply impulse to the body
  109. body.applyImpulse(jumpforce);
  110. this.okToJump = false;
  111. }
  112. } else {
  113. this.okToJump = true;
  114. }
  115. }
  116. if (this.softGrounded && vec3.length(moveDir) > 0.0) {
  117. this.idle = false;
  118. } else {
  119. this.idle = true;
  120. }
  121. // Reset grounded flag for next frame
  122. this.onGround = true;
  123. }
  124. MoveCamera(timeStep) {
  125. // Movement speed as world units per second
  126. const MOVE_SPEED = 10.0;
  127. // Mouse sensitivity as degrees per pixel
  128. const MOUSE_SENSITIVITY = 0.1;
  129. this.yaw = this.yaw + MOUSE_SENSITIVITY * this.mouseMoveX;
  130. this.pitch = this.pitch + MOUSE_SENSITIVITY * this.mouseMoveY;
  131. if (this.pitch < -90) {
  132. this.pitch = -90;
  133. }
  134. if (this.pitch > 90) {
  135. this.pitch = 90;
  136. }
  137. // Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
  138. this.cameraNode.rotation = QuatFromEuler(this.pitch, this.yaw, 0.0);
  139. let speed = MOVE_SPEED * timeStep;
  140. //translate camera on the amount of speed value
  141. if (this.moveForward) {
  142. this.cameraNode.translate([0.0, 0.0, this.speed]);
  143. }
  144. if (this.moveBackwards) {
  145. this.cameraNode.translate([0.0, 0.0, -this.speed]);
  146. }
  147. if (this.moveLeft) {
  148. this.cameraNode.translate([-speed, 0.0, 0.0]);
  149. }
  150. if (this.moveRight) {
  151. this.cameraNode.translate([speed, 0.0, 0.0]);
  152. }
  153. }
  154. UpdateControls() {
  155. let input = Atomic.input;
  156. this.moveForward = false;
  157. this.moveBackwards = false;
  158. this.moveLeft = false;
  159. this.moveRight = false;
  160. this.mouseMoveX = 0.0;
  161. this.mouseMoveY = 0.0;
  162. this.button0 = false;
  163. this.button1 = false;
  164. // Movement speed as world units per second
  165. // let MOVE_SPEED = 20.0; -- unused
  166. // Mouse sensitivity as degrees per pixel
  167. // let MOUSE_SENSITIVITY = 0.1; -- unused
  168. //check input
  169. if (input.getKeyDown(Atomic.KEY_W) || input.getKeyDown(Atomic.KEY_UP)) {
  170. this.moveForward = true;
  171. }
  172. if (input.getKeyDown(Atomic.KEY_S) || input.getKeyDown(Atomic.KEY_DOWN)) {
  173. this.moveBackwards = true;
  174. }
  175. if (input.getKeyDown(Atomic.KEY_A) || input.getKeyDown(Atomic.KEY_LEFT)) {
  176. this.moveLeft = true;
  177. }
  178. if (input.getKeyDown(Atomic.KEY_D) || input.getKeyDown(Atomic.KEY_RIGHT)) {
  179. this.moveRight = true;
  180. }
  181. if (input.getKeyPress(Atomic.KEY_F)) {
  182. this.button0 = true;
  183. }
  184. if (input.getKeyPress(Atomic.KEY_SPACE)) {
  185. this.button1 = true;
  186. }
  187. //if we are on mobile
  188. if (Atomic.platform == "Android" || Atomic.platform == "iOS") {
  189. //iterate through each TouchState, if it doesn't touch any widgets, use it as a `mouse`
  190. for (let i = 0; i < Atomic.input.getNumTouches(); i++) {
  191. let touchState = Atomic.input.getTouch(i);
  192. if (touchState.touchedWidget == null) {
  193. let delta = touchState.delta;
  194. this.mouseMoveX = delta[0];
  195. this.mouseMoveY = delta[1];
  196. }
  197. }
  198. //if its a desktop
  199. } else {
  200. // update mouse coordinates
  201. this.mouseMoveX = input.getMouseMoveX();
  202. this.mouseMoveY = input.getMouseMoveY();
  203. }
  204. }
  205. update(timeStep) {
  206. this.UpdateControls();
  207. //if it's a free view
  208. if (this.cameraMode != 2) {
  209. this.yaw += this.mouseMoveX * YAW_SENSITIVITY;
  210. this.pitch += this.mouseMoveY * YAW_SENSITIVITY;
  211. }
  212. if (this.pitch < -80) {
  213. this.pitch = -80;
  214. }
  215. if (this.pitch > 80) {
  216. this.pitch = 80;
  217. }
  218. if (this.button0) {
  219. this.cameraMode++;
  220. if (this.cameraMode == 3) {
  221. this.cameraMode = 0;
  222. }
  223. }
  224. }
  225. //that function called right after update function
  226. postUpdate(timestep: number) {
  227. // Get camera lookat dir from character yaw + pitch
  228. let rot = this.node.getRotation();
  229. //create quaternion
  230. let dir = quat.create();
  231. //set X value
  232. quat.setAxisAngle(dir, [1, 0, 0], (this.pitch * Math.PI / 180.0));
  233. quat.multiply(dir, [rot[1], rot[2], rot[3], rot[0]], dir);
  234. let headNode = this.node.getChild("Head_Tip", true);
  235. //if it's a FPS view
  236. if (this.cameraMode == 1) {
  237. let headPos = <number[]>headNode.getWorldPosition();
  238. let offset = [0.0, 0.15, 0.2];
  239. vec3.add(headPos, headPos, vec3.transformQuat(offset, offset, [rot[1], rot[2], rot[3], rot[0]]));
  240. this.cameraNode.setPosition(headPos);
  241. this.cameraNode.setRotation([dir[3], dir[0], dir[1], dir[2]]);
  242. quat.setAxisAngle(dir, [0, 1, 0], (this.yaw * Math.PI / 180.0));
  243. this.node.setRotation([dir[3], dir[0], dir[1], dir[2]]);
  244. }
  245. //if it's a third person view
  246. if (this.cameraMode == 0) {
  247. let aimPoint = <number[]>this.node.getWorldPosition();
  248. let aimOffset = [0, 1.7, 0];
  249. vec3.transformQuat(aimOffset, aimOffset, dir);
  250. vec3.add(aimPoint, aimPoint, aimOffset);
  251. let rayDir = vec3.create();
  252. vec3.transformQuat(rayDir, [0, 0, -1], dir);
  253. vec3.scale(rayDir, rayDir, 8);
  254. vec3.add(aimPoint, aimPoint, rayDir);
  255. this.cameraNode.setPosition(aimPoint);
  256. this.cameraNode.setRotation([dir[3], dir[0], dir[1], dir[2]]);
  257. quat.setAxisAngle(dir, [0, 1, 0], (this.yaw * Math.PI / 180.0));
  258. this.node.setRotation([dir[3], dir[0], dir[1], dir[2]]);
  259. }
  260. else {
  261. this.MoveCamera(timestep);
  262. }
  263. }
  264. }
  265. function QuatFromEuler(x, y, z) {
  266. const M_PI = 3.14159265358979323846264338327950288;
  267. let q = [0, 0, 0, 0];
  268. x *= (M_PI / 360);
  269. y *= (M_PI / 360);
  270. z *= (M_PI / 360);
  271. const sinX = Math.sin(x);
  272. const cosX = Math.cos(x);
  273. const sinY = Math.sin(y);
  274. const cosY = Math.cos(y);
  275. const sinZ = Math.sin(z);
  276. const cosZ = Math.cos(z);
  277. q[0] = cosY * cosX * cosZ + sinY * sinX * sinZ;
  278. q[1] = cosY * sinX * cosZ + sinY * cosX * sinZ;
  279. q[2] = sinY * cosX * cosZ - cosY * sinX * sinZ;
  280. q[3] = cosY * cosX * sinZ - sinY * sinX * cosZ;
  281. return q;
  282. }
  283. export = AvatarController;