自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

265行代碼實(shí)現(xiàn)第一人稱游戲引擎

開發(fā) 項(xiàng)目管理 后端
本文沒有涉及復(fù)雜的數(shù)學(xué)計(jì)算,只用到了光線投射技術(shù)。你可能已經(jīng)見識(shí)過這種技術(shù)了,比如《上古卷軸2 : 匕首雨》、《毀滅公爵3D》還有 Notch Persson 最近在 ludum dare 上的參賽作品。Notch 認(rèn)為它夠好,我就認(rèn)為它夠好!

今天,讓我們進(jìn)入一個(gè)可以伸手觸摸的世界吧。在這篇文章里,我們將從零開始快速完成一次第一人稱探索。本文沒有涉及復(fù)雜的數(shù)學(xué)計(jì)算,只用到了光線投射技術(shù)。你可能已經(jīng)見識(shí)過這種技術(shù)了,比如《上古卷軸2 : 匕首雨》、《毀滅公爵3D》還有 Notch Persson 最近在 ludum dare 上的參賽作品。Notch 認(rèn)為它夠好,我就認(rèn)為它夠好!

 [Demo (arrow keys / touch)] [Source]

[[114219]]

用了光線投射就像開掛一樣,作為一名懶得出油的程序員,我表示非常喜歡。你可以舒暢地浸入到3D環(huán)境中而不受“真3D”復(fù)雜性的束縛。舉例來說,光線投射算法消耗線性時(shí)間,所以不用優(yōu)化也可以加載一個(gè)巨大的世界,它執(zhí)行的速度跟小型世界一樣快。水平面被定義成簡單的網(wǎng)格而不是多邊形網(wǎng)面樹,所以即使沒有 3D 建?;A(chǔ)或數(shù)學(xué)博士學(xué)位也可以直接投入進(jìn)去學(xué)習(xí)。

利用這些技巧很容易就可以做一些讓人嗨爆的事情。15分鐘之后,你會(huì)到處拍下你辦公室的墻壁,然后檢查你的 HR 文檔看有沒有規(guī)則禁止“工作場所槍戰(zhàn)建模”。

玩家

我們從何處投射光線?這就是玩家對(duì)象(Player)的作用,只需要三個(gè)屬性 x,y,direction。

  1. function Player(x, y, direction) {  
  2.   this.x = x;  
  3.   this.y = y;  
  4.   this.direction = direction;  
  5. }  

地圖

我們將地圖存作簡單的二維數(shù)組。數(shù)組中,0代表沒墻,1代表有墻。你還可以做得更復(fù)雜些,比如給墻設(shè)任意高度,或者將多個(gè)墻數(shù)據(jù)的“故事(stories)”打包進(jìn)數(shù)組。但作為我們的第一次嘗試,用0-1就足夠了。

  1. function Map(size) {  
  2.   this.size = size;  
  3.   this.wallGrid = new Uint8Array(size * size);  
  4. }  

投射一束光線

這里就是竅門:光線投射引擎不會(huì)一次性繪制出整個(gè)場景。相反,它把場景分成獨(dú)立的列然后一條一條地渲染。每一列都代表從玩家特定角度投射出的一條光線。如果光線碰到墻壁,引擎會(huì)計(jì)算玩家到墻的距離然后在該列中畫出一個(gè)矩形。矩形的高度取決于光線的長度——越遠(yuǎn)則越短。

[[114220]]

繪畫的光線越多,顯示效果就會(huì)越平滑。

 

1. 找到每條光線的角度

我們首先找出每條光線投射的角度。角度取決于三點(diǎn):玩家面向的方向,攝像機(jī)的視野,還有正在繪畫的列。

  1. var angle = this.fov * (column / this.resolution - 0.5);  
  2. var ray = map.cast(player, player.direction + angle, this.range); 

2. 通過網(wǎng)格跟蹤每條光線

接下來,我們要檢查每條光線經(jīng)過的墻。這里的目標(biāo)是最終得出一個(gè)數(shù)組,列出了光線離開玩家后經(jīng)過的每面墻。

[[114221]]

從玩家開始,我們找出最接近的橫向(stepX)和縱向(stepY)網(wǎng)格坐標(biāo)線。移到最近的地方然后檢查是否有墻(inspect)。一直重復(fù)檢查直到跟蹤完每條線的所有長度。

  1. function ray(origin) {  
  2.   var stepX = step(sin, cos, origin.x, origin.y);  
  3.   var stepY = step(cos, sin, origin.y, origin.x, true);  
  4.   var nextStep = stepX.length2 < stepY.length2  
  5.     ? inspect(stepX, 1, 0, origin.distance, stepX.y)  
  6.     : inspect(stepY, 0, 1, origin.distance, stepY.x);  
  7.    
  8.   if (nextStep.distance > range) return [origin];  
  9.   return [origin].concat(ray(nextStep));  

尋找網(wǎng)格交點(diǎn)很簡單:只需要對(duì) x 向下取整(1,2,3…),然后乘以光線的斜率(rise/run)得出 y。

  1. var dx = run > 0 ? Math.floor(x + 1) - x : Math.ceil(x - 1) - x;  
  2. var dy = dx * (rise / run); 

現(xiàn)在看出了這個(gè)算法的亮點(diǎn)沒有?我們不用關(guān)心地圖有多大!只需要關(guān)注網(wǎng)格上特定的點(diǎn)——與每幀的點(diǎn)數(shù)大致相同。樣例中的地圖是32×32,而32,000×32,000的地圖一樣跑得這么快!

 

3. 繪制一列

跟蹤完一條光線后,我們就要畫出它在路徑上經(jīng)過的所有墻。

  1. var z = distance * Math.cos(angle);  
  2. var wallHeight = this.height * height / z; 

我們通過墻高度的最大除以 z 來覺得它的高度。越遠(yuǎn)的墻,就畫得越短。

 

額,這里用 cos 是怎么回事?如果直接使用原來的距離,就會(huì)產(chǎn)生一種超廣角的效果(魚眼鏡頭)。為什么?想象你正面向一面墻,墻的左右邊緣離你的距離比墻中心要遠(yuǎn)。于是原本直的墻中心就會(huì)膨脹起來了!為了以我們真實(shí)所見的效果去渲染墻面,我們通過投射的每條光線一起構(gòu)建了一個(gè)三角形,通過 cos 算出垂直距離。如圖:

[[114222]]

我向你保證,這里已經(jīng)是本文最難的數(shù)學(xué)啦。

 

渲染出來

我們用攝像頭對(duì)象 Camera 從玩家視角畫出地圖的每一幀。當(dāng)我們從左往右掃過屏幕時(shí)它會(huì)負(fù)責(zé)渲染每一列。

 
在繪制墻壁之前,我們先渲染一個(gè)天空盒(skybox)——就是一張大的背景圖,有星星和地平線,畫完墻后我們還會(huì)在前景放個(gè)武器。
  1. Camera.prototype.render = function(player, map) {  
  2.   this.drawSky(player.direction, map.skybox, map.light);  
  3.   this.drawColumns(player, map);  
  4.   this.drawWeapon(player.weapon, player.paces);  
  5. }; 

攝像機(jī)最重要的屬性是分辨率(resolution)、視野(fov)和射程(range)。

  • 分辨率決定了每幀要畫多少列,即要投射多少條光線。
  • 視野決定了我們能看的寬度,即光線的角度。
  • 射程決定了我們能看多遠(yuǎn),即光線長度的最大值

組合起來

使用控制對(duì)象 Controls 監(jiān)聽方向鍵(和觸摸事件)。使用游戲循環(huán)對(duì)象 GameLoop 調(diào)用 requestAnimationFrame 請求渲染幀。這里的 gameloop 只有三行

  1. oop.start(function frame(seconds) {  
  2.   map.update(seconds);  
  3.   player.update(controls.states, map, seconds);  
  4.   camera.render(player, map);  
  5. });   
 

細(xì)節(jié)

雨滴

雨滴是用大量隨機(jī)放置的短墻模擬的。

  1. var rainDrops = Math.pow(Math.random(), 3) * s;  
  2. var rain = (rainDrops > 0) && this.project(0.1, angle, step.distance);  
  3.    
  4. ctx.fillStyle = '#ffffff';  
  5. ctx.globalAlpha = 0.15;  
  6. while (--rainDrops > 0) ctx.fillRect(left, Math.random() * rain.top, 1, rain.height); 

這里沒有畫出墻完全的寬度,而是畫了一個(gè)像素點(diǎn)的寬度。

 

照明和閃電

照明其實(shí)就是明暗處理。所有的墻都是以完全亮度畫出來,然后覆蓋一個(gè)帶有一定不透明度的黑色矩形。不透明度決定于距離與墻的方向(N/S/E/W)。

  1. ctx.fillStyle = '#000000';  
  2. ctx.globalAlpha = Math.max((step.distance + step.shading) / this.lightRange - map.light, 0);  
  3. ctx.fillRect(left, wall.top, width, wall.height); 

要模擬閃電,map.light 隨機(jī)達(dá)到2然后再快速地淡出。

 

碰撞檢測

要防止玩家穿墻,我們只要用他要到的位置跟地圖比較。分開檢查 x 和 y 玩家就可以靠著墻滑行。

  1. Player.prototype.walk = function(distance, map) {  
  2.   var dx = Math.cos(this.direction) * distance;  
  3.   var dy = Math.sin(this.direction) * distance;  
  4.   if (map.get(this.x + dx, this.y) <= 0) this.x += dx;  
  5.   if (map.get(this.x, this.y + dy) <= 0) this.y += dy;  
  6. }; 

墻壁貼圖

沒有貼圖(texture)的墻面看起來會(huì)比較無趣。但我們怎么把貼圖的某個(gè)部分對(duì)應(yīng)到特定的列上?這其實(shí)很簡單:取交叉點(diǎn)坐標(biāo)的小數(shù)部分。

  1. step.offset = offset - Math.floor(offset);  
  2. var textureX = Math.floor(texture.width * step.offset); 

舉例來說,一面墻上的交點(diǎn)為(10,8.2),于是取小數(shù)部分0.2。這意味著交點(diǎn)離墻左邊緣20%遠(yuǎn)(8),離墻右邊緣80%遠(yuǎn)(9)。所以我們用 0.2 * texture.width 得出貼圖的 x 坐標(biāo)。

 

試一試

恐怖廢墟中逛一逛。
還有人擴(kuò)展了社區(qū)版
Fredrik Wallgren 實(shí)現(xiàn)了 Java 移植。
 

接下來做什么?

因?yàn)楣饩€投射器是如此地快速、簡單,你可以快速地實(shí)現(xiàn)許多想法。你可以做個(gè)地牢探索者(Dungeon Crawler)、第一人稱射手、或者俠盜飛車式沙盒。靠!常數(shù)級(jí)的時(shí)間消耗真讓我想做一個(gè)老式的大型多人在線角色扮演游戲,包含大量的、程序自動(dòng)生成的世界。這里有一些帶你起步的難題:

  • 浸入式體驗(yàn)。樣例在求你為它加上全屏、鼠標(biāo)定位、下雨背景和閃電時(shí)同時(shí)出現(xiàn)雷響。
  • 室內(nèi)級(jí)別。用對(duì)稱漸變?nèi)〈炜蘸?。或者,你覺得自己很屌的話,嘗試用瓷片渲染地板和天花板。(可以這么想:所有墻面畫出來之后,畫面剩下的空隙就是地板和天花板了)
  • 照明對(duì)象。我們已經(jīng)有了一個(gè)相當(dāng)健壯的照明模型。為何不將光源放到地圖上,通過它們計(jì)算墻的照明?光源占了80%大氣層。
  • 良好的觸摸事件。我已經(jīng)搞定了一些基本的觸摸操作,手機(jī)和平板的小伙伴們可以嘗試一樣 demo。但這里還有巨大的提升空間。
  • 攝像機(jī)特效。比如放大縮小、模糊、醉漢模式等等。有了光線投射器這些都顯得特別簡單。先從控制臺(tái)中修改 camera.fov 開始。

