注:本系列教程全部翻譯完之后可能會(huì)以 PDF 的形式發(fā)布。
如果有什么錯(cuò)誤可以留言或 EMAIL : kakashi9bi@gmail.com 給我。
jME 版本 : jME_2.0.1_Stable
開(kāi)發(fā)工具: MyEclipse8.5
操作系統(tǒng): Window7/Vista
這個(gè)向?qū)е形覀兩婕暗揭恍┖猛娴模覀儗槲覀兊挠螒蚣虞d地形(下文將使用 Terrain 代替)。這里對(duì)于我想要的類(lèi)型的 terrain 有一些要求:
l 每次隨機(jī)
l 不需太多三角形
l 為了跳躍“崎嶇”
l 對(duì)于快速的交通工具足夠大
我們將在第二課中的框架上構(gòu)建。首先,由清除 Sphere 渲染代碼開(kāi)始。我們不再需要這個(gè)例子。你現(xiàn)在應(yīng)該有相當(dāng)干凈的框架用于工作。現(xiàn)在,我們將創(chuàng)建的地形會(huì)相當(dāng)大。所以我想改變 Camera 的位置保證地形在視野里面。因此,在 initSystem 中作出如下改變:
Vector3f loc = new Vector3f(0.0f,0.0f,25.0f);
改為:
Vector3f loc = new Vector3f(500f,150f,500f);
這向上、遠(yuǎn)、后移動(dòng),確保我們對(duì)地形有恰當(dāng)?shù)囊曇啊?
現(xiàn)在,在 initGame 方法里面我們將加入一個(gè)對(duì)新方法的調(diào)用,這為這個(gè) scene 增加一個(gè) TerrainBlock 。這個(gè) TerrainBlock 叫做 tb 并應(yīng)該在類(lèi)頂部定義。這個(gè)新的方法叫做 buildTerrain 并應(yīng)該在增加 tb 到 scene 之前調(diào)用。你應(yīng)該像下面一樣:
protected void initGame() {
scene = new Node( "Scene Graph Node" );
buildTerrain();
scene .attachChild( tb );
// 更新 scene 用于渲染
scene . updateGeometricState (0.0f, true );
scene .updateRenderState();
}
這引導(dǎo)我們到這個(gè)向?qū)У暮诵模? buildTerrain 。
這里有我們 terrain 創(chuàng)建的核心:
1、 創(chuàng)建一個(gè) heightmap
2、 從 heightmap 生成網(wǎng)格(下文將以 Mesh 代替)
3、 生成基于高度的紋理
3.1 、創(chuàng)建一個(gè) heightmap
AbstractHeightMap 定義了一個(gè)方法用于保存高度數(shù)據(jù)。在它的核心,主要是一個(gè)二維矩陣的數(shù)據(jù),任何一個(gè)點(diǎn)( X,Z )的高度 Y 。然而這不允許創(chuàng)建復(fù)雜 terrain (窯洞、懸崖等等)。它提供了很基礎(chǔ)的方形 terrain ,然而這正是我們 FlagRush 中所需要的。
我們將創(chuàng)建一個(gè) MidPointHeightMap ,它使用中點(diǎn)取代不規(guī)則碎片。這將允許地形足夠有趣和真實(shí),為我們提供了一些顛簸和跳躍。
創(chuàng)建這個(gè) heightmap 很直截了當(dāng),在我們 buildTerrain 方法中的第一行:
/**
* 創(chuàng)建 heightmap 和 terrainBlock
*/
private void buildTerrain() {
// 生成隨機(jī)地形數(shù)據(jù)
MidPointHeightMap heightMap = new MidPointHeightMap(64,1f);
……
}
我們調(diào)用 MidPointHeightMap 的構(gòu)造方法創(chuàng)建一個(gè)新的 heightMap 對(duì)象。它只需要 2 個(gè)參數(shù):大小和粗糙程度。
MidPointHeightMap 的大小必須是 2 的冪。那就是 2 、 4 、 8 、 16 、 32 、 64 等等。在我們的例子中,我們選擇 64 。這正好符合我們的需要(我們的行為將被局限在一個(gè)相當(dāng)小的舞臺(tái))。粗糙程度才是有趣的東西。這個(gè)值越低,則 terrain 越粗糙,反之越平滑。我們先選擇它為 1 ,讓 terrain 看起來(lái)像地獄般凹凸還帶著尖刺。然而,我們還沒(méi)設(shè)置完,這些尖刺將被調(diào)下來(lái)。
我們將定義一個(gè) terrain 縮放因數(shù)。這將簡(jiǎn)單拉伸或擠壓 mesh 以滿(mǎn)足我們的需求。所以,增加:
// 縮放數(shù)據(jù)
Vector3f terrainScale = new Vector3f(20, .5f, 20);
到 buildTerrain 方法。這意味著:我們將拉伸 terrain 的 X 和 Z 的值 20 。這將讓 terrain 感覺(jué)更大(實(shí)際上大了 20 倍)。然而與此同時(shí),我們讓 Y 值減少了一半。這將得到我們想要的凹凸感,但讓它們處于一個(gè)合理的值(不會(huì)太突然)。
3.2 、生成 Terrain Mesh
現(xiàn)在,我們已經(jīng)設(shè)置好了數(shù)據(jù),我們能真正創(chuàng)建 mesh 。我們將創(chuàng)建一個(gè) TerrainBlock ,它是一個(gè)簡(jiǎn)單的 Geometry 。這個(gè)將增加到 scene 里,就像我們之前增加 Sphere 那樣。
// 創(chuàng)建一個(gè) terrain block
tb = new TerrainBlock(
"terrain" ,
heightMap.getSize(),
terrainScale,
heightMap.getHeightMap(),
new Vector3f(0, 0, 0)
);
tb . setModelBound ( new BoundingBox());
tb .updateModelBound();
TerrainBlock 接受一些參數(shù),大多數(shù)都很直接。首先,是 terrain 的名字。 heightMap 的大小,接著是我們之前所設(shè)的 terrain 的縮放值。接著給出 heightMap 真正的數(shù)據(jù)。下一個(gè)參數(shù)定義了 terrain 的起點(diǎn)。我們這里沒(méi)有理由設(shè)置一些奇怪的值,因此設(shè)置了基本的( 0 , 0 , 0 )。
我們接著設(shè)置了 terrain 的 BoundingVolume 。
你現(xiàn)在或許能繼續(xù)并運(yùn)行游戲,看到類(lèi)似下面的一些東西:
這里并不能看到很多東西,因?yàn)? terrain 僅是一大塊白色。我們需要應(yīng)用 texture 去讓它有一點(diǎn)層次感。
3.3 、生成 Texture
創(chuàng)建一個(gè) Texture 將通過(guò)使用 ProceduralTextureGenerator 。這個(gè)類(lèi)將生成一個(gè)基于 heightmap 的高度的紋理,并在多個(gè) texture 間混合。一個(gè) texture 被指定到一個(gè)高度區(qū)域,而它們之后混合進(jìn)單一的 texture map 。這允許我們很容易創(chuàng)建一個(gè)看起來(lái)相當(dāng)真實(shí)的 Terrain 。在我們的例子中,我們將使用 3 張 texture ,一個(gè)用于低區(qū)域的草地 texture ,中部的巖石和高處的雪。
// 通過(guò)三個(gè)紋理生成地形紋理
ProceduralTextureGenerator pt =
new ProceduralTextureGenerator(heightMap);
pt.addTexture(
new ImageIcon(
getClass().getClassLoader()
.getResource( "res/grassb.png" )
),
-128, 0, 128
);
pt.addTexture(
new ImageIcon(
getClass().getClassLoader()
.getResource( "res/dirt.jpg" )
),
0, 128, 256
);
pt.addTexture(
new ImageIcon(
getClass().getClassLoader()
.getResource( "res/highest.jpg" )
),
128, 256, 374
);
pt.createTexture(32);
你將注意到每個(gè) Texture 有 3 個(gè)值。這描述了這個(gè) texture 將被應(yīng)用到低的,最佳的和高的海拔。例如( dirt.jpg )將混合從海拔 0-256 。 heightmap 生成從 0-256 的值。所以這意味著 dirt 在 128 將更強(qiáng)烈(看得更多),然后向 0 和 256 混合其它的 texture 。同時(shí)其它的 2 個(gè) texture 被填充在低和高的區(qū)域。
addTexture 接受 ImageIcon 對(duì)象去定義 texture 數(shù)據(jù)。在這個(gè)例子中,我們通過(guò)我們的類(lèi)的 getResource 方法獲取到的 URL 創(chuàng)建 ImageIcon 。這個(gè)在 classpath 里面搜索 images 。這當(dāng)然不是一定要這么做, ImageIcon 能在其它某個(gè)地方被創(chuàng)建,它將適用于你應(yīng)用程序。
createTexture 真正創(chuàng)建了我們需要使用的 texture 。在這個(gè)例子中,我讓它生成一個(gè) 32X32 像素的 texture 。雖然這個(gè)看起來(lái)很小,但是我并不需要它的細(xì)節(jié)。這只是用于基礎(chǔ)顏色,之后我們將創(chuàng)建更詳細(xì)的 texture 和對(duì)象。
例如:在運(yùn)行游戲期間,我保存了一個(gè)生成的 texture 。它看起來(lái)像這樣:
你能看到三個(gè) texture ( grassb , dirt , highest )是怎樣被混合為一個(gè)單一的 texture 。白色的區(qū)域?qū)?huì)是 terrain 的高點(diǎn),而 grass 將是 terrain 的低點(diǎn)。
現(xiàn)在我們已經(jīng)生成了 Terrain ,我們把它放入一個(gè) TextureState 并把它應(yīng)用到 terrain 。
// 將紋理賦予地形
ts = display .getRenderer().createTextureState();
Texture t1 = TextureManager. loadTexture (
pt.getImageIcon().getImage(),
Texture.MinificationFilter. Trilinear ,
Texture.MagnificationFilter. Bilinear ,
true
);
ts .setTexture(t1, 0);
tb .setRenderState( ts );
通過(guò)這樣, terrain 就能正常工作了。你現(xiàn)在能運(yùn)行游戲并看到類(lèi)似下面的:
注意: 我一直說(shuō)類(lèi)似,因?yàn)槲覀兪褂玫氖请S機(jī)方法去生成 terrain 。所以它每次都將不同。
3.4 、創(chuàng)建燈光( Light )
盡管使用了 texture ,我們依然很難辨別出 terrain 。那是因?yàn)闆](méi)有燈光和陰影幫助我們辨別 terrain 的部分。所以,讓我們繼續(xù)并增加一個(gè)“太陽(yáng)”。增加一個(gè) buildLighting 到你的 initGame 。我們將增加一個(gè) DirectionalLight 去照耀 terrain 。增加 light 有 2 部分。首先,創(chuàng)建 DirectionalLight ,然后把它增加到 LightState 。
private void buildLighting() {
/* 設(shè)置一個(gè)基礎(chǔ)、默認(rèn)燈光 */
DirectionalLight light = new DirectionalLight();
light.setDiffuse( new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
light.setAmbient( new ColorRGBA(0.5f, 0.5f, 0.5f, 1.0f));
light.setDirection( new Vector3f(1, -1, 0));
light.setEnabled( true );
LightState lightState =
display .getRenderer().createLightState();
lightState.attach(light);
scene .setRenderState(lightState);
}
這個(gè) DirectionalLight 被設(shè)置于照耀( 1 , -1 , 0 )那個(gè)方向(向下和向右)。它接著被增加到 LightState 并應(yīng)用到 scene 。
這為 terrain 增加了一些層次感,而你能更好辨認(rèn)出地形特征。
3.5 、總結(jié)
我們現(xiàn)在擁有了一個(gè)可以在上面奔跑的平面。然而,那還是存在令人討厭的黑色背景。下一節(jié)課我們將適當(dāng)關(guān)注個(gè)問(wèn)題。
3.6 、源碼
import javax.swing.ImageIcon;
import com.jme.app.BaseGame;
import com.jme.bounding.BoundingBox;
import com.jme.image.Texture;
import com.jme.input.KeyBindingManager;
import com.jme.input.KeyInput;
import com.jme.light.DirectionalLight;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.Node;
import com.jme.scene.state.LightState;
import com.jme.scene.state.TextureState;
import com.jme.system.DisplaySystem;
import com.jme.system.JmeException;
import com.jme.util.TextureManager;
import com.jme.util.Timer;
import com.jmex.terrain.TerrainBlock;
import com.jmex.terrain.util.MidPointHeightMap;
import com.jmex.terrain.util.ProceduralTextureGenerator;
public class Lesson3 extends BaseGame{
private int width , height ;
private int freq , depth ;
private boolean fullscreen ;
// 我們的 camera 對(duì)象,用于觀看 scene
private Camera cam ;
protected Timer timer ;
private Node scene ;
private TextureState ts ;
private TerrainBlock tb ;
public static void main(String[] args) {
Lesson3 app = new Lesson3();
java.net.URL url = app.getClass().getClassLoader()
.getResource( "res/logo.png" );
app.setConfigShowMode(ConfigShowMode. AlwaysShow ,url);
app.start();
}
/*
* 清除 texture
*/
protected void cleanup() {
ts .deleteAll();
}
protected void initGame() {
scene = new Node( "Scene Graph Node" );
buildTerrain();
buildLighting();
scene .attachChild( tb );
// 更新 scene 用于渲染
scene .updateGeometricState(0.0f, true );
scene .updateRenderState();
}
private void buildLighting() {
/* 設(shè)置一個(gè)基礎(chǔ)、默認(rèn)燈光 */
DirectionalLight light = new DirectionalLight();
light.setDiffuse( new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
light.setAmbient( new ColorRGBA(0.5f, 0.5f, 0.5f, 1.0f));
light.setDirection( new Vector3f(1, -1, 0));
light.setEnabled( true );
LightState lightState =
display .getRenderer().createLightState();
lightState.attach(light);
scene .setRenderState(lightState);
}
/**
* 創(chuàng)建 heightmap 和 terrainBlock
*/
private void buildTerrain() {
// 生成隨機(jī)地形數(shù)據(jù)
MidPointHeightMap heightMap = new MidPointHeightMap(64,1f);
// 縮放數(shù)據(jù)
Vector3f terrainScale = new Vector3f(20, .5f, 20);
// 創(chuàng)建一個(gè) terrain block
tb = new TerrainBlock(
"terrain" ,
heightMap.getSize(),
terrainScale,
heightMap.getHeightMap(),
new Vector3f(0, 0, 0)
);
tb .setModelBound( new BoundingBox());
tb .updateModelBound();
// 通過(guò)三個(gè)紋理生成地形紋理
ProceduralTextureGenerator pt =
new ProceduralTextureGenerator(heightMap);
pt.addTexture(
new ImageIcon(
getClass().getClassLoader()
.getResource( "res/grassb.png" )
),
-128, 0, 128
);
pt.addTexture(
new ImageIcon(
getClass().getClassLoader()
.getResource( "res/dirt.jpg" )
),
0, 128, 256
);
pt.addTexture(
new ImageIcon(
getClass().getClassLoader()
.getResource( "res/highest.jpg" )
),
128, 256, 374
);
pt.createTexture(32);
// 將紋理賦予地形
ts = display .getRenderer().createTextureState();
Texture t1 = TextureManager. loadTexture (
pt.getImageIcon().getImage(),
Texture.MinificationFilter. Trilinear ,
Texture.MagnificationFilter. Bilinear ,
true
);
ts .setTexture(t1, 0);
tb .setRenderState( ts );
}
protected void initSystem() {
// 保存屬性信息
width = settings .getWidth();
height = settings .getHeight();
depth = settings .getDepth();
freq = settings .getFrequency();
fullscreen = settings .isFullscreen();
try {
display = DisplaySystem. getDisplaySystem (
settings .getRenderer()
);
display .createWindow(
width , height , depth , freq , fullscreen
);
cam = display .getRenderer().createCamera( width , height );
} catch (JmeException e){
e.printStackTrace();
System. exit (-1);
}
// 設(shè)置背景為黑色
display .getRenderer().setBackgroundColor(ColorRGBA. black );
// 初始化攝像機(jī)
cam .setFrustumPerspective(
45.0f,
( float ) width /( float ) height ,
1f,
1000f
);
Vector3f loc = new Vector3f(500f,150f,500f);
Vector3f left = new Vector3f(-1.0f,0.0f,0.0f);
Vector3f up = new Vector3f(0.0f,1.0f,0.0f);
Vector3f dir = new Vector3f(0.0f,0.0f,-1.0f);
// 將攝像機(jī)移到正確位置和方向
cam .setFrame(loc, left, up, dir);
// 我們改變自己的攝像機(jī)位置和視錐的標(biāo)志
cam .update();
// 獲取一個(gè)高分辨率用于 FPS 更新
timer = Timer. getTimer ();
display .getRenderer().setCamera( cam );
KeyBindingManager. getKeyBindingManager ().set(
"exit" ,
KeyInput. KEY_ESCAPE
);
}
/*
* 如果分辨率改變將被調(diào)用
*/
protected void reinit() {
display .recreateWindow( width , height , depth , freq , fullscreen );
}
/*
* 繪制場(chǎng)景圖
*/
protected void render( float interpolation) {
// 清除屏幕
display .getRenderer().clearBuffers();
display .getRenderer().draw( scene );
}
/*
* 在 update 期間,我們只需尋找 Escape 按鈕
* 并更新 timer 去獲取幀率
*/
protected void update( float interpolation) {
// 更新 timer 去獲取幀率
timer .update();
interpolation = timer .getTimePerFrame();
// 當(dāng) Escape 被按下時(shí),我們退出游戲
if (KeyBindingManager. getKeyBindingManager ()
.isValidCommand( "exit" )
){
finished = true ;
}
}
}
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元

