Advertisement
here2share

js hue jumper -- solo racing

Mar 27th, 2021
2,052
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. <html>
  2. <!--/*
  3.  
  4. HUE JUMPER - By Frank Force
  5. Low fi retro inspired endless runner in only 2 kilobytes!
  6.  
  7. Features
  8. - Retro style 3D rendering engine in full HD
  9. - Realistic driving physics and collisions
  10. - Random level generation with increasing difficulty
  11. - Gradient sky with sun and moon
  12. - Procedurally generated mountain range
  13. - Random trees and rocks
  14. - Camera rumble and slow when off road
  15. - Checkpoint system, road markers, and hue shift
  16. - Time and distance display
  17.  
  18. */-->
  19. <title>Hue Jumper</title>
  20. <meta charset="utf-8">
  21. <body bgcolor=#000>
  22. <canvas id=c style='touch-action:none;position:absolute;left:0px;top:0px;width:100%;height:100%'></canvas>
  23. <a hidden id=downloadLink></a>
  24. <script>
  25.    
  26. 'use strict';                        // strict mode
  27.    
  28. // debug settings
  29. const debug = 0;                     // enable debug features
  30. const usePointerLock = 1;            // remove pointer lock for 2k build
  31.  
  32. // draw settings
  33. const context = c.getContext('2d');  // canvas 2d context
  34. const drawDistance = 800;            // how many road segments to draw in front of player
  35. const cameraDepth = 1;               // FOV of camera (1 / Math.tan((fieldOfView/2) * Math.PI/180))
  36. const roadSegmentLength = 100;       // length of each road segment
  37. const roadWidth = 500;               // how wide is road
  38. const warningTrackWidth = 150;       // with of road plus warning track
  39. const dashLineWidth = 9;             // width of the dashed line in the road
  40. const maxPlayerX = 2e3;              // player can not move this far from center of road
  41. const mountainCount = 30;            // how many mountains are there
  42. const timeDelta = 1/60;              // inverse frame rate
  43.  
  44. // player settings
  45. const playerHeight = 150;            // how high is player above ground
  46. const playerMaxSpeed = 300;          // limit max player speed
  47. const playerAccel = 1;               // player acceleration
  48. const playerBrake = -3;              // player acceleration when breaking
  49. const playerTurnControl = .2;        // player turning rate
  50. const playerJumpSpeed = 25;          // z speed added for jump
  51. const playerSpringConstant = .01;    // spring players pitch
  52. const playerCollisionSlow = .1;      // slow down from collisions
  53. const pitchLerp = .1;                // speed that camera pitch changes
  54. const pitchSpringDamping = .9;       // dampen the pitch spring
  55. const elasticity = 1.2;              // bounce elasticity (2 is full bounce, 1 is none)
  56. const centrifugal = .002;            // how much to pull player on turns
  57. const forwardDamping = .999;         // dampen player z speed
  58. const lateralDamping = .7;           // dampen player x speed
  59. const offRoadDamping = .98;          // more damping when off road
  60. const gravity = -1;                  // gravity to apply in y axis
  61. const cameraHeadingScale = 2;        // scale of player turning to rotate camera
  62. const worldRotateScale = .00005;     // how much to rotate world around turns
  63.    
  64. // level settings
  65. const maxTime = 20;                  // time to start with
  66. const checkPointTime = 10;           // how much time for getting to checkpoint
  67. const checkPointDistance = 1e5;      // how far between checkpoints
  68. const checkpointMaxDifficulty = 9;   // how many checkpoints before max difficulty
  69. const roadEnd = 1e4;                 // how many sections until end of the road
  70.    
  71. // global game variables  
  72. let playerPos;                  // player position 3d vector
  73. let playerVelocity;             // player velocity 3d vector
  74. let playerPitchSpring;          // spring for player pitch bounce
  75. let playerPitchSpringVelocity;  // velocity of pitch spring
  76. let playerPitchRoad;            // pitch of road, or 0 if player is in air
  77. let playerAirFrame;             // how many frames player has been in air
  78. let worldHeading;               // heading to turn skybox
  79. let randomSeed;                 // random seed for level
  80. let startRandomSeed;            // save the starting seed for active use
  81. let nextCheckPoint;             // distance of next checkpoint
  82. let hueShift;                   // current hue shift for all hsl colors
  83. let road;                       // the list of road segments
  84. let time;                       // time left before game over
  85. let lastUpdate = 0;             // time of last update
  86. let timeBuffer = 0;             // frame rate adjustment
  87.  
  88. function StartLevel()
  89. {
  90.     /////////////////////////////////////////////////////////////////////////////////////
  91.     // build the road with procedural generation
  92.     /////////////////////////////////////////////////////////////////////////////////////
  93.  
  94.     let roadGenSectionDistanceMax = 0;          // init end of section distance
  95.     let roadGenWidth = roadWidth;               // starting road width
  96.     let roadGenSectionDistance = 0;             // distance left for this section
  97.     let roadGenTaper = 0;                       // length of taper
  98.     let roadGenWaveFrequencyX = 0;              // X wave frequency
  99.     let roadGenWaveFrequencyY = 0;              // Y wave frequency
  100.     let roadGenWaveScaleX = 0;                  // X wave amplitude (turn size)
  101.     let roadGenWaveScaleY = 0;                  // Y wave amplitude (hill size)
  102.     startRandomSeed = randomSeed = Date.now();  // set random seed
  103.     road = [];                                  // clear list of road segments
  104.    
  105.     // generate the road
  106.     for( let i = 0; i < roadEnd*2; ++i )                                      // build road past end
  107.     {
  108.         if (roadGenSectionDistance++ > roadGenSectionDistanceMax)             // check for end of section
  109.         {
  110.             // calculate difficulty percent
  111.             const difficulty = Math.min(1, i*roadSegmentLength/checkPointDistance/checkpointMaxDifficulty); // difficulty
  112.            
  113.             // randomize road settings
  114.             roadGenWidth = roadWidth*Random(1-difficulty*.7, 3-2*difficulty);        // road width
  115.             roadGenWaveFrequencyX = Random(Lerp(difficulty, .01, .02));              // X frequency
  116.             roadGenWaveFrequencyY = Random(Lerp(difficulty, .01, .03));              // Y frequency
  117.             roadGenWaveScaleX = i > roadEnd ? 0 : Random(Lerp(difficulty, .2, .6));  // X scale
  118.             roadGenWaveScaleY = Random(Lerp(difficulty, 1e3, 2e3));                  // Y scale
  119.            
  120.             // apply taper and move back
  121.             roadGenTaper = Random(99, 1e3)|0;                           // randomize taper
  122.             roadGenSectionDistanceMax = roadGenTaper + Random(99, 1e3); // randomize segment distance
  123.             roadGenSectionDistance = 0;                                 // reset section distance
  124.             i -= roadGenTaper;                                          // subtract taper
  125.         }
  126.        
  127.         // make a wavy road
  128.         const x = Math.sin(i*roadGenWaveFrequencyX) * roadGenWaveScaleX;      // road X
  129.         const y = Math.sin(i*roadGenWaveFrequencyY) * roadGenWaveScaleY;      // road Y
  130.         road[i] = road[i]? road[i] : {x:x, y:y, w:roadGenWidth};              // get or make road segment
  131.        
  132.         // apply taper from last section
  133.         const p = Clamp(roadGenSectionDistance / roadGenTaper, 0, 1);         // get taper percent
  134.         road[i].x = Lerp(p, road[i].x, x);                                    // X pos and taper
  135.         road[i].y = Lerp(p, road[i].y, y);                                    // Y pos and taper
  136.         road[i].w = i > roadEnd ? 0 : Lerp(p, road[i].w, roadGenWidth);       // check for road end, width and taper
  137.         road[i].a = road[i-1] ? Math.atan2(road[i-1].y-road[i].y, roadSegmentLength) : 0; // road pitch angle
  138.     }  
  139.    
  140.     /////////////////////////////////////////////////////////////////////////////////////
  141.     // init game
  142.     /////////////////////////////////////////////////////////////////////////////////////
  143.      
  144.     // reset everything
  145.     playerVelocity = new Vector3
  146.     (
  147.         playerPitchSpring =
  148.         playerPitchSpringVelocity =
  149.         playerPitchRoad =  
  150.         hueShift = 0
  151.     );
  152.     playerPos = new Vector3(0, playerHeight);   // set player pos
  153.     worldHeading = randomSeed;                  // randomize world heading
  154.     nextCheckPoint = checkPointDistance;        // init next checkpoint
  155.     time = maxTime;                             // set the starting time
  156. }
  157.    
  158. function Update()
  159. {
  160.     // time regulation, in case running faster then 60 fps, though it causes judder REMOVE FROM MINFIED
  161.     const now = performance.now();
  162.     if (lastUpdate)
  163.     {
  164.         // limit to 60 fps
  165.         const delta = now - lastUpdate;
  166.         if (timeBuffer + delta < 0)
  167.         {
  168.             // running fast
  169.             requestAnimationFrame(Update);
  170.             return;
  171.         }
  172.        
  173.         // update time buffer
  174.         timeBuffer += delta;
  175.         timeBuffer -= timeDelta * 1e3;
  176.         if (timeBuffer > timeDelta * 1e3)
  177.             timeBuffer = 0; // if running too slow
  178.     }
  179.     lastUpdate = now;
  180.    
  181.     // start frame
  182.     if (snapshot) {c.width|0} else                                  // DEBUG REMOVE FROM MINFIED
  183.         c.width = window.innerWidth,c.height = window.innerHeight;  // clear the screen and set size
  184.    
  185.     if (!c.width) // REMOVE FROM MINFIED
  186.     {
  187.         // fix bug on itch, wait for canvas before updating
  188.         requestAnimationFrame(Update);
  189.         return;
  190.     }
  191.    
  192.     if (usePointerLock && document.pointerLockElement !== c && !touchMode) // set mouse down if pointer lock released
  193.         mouseDown = 1;
  194.    
  195.     UpdateDebugPre(); // DEBUG REMOVE FROM MINFIED
  196.    
  197.     /////////////////////////////////////////////////////////////////////////////////////
  198.     // update player - controls and physics
  199.     /////////////////////////////////////////////////////////////////////////////////////
  200.    
  201.     // get player road segment
  202.     const playerRoadSegment = playerPos.z/roadSegmentLength|0;         // current player road segment
  203.     const playerRoadSegmentPercent = playerPos.z/roadSegmentLength%1;  // how far player is along current segment
  204.    
  205.     // get lerped values between last and current road segment
  206.     const playerRoadX = Lerp(playerRoadSegmentPercent, road[playerRoadSegment].x, road[playerRoadSegment+1].x);
  207.     const playerRoadY = Lerp(playerRoadSegmentPercent, road[playerRoadSegment].y, road[playerRoadSegment+1].y) + playerHeight;
  208.     const roadPitch = Lerp(playerRoadSegmentPercent, road[playerRoadSegment].a, road[playerRoadSegment+1].a);
  209.    
  210.     const playerVelocityLast = playerVelocity.Add(0);                      // save last velocity
  211.     playerVelocity.y += gravity;                                           // gravity
  212.     playerVelocity.x *= lateralDamping;                                    // apply lateral damping
  213.     playerVelocity.z = Math.max(0, time ? forwardDamping*playerVelocity.z : 0); // apply damping, prevent moving backwards
  214.     playerPos = playerPos.Add(playerVelocity);                             // add player velocity
  215.    
  216.     const playerTurnAmount = Lerp(playerVelocity.z/playerMaxSpeed, mouseX * playerTurnControl, 0); // turning
  217.     playerVelocity.x +=                                          // update x velocity
  218.         playerVelocity.z * playerTurnAmount -                    // apply turn
  219.         playerVelocity.z ** 2 * centrifugal * playerRoadX;       // apply centrifugal force
  220.     playerPos.x = Clamp(playerPos.x, -maxPlayerX, maxPlayerX);   // limit player x position
  221.    
  222.     // check if on ground
  223.     if (playerPos.y < playerRoadY)
  224.     {
  225.         // bounce velocity against ground normal
  226.         playerPos.y = playerRoadY;                                                                // match y to ground plane
  227.         playerAirFrame = 0;                                                                       // reset air grace frames
  228.         playerVelocity = new Vector3(0, Math.cos(roadPitch), Math.sin(roadPitch))                 // get ground normal
  229.             .Multiply(-elasticity *                                                               // apply bounce
  230.                (Math.cos(roadPitch) * playerVelocity.y + Math.sin(roadPitch) * playerVelocity.z)) // dot of road and velocity
  231.             .Add(playerVelocity);                                                                 // add velocity
  232.  
  233.         playerVelocity.z +=
  234.             mouseDown? playerBrake :                                                // apply brake              
  235.             Lerp(playerVelocity.z/playerMaxSpeed, mouseWasPressed*playerAccel, 0);  // apply accel
  236.        
  237.         if (Math.abs(playerPos.x) > road[playerRoadSegment].w)                      // check if off road
  238.         {
  239.             playerVelocity.z *= offRoadDamping;                                     // slow down when off road
  240.             playerPitchSpring += Math.sin(playerPos.z/99)**4/99;                    // bump when off road
  241.         }
  242.     }
  243.  
  244.     // update jump
  245.     if (playerAirFrame++<6 && mouseDown && mouseUpFrames && mouseUpFrames<9 && time)  // check for jump
  246.     {
  247.         playerVelocity.y += playerJumpSpeed;                                          // apply jump velocity
  248.         playerAirFrame = 9;                                                           // prevent jumping again
  249.     }
  250.     mouseUpFrames = mouseDown? 0 : mouseUpFrames+1;                                   // update mouse up frames for double click
  251.     const airPercent = (playerPos.y-playerRoadY)/99;                                  // calculate above ground percent
  252.     playerPitchSpringVelocity += Lerp(airPercent,0,playerVelocity.y/4e4);             // pitch down with vertical velocity
  253.    
  254.     // update player pitch
  255.     playerPitchSpringVelocity += (playerVelocity.z - playerVelocityLast.z)/2e3;       // pitch down with forward accel
  256.     playerPitchSpringVelocity -= playerPitchSpring * playerSpringConstant;            // apply pitch spring constant
  257.     playerPitchSpringVelocity *= pitchSpringDamping;                                  // dampen pitch spring
  258.     playerPitchSpring += playerPitchSpringVelocity;                                   // update pitch spring        
  259.     playerPitchRoad = Lerp(pitchLerp, playerPitchRoad, Lerp(airPercent,-roadPitch,0));// match pitch to road
  260.     const playerPitch = playerPitchSpring + playerPitchRoad;                          // update player pitch
  261.    
  262.     if (playerPos.z > nextCheckPoint)          // crossed checkpoint
  263.     {
  264.         time += checkPointTime;                // add more time
  265.         nextCheckPoint += checkPointDistance;  // set next checkpoint
  266.         hueShift += 36;                        // shift hue
  267.     }
  268.    
  269.     /////////////////////////////////////////////////////////////////////////////////////
  270.     // draw background - sky, sun/moon, mountains, and horizon
  271.     /////////////////////////////////////////////////////////////////////////////////////
  272.    
  273.     // multi use local variables
  274.     let x, y, w, i;
  275.  
  276.     randomSeed = startRandomSeed;                                                                 // set start seed
  277.     worldHeading = ClampAngle(worldHeading + playerVelocity.z * playerRoadX * worldRotateScale);  // update world angle
  278.    
  279.     // pre calculate projection scale, flip y because y+ is down on canvas
  280.     const projectScale = (new Vector3(1, -1, 1)).Multiply(c.width/2/cameraDepth);                 // get projection scale
  281.     const cameraHeading = playerTurnAmount * cameraHeadingScale;                                  // turn camera with player
  282.     const cameraOffset = Math.sin(cameraHeading)/2;                                               // apply heading with offset
  283.    
  284.     // draw sky
  285.     const lighting = Math.cos(worldHeading);                                    // brightness from sun
  286.     const horizon = c.height/2 - Math.tan(playerPitch) * projectScale.y;        // get horizon line
  287.     const g = context.createLinearGradient(0,horizon-c.height/2,0,horizon);     // linear gradient for sky
  288.     g.addColorStop(0,LSHA(39+lighting*25,49+lighting*19,230-lighting*19));      // top sky color
  289.     g.addColorStop(1,LSHA(5,79,250-lighting*9));                                // bottom sky color
  290.     DrawPoly(c.width/2, 0, c.width/2, c.width/2, c.height, c.width/2, g);       // draw sky
  291.    
  292.     // draw sun and moon
  293.     for( i = 2; i--; )                                                          // 0 is sun, 1 is moon
  294.     {
  295.         const g = context.createRadialGradient(                                 // radial gradient for sun
  296.             x = c.width*(.5+Lerp(                                               // angle 0 is center
  297.                 (worldHeading/Math.PI/2+.5+i/2)%1,                              // sun angle percent
  298.                 4, -4)-cameraOffset),                                           // sun x pos, move far away for wrap
  299.             y = horizon - c.width/5,                                            // sun y pos
  300.             c.width/25,                                                         // sun size
  301.             x, y, i?c.width/23:c.width);                                        // sun end pos & size
  302.         g.addColorStop(0, LSHA(i?70:99));                                       // sun start color
  303.         g.addColorStop(1, LSHA(0,0,0,0));                                       // sun end color
  304.         DrawPoly(c.width/2, 0, c.width/2, c.width/2, c.height, c.width/2, g);   // draw sun
  305.     }
  306.  
  307.     // draw mountains
  308.     for( i = mountainCount; i--; )                                              // draw every mountain
  309.     {
  310.         const angle = ClampAngle(worldHeading+Random(19));                      // mountain random angle
  311.         const lighting = Math.cos(angle-worldHeading);                          // mountain lighting
  312.         DrawPoly(
  313.             x = c.width*(.5+Lerp(angle/Math.PI/2+.5, 4, -4)-cameraOffset),      // mountain x pos, move far away for wrap
  314.             y = horizon,                                                        // mountain base
  315.             w = Random(.2,.8)**2*c.width/2,                                     // mountain width
  316.             x+w*Random(-.5,.5),                                                 // random tip skew
  317.             y - Random(.5,.8)*w, 0,                                             // mountain height
  318.             LSHA(Random(15,25)+i/3-lighting*9,i/2+Random(19),Random(220,230))); // mountain color
  319.     }
  320.    
  321.     // draw horizon
  322.     DrawPoly(c.width/2, horizon, c.width/2, c.width/2, c.height, c.width/2,     // horizon pos & size
  323.         LSHA(25, 30, 95));                                                      // horizon color
  324.    
  325.     /////////////////////////////////////////////////////////////////////////////////////
  326.     // draw road and objects
  327.     /////////////////////////////////////////////////////////////////////////////////////
  328.    
  329.     // calculate road x offsets and projections
  330.     for( x = w = i = 0; i < drawDistance+1; )
  331.     {
  332.         // create road world position
  333.         let p = new Vector3(                                                     // set road position
  334.             x += w += road[playerRoadSegment+i].x,                               // sum local road offsets
  335.             road[playerRoadSegment+i].y, (playerRoadSegment+i)*roadSegmentLength)// road y and z pos
  336.                 .Add(playerPos.Multiply(-1));                                    // subtract to get local space
  337.  
  338.         p.x = p.x*Math.cos(cameraHeading) - p.z*Math.sin(cameraHeading); // rotate camera heading
  339.        
  340.         // tilt camera pitch
  341.         const z = 1 / (p.z*Math.cos(playerPitch) - p.y*Math.sin(playerPitch)); // invert z for projection
  342.         p.y = p.y*Math.cos(playerPitch) - p.z*Math.sin(playerPitch);
  343.         p.z = z;
  344.        
  345.         // project road segment to canvas space
  346.         road[playerRoadSegment+i++].p =                 // set projected road point
  347.             p.Multiply(new Vector3(z, z, 1))            // projection
  348.             .Multiply(projectScale)                     // scale
  349.             .Add(new Vector3(c.width/2,c.height/2))     // center on canvas
  350.     }
  351.    
  352.     // draw the road segments
  353.     let segment2 = road[playerRoadSegment+drawDistance];                     // store the last segment
  354.     for( i = drawDistance; i--; )                                            // iterate in reverse
  355.     {
  356.         const segment1 = road[playerRoadSegment+i];                        
  357.         randomSeed = startRandomSeed + playerRoadSegment + i;                // random seed for this segment
  358.         const lighting = Math.sin(segment1.a) * Math.cos(worldHeading)*99;   // calculate segment lighting
  359.         const p1 = segment1.p;                                               // projected point
  360.         const p2 = segment2.p;                                               // last projected point
  361.        
  362.         if (p1.z < 1e5 && p1.z > 0)                                          // check near and far clip
  363.         {
  364.             // draw road segment
  365.             if (i % (Lerp(i/drawDistance,1,9)|0) == 0)                       // fade in road resolution
  366.             {
  367.                 // ground
  368.                 DrawPoly(c.width/2, p1.y, c.width/2, c.width/2, p2.y, c.width/2,    // ground top & bottom
  369.                     LSHA(25+lighting, 30, 95));                                     // ground color
  370.  
  371.                 // warning track
  372.                 if (segment1.w > 400)                                               // no warning track if thin
  373.                     DrawPoly(p1.x, p1.y, p1.z*(segment1.w+warningTrackWidth),       // warning track top
  374.                         p2.x, p2.y, p2.z*(segment2.w+warningTrackWidth),            // warning track bottom
  375.                         LSHA(((playerRoadSegment+i)%19<9? 50: 20)+lighting));       // warning track stripe color
  376.                
  377.                 // road
  378.                 const z = (playerRoadSegment+i)*roadSegmentLength;                  // segment distance
  379.                 DrawPoly(p1.x, p1.y, p1.z*segment1.w,                               // road top
  380.                     p2.x, p2.y, p2.z*segment2.w,                                    // road bottom
  381.                     LSHA((z%checkPointDistance < 300 ? 70 : 7)+lighting)); // road color and checkpoint
  382.                    
  383.                 // dashed lines
  384.                 if (segment1.w > 300)                                               // no dash lines if very thin
  385.                     (playerRoadSegment+i)%9==0 && i < drawDistance/3 &&             // make dashes and skip if far out
  386.                         DrawPoly(p1.x, p1.y, p1.z*dashLineWidth,                    // dash lines top
  387.                         p2.x, p2.y, p2.z*dashLineWidth,                             // dash lines bottom
  388.                         LSHA(70+lighting));                                         // dash lines color
  389.  
  390.                 segment2 = segment1;                                                // prep for next segment
  391.             }
  392.  
  393.             // random object (tree or rock)
  394.             if (Random()<.2 && playerRoadSegment+i>29)                           // check for road object
  395.             {
  396.                 // player object collision check
  397.                 const z = (playerRoadSegment+i)*roadSegmentLength;               // segment distance
  398.                 const height = (Random(2)|0) * 400;                              // object type & height
  399.                 x = 2*roadWidth * Random(10,-10) * Random(9);                    // choose object pos
  400.                 if (!segment1.h                                                  // prevent hitting the same object
  401.                     && Math.abs(playerPos.x - x) < 200                           // x collision
  402.                     && Math.abs(playerPos.z - z) < 200                           // z collision
  403.                     && playerPos.y-playerHeight < segment1.y+200+height)         // y collision + object height
  404.                 {
  405.                     playerVelocity = playerVelocity.Multiply(segment1.h = playerCollisionSlow); // stop player and mark hit
  406.                 }
  407.  
  408.                 // draw road object
  409.                 const alpha = Lerp(i/drawDistance, 4, 0);                        // fade in object alpha
  410.                 if (height)                                                      // tree          
  411.                 {
  412.                     DrawPoly(x = p1.x+p1.z * x, p1.y, p1.z*29,                   // trunk bottom
  413.                         x, p1.y-99*p1.z, p1.z*29,                                // trunk top
  414.                         LSHA(5+Random(9), 50+Random(9), 29+Random(9), alpha));   // trunk color
  415.                     DrawPoly(x, p1.y-Random(50,99)*p1.z, p1.z*Random(199,250),   // leaves bottom
  416.                         x, p1.y-Random(600,800)*p1.z, 0,                         // leaves top
  417.                         LSHA(25+Random(9), 80+Random(9), 9+Random(29), alpha));  // leaves color
  418.                 }
  419.                 else                                                                           // rock
  420.                 {
  421.                     DrawPoly(x = p1.x+p1.z * x, p1.y, p1.z*Random(200,250),                    // rock bottom
  422.                         x+p1.z*(Random(99,-99)), p1.y-Random(200,250)*p1.z, p1.z*Random(99),   // rock top
  423.                         LSHA(50+Random(19), 25+Random(19), 209+Random(9), alpha));             // rock color
  424.                 }
  425.             }
  426.         }
  427.     }
  428.    
  429.     UpdateDebugPost(); // DEBUG REMOVE FROM MINFIED
  430.    
  431.     /////////////////////////////////////////////////////////////////////////////////////
  432.     // draw and update time
  433.     /////////////////////////////////////////////////////////////////////////////////////
  434.    
  435.     if (mouseWasPressed)
  436.     {
  437.         DrawText(Math.ceil(time = Clamp(time - timeDelta, 0, maxTime)), 9); // show and update time
  438.         context.textAlign = 'right';                                        // set right alignment for distance
  439.         DrawText(0|playerPos.z/1e3, c.width-9);                             // show distance
  440.     }
  441.     else
  442.     {
  443.         context.textAlign = 'center';        // set center alignment for title
  444.         DrawText('HUE JUMPER', c.width/2);   // draw title text
  445.     }
  446.    
  447.     requestAnimationFrame(Update);           // kick off next frame
  448. }
  449.    
  450. /////////////////////////////////////////////////////////////////////////////////////
  451. // math and helper functions
  452. /////////////////////////////////////////////////////////////////////////////////////
  453.    
  454. const LSHA       = (l, s=0, h=0, a=1) =>`hsl(${ h + hueShift },${ s }%,${ l }%,${ a })`;
  455. const Clamp      = (v, min, max)      => Math.min(Math.max(v, min), max);
  456. const ClampAngle = (a)                => (a+Math.PI) % (2*Math.PI) + (a+Math.PI<0? Math.PI : -Math.PI);
  457. const Lerp       = (p, a, b)          => a + Clamp(p, 0, 1) * (b-a);
  458. const Random     = (max=1, min=0)     => Lerp((Math.sin(++randomSeed)+1)*1e5%1, min, max);
  459.    
  460. // simple 3d vector class
  461. class Vector3
  462. {
  463.     constructor(x=0, y=0, z=0) { this.x = x; this.y = y; this.z = z }
  464.     Add(v)      { v = isNaN(v) ? v : new Vector3(v,v,v); return new Vector3( this.x + v.x, this.y + v.y, this.z + v.z); }
  465.     Multiply(v) { v = isNaN(v) ? v : new Vector3(v,v,v); return new Vector3( this.x * v.x, this.y * v.y, this.z * v.z); }
  466. }
  467.    
  468. // draw a trapazoid shaped poly
  469. function DrawPoly(x1, y1, w1, x2, y2, w2, fillStyle)
  470. {
  471.     context.beginPath(context.fillStyle = fillStyle);
  472.     context.lineTo(x1-w1, y1|0);
  473.     context.lineTo(x1+w1, y1|0);
  474.     context.lineTo(x2+w2, y2|0);
  475.     context.lineTo(x2-w2, y2|0);
  476.     context.fill();
  477. }
  478.  
  479. // draw outlined hud text
  480. function DrawText(text, posX)
  481. {
  482.     context.font = '9em impact';           // set font size
  483.     context.fillStyle = LSHA(99,0,0,.5);   // set font
  484.     context.fillText(text, posX, 129);     // fill text
  485.     context.lineWidth = 3;                 // line width
  486.     context.strokeText(text, posX, 129);   // outline text
  487. }
  488.  
  489. /////////////////////////////////////////////////////////////////////////////////////
  490. // mouse input
  491. /////////////////////////////////////////////////////////////////////////////////////
  492.  
  493. let mouseDown       = 0;
  494. let mouseWasPressed = 0;
  495. let mouseUpFrames   = 0;
  496. let mouseX          = 0;
  497. let mouseLockX      = 0;
  498. let touchMode       = 0;
  499.    
  500. onmouseup   = e => mouseDown = 0;
  501. onmousedown = e =>
  502. {
  503.     if (mouseWasPressed)
  504.         mouseDown = 1;
  505.     mouseWasPressed = 1;
  506.     if (usePointerLock && e.button == 0 && document.pointerLockElement !== c)
  507.     {
  508.         c.requestPointerLock = c.requestPointerLock || c.mozRequestPointerLock;
  509.         c.requestPointerLock();
  510.         mouseLockX = 0;
  511.     }
  512. }
  513.  
  514. onmousemove = e =>
  515. {
  516.     if (!usePointerLock)
  517.     {
  518.         mouseX = e.x/window.innerWidth*2-1
  519.         return;
  520.     }
  521.    
  522.     if (document.pointerLockElement !== c)
  523.         return;
  524.    
  525.     // adjust for pointer lock
  526.     mouseLockX += e.movementX;
  527.     mouseLockX = Clamp(mouseLockX, -window.innerWidth/2,  window.innerWidth/2);
  528.    
  529.     // apply curve to input
  530.     const inputCurve = 1.5;
  531.     mouseX = mouseLockX;
  532.     mouseX /= window.innerWidth/2;
  533.     mouseX = Math.sign(mouseX) * (1-(1-Math.abs(mouseX))**inputCurve);
  534.     mouseX *= window.innerWidth/2;
  535.     mouseX += window.innerWidth/2;
  536.     mouseX = mouseX/window.innerWidth*2-1
  537. }
  538.  
  539. /////////////////////////////////////////////////////////////////////////////////////
  540. // touch control
  541. /////////////////////////////////////////////////////////////////////////////////////
  542.  
  543. if (typeof ontouchend != 'undefined')
  544. {
  545.     let ProcessTouch = e =>
  546.     {
  547.         e.preventDefault();
  548.         mouseDown = !(e.touches.length > 0);
  549.         mouseWasPressed = 1;
  550.         touchMode = 1;
  551.        
  552.         if (mouseDown)
  553.             return;
  554.  
  555.         // average all touch positions
  556.         let x = 0, y = 0;
  557.         for (let touch of e.touches)
  558.         {
  559.             x += touch.clientX;
  560.             y += touch.clientY;
  561.         }
  562.         mouseX = x/e.touches.length;
  563.         mouseX = mouseX/window.innerWidth*2-1
  564.     }
  565.  
  566.     c.addEventListener('touchstart',  ProcessTouch, false);
  567.     c.addEventListener('touchmove',   ProcessTouch, false);
  568.     c.addEventListener('touchcancel', ProcessTouch, false);
  569.     c.addEventListener('touchend',    ProcessTouch, false);
  570. }
  571.    
  572. /////////////////////////////////////////////////////////////////////////////////////
  573. // debug stuff
  574. /////////////////////////////////////////////////////////////////////////////////////
  575.  
  576. let debugPrintLines;
  577. let snapshot;
  578.    
  579. function UpdateDebugPre()
  580. {
  581.     debugPrintLines = [];
  582.    
  583.     if (inputWasPushed[82]) // R = restart
  584.     {
  585.         mouseLockX = 0;
  586.         StartLevel();
  587.     }
  588.    
  589.     if (inputWasPushed[49]) // 1 = screenshot
  590.     {
  591.         snapshot = 1;
  592.        
  593.         // use 1080p resolution
  594.         c.width = 1920;
  595.         c.height = 1080;
  596.     }
  597. }
  598.    
  599. function UpdateDebugPost()
  600. {
  601.     if (snapshot)
  602.     {
  603.         SaveSnapshot();
  604.         snapshot = 0;
  605.     }
  606.    
  607.     UpdateInput();
  608.    
  609.     if (!debug)
  610.         return;
  611.    
  612.     UpdateFps();
  613.    
  614.     context.font='2em"';
  615.     for (let i in debugPrintLines)
  616.     {
  617.         let line = debugPrintLines[i];
  618.         context.fillStyle = line.color;
  619.         context.fillText(line.text,c.width/2,35+35*i);
  620.     }
  621. }
  622.    
  623. function DebugPrint(text, color='#F00')
  624. {
  625.     if (!debug)
  626.         return;
  627.    
  628.     if (typeof text == 'object')
  629.         text += JSON.stringify(text);
  630.    
  631.     let line = {text:text, color:color};
  632.     debugPrintLines.push(line);
  633. }
  634.    
  635. function SaveSnapshot()
  636. {    
  637.     downloadLink.download="snapshot.png";
  638.     downloadLink.href=c.toDataURL("image/jpg").replace("image/jpg", "image/octet-stream");
  639.     downloadLink.click();
  640. }
  641.  
  642. /////////////////////////////////////////////////////////////////////////////////////
  643. // frame rate counter
  644. /////////////////////////////////////////////////////////////////////////////////////
  645.    
  646. let lastFpsMS = 0;
  647. let averageFps = 0;
  648. function UpdateFps()
  649. {
  650.     let ms = performance.now();
  651.     let deltaMS = ms - lastFpsMS;
  652.     lastFpsMS = ms;
  653.    
  654.     let fps = 1/(deltaMS/1e3);
  655.     averageFps = averageFps*.9 + fps*.1;
  656.     context.font='3em"';
  657.     context.fillStyle='#0007';
  658.     context.fillText(averageFps|0,c.width-90,c.height-40);
  659. }
  660.  
  661. /////////////////////////////////////////////////////////////////////////////////////
  662. // keyboard control
  663. /////////////////////////////////////////////////////////////////////////////////////
  664.  
  665. let inputIsDown = [];
  666. let inputWasDown = [];
  667. let inputWasPushed = [];
  668. onkeydown = e => inputIsDown[e.keyCode] = 1;
  669. onkeyup   = e => inputIsDown[e.keyCode] = 0;
  670. function UpdateInput()
  671. {
  672.     inputWasPushed = inputIsDown.map((e,i) => e && !inputWasDown[i]);
  673.     inputWasDown = inputIsDown.slice();
  674. }
  675.    
  676. /////////////////////////////////////////////////////////////////////////////////////
  677. // init hue jumper
  678. /////////////////////////////////////////////////////////////////////////////////////
  679.    
  680. // startup and kick off update loop
  681. StartLevel();
  682. Update();
  683.    
  684. </script>
  685. </body>
  686. </html>
  687.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement