3D 赛车【附源码】设计实现
- 2022 年 5 月 30 日
本文字数:16996 字
阅读完需:约 56 分钟
3D 赛车
3D 赛车【附源码】
我的网站已经上线了 http://javapub.net.cn/
文末源码、免费获取
文末源码、免费获取
文末源码、免费获取
点赞再看,养成习惯
适合人群:初级学习者和爱好者,下面有展示图。计算机毕业设计、java 精品项目
@[toc]
1 前言
🚀获取源码,文末公众号回复【赛车】,即可。⭐欢迎点赞留言
2 正文
公众号:JavaPub
2.1 展示预览
13MB GIF 可以欣赏:https://tvax4.sinaimg.cn/large/007F3CC8ly1h1onp5g8z9g31fz0pi7ws.gif
<img src="https://tva1.sinaimg.cn/large/007F3CC8ly1h1onp5g8z9g31fz0pi7ws.gif" alt="动画" width="1871" data-width="1871" data-height="918">
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Pbf7X6O3-1651070934070)(https://tvax3.sinaimg.cn/large/007F3CC8ly1h1onp5g8z9g31fz0pi7ws.gif)]
2.2 项目结构
2.2 主要代码展示
<html>
<!--/*
HUE JUMPER - By Frank Force
Low fi retro inspired endless runner in only 2 kilobytes!
Features
- Retro style 3D rendering engine in full HD
- Realistic driving physics and collisions
- Random level generation with increasing difficulty
- Gradient sky with sun and moon
- Procedurally generated mountain range
- Random trees and rocks
- Camera rumble and slow when off road
- Checkpoint system, road markers, and hue shift
- Time and distance display
*/-->
<title>Hue Jumper</title>
<meta charset="utf-8">
<body bgcolor=#000>
<canvas id=c style='touch-action:none;position:absolute;left:0px;top:0px;width:100%;height:100%'></canvas>
<a hidden id=downloadLink></a>
<script>
'use strict'; // strict mode
// debug settings
const debug = 0; // enable debug features
const usePointerLock = 1; // remove pointer lock for 2k build
// draw settings
const context = c.getContext('2d'); // canvas 2d context
const drawDistance = 800; // how many road segments to draw in front of player
const cameraDepth = 1; // FOV of camera (1 / Math.tan((fieldOfView/2) * Math.PI/180))
const roadSegmentLength = 100; // length of each road segment
const roadWidth = 500; // how wide is road
const warningTrackWidth = 150; // with of road plus warning track
const dashLineWidth = 9; // width of the dashed line in the road
const maxPlayerX = 2e3; // player can not move this far from center of road
const mountainCount = 30; // how many mountains are there
const timeDelta = 1/60; // inverse frame rate
// player settings
const playerHeight = 150; // how high is player above ground
const playerMaxSpeed = 300; // limit max player speed
const playerAccel = 1; // player acceleration
const playerBrake = -3; // player acceleration when breaking
const playerTurnControl = .2; // player turning rate
const playerJumpSpeed = 25; // z speed added for jump
const playerSpringConstant = .01; // spring players pitch
const playerCollisionSlow = .1; // slow down from collisions
const pitchLerp = .1; // speed that camera pitch changes
const pitchSpringDamping = .9; // dampen the pitch spring
const elasticity = 1.2; // bounce elasticity (2 is full bounce, 1 is none)
const centrifugal = .002; // how much to pull player on turns
const forwardDamping = .999; // dampen player z speed
const lateralDamping = .7; // dampen player x speed
const offRoadDamping = .98; // more damping when off road
const gravity = -1; // gravity to apply in y axis
const cameraHeadingScale = 2; // scale of player turning to rotate camera
const worldRotateScale = .00005; // how much to rotate world around turns
// level settings
const maxTime = 20; // time to start with
const checkPointTime = 10; // how much time for getting to checkpoint
const checkPointDistance = 1e5; // how far between checkpoints
const checkpointMaxDifficulty = 9; // how many checkpoints before max difficulty
const roadEnd = 1e4; // how many sections until end of the road
// global game variables
let playerPos; // player position 3d vector
let playerVelocity; // player velocity 3d vector
let playerPitchSpring; // spring for player pitch bounce
let playerPitchSpringVelocity; // velocity of pitch spring
let playerPitchRoad; // pitch of road, or 0 if player is in air
let playerAirFrame; // how many frames player has been in air
let worldHeading; // heading to turn skybox
let randomSeed; // random seed for level
let startRandomSeed; // save the starting seed for active use
let nextCheckPoint; // distance of next checkpoint
let hueShift; // current hue shift for all hsl colors
let road; // the list of road segments
let time; // time left before game over
let lastUpdate = 0; // time of last update
let timeBuffer = 0; // frame rate adjustment
function StartLevel()
{
/////////////////////////////////////////////////////////////////////////////////////
// build the road with procedural generation
/////////////////////////////////////////////////////////////////////////////////////
let roadGenSectionDistanceMax = 0; // init end of section distance
let roadGenWidth = roadWidth; // starting road width
let roadGenSectionDistance = 0; // distance left for this section
let roadGenTaper = 0; // length of taper
let roadGenWaveFrequencyX = 0; // X wave frequency
let roadGenWaveFrequencyY = 0; // Y wave frequency
let roadGenWaveScaleX = 0; // X wave amplitude (turn size)
let roadGenWaveScaleY = 0; // Y wave amplitude (hill size)
startRandomSeed = randomSeed = Date.now(); // set random seed
road = []; // clear list of road segments
// generate the road
for( let i = 0; i < roadEnd*2; ++i ) // build road past end
{
if (roadGenSectionDistance++ > roadGenSectionDistanceMax) // check for end of section
{
// calculate difficulty percent
const difficulty = Math.min(1, i*roadSegmentLength/checkPointDistance/checkpointMaxDifficulty); // difficulty
// randomize road settings
roadGenWidth = roadWidth*Random(1-difficulty*.7, 3-2*difficulty); // road width
roadGenWaveFrequencyX = Random(Lerp(difficulty, .01, .02)); // X frequency
roadGenWaveFrequencyY = Random(Lerp(difficulty, .01, .03)); // Y frequency
roadGenWaveScaleX = i > roadEnd ? 0 : Random(Lerp(difficulty, .2, .6)); // X scale
roadGenWaveScaleY = Random(Lerp(difficulty, 1e3, 2e3)); // Y scale
// apply taper and move back
roadGenTaper = Random(99, 1e3)|0; // randomize taper
roadGenSectionDistanceMax = roadGenTaper + Random(99, 1e3); // randomize segment distance
roadGenSectionDistance = 0; // reset section distance
i -= roadGenTaper; // subtract taper
}
// make a wavy road
const x = Math.sin(i*roadGenWaveFrequencyX) * roadGenWaveScaleX; // road X
const y = Math.sin(i*roadGenWaveFrequencyY) * roadGenWaveScaleY; // road Y
road[i] = road[i]? road[i] : {x:x, y:y, w:roadGenWidth}; // get or make road segment
// apply taper from last section
const p = Clamp(roadGenSectionDistance / roadGenTaper, 0, 1); // get taper percent
road[i].x = Lerp(p, road[i].x, x); // X pos and taper
road[i].y = Lerp(p, road[i].y, y); // Y pos and taper
road[i].w = i > roadEnd ? 0 : Lerp(p, road[i].w, roadGenWidth); // check for road end, width and taper
road[i].a = road[i-1] ? Math.atan2(road[i-1].y-road[i].y, roadSegmentLength) : 0; // road pitch angle
}
/////////////////////////////////////////////////////////////////////////////////////
// init game
/////////////////////////////////////////////////////////////////////////////////////
// reset everything
playerVelocity = new Vector3
(
playerPitchSpring =
playerPitchSpringVelocity =
playerPitchRoad =
hueShift = 0
);
playerPos = new Vector3(0, playerHeight); // set player pos
worldHeading = randomSeed; // randomize world heading
nextCheckPoint = checkPointDistance; // init next checkpoint
time = maxTime; // set the starting time
}
function Update()
{
// time regulation, in case running faster then 60 fps, though it causes judder REMOVE FROM MINFIED
const now = performance.now();
if (lastUpdate)
{
// limit to 60 fps
const delta = now - lastUpdate;
if (timeBuffer + delta < 0)
{
// running fast
requestAnimationFrame(Update);
return;
}
// update time buffer
timeBuffer += delta;
timeBuffer -= timeDelta * 1e3;
if (timeBuffer > timeDelta * 1e3)
timeBuffer = 0; // if running too slow
}
lastUpdate = now;
// start frame
if (snapshot) {c.width|0} else // DEBUG REMOVE FROM MINFIED
c.width = window.innerWidth,c.height = window.innerHeight; // clear the screen and set size
if (!c.width) // REMOVE FROM MINFIED
{
// fix bug on itch, wait for canvas before updating
requestAnimationFrame(Update);
return;
}
if (usePointerLock && document.pointerLockElement !== c && !touchMode) // set mouse down if pointer lock released
mouseDown = 1;
UpdateDebugPre(); // DEBUG REMOVE FROM MINFIED
/////////////////////////////////////////////////////////////////////////////////////
// update player - controls and physics
/////////////////////////////////////////////////////////////////////////////////////
// get player road segment
const playerRoadSegment = playerPos.z/roadSegmentLength|0; // current player road segment
const playerRoadSegmentPercent = playerPos.z/roadSegmentLength%1; // how far player is along current segment
// get lerped values between last and current road segment
const playerRoadX = Lerp(playerRoadSegmentPercent, road[playerRoadSegment].x, road[playerRoadSegment+1].x);
const playerRoadY = Lerp(playerRoadSegmentPercent, road[playerRoadSegment].y, road[playerRoadSegment+1].y) + playerHeight;
const roadPitch = Lerp(playerRoadSegmentPercent, road[playerRoadSegment].a, road[playerRoadSegment+1].a);
const playerVelocityLast = playerVelocity.Add(0); // save last velocity
playerVelocity.y += gravity; // gravity
playerVelocity.x *= lateralDamping; // apply lateral damping
playerVelocity.z = Math.max(0, time ? forwardDamping*playerVelocity.z : 0); // apply damping, prevent moving backwards
playerPos = playerPos.Add(playerVelocity); // add player velocity
const playerTurnAmount = Lerp(playerVelocity.z/playerMaxSpeed, mouseX * playerTurnControl, 0); // turning
playerVelocity.x += // update x velocity
playerVelocity.z * playerTurnAmount - // apply turn
playerVelocity.z ** 2 * centrifugal * playerRoadX; // apply centrifugal force
playerPos.x = Clamp(playerPos.x, -maxPlayerX, maxPlayerX); // limit player x position
// check if on ground
if (playerPos.y < playerRoadY)
{
// bounce velocity against ground normal
playerPos.y = playerRoadY; // match y to ground plane
playerAirFrame = 0; // reset air grace frames
playerVelocity = new Vector3(0, Math.cos(roadPitch), Math.sin(roadPitch)) // get ground normal
.Multiply(-elasticity * // apply bounce
(Math.cos(roadPitch) * playerVelocity.y + Math.sin(roadPitch) * playerVelocity.z)) // dot of road and velocity
.Add(playerVelocity); // add velocity
playerVelocity.z +=
mouseDown? playerBrake : // apply brake
Lerp(playerVelocity.z/playerMaxSpeed, mouseWasPressed*playerAccel, 0); // apply accel
if (Math.abs(playerPos.x) > road[playerRoadSegment].w) // check if off road
{
playerVelocity.z *= offRoadDamping; // slow down when off road
playerPitchSpring += Math.sin(playerPos.z/99)**4/99; // bump when off road
}
}
// update jump
if (playerAirFrame++<6 && mouseDown && mouseUpFrames && mouseUpFrames<9 && time) // check for jump
{
playerVelocity.y += playerJumpSpeed; // apply jump velocity
playerAirFrame = 9; // prevent jumping again
}
mouseUpFrames = mouseDown? 0 : mouseUpFrames+1; // update mouse up frames for double click
const airPercent = (playerPos.y-playerRoadY)/99; // calculate above ground percent
playerPitchSpringVelocity += Lerp(airPercent,0,playerVelocity.y/4e4); // pitch down with vertical velocity
// update player pitch
playerPitchSpringVelocity += (playerVelocity.z - playerVelocityLast.z)/2e3; // pitch down with forward accel
playerPitchSpringVelocity -= playerPitchSpring * playerSpringConstant; // apply pitch spring constant
playerPitchSpringVelocity *= pitchSpringDamping; // dampen pitch spring
playerPitchSpring += playerPitchSpringVelocity; // update pitch spring
playerPitchRoad = Lerp(pitchLerp, playerPitchRoad, Lerp(airPercent,-roadPitch,0));// match pitch to road
const playerPitch = playerPitchSpring + playerPitchRoad; // update player pitch
if (playerPos.z > nextCheckPoint) // crossed checkpoint
{
time += checkPointTime; // add more time
nextCheckPoint += checkPointDistance; // set next checkpoint
hueShift += 36; // shift hue
}
/////////////////////////////////////////////////////////////////////////////////////
// draw background - sky, sun/moon, mountains, and horizon
/////////////////////////////////////////////////////////////////////////////////////
// multi use local variables
let x, y, w, i;
randomSeed = startRandomSeed; // set start seed
worldHeading = ClampAngle(worldHeading + playerVelocity.z * playerRoadX * worldRotateScale); // update world angle
// pre calculate projection scale, flip y because y+ is down on canvas
const projectScale = (new Vector3(1, -1, 1)).Multiply(c.width/2/cameraDepth); // get projection scale
const cameraHeading = playerTurnAmount * cameraHeadingScale; // turn camera with player
const cameraOffset = Math.sin(cameraHeading)/2; // apply heading with offset
// draw sky
const lighting = Math.cos(worldHeading); // brightness from sun
const horizon = c.height/2 - Math.tan(playerPitch) * projectScale.y; // get horizon line
const g = context.createLinearGradient(0,horizon-c.height/2,0,horizon); // linear gradient for sky
g.addColorStop(0,LSHA(39+lighting*25,49+lighting*19,230-lighting*19)); // top sky color
g.addColorStop(1,LSHA(5,79,250-lighting*9)); // bottom sky color
DrawPoly(c.width/2, 0, c.width/2, c.width/2, c.height, c.width/2, g); // draw sky
// draw sun and moon
for( i = 2; i--; ) // 0 is sun, 1 is moon
{
const g = context.createRadialGradient( // radial gradient for sun
x = c.width*(.5+Lerp( // angle 0 is center
(worldHeading/Math.PI/2+.5+i/2)%1, // sun angle percent
4, -4)-cameraOffset), // sun x pos, move far away for wrap
y = horizon - c.width/5, // sun y pos
c.width/25, // sun size
x, y, i?c.width/23:c.width); // sun end pos & size
g.addColorStop(0, LSHA(i?70:99)); // sun start color
g.addColorStop(1, LSHA(0,0,0,0)); // sun end color
DrawPoly(c.width/2, 0, c.width/2, c.width/2, c.height, c.width/2, g); // draw sun
}
// draw mountains
for( i = mountainCount; i--; ) // draw every mountain
{
const angle = ClampAngle(worldHeading+Random(19)); // mountain random angle
const lighting = Math.cos(angle-worldHeading); // mountain lighting
DrawPoly(
x = c.width*(.5+Lerp(angle/Math.PI/2+.5, 4, -4)-cameraOffset), // mountain x pos, move far away for wrap
y = horizon, // mountain base
w = Random(.2,.8)**2*c.width/2, // mountain width
x+w*Random(-.5,.5), // random tip skew
y - Random(.5,.8)*w, 0, // mountain height
LSHA(Random(15,25)+i/3-lighting*9,i/2+Random(19),Random(220,230))); // mountain color
}
// draw horizon
DrawPoly(c.width/2, horizon, c.width/2, c.width/2, c.height, c.width/2, // horizon pos & size
LSHA(25, 30, 95)); // horizon color
/////////////////////////////////////////////////////////////////////////////////////
// draw road and objects
/////////////////////////////////////////////////////////////////////////////////////
// calculate road x offsets and projections
for( x = w = i = 0; i < drawDistance+1; )
{
// create road world position
let p = new Vector3( // set road position
x += w += road[playerRoadSegment+i].x, // sum local road offsets
road[playerRoadSegment+i].y, (playerRoadSegment+i)*roadSegmentLength)// road y and z pos
.Add(playerPos.Multiply(-1)); // subtract to get local space
p.x = p.x*Math.cos(cameraHeading) - p.z*Math.sin(cameraHeading); // rotate camera heading
// tilt camera pitch
const z = 1 / (p.z*Math.cos(playerPitch) - p.y*Math.sin(playerPitch)); // invert z for projection
p.y = p.y*Math.cos(playerPitch) - p.z*Math.sin(playerPitch);
p.z = z;
// project road segment to canvas space
road[playerRoadSegment+i++].p = // set projected road point
p.Multiply(new Vector3(z, z, 1)) // projection
.Multiply(projectScale) // scale
.Add(new Vector3(c.width/2,c.height/2)) // center on canvas
}
// draw the road segments
let segment2 = road[playerRoadSegment+drawDistance]; // store the last segment
for( i = drawDistance; i--; ) // iterate in reverse
{
const segment1 = road[playerRoadSegment+i];
randomSeed = startRandomSeed + playerRoadSegment + i; // random seed for this segment
const lighting = Math.sin(segment1.a) * Math.cos(worldHeading)*99; // calculate segment lighting
const p1 = segment1.p; // projected point
const p2 = segment2.p; // last projected point
if (p1.z < 1e5 && p1.z > 0) // check near and far clip
{
// draw road segment
if (i % (Lerp(i/drawDistance,1,9)|0) == 0) // fade in road resolution
{
// ground
DrawPoly(c.width/2, p1.y, c.width/2, c.width/2, p2.y, c.width/2, // ground top & bottom
LSHA(25+lighting, 30, 95)); // ground color
// warning track
if (segment1.w > 400) // no warning track if thin
DrawPoly(p1.x, p1.y, p1.z*(segment1.w+warningTrackWidth), // warning track top
p2.x, p2.y, p2.z*(segment2.w+warningTrackWidth), // warning track bottom
LSHA(((playerRoadSegment+i)%19<9? 50: 20)+lighting)); // warning track stripe color
// road
const z = (playerRoadSegment+i)*roadSegmentLength; // segment distance
DrawPoly(p1.x, p1.y, p1.z*segment1.w, // road top
p2.x, p2.y, p2.z*segment2.w, // road bottom
LSHA((z%checkPointDistance < 300 ? 70 : 7)+lighting)); // road color and checkpoint
// dashed lines
if (segment1.w > 300) // no dash lines if very thin
(playerRoadSegment+i)%9==0 && i < drawDistance/3 && // make dashes and skip if far out
DrawPoly(p1.x, p1.y, p1.z*dashLineWidth, // dash lines top
p2.x, p2.y, p2.z*dashLineWidth, // dash lines bottom
LSHA(70+lighting)); // dash lines color
segment2 = segment1; // prep for next segment
}
// random object (tree or rock)
if (Random()<.2 && playerRoadSegment+i>29) // check for road object
{
// player object collision check
const z = (playerRoadSegment+i)*roadSegmentLength; // segment distance
const height = (Random(2)|0) * 400; // object type & height
x = 2*roadWidth * Random(10,-10) * Random(9); // choose object pos
if (!segment1.h // prevent hitting the same object
&& Math.abs(playerPos.x - x) < 200 // x collision
&& Math.abs(playerPos.z - z) < 200 // z collision
&& playerPos.y-playerHeight < segment1.y+200+height) // y collision + object height
{
playerVelocity = playerVelocity.Multiply(segment1.h = playerCollisionSlow); // stop player and mark hit
}
// draw road object
const alpha = Lerp(i/drawDistance, 4, 0); // fade in object alpha
if (height) // tree
{
DrawPoly(x = p1.x+p1.z * x, p1.y, p1.z*29, // trunk bottom
x, p1.y-99*p1.z, p1.z*29, // trunk top
LSHA(5+Random(9), 50+Random(9), 29+Random(9), alpha)); // trunk color
DrawPoly(x, p1.y-Random(50,99)*p1.z, p1.z*Random(199,250), // leaves bottom
x, p1.y-Random(600,800)*p1.z, 0, // leaves top
LSHA(25+Random(9), 80+Random(9), 9+Random(29), alpha)); // leaves color
}
else // rock
{
DrawPoly(x = p1.x+p1.z * x, p1.y, p1.z*Random(200,250), // rock bottom
x+p1.z*(Random(99,-99)), p1.y-Random(200,250)*p1.z, p1.z*Random(99), // rock top
LSHA(50+Random(19), 25+Random(19), 209+Random(9), alpha)); // rock color
}
}
}
}
UpdateDebugPost(); // DEBUG REMOVE FROM MINFIED
/////////////////////////////////////////////////////////////////////////////////////
// draw and update time
/////////////////////////////////////////////////////////////////////////////////////
if (mouseWasPressed)
{
DrawText(Math.ceil(time = Clamp(time - timeDelta, 0, maxTime)), 9); // show and update time
context.textAlign = 'right'; // set right alignment for distance
DrawText(0|playerPos.z/1e3, c.width-9); // show distance
}
else
{
context.textAlign = 'center'; // set center alignment for title
DrawText('HUE JUMPER', c.width/2); // draw title text
}
requestAnimationFrame(Update); // kick off next frame
}
/////////////////////////////////////////////////////////////////////////////////////
// math and helper functions
/////////////////////////////////////////////////////////////////////////////////////
const LSHA = (l, s=0, h=0, a=1) =>`hsl(${ h + hueShift },${ s }%,${ l }%,${ a })`;
const Clamp = (v, min, max) => Math.min(Math.max(v, min), max);
const ClampAngle = (a) => (a+Math.PI) % (2*Math.PI) + (a+Math.PI<0? Math.PI : -Math.PI);
const Lerp = (p, a, b) => a + Clamp(p, 0, 1) * (b-a);
const Random = (max=1, min=0) => Lerp((Math.sin(++randomSeed)+1)*1e5%1, min, max);
// simple 3d vector class
class Vector3
{
constructor(x=0, y=0, z=0) { this.x = x; this.y = y; this.z = z }
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); }
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); }
}
// draw a trapazoid shaped poly
function DrawPoly(x1, y1, w1, x2, y2, w2, fillStyle)
{
context.beginPath(context.fillStyle = fillStyle);
context.lineTo(x1-w1, y1|0);
context.lineTo(x1+w1, y1|0);
context.lineTo(x2+w2, y2|0);
context.lineTo(x2-w2, y2|0);
context.fill();
}
// draw outlined hud text
function DrawText(text, posX)
{
context.font = '9em impact'; // set font size
context.fillStyle = LSHA(99,0,0,.5); // set font
context.fillText(text, posX, 129); // fill text
context.lineWidth = 3; // line width
context.strokeText(text, posX, 129); // outline text
}
/////////////////////////////////////////////////////////////////////////////////////
// mouse input
/////////////////////////////////////////////////////////////////////////////////////
let mouseDown = 0;
let mouseWasPressed = 0;
let mouseUpFrames = 0;
let mouseX = 0;
let mouseLockX = 0;
let touchMode = 0;
onmouseup = e => mouseDown = 0;
onmousedown = e =>
{
if (mouseWasPressed)
mouseDown = 1;
mouseWasPressed = 1;
if (usePointerLock && e.button == 0 && document.pointerLockElement !== c)
{
c.requestPointerLock = c.requestPointerLock || c.mozRequestPointerLock;
c.requestPointerLock();
mouseLockX = 0;
}
}
onmousemove = e =>
{
if (!usePointerLock)
{
mouseX = e.x/window.innerWidth*2-1
return;
}
if (document.pointerLockElement !== c)
return;
// adjust for pointer lock
mouseLockX += e.movementX;
mouseLockX = Clamp(mouseLockX, -window.innerWidth/2, window.innerWidth/2);
// apply curve to input
const inputCurve = 1.5;
mouseX = mouseLockX;
mouseX /= window.innerWidth/2;
mouseX = Math.sign(mouseX) * (1-(1-Math.abs(mouseX))**inputCurve);
mouseX *= window.innerWidth/2;
mouseX += window.innerWidth/2;
mouseX = mouseX/window.innerWidth*2-1
}
/////////////////////////////////////////////////////////////////////////////////////
// touch control
/////////////////////////////////////////////////////////////////////////////////////
if (typeof ontouchend != 'undefined')
{
let ProcessTouch = e =>
{
e.preventDefault();
mouseDown = !(e.touches.length > 0);
mouseWasPressed = 1;
touchMode = 1;
if (mouseDown)
return;
// average all touch positions
let x = 0, y = 0;
for (let touch of e.touches)
{
x += touch.clientX;
y += touch.clientY;
}
mouseX = x/e.touches.length;
mouseX = mouseX/window.innerWidth*2-1
}
c.addEventListener('touchstart', ProcessTouch, false);
c.addEventListener('touchmove', ProcessTouch, false);
c.addEventListener('touchcancel', ProcessTouch, false);
c.addEventListener('touchend', ProcessTouch, false);
}
/////////////////////////////////////////////////////////////////////////////////////
// debug stuff
/////////////////////////////////////////////////////////////////////////////////////
let debugPrintLines;
let snapshot;
function UpdateDebugPre()
{
debugPrintLines = [];
if (inputWasPushed[82]) // R = restart
{
mouseLockX = 0;
StartLevel();
}
if (inputWasPushed[49]) // 1 = screenshot
{
snapshot = 1;
// use 1080p resolution
c.width = 1920;
c.height = 1080;
}
}
function UpdateDebugPost()
{
if (snapshot)
{
SaveSnapshot();
snapshot = 0;
}
UpdateInput();
if (!debug)
return;
UpdateFps();
context.font='2em"';
for (let i in debugPrintLines)
{
let line = debugPrintLines[i];
context.fillStyle = line.color;
context.fillText(line.text,c.width/2,35+35*i);
}
}
function DebugPrint(text, color='#F00')
{
if (!debug)
return;
if (typeof text == 'object')
text += JSON.stringify(text);
let line = {text:text, color:color};
debugPrintLines.push(line);
}
function SaveSnapshot()
{
downloadLink.download="snapshot.png";
downloadLink.href=c.toDataURL("image/jpg").replace("image/jpg", "image/octet-stream");
downloadLink.click();
}
/////////////////////////////////////////////////////////////////////////////////////
// frame rate counter
/////////////////////////////////////////////////////////////////////////////////////
let lastFpsMS = 0;
let averageFps = 0;
function UpdateFps()
{
let ms = performance.now();
let deltaMS = ms - lastFpsMS;
lastFpsMS = ms;
let fps = 1/(deltaMS/1e3);
averageFps = averageFps*.9 + fps*.1;
context.font='3em"';
context.fillStyle='#0007';
context.fillText(averageFps|0,c.width-90,c.height-40);
}
/////////////////////////////////////////////////////////////////////////////////////
// keyboard control
/////////////////////////////////////////////////////////////////////////////////////
let inputIsDown = [];
let inputWasDown = [];
let inputWasPushed = [];
onkeydown = e => inputIsDown[e.keyCode] = 1;
onkeyup = e => inputIsDown[e.keyCode] = 0;
function UpdateInput()
{
inputWasPushed = inputIsDown.map((e,i) => e && !inputWasDown[i]);
inputWasDown = inputIsDown.slice();
}
/////////////////////////////////////////////////////////////////////////////////////
// init hue jumper
/////////////////////////////////////////////////////////////////////////////////////
// startup and kick off update loop
StartLevel();
Update();
</script>
</body>
</html>
JavaPub
源码下载
获取源码,公众号回复【赛车】,即可。更多最新 Java 面试题加群、见群公告。~
不会还有人没 点赞 + 关注 + 收藏 吧!
系列推荐:
查看更多博主首页更多实战项目 >>>
项目源码获取方法
点赞本文,然后私信我,我免费分享给你哈~
版权声明: 本文为 InfoQ 作者【JavaPub】的原创文章。
原文链接:【http://xie.infoq.cn/article/b9b454cb80f0c2c99abe67ef6】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
JavaPub
原创技术公众号:JavaPub 2018.12.02 加入
原创技术公众号:JavaPub | 限时免费领取原创PDF
评论