UI를 만들다 보면 시간과 관련된 개발을 할 때가 꽤 많습니다.


단순히 시계 표시도 있고, 카운터도 많죠... 시간에 관한 프로그레스 바도 있고 암튼 게임 장르를 가리지 않고 굉장히 많습니다.



간단하게는 enterframe 로 만들겠지만 이 방식도 문제가 있습니다. 시스템이 버벅거리면 오차가 발생하기 때문입니다


저같은 경우는 다음과 같은 라이브러리를 만들어서 사용합니다


https://github.com/minarto/scaleform-as2-libraries/blob/master/src/com/minarto/data/DBind.as


예를 든건 as2버전이지만, as3 버전도 이론적으로 똑같습니다. (라이브러리에 올라가있는 as3 버전은 옛날 코드이니 문제가 발생할 수 있습니다)



스케일폼에서는 시간을 어쨌든 클라이언트(서버)로 부터 받아야 합니다. 로컬의 시간은 유저가 언제든지 변경 가능하기 때문입니다.





그렇다고 해서 서버로부터 매초... 아니 매 밀리초마다 시간을 갱신할 수도 없습니다. 그러기에는 서버부하, 대역폭, 인보크 타임 증가 등 너무 많은 손실이 생깁니다.



저 코드는 클라이언트가(정확히는 스케일폼이) 동작해서 경과된 시간을 기반으로 합니다. 그래서 서버 시간과의 오차를 가지고선 계속 가감을 해주는 방식입니다.


서버에서 자주 최신 시간(?)을 받아주면 오차는 더 줄겠지만, 뭐 어쨌든 그건 클라이언트나 서버가 하기 나름입니다.


코드 일부만 잠시 살펴보겠습니다.



	import com.minarto.data.*;

class com.minarto.data.DBind extends Bind
{
	static private function getOffsetTime():Number
	{
		var bind:Bind = BindDic.get("main"), offsetTime:Number, date:Date;
		
		var onTime = function($year:Number, $month:Number, $date:Number, $hour:Number, $min:Number, $sec:Number, $millisec:Number)
		{
			var date:Date = new Date($year, $month, $date, $hour, $min, $sec || 0, $millisec || 0);

			offsetTime = date.getTime() - getTimer();
		}
		
		bind.add("serverDate", null, onTime);
		
		date = new Date;
		bind.evt("serverDate", date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds());
				
		getOffsetTime = function()
		{
			return	offsetTime;
		}
		
		return	getOffsetTime();
	}	

	/**
	 * get current date
	 * @return
	 */
	static public function getDate():Date
	{
		return	new Date(getOffsetTime() + getTimer());
	}	
	
	public function toString():String
	{
		return	"[com.minarto.data.DBind interval:" + getInterval() + "sec]"
	}
	
	
	private var interval:Number = 100, intervalID:Number;	
	
	/**
	 * set interval
	 * @param	$interval
	 */
	public function init($interval:Number):Void
	{
		interval = ($interval || 0.1) * 1000;
		
		if(intervalID)
		{
			this.play();
		}
	}	
	
	private function intervalFunc():Void
	{
		var key:String, date:Date = getDate();
		
		for (key in handlerDic)
		{
			evt(key, date);
		}
	}
	
	
	/**
	 * play
	 */		
	public function play():Void
	{
		clearInterval(intervalID);
		intervalID = setInterval(this, "intervalFunc", interval);
	}	
	
	/**
	 * stop
	 */		
	public function stop():Void
	{
		clearInterval(intervalID);
		intervalID = NaN;
	}	
	
	/**
	 * is playing
	 */		
	public function getIsPlaying():Boolean
	{
		return	Boolean(intervalID);
	}	

	/**
	 * get interval
	 */
	public function getInterval():Number
	{
		return	interval * 0.001;
	}
}


이 코드를 이해하시려면 Bind 코드도 알아두시는게 좋지만, 그렇지 않아도 흐름은 보실 수 있을 겁니다


클라(서버)로 부터 현재 시간을 받죠. "serverDate" 라는 값으로 받아서 현재까지의 경과시간과의 오차를 구합니다. 그게 바로 offsetTime 입니다.


그걸 구했다면 이 클래스에 설정된 시간(interval값)마다 갱신하면서 그 오차값을 더해서 현재의 시간을 구해주는 코드입니다.



클라는 서버시간 갱신을 1분에 한번씩 해도 될 것이고, 1시간에 한번씩 해도 되겠죠... 마지막 오차값을 사용해서 시간을 구할 뿐입니다.


이 방식을 사용하면 1ms 마다 갱신해야할 UI 요소가 있다고 하더라도 클라나 서버와의 시간 갱신은 필요가 없습니다.

(물론 오차가 두렵다면 서버시간 갱신을 자주 해주면 되는 겁니다)





Posted by 미나토

댓글을 달아 주세요