久草视频2-久草视-久草社区视频-久草色在线-久草色视频-久草软件

如何用不到 2KB 的 JavaScript 代碼寫一個 3D 賽車游戲?

我是創始人李巖:很抱歉!給自己產品做個廣告,點擊進來看看。  

?幾個月前,JS1k游戲制作節(JS1K game jam)傳出不再舉辦消息后,許多游戲迷開始哀嚎。

? ? ? ? 如何用不到 2KB 的 JavaScript 代碼寫一個 3D 賽車游戲? ? ? ?

Frank Force 也是其中一位,但他還有另一層身份——一位德克薩斯州奧斯汀的獨立游戲設計師。Frank Force 在游戲行業工作了20年,參與過9款主流游戲、47個獨立游戲的設計。在聽到這個消息后,他馬上和其他開發朋友討論了這個問題,并決定做點什么為此紀念。

在此期間,他們受到三重因素的啟發。一是賽車游戲,包括懷舊向的80年代賽車游戲,他們在非常早期的硬件上推動實時 3D 圖形,所以作者沿用了相同的技術,用純 JavaScript 從頭開始實現做 3D 圖形和物理引擎;還有一些現代賽車游戲帶來了視覺設計的靈感,比如《Distance》和《Lonely Mountains: Downhill》;二是之前 Jake Gordon 用 JavaScript 創建一個 虛擬3D賽車的項目 ,并分享了代碼;三是 Chris Glover 曾經做過一款小到只有 1KB 的 JS1k 賽車游戲 《Moto1kross by Chris Glover》 。

于是 Frank 和他的朋友們決定做一個壓縮后只有 2KB 的 3D 賽車游戲。2KB 到底有多小呢?提供一個參考,一個3.5英寸軟盤可以容納700多個這樣的游戲。

如何用不到 2KB 的 JavaScript 代碼寫一個 3D 賽車游戲?

他給這個游戲取名 Hue Jumper。關于名字的由來,Frank 表示,游戲的核心操作是移動。當玩家通過一個關卡時,游戲世界就會換一個顏色色調。“在我想象中,每通過過一個關卡,玩家都會跳轉到另一個維度,有著完全不同的色調。”

做完這個游戲后,Frank 將包含了游戲的全部 JavaScript 代碼都發布在他的 個人博客 上,其中用到的軟件主要也是免費或開源軟件的。游戲代碼發布在? CodePen ,可以在 iframe 中試玩,有興趣的朋友可以去看看。

如何用不到 2KB 的 JavaScript 代碼寫一個 3D 賽車游戲?

以下是原博內容,AI源創評論進行了不改變原意的編譯:

確定最高目標

因為嚴格的大小限制,我需要非常仔細對待我的程序。我的總體策略是盡可能保持一切簡單,為最終目標服務。

為了幫助壓縮代碼,我使用了? Google Closure Compiler ,它刪除了所有空格,將變量重命名為1個字母字符,并進行了一些輕量級優化。

用戶可以通過 Google Closure Compiler 官網在線跑代碼。不幸的是,Closure Compiler 做了一些沒有幫助的事情,比如替換模板字符串、默認參數和其他幫助節省空間的ES6特性。所以我需要手動撤銷其中一些事情,并執行一些更“危險”的壓縮技術來擠出最后一個字節空間。在壓縮方面,這不算很成功,大部分擠出的空間來自代碼本身的結構優化。

代碼需要壓縮到2KB。如果不是非要這么做不可,有一個類似的但功能沒那么強的工具叫做 RegPack 。

無論哪種方式,策略都是一樣的:盡最大可能重復代碼,然后用壓縮工具壓縮。最好的例子是 c.width,c.height和 Math。因此,在閱讀這段代碼時,請記住,你經常會看到我不斷重復一些東西,最終目的就是為了壓縮。

HTML

其實我的游戲很少使用 html ,因為它主要用到的是 JavaScript 。但這是創建全屏畫布 Canvas ,也能將畫布 Canvas 設為窗口內部大小的代碼最小方法。我不知道為什么在 CodePen 上有必要添加 overflow:hiddento the body,當直接打開時按理說也可以運行。

我將 JavaScript 封裝在一個 onload 調用,得到了一個更小的最終版本…< body style = margin:0 onload = " code _ goes _ here " > < canvas id = c >但是,在開發過程中,我不喜歡用這個壓縮設置,因為代碼存儲在一個字符串中,所以編輯器不能正確地高亮顯示語法。

<body style=margin:0>

<canvas id=c>

<script>

常量

有許多常量在各方面控制著游戲。當代碼被 Google Closure 這樣的工具縮小時,這些常量將被替換,就像 C++ 中的 #define 一樣,把它們放在第一位會加快游戲微調的過程。

// draw settings

const context = c.getContext`2d`; // canvas context

const drawDistance = 800; ? ? ? ? // how far ahead to draw

const cameraDepth = 1; ? ? ? ? ? ?// FOV of camera

const segmentLength = 100; ? ? ? ?// length of each road segment

const roadWidth = 500; ? ? ? ? ? ?// how wide is road

const curbWidth = 150; ? ? ? ? ? ?// with of warning track

const dashLineWidth = 9; ? ? ? ? ?// width of the dashed line

const maxPlayerX = 2e3; ? ? ? ? ? // limit player offset

const mountainCount = 30; ? ? ? ? // how many mountains are there

const timeDelta = 1/60; ? ? ? ? ? // inverse frame rate

const PI = Math.PI; ? ? ? ? ? ? ? // shorthand for Math.PI

?

// player settings

const height = 150; ? ? ? ? ? ? ? // high of player above ground

const maxSpeed = 300; ? ? ? ? ? ? // limit max player speed

const playerAccel = 1; ? ? ? ? ? ?// player forward acceleration

const playerBrake = -3; ? ? ? ? ? // player breaking acceleration

const turnControl = .2; ? ? ? ? ? // player turning rate

const jumpAccel = 25; ? ? ? ? ? ? // z speed added for jump

const springConstant = .01; ? ? ? // spring players pitch

const collisionSlow = .1; ? ? ? ? // slow down from collisions

const pitchLerp = .1; ? ? ? ? ? ? // rate camera pitch changes

const pitchSpringDamp = .9; ? ? ? // dampen the pitch spring

const elasticity = 1.2; ? ? ? ? ? // bounce elasticity

const centrifugal = .002; ? ? ? ? // how much turns pull player

const forwardDamp = .999; ? ? ? ? // dampen player z speed

const lateralDamp = .7; ? ? ? ? ? // dampen player x speed

const offRoadDamp = .98; ? ? ? ? ?// more damping when off road

const gravity = -1; ? ? ? ? ? ? ? // gravity to apply in y axis

const cameraTurnScale = 2; ? ? ? ?// how much to rotate camera

const worldRotateScale = .00005; ?// how much to rotate world

? ?

// level settings

const maxTime = 20; ? ? ? ? ? ? ? // time to start

const checkPointTime = 10; ? ? ? ?// add time at checkpoints

const checkPointDistance = 1e5; ? // how far between checkpoints

const maxDifficultySegment = 9e3; // how far until max difficulty

const roadEnd = 1e4; ? ? ? ? ? ? ?// how far until end of road

鼠標控制

鼠標是唯一的輸入系統。通過這段代碼,我們可以跟蹤鼠標點擊和光標位置,位置顯示為-1到1之間的值。

雙擊是通過 mouseUpFrames 實現的。mousePressed 變量只在玩家第一次點擊開始游戲時使用這么一次。

mouseDown ? ? =

mousePressed ?=

mouseUpFrames =

mouseX ? ? ? ?= 0;

? ?

onmouseup ? =e=> mouseDown = 0;

onmousedown =e=> mousePressed ? mouseDown = 1 : mousePressed = 1;

onmousemove =e=> mouseX = e.x/window.innerWidth*2 - 1;

數學函數

這個游戲使用了一些函數來簡化代碼和減少重復,一些標準的數學函數用于 Clamp 和 Lerp 值。?ClampAngle 是有用的,因為它在 -PI 和 PI 之間 wrap angles,在許多游戲中已經廣泛應用。

R函數就像個魔術師,因為它生成隨機數,通過取當前隨機數種子的正弦,乘以一個大數字,然后看分數部分來實現的。其實有很多方法可以做到,但這是最小的方法之一,而且對我們來說也是足夠隨機。

我們將使用這個隨機生成器來創建各種程序,且不需要保存任何數據。例如,山脈、巖石和樹木的變化不用存到內存。在這種情況下,目標不是減少內存,而是去除存儲和檢索數據所需的代碼。

因為這是一個“真正的3D”游戲,所以有一個 3D vector class?非常有用,它也能減少代碼量。這個 class 只包含這個游戲必需的基本元素,一個帶有加法和乘法函數的 constructor 可以接受標量或向量參數。為了確定標量是否被傳入,我們只需檢查它是否小于一個大數。更正確的方法是使用 isNan 或者檢查它的類型是否是 Vec3,但是這需要更多的存儲。

Clamp ? ? =(v, a, b) ?=> Math.min(Math.max(v, a), b);
ClampAngle=(a) ? ? ? ?=> (a+PI) % (2*PI) + (a+PI<0? PI : -PI);
Lerp ? ? ?=(p, a, b) ?=> a + Clamp(p, 0, 1) * (b-a);
R ? ? ? ? =(a=1, b=0) => Lerp((Math.sin(++randSeed)+1)*1e5%1,a,b);
?
class Vec3 // 3d vector class
{
?constructor(x=0, y=0, z=0) {this.x = x; this.y = y; this.z = z;}
?
?Add=(v)=>(
? ?v = v < 1e5 ? new Vec3(v,v,v) : v,
? ?new Vec3( this.x + v.x, this.y + v.y, this.z + v.z ));
? ?
?Multiply=(v)=>(
? ?v = v < 1e5 ? new Vec3(v,v,v) : v,
? ?new Vec3( this.x * v.x, this.y * v.y, this.z * v.z ));
}

Render Functions渲染函數

LSHA 通過模板字符串生成一組標準的 HSLA (色調、飽和度、亮度、alpha)顏色,并且剛剛被重新排序,所以更常用的 component 排在第一位。每過一關換一個整體色調也是通過這設置的。

DrawPoly 繪制一個梯形形狀,用于渲染場景中的一切。使用 |0 將 Ycomponent 轉換為整數,以確保每段多邊形道路都能無縫連接,不然路段之間就會有一條細線。

DrawText 則用于顯示時間、距離和游戲標題等文本渲染。

?LSHA=(l,s=0,h=0,a=1)=>`hsl(${h+hueShift},${s}%,${l}%,${a})`;

// draw a trapazoid shaped poly
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
DrawText=(text, posX)=>
{
? ?context.font = '9em impact'; ? ? ? ? // set font size
? ?context.fillStyle = LSHA(99,0,0,.5); // set font color
? ?context.fillText(text, posX, 129); ? // fill text
? ?context.lineWidth = 3; ? ? ? ? ? ? ? // line width
? ?context.strokeText(text, posX, 129); // outline text
}

設計軌道

如何用不到 2KB 的 JavaScript 代碼寫一個 3D 賽車游戲?

首先,我們必須生成完整的軌道,而且準備做到每次游戲軌道都是不同的。如何做呢?我們建立了一個道路段列表,存儲道路在軌道上每一關卡的位置和寬度。軌道生成器是非常基礎的操作,不同頻率、振幅和寬度的道路都會逐漸變窄,沿著跑道的距離決定這一段路有多難。

atan2 函數可以用來計算道路俯仰角,據此來設計物理運動和光線。

?roadGenLengthMax = ? ? ? ? ? ? ? ? ? ? // end of section
roadGenLength = ? ? ? ? ? ? ? ? ? ? ? ?// distance left
roadGenTaper = ? ? ? ? ? ? ? ? ? ? ? ? // length of taper
roadGenFreqX = ? ? ? ? ? ? ? ? ? ? ? ? // X wave frequency
roadGenFreqY = ? ? ? ? ? ? ? ? ? ? ? ? // Y wave frequency
roadGenScaleX = ? ? ? ? ? ? ? ? ? ? ? ?// X wave amplitude
roadGenScaleY = 0; ? ? ? ? ? ? ? ? ? ? // Y wave amplitude
roadGenWidth = roadWidth; ? ? ? ? ? ? ?// starting road width
startRandSeed = randSeed = Date.now(); // set random seed
road = []; ? ? ? ? ? ? ? ? ? ? ? ? ? ? // clear road

// generate the road
for( i = 0; i < roadEnd*2; ++i ) ? ? ? ? ?// build road past end
{
?if (roadGenLength++ > roadGenLengthMax) // is end of section?
?{
? ?// calculate difficulty percent
? ?d = Math.min(1, i/maxDifficultySegment);
?
? ?// randomize road settings
? ?roadGenWidth = roadWidth*R(1-d*.7,3-2*d); ? ? ? ?// road width
? ?roadGenFreqX = R(Lerp(d,.01,.02)); ? ? ? ? ? ? ? // X curves
? ?roadGenFreqY = R(Lerp(d,.01,.03)); ? ? ? ? ? ? ? // Y bumps
? ?roadGenScaleX = i>roadEnd ? 0 : R(Lerp(d,.2,.6));// X scale
? ?roadGenScaleY = R(Lerp(d,1e3,2e3)); ? ? ? ? ? ? ?// Y scale
?
? ?// apply taper and move back
? ?roadGenTaper = R(99, 1e3)|0; ? ? ? ? ? ? ? ? // random taper
? ?roadGenLengthMax = roadGenTaper + R(99,1e3); // random length
? ?roadGenLength = 0; ? ? ? ? ? ? ? ? ? ? ? ? ? // reset length
? ?i -= roadGenTaper; ? ? ? ? ? ? ? ? ? ? ? ? ? // subtract taper
?}
?
?// make a wavy road
?x = Math.sin(i*roadGenFreqX) * roadGenScaleX;
?y = Math.sin(i*roadGenFreqY) * roadGenScaleY;
?road[i] = road[i]? road[i] : {x:x, y:y, w:roadGenWidth};
?
?// apply taper from last section and lerp values
?p = Clamp(roadGenLength / roadGenTaper, 0, 1);
?road[i].x = Lerp(p, road[i].x, x);
?road[i].y = Lerp(p, road[i].y, y);
?road[i].w = i > roadEnd ? 0 : Lerp(p, road[i].w, roadGenWidth);
? ?
?// calculate road pitch angle
?road[i].a = road[i-1] ?
? ?Math.atan2(road[i-1].y-road[i].y, segmentLength) : 0;
}

