`
windybell
  • 浏览: 14880 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
社区版块
存档分类
最新评论

昼夜系统-游戏中的时间

    博客分类:
  • JME3
阅读更多
我希望在游戏中能够有昼夜变化,四季变化,这样就意味着游戏中将会有一套自己的时间算法。假设游戏中时间流逝的速度为现实世界的30倍,那么一天就等于在游戏中过了一个月。

随着游戏中时间的变化,会有日出和日落,也会有四季变化。不同的时刻,太阳光照的角度不同,人在地上的影子方向也不一样。比如夏天太阳高度会高一些,冬天则会低一些。



我把游戏世界的位置定位在地球北纬30°(跟武汉一样),那么一年中,春分和秋分两个节气的中午12时,太阳日照高度就是60°。夏至和冬至的太阳高度则要分别加减黄赤夹角(23°26′),为83.5°和36.5°。

我假设游戏中地球公转轨迹是一个非常完美的圆形,那么太阳的高度变化正好是一个正弦函数。地球公转第day天,那么转过的角度就是:

引用

beta = 2 * 3.1415926 * day / 360,


日照高度theta为:

引用

theta = 60°+ 23.5°* sin(beta)。


废话不多说了,上代码。

package org.pstale.utils;

import com.jme3.math.Vector3f;

/**
 * 游戏中的时间系统
 * @author yanmaoyuan
 *
 */
public class GameDate {
	
	// 我们假设游戏中时间流动的速度是现实的30倍!
	public final static int GAME_MINUTE = 2;// 游戏1分钟	= 现实2秒
	public final static int GAME_HOUR = 120;// 游戏1小时	= 60游戏分钟 = 现实2分钟
	public final static int GAME_DAY = 2880;// 游戏1天	= 24游戏小时 = 现实48分钟
	public final static int GAME_MONTH = 86400;// 游戏1月 =30游戏天 = 现实1天
	public final static int GAME_YEAR = 1036800;// 游戏1年 = 12游戏月 = 现实12天

	
	// 地球黄赤交角为23°26′
	private final static double ECLIPTIC_OBLIQUITY = Math.PI * 23.5f / 180f;
	// 假设世界的纬度为30°N,春分时中午12点太阳高度为60°
	private final static double WUHAN_LATITUDE = Math.PI * 60f / 180f;
	
	private final static double SEC_DEG = 2 * Math.PI / GAME_DAY;// 地球每秒转动的角度
	
	private final static double SIN_HOUR_DEG = Math.sin(Math.PI / 12);// 日出一小时的高度
	
	private long totalSec;// 游戏从开始到现在总共经过的秒数
	
	private int year_sec;
	private int month_sec;
	private int date_sec;
	private int hour_sec;
	
	private int year;// 年份>=0
	private int month;// 月份 [0~11]
	private int date;// 日期 [0~29]
	private int day;// 一年中的第几天[0~359]
	
	private int hour;// 小时[0~23]
	private int minute;// 分钟[0~59]
	
	private double alpha;// 我们假设6点钟日出,α代表时针相对于6点钟的位置。
	
	private double theta;// 我们假设一年每个月正午12点阳光的高度为θ
	
	public GameDate() {
		totalSec = 0l;
		sunDirection = new Vector3f();
		
		updateTime();
	}
	public GameDate(long lastTime) {
		totalSec = lastTime;
		sunDirection = new Vector3f();
		
		updateTime();
	}
	
	public void update() {
		totalSec++;
		updateTime();
	}
	
	private void updateTime() {
		
		//////////// 年月日
		year_sec = (int) (totalSec % GAME_YEAR);// 游戏中的一年过了多少秒。
		year = (int) (totalSec / GAME_YEAR);// 经过了几年了?
		day = year_sec / GAME_DAY;// 这是一年的第几天?
		
		month_sec = year_sec % GAME_MONTH;// 游戏中一个月过了多少秒
		month = year_sec/GAME_MONTH;// 经过了几个月了?
		
		date_sec = month_sec % GAME_DAY;// 游戏中的一天过了多少秒
		date = month_sec/GAME_DAY;// 经过了几天了?

		/////////////时分秒
		hour_sec = date_sec % GAME_HOUR;// 游戏中的一小时过了多少秒
		hour = date_sec / GAME_HOUR;// 今天过了几小时了?
		
		minute = (int) (hour_sec / GAME_MINUTE);// 一小时过了几分钟了?

		
		// 我们假设6点钟日出,α代表时针相对于6点钟的位置。
		alpha = SEC_DEG * (date_sec - GAME_HOUR * 6);// 根据一天的时间,计算时钟的角度
		updateDayAndNight();
		
		theta = getTheta();// 根据地球公转的角度,计算日照高度。
		updateSunDirection();
	}
	
	/**
	 * 游戏从开始到现在总共经过的秒数
	 * @return
	 */
	public long currentTimeInSecond() {
		return totalSec;
	}
	
	/**
	 * 下面来计算太阳高度。太阳每天升起的高度都不一样,随地球公转而变化。
	 */
	public double getTheta() {
		
		// 春分是春三月的中节,因此日期要回退45天
		double year_angle = Math.PI * 2 * (day - 45) / 360;

		// 世界的实际日照角度为
		this.theta = WUHAN_LATITUDE + ECLIPTIC_OBLIQUITY * Math.sin(year_angle);
		
		return theta;
	}
	private float lightPower;// 光照强度
	private boolean isDay;// 是否是白天
	public void updateDayAndNight() {
		// 计算阳光强度
		// 日出和日落时,太阳的亮度会渐变,当高度达到PI/6的时候,天就整个亮了。
		// 让日出时间提前1个小时,让日落时间推后1个小时。
		lightPower = (float) ((Math.sin(alpha) + SIN_HOUR_DEG) * 2);
		if (lightPower > 1f)
			lightPower = 1f;

		if (lightPower < 0) {// 太阳落下了
			lightPower = 0f;
			if (isDay)
				isDay = false;// 黑夜
		} else {
			if (!isDay)
				isDay = true;// 白天
		}
	}
	public float getLightPower() {
		return lightPower;
	}
	public boolean isDay() {
		return isDay;
	}
	private Vector3f sunDirection;// 光照角度
	/**
	 * 下面来计算光照角度
	 */
	public void updateSunDirection() {
		double x = -Math.cos(alpha);
		double y = -Math.sin(alpha) * Math.sin(theta);
		double z = -Math.sin(alpha) * Math.cos(theta);

		sunDirection.set((float) x, (float) y, (float) z);
	}
	public Vector3f getSunDirection() {
		return sunDirection;
	}
	public int getYear() {
		return year;
	}
	public int getMonth() {
		return month;
	}
	public int getDate() {
		return date;
	}
	public int getHour() {
		return hour;
	}
	public int getMinute() {
		return minute;
	}
}


然后我们写一个实例代码,来看看日出日落的效果。
注意:下例中所有模型,都来源于JME3自带的例子。

package org.pstale.utils;

import com.jme3.app.SimpleApplication;
import com.jme3.font.BitmapFont;
import com.jme3.font.BitmapText;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Cylinder;
import com.jme3.shadow.BasicShadowRenderer;
import com.jme3.shadow.DirectionalLightShadowRenderer;
import com.jme3.texture.Texture.WrapMode;
import com.jme3.util.TangentBinormalGenerator;

/**
 * 游戏时间以及昼夜系统测试
 * @author yanmaoyuan
 *
 */
public class TestGameDate extends SimpleApplication {

	private GameDate gameDate;// 游戏时间
	private DirectionalLight sunLight;// 太阳光
	private BitmapText gui;// 用来显示游戏时间

	private Geometry sunBox;// 用一个小方块来模拟代表太阳
	@Override
	public void simpleInitApp() {
		// 初始化游戏时间
		gameDate = new GameDate();

		// 初始化镜头
        cam.setLocation(new Vector3f(27.492603f, 29.138166f, -13.232513f));
        cam.setRotation(new Quaternion(0.25168246f, -0.10547892f, 0.02760565f, 0.96164864f));
        flyCam.setMoveSpeed(30);

        // 我们创建一个红色小方块,用来代表太阳,它会根据时间的变化而移动。
        Box box = new Box(5,5,5);
        sunBox = new Geometry("Box", box);
        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        mat.setColor("Color", ColorRGBA.Red);
        sunBox.setMaterial(mat);
        sunBox.setShadowMode(ShadowMode.Off);
        rootNode.attachChild(sunBox);

        setupGui();
        setupLighting();
        setupFloor();
        setupSignpost();
		
	}
	
	/**
	 * 创建gui,用来显示时间
	 */
	public void setupGui() {
		BitmapFont guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
		gui = new BitmapText(guiFont, false);
		gui.setText("00:00");
		guiNode.attachChild(gui);
		
		// 把gui放在屏幕顶部居中
		float width = (settings.getWidth() - gui.getLineWidth())/2;
		float height = settings.getHeight();
		gui.setLocalTranslation(width, height, 0);

	}
	
	/**
	 * 创建光源
	 */
	public void setupLighting() {
		// 阳光
		sunLight = new DirectionalLight();
		sunLight.setColor(ColorRGBA.White.clone());
		sunLight.setDirection(gameDate.getSunDirection());
		rootNode.addLight(sunLight);
		
		// 设置一个很淡的环境光
        AmbientLight al = new AmbientLight();
        al.setColor(ColorRGBA.White.mult(0.3f));
        rootNode.addLight(al);
        
        rootNode.setShadowMode(ShadowMode.CastAndReceive);
        
        // 阳光产生影子全靠这玩意了!
        DirectionalLightShadowRenderer dlsr = new DirectionalLightShadowRenderer(assetManager, 512, 4);
        dlsr.setLight(sunLight);
        viewPort.addProcessor(dlsr);
	}
	
	/**
	 * 创建一个地板,这样我们才能看见影子。
	 */
    public void setupFloor() {
        Material mat = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m");
        mat.getTextureParam("DiffuseMap").getTextureValue().setWrap(WrapMode.Repeat);
        mat.getTextureParam("NormalMap").getTextureValue().setWrap(WrapMode.Repeat);
        mat.setBoolean("UseMaterialColors", true);
        mat.setColor("Diffuse", ColorRGBA.White.clone());
        mat.setColor("Ambient", ColorRGBA.White.clone());
        // mat.setColor("Specular", ColorRGBA.White.clone());
        // mat.getTextureParam("ParallaxMap").getTextureValue().setWrap(WrapMode.Repeat);
        mat.setFloat("Shininess", 0);
        // mat.setBoolean("VertexLighting", true);

        Box floor = new Box(100, 1f, 100);
        TangentBinormalGenerator.generate(floor);
        floor.scaleTextureCoordinates(new Vector2f(5, 5));
        Geometry floorGeom = new Geometry("Floor", floor);
        floorGeom.setMaterial(mat);
        floorGeom.setShadowMode(ShadowMode.Receive);// 地板只接受影子,不产生影子。
        rootNode.attachChild(floorGeom);
    }

    /**
     * 创建一个sign,我们可以看到它在阳光下的影子。
     */
    public void setupSignpost() {
        Spatial signpost = assetManager.loadModel("Models/Sign Post/Sign Post.mesh.xml");
        Material mat = assetManager.loadMaterial("Models/Sign Post/Sign Post.j3m");
        signpost.setMaterial(mat);
        
        signpost.rotate(0, FastMath.HALF_PI, 0);
        signpost.setLocalTranslation(0, 3.5f, 0);
        signpost.setLocalScale(4);
        signpost.setShadowMode(ShadowMode.CastAndReceive);
        TangentBinormalGenerator.generate(signpost);
        rootNode.attachChild(signpost);
    }

	@Override
	public void simpleUpdate(float tpf) {
		// 更新游戏时间
		gameDate.update();
		// 更新gui,显示当前时间
		gui.setText(String.format("%02d:%02d", gameDate.getHour(), gameDate.getMinute()));
		// 更新阳光亮度
		float power = gameDate.getLightPower();
		sunLight.setColor(ColorRGBA.White.clone().mult(power));
		// 更新光照角度
		sunLight.setDirection(gameDate.getSunDirection());
		
		
		sunBox.setLocalTranslation(gameDate.getSunDirection().mult(-100f));
	}

	public static void main(String[] args) {
		TestGameDate app = new TestGameDate();
		app.start();
	}

}
  • 大小: 9.8 MB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics