8.6 、为旗杆增加布 我想要让 Flag 看起来像 很好,像 Flag 。为了这么做,我们需要模拟一个布的 Flag , attach 到旗杆。有什么更好的方式完成这件事,还是使用 jME 的 ClothPatch 功能。这将允许我们去创建一个弹簧( spring )点的 matrix ,它们由不同方
8.6 、为旗杆增加布
我想要让 Flag 看起来像 … 很好,像 Flag 。为了这么做,我们需要模拟一个布的 Flag , attach 到旗杆。有什么更好的方式完成这件事,还是使用 jME 的 ClothPatch 功能。这将允许我们去创建一个弹簧( spring )点的 matrix ,它们由不同方向的外力( force )调整(引力和风力)。我已经为这个向导创建了我自己的风力,而我们将在下面讨论。
首先,增加对象到 Flag 类。
// 用于制作 Flag 的 Cloth
private ClothPatch cloth ;
// 风的参数
private float windStrength = 15f;
private Vector3f windDirection = new Vector3f(0.8f, 0, 0.2f);
private SpringPointForce gravity , wind ;
在 Flag 的构造参数中,我们将创建一个 ClothPatch 。这个 Cloth 将是 25*25 的 matrix ,给它一个相当详细的 cloth 。 cloth 上的点越多,它运行得越慢,所以你应该在 flag 的视觉外观和它对游戏的影响之间选个平衡点。 25*25 给我一个可接受的性能和外观的比例。在创建 cloth 之后,我们将增加我们的 force 。我们增加的第一个 force 是我们为这个向导创建的自定义的 force ,叫做 RandomFlagWindForce 。我们也将增加一个默认的重力它是由 ClothUtils 创建的,这将在风减小的时候把 Flag 拉下来。
// 创建一个 cloth patch 将处理我们 flag
cloth = new ClothPatch( "cloth" , 25, 25, 1f, 10);
// 将我们自定义的风力增加到 cloth
wind = new RandomFlagWindForce( windStrength , windDirection );
cloth .addForce( wind );
// 增加一个简单的重力
gravity = ClothUtils. createBasicGravity ();
cloth .addForce( gravity );
由于 cloth 是标准的 jME Spatial ,我们和平常一样应用 RenderState 。我们将为 Flag 增加 texture ,使用 jME monkey 的 logo 。这个是一个让 monkey 的头在你脑海留下烙印的企图。
// 创建一个将 flag 显示的 texture ,一起推进 jME 发展!
TextureState ts = DisplaySystem. getDisplaySystem ()
.getRenderer().createTextureState();
ts.setTexture(
TextureManager. loadTexture (
Flag. class .getClassLoader()
.getResource( "res/logo.png" ),
Texture.MinificationFilter. Trilinear ,
Texture.MagnificationFilter. Bilinear
)
);
cloth .setRenderState(ts);
当我开始增加一个 flag 到场景时,我对 light 非常不满意,因为 flag 大部分时间都是阴影。因此,我决定增加一个 light ,它只影响 flag 。这个 light 应该跟着 Flag 移动,并且根据 Flag 定位自己。因此,使用一个 LightNode 是最好的解决方案。 LightNode 允许你对待一个 light 就像 scene 中的其它元素一样,通过移动 light 的父亲 node 移动它。
// 我们将使用一个 LightNode 去给 Flag 增加 light ,使用 Node
// 是因为它允许 light 随着 FLag 移动
// 首先创建 light
PointLight dr = new PointLight();
dr.setEnabled( true );
dr.setDiffuse( new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
dr.setAmbient( new ColorRGBA(0.5f, 0.5f, 0.5f, 1.0f));
dr.setLocation( new Vector3f(0.5f, -0.5f, 0));
// 接下来是 state
LightState lightState = DisplaySystem. getDisplaySystem ()
.getRenderer(). createLightState ();
lightState.setEnabled( true );
lightState.setTwoSidedLighting( true );
lightState.attach(dr);
setRenderState(lightState);
// 最后是结点
LightNode lightNode = new LightNode( "light" );
lightNode.setLight(dr);
lightNode.setLocalTranslation( new Vector3f(15,0,0));
attachChild(lightNode);
cloth .setRenderState(lightState);
Flag 有一点和前面不同,那就是我们想看到它所有面的三角形。前面,我们设置了 scene 的 CullState 去剔除所有背后的三角形。因此,我们将增加另一个 CUllState 给 cloth ,让它不要剔除任何三角形。它将看到所有的三角形。
// 我们想看 flag 所有的面,所以我们将关闭 Cull
CullState cs = DisplaySystem. getDisplaySystem ()
.getRenderer().createCullState();
cs.setCullFace(CullState.Face. None );
cloth .setRenderState(cs);
this .attachChild( cloth );
下一步,我们需要为 cloth 创建一些点去固定它。如果我们不这么做,整个 cloth 将在 force 应用于它身上时被[吹走]。我们需要在旗杆上 attach 一些点去保住 cloth 。我们通过设置点的质量为无穷大来这么做。因此,没有任何 force 能移走它。为了模拟这个方法,一个 flag 被 attach 到一个旗杆, flag 上边缘的一些和旗杆接触的点将被 attach ,下面的也是。这些点被设置在一个基础的二维 matrix 中,所以我们将 attach 点( 0 , 1 , 2 , 3 , 4 )到旗杆,点( 500 , 525 , 550 , 575 , 600 )也一样。我们也调整这些点在 y 的位置让它们偏移,从而让 cloth 形成一点褶皱。
// 我们需要 attach 一些点到旗杆,这些点不应该被移动。
// 因此,我们将 attach 顶部和底部的 5 个点去让它们足够高而
// 且没有任何 forece 能移动它。我也稍微移动这些点的位置去
// 形成褶皱让它更真实。
for ( int i=0; i
cloth .getSystem().getNode(i*25). position . y *= .8f;
cloth .getSystem().getNode(i*25)
.setMass(Float. POSITIVE_INFINITY );
}
for ( int i=24; i>19; i--){
cloth .getSystem().getNode(i*25). position . y *= .8f;
cloth .getSystem().getNode(i*25)
.setMass(Float. POSITIVE_INFINITY );
}
最后,我创建自定义风力。为了模仿旗杆上 flag 的效果,我们也随机化风向。风向将基于它之前的位置
/**
* 在每次 update cloth 时调用。将轻微调整风向和强度、
*/
public void apply( float dt, SpringPoint node) {
windDirection . x += dt * (FastMath. nextRandomFloat () - .5f);
windDirection . z += dt * (FastMath. nextRandomFloat () - .5f);
windDirection .normalize();
float tStr = FastMath. nextRandomFloat () * strength ;
node. acceleration .addLocal(
windDirection . x * tStr,
windDirection . y * tStr,
windDirection . z * tStr
);
}
通过那样,我们现在有一个看起来真实的 flag ,而且我们以自己的方式去为我们游戏中添加元素。下一课,我们只需能获取 flag 。
8.7 、源码
import java.io.IOException;
import java.net.URL;
import java.util.HashMap;
import javax.swing.ImageIcon;
import com.jme.app.BaseGame;
import com.jme.bounding.BoundingBox;
import com.jme.image.Texture;
import com.jme.input.ChaseCamera;
import com.jme.input.InputHandler;
import com.jme.input.KeyBindingManager;
import com.jme.input.KeyInput;
import com.jme.input.thirdperson.ThirdPersonMouseLook;
import com.jme.light.DirectionalLight;
import com.jme.math.FastMath;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.Renderer;
import com.jme.scene.Node;
import com.jme.scene.Skybox;
import com.jme.scene.shape.Box ;
import com.jme.scene.state.CullState;
import com.jme.scene.state.LightState;
import com.jme.scene.state.TextureState;
import com.jme.scene.state.ZBufferState;
import com.jme.system.DisplaySystem;
import com.jme.system.JmeException;
import com.jme.util.TextureManager;
import com.jme.util.Timer;
import com.jme.util.export.binary.BinaryImporter;
import com.jmex.model.converters.MaxToJme ;
import com.jmex.terrain.TerrainBlock;
import com.jmex.terrain.util.MidPointHeightMap;
import com.jmex.terrain.util.ProceduralTextureGenerator;
public class Lesson8 extends BaseGame{
private int width , height ;
private int freq , depth ;
private boolean fullscreen ;
// 我们的 camera 对象,用于观看 scene
private Camera cam ;
protected Timer timer ;
private Node scene ;
private TextureState ts ;
private TerrainBlock tb ;
private ForceFieldFence fence ;
private Skybox skybox ;
private Vehicle player ;
private ChaseCamera chaser ;
private InputHandler input ;
// 保存 terrain 的任何一个给出点的法向
private Vector3f normal = new Vector3f();
private float agl ;
private Flag flag ;
public static void main(String[] args) {
Lesson8 app = new Lesson8();
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() {
display .setTitle( "Flag Rush" );
scene = new Node( "Scene Graph Node" );
ZBufferState buf = display .getRenderer().createZBufferState();
buf.setEnabled( true );
buf.setFunction(ZBufferState.TestFunction. LessThanOrEqualTo );
scene .setRenderState(buf);
buildTerrain();
buildFlag();
buildLighting();
buildEnvironment();
createSkybox();
buildPlayer();
buildChaseCamera();
buildInput();
CullState cs = display .getRenderer().createCullState();
cs.setCullFace(CullState.Face. Back );
scene .setRenderState(cs);
// 更新 scene 用于渲染
scene .updateGeometricState(0.0f, true );
scene .updateRenderState();
}
private void buildFlag() {
flag = new Flag( tb );
scene .attachChild( flag );
flag .placeFlag();
}
private void buildInput() {
input = new FlagRushInputHandler(
player ,
settings .getRenderer()
);
}
private void buildChaseCamera() {
HashMap props = new HashMap ();
props.put(ThirdPersonMouseLook. PROP_MAXROLLOUT , "6" );
props.put(ThirdPersonMouseLook. PROP_MINROLLOUT , "3" );
props.put(
ThirdPersonMouseLook. PROP_MAXASCENT ,
"" +45*FastMath. DEG_TO_RAD
);
props.put(
ChaseCamera. PROP_INITIALSPHERECOORDS ,
new Vector3f(5,0,30*FastMath. DEG_TO_RAD )
);
chaser = new ChaseCamera( cam , player , props);
chaser .setMaxDistance(8);
chaser .setMinDistance(2);
}
private void buildPlayer() {
Node model = null ;
URL maxFile = Lesson8. class .getClassLoader()
.getResource( "res/bike.jme" );
try {
model = (Node)BinaryImporter. getInstance ().load(
maxFile.openStream()
);
model.setModelBound( new BoundingBox());
model.updateModelBound();
model.setLocalScale(0.0025f);
} catch (IOException e1) {
e1.printStackTrace();
}
// 设置 Vehicle 的属性(这些数字能被认为是单元 / 秒 Unit/S )
player = new Vehicle( "Player Node" ,model);
player .setAcceleration(15);
player .setBraking(25);
player .setTurnSpeed(2.5f);
player .setWeight(25);
player .setMaxSpeed(25);
player .setMinSpeed(15);
player .setLocalTranslation( new Vector3f(100,0, 100));
player .updateWorldBound();
player .setRenderQueueMode(Renderer. QUEUE_OPAQUE );
scene .attachChild( player );
scene .updateGeometricState(0, true );
agl = ((BoundingBox)( player .getWorldBound())). yExtent ;
}
private void createSkybox() {
skybox = new Skybox( "skybox" ,10,10,10);
Texture north = TextureManager. loadTexture (
Lesson8. class .getClassLoader()
.getResource( "res/texture/north.jpg" ),
Texture.MinificationFilter. BilinearNearestMipMap ,
Texture.MagnificationFilter. Bilinear
);
Texture south = TextureManager. loadTexture (
Lesson8. class .getClassLoader()
.getResource( "res/texture/south.jpg" ),
Texture.MinificationFilter. BilinearNearestMipMap ,
Texture.MagnificationFilter. Bilinear
);
Texture east = TextureManager. loadTexture (
Lesson8. class .getClassLoader()
.getResource( "res/texture/east.jpg" ),
Texture.MinificationFilter. BilinearNearestMipMap ,
Texture.MagnificationFilter. Bilinear
);
Texture west = TextureManager. loadTexture (
Lesson8. class .getClassLoader()
.getResource( "res/texture/west.jpg" ),
Texture.MinificationFilter. BilinearNearestMipMap ,
Texture.MagnificationFilter. Bilinear
);
Texture up = TextureManager. loadTexture (
Lesson8. class .getClassLoader()
.getResource( "res/texture/top.jpg" ),
Texture.MinificationFilter. BilinearNearestMipMap ,
Texture.MagnificationFilter. Bilinear
);
Texture down = TextureManager. loadTexture (
Lesson8. class .getClassLoader()
.getResource( "res/texture/bottom.jpg" ),
Texture.MinificationFilter. BilinearNearestMipMap ,
Texture.MagnificationFilter. Bilinear
);
skybox .setTexture(Skybox.Face. North , north);
skybox .setTexture(Skybox.Face. West , west);
skybox .setTexture(Skybox.Face. South , south);
skybox .setTexture(Skybox.Face. East , east);
skybox .setTexture(Skybox.Face. Up , up);
skybox .setTexture(Skybox.Face. Down , down);
skybox .preloadTextures();
scene .attachChild( skybox );
}
private void buildEnvironment() {
fence = new ForceFieldFence( "forceFieldFence" );
// 我们将手工做一些调整去让它更好适应 terrain
// 首先我们将实体 [ 模型 ] 放大
fence .setLocalScale(5);
// 现在,让我们移动 fence 到 terrain 的高度并有一点陷入它里面
fence .setLocalTranslation(
new Vector3f(25, tb .getHeight(25,25)+10,25)
);
scene .attachChild( fence );
}
private void buildLighting() {
/* 设置一个基础、默认灯光 */
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.setEnabled( true );
lightState.attach(light);
scene .setRenderState(lightState);
}
/**
* 创建 heightmap 和 terrainBlock
*/
private void buildTerrain() {
// 生成随机地形数据
MidPointHeightMap heightMap = new MidPointHeightMap(64,1f);
// 缩放数据
Vector3f terrainScale = new Vector3f(4, .0575f, 4);
// 创建一个 terrain block
tb = new TerrainBlock(
"terrain" ,
heightMap.getSize(),
terrainScale,
heightMap.getHeightMap(),
new Vector3f(0, 0, 0)
);
tb .setModelBound( new BoundingBox());
tb .updateModelBound();
// 通过三个纹理生成地形纹理
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, 384
);
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);
// 加载细节纹理并为 2 个 terrain 的 texture 设置组合模型
Texture t2 = TextureManager. loadTexture (
Lesson8. class .getClassLoader()
.getResource( "res/Detail.jpg" ),
Texture.MinificationFilter. Trilinear ,
Texture.MagnificationFilter. Bilinear
);
ts .setTexture(t2, 1);
t2.setWrap(Texture.WrapMode. Repeat );
t1.setApply(Texture.ApplyMode. Combine );
t1.setCombineFuncRGB(Texture.CombinerFunctionRGB. Modulate );
t1.setCombineSrc0RGB(Texture.CombinerSource. CurrentTexture );
t1.setCombineOp0RGB(Texture.CombinerOperandRGB. SourceColor );
t1.setCombineSrc1RGB(Texture.CombinerSource. PrimaryColor );
t1.setCombineOp1RGB(Texture.CombinerOperandRGB. SourceColor );
t2.setApply(Texture.ApplyMode. Combine );
t2.setCombineFuncRGB(Texture.CombinerFunctionRGB. AddSigned );
t2.setCombineSrc0RGB(Texture.CombinerSource. CurrentTexture );
t2.setCombineOp0RGB(Texture.CombinerOperandRGB. SourceColor );
t2.setCombineSrc1RGB(Texture.CombinerSource. Previous );
t2.setCombineOp1RGB(Texture.CombinerOperandRGB. SourceColor );
tb .setRenderState( ts );
tb .setDetailTexture(1, 16);
tb .setRenderQueueMode(Renderer. QUEUE_OPAQUE );
scene .attachChild( tb 查看更多关于jMonkeyEngine译文FlagRush8(2)增加随机的Flag的详细内容...