啟動游戲

現在跑道就緒,我們只需要預置一些變量就可以開始游戲了。

?// reset everything
velocity = new Vec3
?( pitchSpring = ?pitchSpringSpeed = ?pitchRoad = hueShift = 0 );
?
position = new Vec3(0, height); ? ? ?// set player start pos
nextCheckPoint = checkPointDistance; // init next checkpoint
time = maxTime; ? ? ? ? ? ? ? ? ? ? ?// set the start time
heading = randSeed; ? ? ? ? ? ? ? ? ?// random world heading

更新玩家

這是主要的更新功能,用來更新和渲染游戲中的一切!一般來說,如果你的代碼中有一個很大的函數,這不是好事,為了更簡潔易懂,我們會把它分幾個成子函數。

首先,我們需要得到一些玩家所在位置的道路信息。為了使物理和渲染感覺平滑,需要在當前和下一個路段之間插入一些數值。

玩家的位置和速度是 3D 向量,并受重力、dampening 和其他因素等影響更新。如果玩家跑在地面上時,會受到加速度影響;當他離開這段路時,攝像機還會抖動。另外,在對游戲測試后,我決定讓玩家在空中時仍然可以跑。

接下來要處理輸入指令,涉及加速、剎車、跳躍和轉彎等操作。雙擊通過 mouseUpFrames 測試。還有一些代碼是來跟蹤玩家在空中停留了多少幀,如果時間很短,游戲允許玩家還可以跳躍。

當玩家加速、剎車和跳躍時,我通過spring system展示相機的俯仰角以給玩家動態運動的感覺。此外,當玩家駕車翻越山丘或跳躍時,相機還會隨著道路傾斜而傾斜。

?Update=()=>
{

// get player road segment
s = position.z / segmentLength | 0; // current road segment
p = position.z / segmentLength % 1; // percent along segment

// get lerped values between last and current road segment
roadX = Lerp(p, road[s].x, road[s+1].x);
roadY = Lerp(p, road[s].y, road[s+1].y) + height;
roadA = Lerp(p, road[s].a, road[s+1].a);

// update player velocity
lastVelocity = velocity.Add(0);
velocity.y += gravity;
velocity.x *= lateralDamp;
velocity.z = Math.max(0, time?forwardDamp*velocity.z:0);

// add velocity to position
position = position.Add(velocity);
?
// limit player x position (how far off road)
position.x = Clamp(position.x, -maxPlayerX, maxPlayerX);

// check if on ground
if (position.y < roadY)
{
?position.y = roadY; // match y to ground plane
?airFrame = 0; ? ? ? // reset air frames
?
?// get the dot product of the ground normal and the velocity
?dp = Math.cos(roadA)*velocity.y + Math.sin(roadA)*velocity.z;
?
?// bounce velocity against ground normal
?velocity = new Vec3(0, Math.cos(roadA), Math.sin(roadA))
? ?.Multiply(-elasticity * dp).Add(velocity);
? ?
?// apply player brake and accel
?velocity.z +=
? ?mouseDown? playerBrake :
? ?Lerp(velocity.z/maxSpeed, mousePressed*playerAccel, 0);
?
?// check if off road
?if (Math.abs(position.x) > road[s].w)
?{
? ?velocity.z *= offRoadDamp; ? ? ? ? ? ? ? ? ? ?// slow down
? ?pitchSpring += Math.sin(position.z/99)**4/99; // rumble
?}
}

// update player turning and apply centrifugal force
turn = Lerp(velocity.z/maxSpeed, mouseX * turnControl, 0);
velocity.x +=
?velocity.z * turn -
?velocity.z ** 2 * centrifugal * roadX;

// update jump
if (airFrame++<6 && time
?&& mouseDown && mouseUpFrames && mouseUpFrames<9)
{
?velocity.y += jumpAccel; // apply jump velocity
?airFrame = 9; ? ? ? ? ? ?// prevent jumping again
}
mouseUpFrames = mouseDown? 0 : mouseUpFrames+1;

// pitch down with vertical velocity when in air
airPercent = (position.y-roadY) / 99;
pitchSpringSpeed += Lerp(airPercent, 0, velocity.y/4e4);

// update player pitch spring
pitchSpringSpeed += (velocity.z - lastVelocity.z)/2e3;
pitchSpringSpeed -= pitchSpring * springConstant;
pitchSpringSpeed *= pitchSpringDamp;
pitchSpring += pitchSpringSpeed;
pitchRoad = Lerp(pitchLerp, pitchRoad, Lerp(airPercent,-roadA,0));
playerPitch = pitchSpring + pitchRoad;

// update heading
heading = ClampAngle(heading + velocity.z*roadX*worldRotateScale);
cameraHeading = turn * cameraTurnScale;

// was checkpoint crossed?
if (position.z > nextCheckPoint)
{
?time += checkPointTime; ? ? ? ? ? ? ? // add more time
?nextCheckPoint += checkPointDistance; // set next checkpoint
?hueShift += 36; ? ? ? ? ? ? ? ? ? ? ? // shift hue
}

預渲染

在渲染之前,canvas 每當高度或寬度被重設時,畫布內容就會被清空。這也適用于自適應窗口的畫布。

我們還計算了將世界點轉換到畫布的投影比例。cameraDepth 值代表攝像機的視場(FOV)。這個游戲是90度。計算結果是 1/Math.tan(fovRadians/2) ,FOV 是90度的時候,計算結果正好是1。另外為了保持屏幕長寬比,投影按 c.width 縮放。

?// clear the screen and set size
c.width = window.innerWidth, c.height = window.innerHeight;

// calculate projection scale, flip y
projectScale = (new Vec3(1,-1,1)).Multiply(c.width/2/cameraDepth);

給世界畫上天空、太陽和月亮

如何用不到 2KB 的 JavaScript 代碼寫一個 3D 賽車游戲?

空氣背景是用全屏的 linear gradient?(徑向漸變)繪制的,它還會根據太陽的位置改變顏色。

為了節省存儲空間,太陽和月亮在同一個循環中,使用了一個帶有透明度的全屏 radial gradient(線性漸變)。

線性和徑向漸變相結合,形成一個完全包圍場景的天空背景。

?// get horizon, offset, and light amount
horizon = c.height/2 - Math.tan(playerPitch)*projectScale.y;
backgroundOffset = Math.sin(cameraHeading)/2;
light = Math.cos(heading);

// create linear gradient for sky
g = context.createLinearGradient(0,horizon-c.height/2,0,horizon);
g.addColorStop(0,LSHA(39+light*25,49+light*19,230-light*19));
g.addColorStop(1,LSHA(5,79,250-light*9));

// draw sky as full screen poly
DrawPoly(c.width/2,0,c.width/2,c.width/2,c.height,c.width/2,g);

// draw sun and moon (0=sun, 1=moon)
for( i = 2 ; i--; )
{
?// create radial gradient
?g = context.createRadialGradient(
? ?x = c.width*(.5+Lerp(
? ? ?(heading/PI/2+.5+i/2)%1,
? ? ?4, -4)-backgroundOffset),
? ?y = horizon - c.width/5,
? ?c.width/25,
? ?x, y, i?c.width/23:c.width);
?g.addColorStop(0, LSHA(i?70:99));
?g.addColorStop(1, LSHA(0,0,0,0));
?
?// draw full screen poly
?DrawPoly(c.width/2,0,c.width/2,c.width/2,c.height,c.width/2,g);
}

給世界畫上山峰、地平線

如何用不到 2KB 的 JavaScript 代碼寫一個 3D 賽車游戲?

山脈是通過在地平線上畫50個三角形,然后根據程序自己生成的。

因為用了光線照明,山脈在面對太陽時會更暗,因為它們處于陰影中。此外,越近的山脈顏色越暗,我想以此來模擬霧氣。這里我有個訣竅,就是微調大小和顏色的隨機值。

背景的最后一部分是繪制地平線,再用純綠填充畫布的底部。

?// set random seed for mountains
randSeed = startRandSeed;

// draw mountains
for( i = mountainCount; i--; )
{
?angle = ClampAngle(heading+R(19));
?light = Math.cos(angle-heading);
?DrawPoly(
? ?x = c.width*(.5+Lerp(angle/PI/2+.5,4,-4)-backgroundOffset),
? ?y = horizon,
? ?w = R(.2,.8)**2*c.width/2,
? ?x + w*R(-.5,.5),
? ?y - R(.5,.8)*w, 0,
? ?LSHA(R(15,25)+i/3-light*9, i/2+R(19), R(220,230)));
}

// draw horizon
DrawPoly(
?c.width/2, horizon, c.width/2, c.width/2, c.height, c.width/2,
?LSHA(25, 30, 95));

將路段投影到畫布空間

在渲染道路之前,我們必須首先獲得投影的道路點。第一部分有點棘手,因為我們的道路的 x 值需要轉換成世界空間位置。為了使道路看起來蜿蜒曲折,我們把x值作為二階導數。這就是為什么有奇怪的代碼“x+=w+=”出現的原因。由于這種工作方式,路段沒有固定的世界空間位置,每一幀都是根據玩家的位置重新計算。

一旦我們有了世界空間位置,我們就可以從道路位置中知道玩家的位置,從而得到本地攝像機空間位置。代碼的其余部分,首先通過旋轉標題、俯仰角來應用變換,然后通過投影變換,做到近大遠小的效果,最后將其移動到畫布空間。

?for( x = w = i = 0; i < drawDistance+1; )
{
?p = new Vec3(x+=w+=road[s+i].x, ? ? // sum local road offsets
? ?road[s+i].y, (s+i)*segmentLength) // road y and z pos
? ? ?.Add(position.Multiply(-1)); ? ?// get local camera space

?// apply camera heading
?p.x = p.x*Math.cos(cameraHeading) - p.z*Math.sin(cameraHeading);
?
?// tilt camera pitch and invert z
?z = 1/(p.z*Math.cos(playerPitch) - p.y*Math.sin(playerPitch));
?p.y = p.y*Math.cos(playerPitch) - p.z*Math.sin(playerPitch);
?p.z = z;
?
?// project road segment to canvas space
?road[s+i++].p = ? ? ? ? ? ? ? ? ? ? ? ? // projected road point
? ?p.Multiply(new Vec3(z, z, 1)) ? ? ? ? // projection
? ?.Multiply(projectScale) ? ? ? ? ? ? ? // scale
? ?.Add(new Vec3(c.width/2,c.height/2)); // center on canvas
}

繪制路段

? 如何用不到 2KB 的 JavaScript 代碼寫一個 3D 賽車游戲?

現在我們有了每個路段的畫布空間點,渲染就相當簡單了。我們需要從后向前畫出每一個路段,或者更具體地說,連接上一路段的梯形多邊形。

為了創建道路,這里有4層渲染:地面,條紋路邊緣,道路本身和白色虛線。每一個都是基于路段的俯仰角和方向來加陰影,并且根據該層的表現還有一些額外的邏輯。

有必要檢查路段是在近還是遠剪輯范圍,以防止渲染出現 bug 。此外,還有一個很好的優化方法是,當道路變得很窄時,可以通過 distance 來減小道路的分辨率。如此,不僅減少了 draw count 一半以上,而且沒有明顯的質量損失,這是一次性能勝利。

?let segment2 = road[s+drawDistance]; // store the last segment
for( i = drawDistance; i--; ) ? ? ? ?// iterate in reverse
{
?// get projected road points
?segment1 = road[s+i];
?p1 = segment1.p;
?p2 = segment2.p;
?
?// random seed and lighting
?randSeed = startRandSeed + s + i;
?light = Math.sin(segment1.a) * Math.cos(heading) * 99;
?
?// check near and far clip
?if (p1.z < 1e5 && p1.z > 0)
?{
? ?// fade in road resolution over distance
? ?if (i % (Lerp(i/drawDistance,1,9)|0) == 0)
? ?{
? ? ?// ground
? ? ?DrawPoly(c.width/2, p1.y, c.width/2,
? ? ? ?c.width/2, p2.y, c.width/2,
? ? ? ?LSHA(25 + light, 30, 95));

? ? ?// curb if wide enough
? ? ?if (segment1.w > 400)
? ? ? ?DrawPoly(p1.x, p1.y, p1.z*(segment1.w+curbWidth),
? ? ? ? ?p2.x, p2.y, p2.z*(segment2.w+curbWidth),
? ? ? ? ?LSHA(((s+i)%19<9? 50: 20) + light));
? ? ?
? ? ?// road and checkpoint marker
? ? ?DrawPoly(p1.x, p1.y, p1.z*segment1.w,
? ? ? ?p2.x, p2.y, p2.z*segment2.w,
? ? ? ?LSHA(((s+i)*segmentLength%checkPointDistance < 300 ?
? ? ? ? ?70 : 7) + light));
? ? ? ?
? ? ?// dashed lines if wide and close enough
? ? ?if ((segment1.w > 300) && (s+i)%9==0 && i < drawDistance/3)
? ? ? ? ?DrawPoly(p1.x, p1.y, p1.z*dashLineWidth,
? ? ? ? ?p2.x, p2.y, p2.z*dashLineWidth,
? ? ? ? ?LSHA(70 + light));

? ? ?// save this segment
? ? ?segment2 = segment1;
? ?}

繪制路邊的樹和石頭

如何用不到 2KB 的 JavaScript 代碼寫一個 3D 賽車游戲?

游戲有兩種不同類型的物體:樹和石頭。首先,我們通過使用 R() 函數來確定是否加一個對象。這是隨機數和隨機數種子特別有意思的地方。我們還將使用 R() 為對象隨機添加不同的形狀和顏色。

最初我還想涉及其他車型,但為了達到 2KB 的要求,必須要進行特別多的削減,因此我最后放棄了這個想法,用風景作為障礙。這些位置是隨機的,也比較靠近道路,不然它們太稀疏,就很容易行駛。為了節省空間,對象高度還決定了對象的類型。

這是通過比較玩家和物體在 3D 空間中的位置來檢查它們之間的碰撞位置。當玩家撞到一個物體時,玩家減速,該物體被標記為“ hit ”,這樣它就可以安全通過。

為了防止對象突然出現在地平線上,透明度會隨著距離的接近而削弱。梯形繪圖函數定義物體的形狀和顏色,另外隨機函數會改變這兩個屬性。

??if (R()<.2 && s+i>29) ? ? ? ? ? ? ? ? ?// is there an object?
? ?{
? ? ?// player object collision check
? ? ?x = 2*roadWidth * R(10,-10) * R(9); ?// choose object pos
? ? ?const objectHeight = (R(2)|0) * 400; // choose tree or rock
? ? ?if (!segment1.h ? ? ? ? ? ? ? ? ? ? ?// dont hit same object
? ? ? ?&& Math.abs(position.x-x)<200 ? ? ? ? ? ? ? ? ? ? ?// X
? ? ? ?&& Math.abs(position.z-(s+i)*segmentLength)<200 ? ?// Z
? ? ? ?&& position.y-height<segment1.y+objectHeight+200) ?// Y
? ? ?{
? ? ? ?// slow player and mark object as hit
? ? ? ?velocity = velocity.Multiply(segment1.h = collisionSlow);
? ? ?}

? ? ?// draw road object
? ? ?const alpha = Lerp(i/drawDistance, 4, 0); ?// fade in object
? ? ?if (objectHeight)
? ? ?{
? ? ? ?// tree trunk
? ? ? ?DrawPoly(x = p1.x+p1.z * x, p1.y, p1.z*29,
? ? ? ? ?x, p1.y-99*p1.z, p1.z*29,
? ? ? ? ?LSHA(5+R(9), 50+R(9), 29+R(9), alpha));
? ? ? ? ?
? ? ? ?// tree leaves
? ? ? ?DrawPoly(x, p1.y-R(50,99)*p1.z, p1.z*R(199,250),
? ? ? ? ?x, p1.y-R(600,800)*p1.z, 0,
? ? ? ? ?LSHA(25+R(9), 80+R(9), 9+R(29), alpha));
? ? ?}
? ? ?else
? ? ?{
? ? ? ?// rock
? ? ? ?DrawPoly(x = p1.x+p1.z*x, p1.y, p1.z*R(200,250),
? ? ? ? ?x+p1.z*(R(99,-99)), p1.y-R(200,250)*p1.z, p1.z*R(99),
? ? ? ? ?LSHA(50+R(19), 25+R(19), 209+R(9), alpha));
? ? ?}
? ?}
?}
}

畫上 HUD,更新時間,請求下一次更新

如何用不到 2KB 的 JavaScript 代碼寫一個 3D 賽車游戲?

游戲的標題、時間和距離是用一個非常基礎的字體渲染系統顯示出來的,就是之前設置的 DrawText 函數。在玩家點擊鼠標之前,它會在屏幕中央顯示標題。

按下鼠標后,游戲開始,然后 HUD 會顯示剩余時間和當前距離。時間也在這塊更新,玩過此類游戲的都知道,時間只在比賽開始后減少。

在這個 massive Update function 結束后,它調用 requestAnimationFrame (Update) 來觸發下一次更新。

?if (mousePressed)
{
?time = Clamp(time - timeDelta, 0, maxTime); // update time
?DrawText(Math.ceil(time), 9); ? ? ? ? ? ? ? // show time
?context.textAlign = 'right'; ? ? ? ? ? ? ? ?// right alignment
?DrawText(0|position.z/1e3, c.width-9); ? ? ?// show distance
}
else
{
?context.textAlign = 'center'; ? ? ?// center alignment
?DrawText('HUE JUMPER', c.width/2); // draw title text
}

requestAnimationFrame(Update); // kick off next frame

} // end of update function

代碼的最后一位

HTML 需要一個結束腳本標簽來讓所有的代碼能夠跑起來。

?Update(); // kick off update loop
</script>

壓縮

這就是整個游戲啦!下方的一小段代碼就是壓縮后的最終結果,我用不同的顏色標注了不同的部分。完成所有這些工作后,你能感受到我在2KB內就做完了整個游戲是多么讓我滿意了嗎?而這還是在zip之前的工作,zip還可以進一步壓縮大小。

如何用不到 2KB 的 JavaScript 代碼寫一個 3D 賽車游戲?

如何用不到 2KB 的 JavaScript 代碼寫一個 3D 賽車游戲?

警告 Caveats

當然,還有很多其他 3D 渲染方法可以同時保證性能和視覺效果。如果我有更多的可用空間,我會更傾向于使用一個 WebGL API 比如 three.js ,我在去年制作的一個類似游戲“Bogus Roads”中用過這個框架。此外,因為它使用的是 requestAnimationFrame ,所以需要一些額外的代碼來確保幀速率不超過60 fps,增強版本中我會這么用,盡管我更喜歡使用 requestAnimationFrame 而不是 setInterval ,因為它是垂直同期的(VSyn,VerticalSynchronization),所以渲染更絲滑。這種代碼的一個主要好處是它非常兼容,可以在任何設備上運行,盡管在我舊 iPhone 上運行有點慢。

游戲代碼被我放到了 GitHub 上的 GPL-3.0 下(https://github.com/KilledByAPixel/HueJumper2k),所以你可以在自己的項目中自由使用它。該庫中還包含 2KB 版本的游戲,準確說是2031字節!歡迎你添加一些其他的功能,比如音樂和音效到“增強”版本中。

后記

雷鋒網注意到,Frank Force 在個人博客發了這篇文章后,在內容、標題的加持下,這篇文章后來被不少國外媒體轉載。在盛贊之余,也有質疑的聲音。網友“Anon”在原文下評論:你是如何在 2KB 安裝一個完整的 javascript 的,除非你可以隨意忽略 dependencies 插件庫的大小,或者你將整個游戲作為 dependency,大小才有可能控制到 2KB,否則就是欺騙。

Frank 回復表示,大多數 small demos 都需要某種運行環境,即使它是可執行的。在這種情況下,就是 javascript 運行時環境,沒有其他 dependencies.。因為 javascript 是解釋的,所以也可以說壓縮后的代碼是在2KB以內的。

雷鋒網發現,有其他網友表示認可 Frank 的說法,他們認為 JS 是一種解釋語言,不能將其與其他編譯語言相比較。

雷鋒網 (公眾號:雷鋒網)

雷鋒網版權文章,未經授權禁止轉載。詳情見。

如何用不到 2KB 的 JavaScript 代碼寫一個 3D 賽車游戲?

隨意打賞

提交建議
微信掃一掃,分享給好友吧。
主站蜘蛛池模板: 99久久精品国产片果冻的功能特点 | 久久色婷婷| 91久久国产综合精品女同国语 | 国产精品3p视频 | 波多野结衣50连登视频 | 精品视频久久久 | 中午日产幕无线码1区 | 国产精品69久久久久999小说 | 97免费在线视频 | 美女与动人物aa交性 | 石原莉奈一区二区三区在线观看 | 蜜乳av一区二区三区 | 情侣偷偷看的羞羞视频网站 | 久久久高清视频 | 欧洲av在线 | 深爱五月综合网 | 亚洲第一综合色 | 国内精品免费午夜又爽又色愉情 | 美女精品视频 | 欧美xxxx精品 | 久草福利在线视频 | 日韩人妻一区二区三区蜜桃视频 | 久久国产精品波多野结衣 | jizz在线播放| 日本中文视频 | 日韩影音| 成熟丰满中国女人少妇 | 久热免费在线视频 | 国产精品短视频 | 欧美一区二区久久久 | 久久av一区二区三区亚洲 | 欧美日韩国产一区二区三区 | 国产偷人妻精品一区 | 小罗莉极品一线天在线 | 中国精学生妹品射精久久 | 中国av免费看 | 波多野结衣家庭主妇 | 国产精品久久久av久久久 | 日本毛片在线观看 | 丰满岳跪趴高撅肥臀尤物在线观看 | 国产伦精品一区二区三区视频网站 | 国产成人精品无码片区在线观看 | 高h放荡受浪受bl | 欧美www| 96看片| 天天干天天操天天摸 | 国产精品8888 | 我要看一级片 | 久久亚洲美女精品国产精品 | 中文字幕永久视频 | 国产成人亚洲综合青青 | 日本少妇又色又爽又高潮看你 | 中文字幕麻豆 | 天天躁夜夜躁狠狠眼泪 | 91在线精品啪婷婷 | 人妻体内射精一区二区三区 | 欧美亚洲第一区 | 少妇被躁爽到高潮无码文 | 欧美精品亚洲精品日韩传电影 | 超碰免费在线播放 | 亚洲国产精品成人综合久久久 | 国产一级二级三级在线观看 | 精品国产老女人乱码 | 四虎4hu永久免费深夜福利 | 99精品国产一区二区三区 | 夜夜高潮夜夜爽国产伦精品 | 国产欧美一区二区精品性色超碰 | 欧美日韩国产三级 | 亚洲国产精品无码观看久久 | 吃奶呻吟打开双腿做受在线视频 | 久久精品国产99久久久 | 国产免费无码一区二区三区 | 免费无码一区二区三区a片 亚洲欧美日韩国产成人 | 国产成人无码一区二区三区在线 | 久久成人a | 天天干天天玩 | 性欧美精品久久久久久久 | 99ri在线| 国产精品高潮呻吟久久av黑人 | 99精品国产高清一区二区麻豆 | 日本人与禽zozzo小小的几孑 | 国产黄色观看 | 欧美少妇一区 | 欧美精品不卡 | 亚洲欧美日韩一区二区三区在线 | 91久久免费 | 亚洲最新av | 午夜精品久久久久 | 日韩激情第一页 | 999国内精品永久免费视频 | 国产九九在线 | 国产精品www在线观看 | 久久久免费观看视频 | 中文字幕亚洲欧美日韩在线不卡 | 折磨小男生性器羞耻的故事 | 欧美手机看片 | yy6080高清性理论片啪 | 欧美va天堂 | 奇米影视久久久 | 久久99精品久久久久久园产越南 | 超碰免费av | 两个奶头被吃高潮视频 | 性生活在线视频 | 成人免费毛片视频 | 中文字幕激情 | 久久久亚洲成人 | www成人在线观看 | 999久久久免费精品国产 | 黄色一级视频免费 | 日韩欧美一区二区三区 | 欧美少妇一区 | 欧美xxxx精品另类 | 欧美系列第一页 | 可以直接免费观看的av网站 | 天堂中文av在线 | 亚洲伦理在线视频 | 97视频精品| 91视频免费视频 | 男人全程不遮挡撒尿视频 | 九九精品成人免费国产片 | 尤物国产在线 | 深夜国产视频 | 精品无码中文字幕在线 | 中文字幕亚洲中文字幕无码码 | 美女裸体自慰在线观看 | 在线观看人成视频免费 | 久久久久久一区二区三区 | www国产亚洲精品久久网站 | 日本69少妇 | 亚洲免费看黄 | 韩国xxx hd videos| 紧身裙女教师三上悠亚红杏 | 综合九九 | 国产精品久久久久久久久久王欧 | 激情内射亚洲一区二区三区 | 97超碰免费在线观看 | 97精品一区 | 国产免费网址 | 国产喂奶挤奶一区二区三区 | 好吊色综合 | 久久精品不卡一区二区 | 少妇特黄v一区二区三区图片 | 天堂成人在线 | 久草福利资源在线观看 | 日本大bbb裸体欣赏 日本大尺度吃奶呻吟视频 日本大尺度吃奶做爰过程 日本大尺度吃奶做爰久久久绯色 | 91精品国产乱码久久 | 日韩亚洲国产中文字幕欧美 | 精品久久国产字幕高潮 | 国产无遮挡又黄又爽在线视频 | 青草一区 | 亚洲视频在线观看免费视频 | 国产娇喘精品一区二区三区图片 | 免费观看成年人视频 | 精品1卡二卡三卡四卡老狼 国内大量偷窥精品视频 | av操操操| 俺去俺来也www色官网cms | 黄色视屏在线 | 中文字幕在线天堂 | 欧美成人国产精品高潮 | 国内少妇偷人精品视频 | 精品国产aⅴ一区二区三区 精品国产va久久久久久久 | 久久久天堂国产精品女人 | 韩国三级hd中文字幕三义 | 天干天干天啪啪夜爽爽99 | 丝袜人妻一区二区三区网站 | 精品国产乱码久久久久久婷婷 | 少妇扒开粉嫩小泬视频 | 国产网站一区二区 | 日本中国内射bbxx | 日韩美女乱淫免费看视频大黄 | 四虎影视永久 | 狠狠色噜噜狠狠狠狠2021天天 | 男女啪啪做爰高潮www成人福利 | 婷婷性多多影院 | 巨粗进入警花哭喊求饶在线观看 | 18禁网站免费无遮挡无码中文 | 91超碰caoporn97人人 | av在线免费网址 | 国产精品777 | 欧美va天堂 | 亚洲区色 | 污污av | 欧美一区二区三区久久精品 | 深夜小视频在线观看 | 肉版如懿传高h | 日韩女人性猛交 | 嫩草网站入口一区二区 | 韩国女同性做爰三级 | 成人高潮片免费软件69视频 | 国产毛片18片毛一级特黄 | 又粗又猛又爽又黄少妇视频网站 | 日韩在线观看免费 | 久草福利网 | 日韩av成人网 | 自拍偷拍999 | 亚洲一区二区91 | 国产午夜福利久久精品 | 亚洲区视频在线观看 | 黄色欧美网站 | 成年视频免费高清在线看 | 性欧美videos另类极品小说 | 正在播放大战肉丝少妇 | 狠狠色噜狠狠狠狠 | 亚洲成a人| 欧美极品jizzhd欧美仙踪林 | 国产91在线精品 | 成人免费xxxxxxx | 国产精品久久久久久久久久久久午夜 | 色偷偷av| 18禁免费无码无遮挡不卡网站 | 影音先锋 成人 | 九九精品视频在线观看 | 久久无码人妻丰满熟妇区毛片 | 卡一卡二在线视频 | 成人羞羞国产免费软件动漫 | 精品国产精品国产偷麻豆 | 嫩草影院一区二区 | 色翁荡息又大又硬又粗视频 | 国产99久久久国产精品成人免费 | 在火车千女人毛片看看 | 综合网色| 国产嫩草一区二区三区在线观看 | 四虎影视亚洲精品一区二区 | av不卡一区二区 | 在线精品视频一区二区 | 日韩激情小说 | 韩国av免费在线观看 | 国产第一av| 电梯男女做爰视频 | 国产乱子伦农村叉叉叉 | 色姑娘久 | 成人久久久精品国产乱码一区二区 | 欧美一区二区三区视频在线观看 | 青青草99| 国产porn在线| 欧美性大战久久久久久久 | 无码日韩精品一区二区免费暖暖 | 中文字幕网站在线观看 | 亚洲国产精品女主播 | 日韩毛片一区 | 亚洲综合在线五月 | 国产成人一区二区三区在线观看 | 成人免费观看黄a大片夜月小说 | 国产精品日韩精品欧美精品 | 无码无遮挡又大又爽又黄的视频 | 久久亚洲精品国产精品紫薇 | 一级高清免费毛片 | 国产成人无码一区二区三区 | 日韩精品无码一区二区三区不卡 | 亚洲aaa精品| 情人伊人久久综合亚洲 | 水蜜桃久久夜色精品一区怎么玩 | 国产浮力第一页草草影院 | 亚洲精品a区 | 四面虎影最新播放网址 | 成人免费一区二区三区 | 欧美69av | 成熟少妇一区二区三区 | youjizz.com在线观看 | 婷婷色中文字幕综合在线 | 91国偷自产一区二区三区女王 | 欧美成人ⅴideosxxxxx | 久久www香蕉免费人成 | 久久婷婷五月综合色国产香蕉 | 99爱精品 | 永久免费d站视频 | 少妇搡bbbb搡bbb搡打电话 | 久久久久爽爽爽爽一区老女人 | 爽插| 噼里啪啦在线看免费观看视频 | 夜夜撸av | 久久亚洲精品中文字幕 | 激情自拍偷拍 | 亚色中文字幕 | av片在线免费观看 | 亚洲精品视频二区 | 7777久久久国产精品 | 国产精品久久午夜夜伦鲁鲁 | 日本韩国免费观看 | 污视频网站在线看 | 精品国产乱码久久久久久免费 | 伊人55 | 欧美xxxx性 | 麻豆一区二区99久久久久 | 不卡av片| 制服丝袜天堂网 | 国产精品码在线观看0000 | 36d大奶| 性一交一乱一色一免费无遮挡 | 国产小受呻吟gv视频在线观看 | 国产精品一区二区三区在线 | 久久精品国产视频 | 魔性诱惑| 成人夜夜 | 丁香五精品蜜臀久久久久99网站 | 91九色蝌蚪国产 | 久久久久久a亚洲欧洲av冫 | 少妇愉情理伦片丰满丰满午夜 | 在线观看国精产品二区1819 | 性娇小13――14欧美 | 夜夜操操操 | 亚洲性无码一区二区三区 | 欧美日二区 | 谁有免费的黄色网址 | 亚洲专区中文字幕 | 天天爽夜夜爽国产精品视频 | 99国产精品无码专区 | 午夜精品久久久久久久 | 欧美日韩亚洲二区 | 成人福利视频一区二区 | 久久人人爽人人爽人人片av东京热 | 欧美亚洲国产精品久久 | 亚洲 另类 春色 国产 | 大尺度舌吻呻吟声 | 国产777爽777| 插入综合网 | 三级网站免费观看 | 天堂资源中文网 | 国产一区二区免费在线 | 性色88av老女人视频 | 国产一二三四在线视频 | 色综合久久天天综合网 | 天天干狠狠干 | 1000部啪啪 | 久久精品视频一区二区 | 最近中文字幕在线视频 | 国产精品久久久久久在线观看 | 制服丝袜在线第一页 | 亚洲精品国产suv一区88 | 视频在线观看h | 成人性生交大片免费看vr | 国产精品夜间视频香蕉 | 91亚洲国产成人精品一区 | 第一色综合| 6080久久| 国产三级全黄裸体 | 人妻 日韩 欧美 综合 制服 | 四虎在线免费 | 青青在线播放 | 欧美精品久久久久久久免费软件 | 亚洲黄色自拍 | 激情春色网 | 欧洲熟妇色xxxx欧美老妇多毛 | 国产无在线观看软件 | 91久久久久久久久久久久 | 色综合 图片区 小说区 | 亚洲宅男av | 中文字幕av一区二区三区 | 亚洲午夜久久久影院 | 五月亚洲综合 | 日韩av官网 | 国产精品第9页 | 亚洲www在线观看 | 欲香欲色天天综合和网 | 波多野吉衣在线观看视频 | 91精品国产一区 | 女主和前任各种做高h | 日本精品入口免费视频 | 国产情侣自拍小视频 | 国产伦理片在线观看 | 国产乱码久久久久 | 欧美综合专区 | 毛片库| 精品视频无码一区二区三区 | 国产精品mm | 欧洲美女高清视频 | 一本久道中文无码字幕av | 操碰视频在线 | 精品人妻人人做人人爽夜夜爽 | 狠狠色婷婷久久综合频道日韩 | 红桃av一区二区三区在线无码av | 最新日本黄色网址 | 蜜桃成人无码区免费视频网站 | 狠狠躁18三区二区一区张津瑜 | 成人av在线网 | 国产又粗又猛又黄又爽无遮挡 | 亚洲男人天堂视频 | 羞羞视频网站在线观看 | 公妇乱淫1~6集全观看不了啦 | 欧美肉欲k8播放毛片欧美 | 成人做爰高潮片免费视频九九九 | 亚洲区自拍 | 国产69久久精品成人看动漫 | 欧美日韩v | 亚洲精品影院 | 人人爱夜夜爽日日做蜜桃 | 乱人伦人妻中文字幕无码久久网 | 三级毛片在线看 | 毛片入口 | 国产又好看的毛片 | 国产高清不卡视频 | 久久亚洲美女精品国产精品 | 久久精品首页 | 日本黄色天堂 | 亚洲国产午夜精品理论片妓女 | 性色网站| 亚洲中文无码a∨在线观看 在线不卡日本v二区到六区 | 国产成人资源 | 俺来俺也去www色在线观看 | 福利视频大全 | 日本免费三片在线播放 | 精品国产99久久久久久宅男i | 精品久久久久久久免费人妻 | 日韩经典在线 | 在线观看aaa | 国产欧美一区二区精品仙草咪 | 欧美日韩黄色网 | 日韩男人天堂 | 久久在线免费 | 欧美成年人在线视频 | 高h震动喷水双性1v1 | 国产黄色a级毛片 | 91精品孕妇哺乳期国产 | 亚洲第一视频在线观看 | 久久国产精品二国产精品 | 欧美爽爽爽 | 理论片高清免费理论片毛毛片 | 国产人免费人成免费视频 | 一本色道久久88亚洲精品综合 | 日韩综合一区二区三区 | 91大尺度 | 亚洲福利在线播放 | 香蕉毛片| 色伊人亚洲综合网站 | 一本视频在线 | 天天躁夜夜躁天干天干2022 | a√在线视频 | 亚洲啪啪网 | 亚洲 欧美日韩 综合 国产 | 亚洲熟妇丰满xxxxx | 精品少妇一区二区三区视频 | eeuss鲁片一区二区三区在线观看 | 亚洲网站在线看 | 91精品国产乱码久久久久久久久 | 在线观看黄网址 | 天天干天天骑 | 免费观看不卡av | 国产福利免费在线 | 亚洲精品久久久久久下一站 | 国产女主播自拍 | 国内精品人妻无码久久久影院 | 亚洲狠狠操 | 日本免费一区二区三区四区五六区 | 国产91在线亚洲 | 国产美女诱惑 | 老色鬼永久视频网站 | 久久久久爽人综合网站 | 亚洲色土| 国产99久久久国产精品免费看 | 精品在线视频一区二区三区 | 乱码精品一区二区三区 | 欧美美女性生活 | 日韩bbw| 69堂国产成人免费视频 | 亚洲欧美色图在线 | 1v1高辣巨肉h各种play | 成人h猎奇视频网站 | 天堂а√在线最新版中文在线 | 国产理论一区二区三区 | 中国广东少妇xxxx做受 | 精品一卡二卡三卡四卡 | 97婷婷狠狠成为人免费视频 | 久久久久亚洲精品 | 亚洲欧美一区二区精品久久久 | 免费在线观看黄色片 | 国产精品视频麻豆 | 337p亚洲精品色噜噜噜 | 国产精品丝袜一区二区 | 免费在线日本 | 成人欧美视频 | 动漫av纯肉无码免费播放 | 色一情一乱一乱一区99av白浆 | 欧美日韩精品一区二区三区在线 | 日本sm极度另类视频 | 国产精品一级片 | 成人性生交大片免费看- | 无码国产69精品久久久久孕妇 | 国产群p视频 | 蜜臀av色欲a片无码精品一区 | 性欢交69精品久久久 | 久久综合给合久久狠狠狠色97 | 720lu牛牛刺激自拍视频 | 毛片毛片毛片毛片毛片毛片毛片 | 日韩成人在线免费视频 | 亚洲顶级毛片 | 成人做爰www网站视频 | 国产高清露脸 | 熟妇人妻av无码一区二区三区 | av一本久道久久综合久久鬼色 | 18禁裸男晨勃露j毛免费观看 | 欧美日韩在线成人 | 国产成人久久av免费看 | 久久精品一区二区国产 | 高清成人 | 本道久久综合无码中文字幕 | 午夜在线影院 | a毛片在线观看 | 图片区 小说区 区 亚洲五月 | 欧美黑人孕妇孕交 | 欧美肥胖老太videossexohd | 天堂成人在线观看 | 日韩欧美激情视频 | 久久综合狠狠综合五十路 | 日日夜夜欧美 | 亚洲乱码国产乱码精品天美传媒 | 亚洲精品中文字幕久久久久 | 久久精品日产第一区二区三区在哪里 | 91精彩刺激对白露脸偷拍 | 一区二区三区四区产品乱 | 精品国产91 | 极品尤物在线观看 | 波多野结衣在线精品视频 | 国产一国产二国产三 | 亚洲免费视 | 中文字幕亚洲在线 | 九九色在线 | 亚洲精品久久久中文字幕痴女 | 国产xxxx做受性欧美88 | 亚洲产国偷v产偷v自拍色戒 | 丰满爆乳无码一区二区三区 | 一级做a免费 | 国产一在线观看 | 亚洲无av码一区二区三区 | 欧美高清性色生活片免费观看 | 韩国中文字幕在线观看 | 青青草久草在线 | 国产精品香蕉在线的人 | 少妇人妻偷人精品一区二区 | 欧美aaa级片| 久久精品一区二区视频 | 99视屏| 欧美成人精品一区二区三区在线观看 | 欧美日韩国产高清 | 污网站在线观看免费 | 国产亚洲精品成人av在线 | 嫩草av影院 | 亚洲精品高清国产一久久 | 农村少妇一区二区三区蜜桃 | 日本欧美www视频网站 | 亚洲精品久久久久久一区二区 | 日本视频网 | 午夜激情毛片 | 2019最新中文字幕在线观看 | 欧美做受又硬又粗又大视频 | 色噜噜狠狠一区二区三区狼国成人 | 欧美精品久久久久久 | 爱情岛亚洲品质自拍极速福利网站 | 亚洲国产成人一区二区精品区 | 精品国产一区二区三区久久 | 日韩专区在线观看 | 伊人三级 | 日韩久久影视 | 国外精品jvid在线观看 | www天天干 | 亚洲欧美日韩一区 | 精品乱码久久久久久中文字幕 | 一线二线三线天堂 | 精品乱码久久久久久中文字幕 | av激情四射 | 91精品毛片一区二区三区 | 99热99re6国产在线播放 | 午夜视频在线免费观看 | 色偷偷久久 | 黑人无套内谢中国美女 | 亚洲最色 | 女人av| 啄木系列成人av在线播放 | 亚洲一区二区在线播放相泽 | 五月天国产在线 | 国产调教夫妻奴av | 最新国产黄色网址 | 欧美aa级| 中国极品少妇xxxxx | 中文字幕av一区二区 | 国产精品一区二区久久 | 婷婷丁香视频 | 午夜亚洲国产理论片一二三四 | 国精品午夜福利视频不卡 | 国产性xxxxx| 天天看片天天爽 | 久久精品综合视频 | 青青草视频在线观看 | 久久久蜜桃一区二区 | 超碰97av在线| 欧美性猛交性大交 | 涩涩网站免费 | 日韩少妇白浆无码系列 | 欧美激情导航 | 少妇精品蜜桃偷拍高潮系列 | 日日干日日干 | 国产午夜性爽视频男人的天堂 | 日本免费观看视频 | 久久国产精品偷 | 亚洲v欧美v日韩v国产v | 国产一级精品毛片 | 超碰97人人爱 | 欧美综合自拍亚洲综合图 | 热99精品 | 久久精品无码专区免费东京热 | 在线不卡一区 | 欧洲熟妇色xxxx欧美老妇免费 | 91av在线免费视频 | 国产丰满大乳奶水在线视频 | 黄色国产片 | 欧美综合自拍亚洲综合图 | 在线观看波多野结衣 | 成人av免费在线 | 狠狠色丁香婷婷综合 | 一国产一级淫片a免费播放口 | 午夜色网站 | 国产精品污www在线观看17c | 成人夜间视频 | 中文字幕视频网 | 国产激情一区二区三区 | 亚洲人成无码网站久久99热国产 |