同往常一樣,如果你造了什么炫爆的東西或者有什么相關(guān)的研究要分享,發(fā) email 給我或 tweet 我,我會(huì)分享給大家的。

 

討論

Hacker News 上的討論。

感謝

本來打算寫兩個(gè)鐘的文章結(jié)果寫了三周。沒有以下的幫助我不可能寫完這篇文章:

原文鏈接: A first-person engine in 265 lines   翻譯: 伯樂在線 - Jaward華仔

譯文鏈接: http://blog.jobbole.com/70956/

責(zé)任編輯:林師授 來源: 伯樂在線
相關(guān)推薦

2021-10-15 06:24:38

AIAR眼鏡人工智能

2012-12-24 09:21:45

iOSUnity3D

2013-10-18 09:29:53

編程開發(fā)

2012-12-24 09:13:23

iOSUnity3D

2013-01-04 13:14:25

筆記本

2013-05-24 14:02:42

2010-05-06 21:09:18

2021-10-15 15:05:32

AI 數(shù)據(jù)人工智能

2021-06-02 16:19:14

技術(shù)研發(fā)指標(biāo)

2023-05-18 15:28:20

人工智能計(jì)算機(jī)科學(xué)

2021-04-23 11:22:57

ThreadJava進(jìn)階Runnable

2023-04-24 15:41:27

ChatGPT人工智能

2010-05-06 21:43:23

2015-06-30 10:36:33

浪潮

2014-09-24 10:47:56

程序員

2014-09-04 11:12:12

2018-07-10 14:40:24

華為
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)