分享
 
 
 

3D编程指南第一部分:快速进入移动JAVA 3D编程世界

王朝java/jsp·作者佚名  2006-01-10
窄屏简体版  字體: |||超大  

作者:numenzq 文章来源:http://www.j2medev.com/Article/ShowArticle.asp?ArticleID=819

下面你可以下载源程序和应用程序包的zip文件,并且继续看该指南。

l 源代码(类和资源)

http://developer.sonyericsson.com/getDocument.do?docId=73849

l 应用程序包(JAR/JAD)

http://developer.sonyericsson.com/getDocument.do?docId=73850

l 关于Redikod的更多信息

http://www.redikod.com/

绪论

在一开始,我希望你知道网络上的几个链接,这些网站有助于你进入M3G领域。

首先,或许是最主要的是在索尼爱立信开发者世界的移动JAVA 3D部分;其次,如果你曾经困惑,那就去索尼爱立信JAVA手机3D论坛。在索尼爱立信开发网,你将找到你问题的答案以及其他信息。

既然你已经知道遇到问题时应该去那里,让我继续指南的讲解。这个讲解的目的是教你怎么设置你自己的3D画布和在屏幕上进行填充。为了填充模型,我首先将向你说明如何装载它们,并告诉你创建M3G模型用到的工具。然后我们将巧妙地处理这台照相机,为了使我们能围绕我们的场景行走。我要让你在座位上感到兴奋和了解如何用M3G快速的开发一个3D应用程序。因此,这个讲解是相当快速和直截了当的,几乎没有复杂的解释。这个指南的其他部分将分别探索M3G的各个主题。

以教育为目的的最佳讲授方法不是讲解代码,它不能包括你可能遇到的所有错误当你编写3D程序的时候。在以后,更多的高级主题将被讲解。

预备知识

在你开始阅读这之前,你应该知道MIDlet类和Canvas类。这不是一个复杂的主题,如果你不清楚,请参考源代码(分布在指南中)并看看M3GMIDlet和M3GCanvas类。你最好有一些3D编程和3D算术基础,但这不是必须的。

画布

当我们开发JSR 184程序时,我们将使用MIDP 2.0规范。这以为着我们得到了几个重要的函数。让我们开始设定我们的画布。这与普通的2D Java策略是相同的,你设定你的MIDlet类,你启动你的Canvas,并用paint方法在你的画布上绘画。这是一个相当容易的过程,所以你应该知道我之前为何快速撇去它了。首先让我们看一看画布类,导入和变量声明。

import javax.microedition.lcdui.Graphics;

import javax.microedition.lcdui.game.GameCanvas;

import javax.microedition.M3G.Camera;

import javax.microedition.M3G.Graphics3D;

import javax.microedition.M3G.Light;

import javax.microedition.M3G.Loader;

import javax.microedition.M3G.Object3D;

import javax.microedition.M3G.Transform;

import javax.microedition.M3G.World;

/**

*

* @author Biovenger

* @version

*/

public class M3GCanvas

extends GameCanvas

implements Runnable {

// Thread-control

boolean running = false;

boolean done = true;

// If the game should end

public static boolean gameOver = false;

// Rendering hints

public static final int STRONG_RENDERING_HINTS = Graphics3D.ANTIALIAS | Graphics3D.TRUE_COLOR | Graphics3D.DITHER;

public static final int WEAK_RENDERING_HINTS = 0;

public static int RENDERING_HINTS = STRONG_RENDERING_HINTS;

// Key array

boolean[] key = new boolean[5];

// Key constants

public static final int FIRE = 0;

public static final int UP = FIRE + 1;

public static final int DOWN = UP + 1;

public static final int LEFT = DOWN + 1;

public static final int RIGHT = LEFT + 1;

这是个相当基础的填充,不过还是让我们迅速看看发生了什么。首先我们有很多导入,我们仅仅导入在该指南里所用到的所有类,并且我们能在JSR 184 API文档中找到它们。我们还定义了一些线程变量,例如:running和 done,这些是显而易见的。

现在,让我们看看绘制迹象(hints),这些“迹象”告诉移动设备在绘制的时候你想要的效果。然而,因为他们是迹象,它不能保证在移动设备上显示他们。这里我定义两个不同的迹象。弱和强,正如你看到的,强绘制迹象控制反混淆现象,真彩色和抖动。弱绘制控制没有迹象的情况,这基本上是你你能得到的最丑和最快的绘制效果。如同你从代码里看见的,迹象能由一些简单的逻辑或组合。我将谈论更多关于迹象在该指南以后的部分里。

其次我们有key数组,那是一个非常简单的数组用来保存被按下的键。如果你好奇键是如何处理的,看看这个例子的源代码。也就是说:如果UP键被按下时,由if(key[UP])询问发现。

M3G文件格式

JSR 184标准有自己的格式,称为M3G。这个非常通用的3D格式能保存大量数据,例如模型、灯光、照相机、纹理、甚至动画。非常好!非均匀是这个格式最大的好处,它能容易的装载入你的应用程序。无论如何,我打赌你在想“我从未听过M3G,又如何创建M3G文件呢?”你并不用考虑这个,我将作出说明。有许多方法创建M3G文件:

1、首先,最新的Discreet's 3D Studio Max内建了M3G的导出接口。仅需点击导出按钮,你就能导出你的M3G文件里的所有场面、动画、骨架、材质等。然而,很多Discreet's的导出有点麻烦并且有一些错误,因此最好使用方法2。

2、HiCorp,同样是一个实现索尼爱立信 JSR 184的导出工具,非常强大的导出器,它可以利用三种最流行的3D建模程序,3D Studio Max, LightWave and Maya。你可以从这里找到它。

(http://developer.sonyericsson.com/site/global/docstools/java/p_java.jsp)

3、Blender,一个强大并且免费的3D建模工具,它也有M3G导出接口。然后,它的一些较早的版本仍然存在错误。点击这里查看Blender。

(http://www.blender3d.org/cms/Home.2.0.html)

如此,我们怎么装载这些强大的文件到我们的程序里呢?非常简单。JSR 184里有个名为Loader的类,并且它正确的装载文件。调用一个简单的方法就可以装载一个M3G文件的所有引用,这个方法叫做Loader.load,有两个不同的参数列表。一个是用String获得一个URL,另一个是获得一个为加工的字节数组。这里是如何使用它的例子:

Object3D[] objects = Loader.load("file.M3G");

Object3D[] objects2 = Loader.load(byteArray, offset);

装载方法总是返回一个Object3D数组,并且有它的一个非常好的原因。最好的是Loader类能比M3G文件装载更多,基本上能保存Object3D的所有子类。然而,你主要使用它来装载M3G文件。

现在,我创建一个简单的M3G文件,叫做map。我要绘制它,我将使用Loader.load方法装载该文件,旧如你看见过的那样,返回一个Object3D数组。我们不能使用Object3D数组去绘制。因此我们需要转换它,使之成为我们能重画的场景。在这个指南里,我们将装载世界(world)结点。世界结点是JSR 184场面图表中最顶层的结点。它控制所有种类的信息,例如:照相机、照明设备、背景和许多网眼。我将讲诉场面图表和场面图表的JSR 184的实施在这个系列后面的部分,你现在只需要了解世界类能控制整个场面,并且知道什么是我们想要的!查看这个方法,它从一个M3G文件中装载世界结点。

/** Loads our world */

private void loadWorld()

{

try

{

// Loading the world is very simple. Note that I like to use a

// res-folder that I keep all files in. If you normally just put your

// resources in the project root, then load it from the root.

Object3D[] buffer = Loader.load("/res/map.M3G");

// Find the world node, best to do it the "safe" way

for(int i = 0; i < buffer.length; i++)

{

if(buffer[i] instanceof World)

{

world = (World)buffer[i];

break;

}

}

// Clean objects

buffer = null;

}

catch(Exception e)

{

// ERROR!

System.out.println("Loading error!");

reportException(e);

}

}

正如你所见的,在我们用Loader类装载Object3D数组后,我们简单地审阅整个数组并找到世界结点。这是查找世界结点最安全的方法。在我们找到世界结点后,我们将跳出循环并清空缓冲(它是不需要的,因为当离开这个方法时,他们会自动的得到清楚,这是好的习惯)

好的,我们现在装载我们的世界结点,我已经告诉过你,它是场面图表的顶层结点并能控制所有场面信息。在我告诉你它的绘制是如何简单之前,先让我们提取照相机,为了我们能在装载的世界中移动。

处理照相机

我们有了我们的世界结点准备绘画,并且现在我们需要一个照相机,它能使我们围绕世界移动。如果你记得,我已经告诉过你,世界结点能控制照相机信息,因此我们应该从世界中提取照相机并操作它。

JSR 184的一个照相机是用Camera类描述的。这个类能很容易的用一些简单的平移和定位方法来操作我们3D应用程序里的照相机。在这个例子里,我们将使用translate(float, float, float)和setOrientation(float, float, float, float)方法。第一个方法是在3D空间里简单的移动照相机,偏移量为x,y,z。因此,如果你想让照相机在X和Z轴方向上各移动3个单位。你可以这样做:

Camera cam = new Camera(); // This is our camera

//Move camera X Y Z

cam.translate(3.0f, 0.0f, 3.0f);

很简单的事!每个方法调用translate进一步调整照相机,因此上面两个调用实际上转变照相机在X和Z轴都是6个单位。旋转也是一样的简单,我会首先说明这个方法。它操作像3D API的所有旋转方法一样。有个参数,第一个是实际的旋转度数,剩下的三个组成一个方向向量(xAxis, yAxis, zAxis)在周围旋转。方向和方向向量在后面的丛书将会讲到,现在仅仅需要知道这些:

//Rotate camera 30 degrees around the X axis

cam.setOrientation(30.0f, 1.0f, 0.0f, 0.0f);

//Rotate camera 30 degrees around the Y axis

cam.setOrientation(30.0f, 0.0f, 1.0f, 0.0f);

//Rotate camera 30 degrees around the Z axis

cam.setOrientation(30.0f, 0.0f, 0.0f, 1.0f);

注意名为setOrientation的方法,它实际意味着清除所有先前你做的旋转。我假设你已经知道了怎么围绕一个坐标轴旋转,在这里并不会提到关于这个主题的更多细节。

你现在知道如何移动和旋转照相机。我将告诉你如何从世界中提取照相机。

/** Loads our camera */

private void loadCamera()

{

// BAD!

if(world == null)

return;

// Get the active camera from the world

cam = world.getActiveCamera();

// Create a light

Light l = new Light();

// Make sure it's AMBIENT

l.setMode(Light.AMBIENT);

// We want a little higher intensity

l.setIntensity(3.0f);

// Add it to our world

world.addChild(l);

}

是这么简单吗?是的,就是这么简单。我们使用getActiveCamera方法从世界中提取照相机。我们将得到世界导出时的哪个照相机。通过上面的方法,我们得到了一个照相机,我们可以把它移动成想要的效果。然而,这个方法还做了其他事,它添加了一个灯光!我们将在后面的部分钻研灯光,但在这里你只需知道如何在世界里添加一个灯光。我创建一个环境光(如果你不知道,环境光是光从所有方向照射所有表面)并添加它到世界里。这个方法让我们得到了一个非常好的有光的世界。我之前告诉过你,世界结点可以控制所有种类的信息,也包括灯光,因此,我们只需要将灯光添加入我们的世界,JSR 184将为我们处理。那不是很容易吗?在我们阅读最后一个部分之前,绘制,让我们的照相机移动。我已经告诉过你boolean数组,键值,控制我们的键信息,因此我们要询问这个数组并是照相机运转。首先,我们需要一些变量来控制我们的照相机。

// Camera rotation

float camRot = 0.0f;

double camSine = 0.0f;

double camCosine = 0.0f;

// Head bobbing

float headDeg = 0.0f;

我们将用上面的变量来控制照相机旋转的轨迹,三角函数和顶点振动。三角函数用于稍后的移动,顶点振动是十分简单的,它使照相机上下振动当我们在世界里行走时,为了一种更自然的感觉。好的,我们需要怎么样做才能移动照相机呢?使用下面的方法:

private void moveCamera() {

// Check controls

if(key[LEFT])

{

camRot += 5.0f;

}

else if(key[RIGHT])

{

camRot -= 5.0f;

}

// Set rotation

cam.setOrientation(camRot, 0.0f, 1.0f, 0.0f);

// Calculate trigonometry for camera movement

double rads = Math.toRadians(camRot);

camSine = Math.sin(rads);

camCosine = Math.cos(rads);

如你所见,这部分方法是很简单的,首先我们检查用户是否按下左或右键,如果被按下,我们仅仅是增加或减少camRot的值,然后旋转照相机。那是如此的简单。余下的几行代码是有趣的,当用户按下左或右时,我们需要旋转顶点,我们是围绕Y轴旋转的,这就意味着方向响亮为0.0f, 1.0f, 0.0f。当我们旋转了照相机后,我们要计算出新的Sine和Cosine角度,这是为以后的移动而计算的,现在我们看另一部分方法:

if(key[UP])

{

// Move forward

cam.translate(-0.1f * (float)camSine, 0.0f, -0.1f * (float)camCosine);

// Bob head

headDeg += 0.5f;

// A simple way to "bob" the camera as the user moves

cam.translate(0.0f, (float)Math.sin(headDeg) / 40.0f, 0.0f);

}

else if(key[DOWN])

{

// Move backward

cam.translate(0.1f * (float)camSine, 0.0f, 0.1f * (float)camCosine);

// Bob head

headDeg -= 0.5f;

// A simple way to "bob" the camera as the user moves

cam.translate(0.0f, (float)Math.sin(headDeg) / 40.0f, 0.0f);

}

// If the user presses the FIRE key, let's quit

if(key[FIRE])

M3GMidlet.die();

}

这里我们检查UP或DOWN键,UP键将移动照相机向前,DOWN键将移动照相机向后。这是一个简单的平移,但我还是要做出简单的说明。照相机总是朝着负Z轴方向的,因此要向前移动照相机,我们只需要在负Z轴上移动它。然而,如果我们旋转照相机,我们不能只沿着Z轴移动了,这样会导致错误。我们想在X轴上移动照相机,因此我们要得到我们希望的动作。这个就需要使用三角函数。这个指南不是讲解3D算术的,我将不会讲更多细节,毕竟你应该知道这些,如果你认为它是复杂的,那就在网络上找一个好的3D算术指南看看吧。

在每次平移后,我们总是要移动顶点用我简单的顶点振动。我只提供在Y轴上转化方法用sine函数,因此它看上去是顶点上下移动,这也是为什么在照相机每次移动时我增加或减少headDeg变量。在代码的末端,我们同样的检查FIRE键,是为了能让用户随时退出程序的。(它能使用在画布创建是我添加的无形的EXIT命令)。

好的,这是我们所有高级照相机动作,现在让我们了解如何绘制世界结点。

绘制

在编码之前,我将告诉你关于立即保留模式绘制。在我们的指南中,保留模式是一种基本模式,你可以用来绘制整个世界结点和所有照相机、灯光和网眼。这是最简单的绘制模式,不过你还是需要控制你的世界。立即模式是直接绘制组中的网眼和顶点数据。这个给你更多的控制权,每个绘画都提供一个变换矩阵,在重绘前变换这个对象。在立即模式,你可以绘制一个世界结点,提供一个转化矩阵让绘制方法调用,不过你得放弃世界结点的漂亮的效果,例如照相机、背景等。在后面的丛书里,我将详细介绍两种绘制模式的不同。现在,让我们看看如何绘制一个世界。

Graphics3D

JSR 184的所有绘制都是靠Graphics3D对象完成的。如果你在立即模式下绘制,它能控制照相机和灯光信息。现在我们不用担心,我在后面讲会讲解到这些。

要用Graphics3D对象绘制,首先你必须绑定它给一个图形上下文。一个图形上下文基本上意味着所有能画的图形对象。如果你想绘制一张图片,它能成为一个图片的图形对象,或者它也可以通过getGraphics()方法获取一个主图形对象。使用主图形对象,你可以直接绘制你想要的场景。获得一个Graphics3D对象是简单的,你只需要调用Graphics3D.getInstance()方法。每个MIDlet只能获得一个Graphics3D对象,这就是为什么只能通过getInstance方法获得该对象。绑定使用bindTarget方法,同时还使用了一些其他方法,让我们看下面的例子:

//Here is our Graphics3D object

Graphics3D g3d = Graphics3D.getInstance();

// Bind to an image

Image img = Image.createImage("myImage.png");

Graphics g = img.getGraphics();

g3d.bindTarget(g);

// Bind to the main Graphics object

g3d.bindTarget(getGraphics());

// We can also supply rendering hints. Remember those? I talked about them at the beginning.

// This is done by using the other form of the bindTarget method.

// It takes a Graphics object to begin with, as always, and then it needs a boolean

// and an integer mask of hints.

// The boolean simply tells the Graphics3D object if it should use a depth buffer

// and you'll probably always set it to 'true'. Here is how we'll use it to bind

// with our hints:

g3d.bindTarget(getGraphics(), true, RENDERING_HINTS);

现在,你知道了如何绑定你的目标,你同样应该知道在循环中如何释放目标。这就意味着在绘制完后,必须释放目标。释放和绑定时有可能出现问题。所以,大多数人维持正个游戏循环在try/catch块里,并把releaseTarget调用放在finally子句里。在这个例子里,我们也将这样做。现在,让我们了解一下绘制方法。绘制不同的事物,你可以使用不同的绘制方法,不过现在我们只对render(World)方法感兴趣。简单吧?是的,你只需要提供你的世界结点,它将帮你绘制世界。让我们看看我们的游戏循环是如何的:

/** Draws to screen

*/

private void draw(Graphics g)

{

// Envelop all in a try/catch block just in case

try

{

// Move the camera around

moveCamera();

// Get the Graphics3D context

g3d = Graphics3D.getInstance();

// First bind the graphics object. We use our pre-defined rendering hints.

g3d.bindTarget(g, true, RENDERING_HINTS);

// Now, just render the world. Simple as pie!

g3d.render(world);

}

catch(Exception e)

{

reportException(e);

}

finally

{

// Always remember to release!

g3d.releaseTarget();

}

}

如此简单的循环,让我们看看它做了些什么!首先调用moveCamera方法,这个用于照相机的移动和旋转的。我们在之前已经见过的。然后,它获得一个Graphics3D实例并绑定到图形对象提供给draw方法。(注意:draw方法在线程的run方法里被调用,并有一个全局图形对象)

它同时添加在我们画布开始时定义的绘制迹象。毕竟是这样做的,它调用g3d.render(world)方法,它将为我们做所有事。它绘制我们的全部场面、网眼、材料、灯光和照相机。

结论

通过上面这些,下面是两张该程序运行时的屏幕截图:

看起来还不错吧?

下面是完整的MIDlet和Canvas类的源代码。代码并不多,如果你认为这个是显示在屏幕上的,你可以在指南的前面下载并运行它,并且有将找到完整JAR/JAD的应用程序包

M3GMidlet

import javax.microedition.lcdui.Command;

import javax.microedition.lcdui.CommandListener;

import javax.microedition.lcdui.Display;

import javax.microedition.lcdui.Displayable;

import javax.microedition.midlet.MIDlet;

import javax.microedition.midlet.MIDletStateChangeException;

public class M3GMidlet extends MIDlet implements CommandListener

{

// A variable that holds the unique display

private Display display = null;

// The canvas

private M3GCanvas canvas = null;

// The MIDlet itself

private static MIDlet self = null;

/** Called when the application starts, and when it is resumed.

* We ignore the resume here and allocate data for our canvas

* in the startApp method. This is generally very bad practice.

*/

protected void startApp() throws MIDletStateChangeException

{

// Allocate

display = Display.getDisplay(this);

canvas = new M3GCanvas(30);

// Add a quit command to the canvas

// This command won't be seen, as we

// are running in fullScreen mode

// but it's always nice to have a quit command

canvas.addCommand(new Command("Quit", Command.EXIT, 1));

// Set the listener to be the MIDlet

canvas.setCommandListener(this);

// Start canvas

canvas.start();

display.setCurrent(canvas);

// Set the self

self = this;

}

/** Called when the game should pause, such as during a call */

protected void pauseApp()

{

}

/** Called when the application should shut down */

protected void destroyApp(boolean unconditional) throws MIDletStateChangeException

{

// Method that shuts down the entire MIDlet

notifyDestroyed();

}

/** Listens to commands and processes */

public void commandAction(Command c, Displayable d) {

// If we get an EXIT command we destroy the application

if(c.getCommandType() == Command.EXIT)

notifyDestroyed();

}

/** Static method that quits our application

* by using the static field 'self' */

public static void die()

{

self.notifyDestroyed();

}

}

M3GCanvas

import javax.microedition.lcdui.Graphics;

import javax.microedition.lcdui.game.GameCanvas;

import javax.microedition.M3G.Camera;

import javax.microedition.M3G.Graphics3D;

import javax.microedition.M3G.Light;

import javax.microedition.M3G.Loader;

import javax.microedition.M3G.Object3D;

import javax.microedition.M3G.Transform;

import javax.microedition.M3G.World;

public class M3GCanvas

extends GameCanvas

implements Runnable {

// Thread-control

boolean running = false;

boolean done = true;

// If the game should end

public static boolean gameOver = false;

// Rendering hints

public static final int STRONG_RENDERING_HINTS = Graphics3D.ANTIALIAS | Graphics3D.TRUE_COLOR | Graphics3D.DITHER;

public static final int WEAK_RENDERING_HINTS = 0;

public static int RENDERING_HINTS = STRONG_RENDERING_HINTS;

// Key array

boolean[] key = new boolean[5];

// Key constants

public static final int FIRE = 0;

public static final int UP = FIRE + 1;

public static final int DOWN = UP + 1;

public static final int LEFT = DOWN + 1;

public static final int RIGHT = LEFT + 1;

// Global identity matrix

Transform identity = new Transform();

// Global Graphics3D object

Graphics3D g3d = null;

// The global world object

World world = null;

// The global camera object

Camera cam = null;

// Camera rotation

float camRot = 0.0f;

double camSine = 0.0f;

double camCosine = 0.0f;

// Head bobbing

float headDeg = 0.0f;

/** Constructs the canvas

*/

public M3GCanvas(int fps)

{

// We don't want to capture keys normally

super(true);

// We want a fullscreen canvas

setFullScreenMode(true);

// Load our world

loadWorld();

// Load our camera

loadCamera();

}

/** When fullscreen mode is set, some devices will call

* this method to notify us of the new width/height.

* However, we don't really care about the width/height

* in this tutorial so we just let it be

*/

public void sizeChanged(int newWidth, int newHeight)

{

}

/** Loads our camera */

private void loadCamera()

{

// BAD!

if(world == null)

return;

// Get the active camera from the world

cam = world.getActiveCamera();

// Create a light

Light l = new Light();

// Make sure it's AMBIENT

l.setMode(Light.AMBIENT);

// We want a little higher intensity

l.setIntensity(3.0f);

// Add it to our world

world.addChild(l);

}

/** Loads our world */

private void loadWorld()

{

try

{

// Loading the world is very simple. Note that I like to use a

// res-folder that I keep all files in. If you normally just put your

// resources in the project root, then load it from the root.

Object3D[] buffer = Loader.load("/res/map.M3G");

// Find the world node, best to do it the "safe" way

for(int i = 0; i < buffer.length; i++)

{

if(buffer[i] instanceof World)

{

world = (World)buffer[i];

break;

}

}

// Clean objects

buffer = null;

}

catch(Exception e)

{

// ERROR!

System.out.println("Loading error!");

reportException(e);

}

}

/** Draws to screen

*/

private void draw(Graphics g)

{

// Envelop all in a try/catch block just in case

try

{

// Move the camera around

moveCamera();

// Get the Graphics3D context

g3d = Graphics3D.getInstance();

// First bind the graphics object. We use our pre-defined rendering hints.

g3d.bindTarget(g, true, RENDERING_HINTS);

// Now, just render the world. Simple as pie!

g3d.render(world);

}

catch(Exception e)

{

reportException(e);

}

finally

{

// Always remember to release!

g3d.releaseTarget();

}

}

/**

*

*/

private void moveCamera() {

// Check controls

if(key[LEFT])

{

camRot += 5.0f;

}

else if(key[RIGHT])

{

camRot -= 5.0f;

}

// Set the orientation

cam.setOrientation(camRot, 0.0f, 1.0f, 0.0f);

// Calculate trigonometry for camera movement

double rads = Math.toRadians(camRot);

camSine = Math.sin(rads);

camCosine = Math.cos(rads);

if(key[UP])

{

// Move forward

cam.translate(-2.0f * (float)camSine, 0.0f, -2.0f * (float)camCosine);

// Bob head

headDeg += 0.5f;

// A simple way to "bob" the camera as the user moves

cam.translate(0.0f, (float)Math.sin(headDeg) / 3.0f, 0.0f);

}

else if(key[DOWN])

{

// Move backward

cam.translate(2.0f * (float)camSine, 0.0f, 2.0f * (float)camCosine);

// Bob head

headDeg -= 0.5f;

// A simple way to "bob" the camera as the user moves

cam.translate(0.0f, (float)Math.sin(headDeg) / 3.0f, 0.0f);

}

// If the user presses the FIRE key, let's quit

if(key[FIRE])

M3GMidlet.die();

}

/** Starts the canvas by firing up a thread

*/

public void start() {

Thread myThread = new Thread(this);

// Make sure we know we are running

running = true;

done = false;

// Start

myThread.start();

}

/** Run, runs the whole thread. Also keeps track of FPS

*/

public void run() {

while(running) {

try {

// Call the process method (computes keys)

process();

// Draw everything

draw(getGraphics());

flushGraphics();

// Sleep to prevent starvation

try{ Thread.sleep(30); } catch(Exception e) {}

}

catch(Exception e) {

reportException(e);

}

}

// Notify completion

done = true;

}

/**

* @param e

*/

private void reportException(Exception e) {

System.out.println(e.getMessage());

System.out.println(e);

e.printStackTrace();

}

/** Pauses the game

*/

public void pause() {}

/** Stops the game

*/

public void stop() { running = false; }

/** Processes keys

*/

protected void process()

{

int keys = getKeyStates();

if((keys & GameCanvas.FIRE_PRESSED) != 0)

key[FIRE] = true;

else

key[FIRE] = false;

if((keys & GameCanvas.UP_PRESSED) != 0)

key[UP] = true;

else

key[UP] = false;

if((keys & GameCanvas.DOWN_PRESSED) != 0)

key[DOWN] = true;

else

key[DOWN] = false;

if((keys & GameCanvas.LEFT_PRESSED) != 0)

key[LEFT] = true;

else

key[LEFT] = false;

if((keys & GameCanvas.RIGHT_PRESSED) != 0)

key[RIGHT] = true;

else

key[RIGHT] = false;

}

/** Checks if thread is running

*/

public boolean isRunning() { return running; }

/** checks if thread has finished its execution completely

*/

public boolean isDone() { return done; }

}

译者心得:

这是一篇非常简单的M3G入门文章,从如何创建M3G文件、程序如何导入M3G文件、获取世界结点、移动照相机、添加灯光、处理事件、到最后的绘制,都做出了详细的讲解。是初学者入门很好的参考学习资料,如果你已经学习了这部分知识,可以继续学习我翻译的第二部分知识。如果上面的内容有误或你有相关疑问,可以与我联系E-MAIL:numenzq@yahoo.com.cn,我会及时与你联系。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有