Lua游戏开发教程之时区问题详解
通过Lua的os.date("!*t")获取UTC时间,结合服务器时区差值,可将游戏内时间统一显示为服务器所在地时间。计算活动倒计时时,需考虑本地时区与服务器时区的差异,转换时间戳后得到准确剩余时间。正确处理时区问题能解决多语种发行中的显示同步难题。
前言
什么是Lua?
Lua 是一个小巧的脚本语言,1993年由巴西里约热内卢天主教大学的一个研究小组开发,初衷很简单——嵌入应用程序,为应用提供灵活的扩展和定制功能。它用标准C编写,几乎能在所有操作系统和平台上编译运行。一个完整的Lua解释器不过200k,在目前所有脚本引擎中,Lua的速度是最快的。这决定了它作为嵌入式脚本的最佳选择。对比一下:Python的内核大约860KB,Perl的内核大约1.1MB,而Lua的内核小于120KB。Lua支持面向对象编程和函数式编程,它提供的通用类型表(table)可以同时实现数组、哈希表、集合、对象的功能。它还支持协同进程机制。作为一门可扩展的语言,Lua提供了简单而稳定的交互接口——比如Lua和C程序可通过一个堆栈交换数据,这让它能够快速与其他语言整合。
归纳起来,Lua语言具备三个明显优点:
- 语言优美、轻巧
- 性能优良、速度快
- 可扩展性强
这些特性恰好和游戏开发领域的需求完美契合。我们需要一门能跟C/C++无缝交互的语言,用来封装底层;它还必须足够简单,让我们能快速、灵活地编写业务逻辑。显然,Lua就是一直在找的那种语言。
目前大部分游戏都在用Lua进行功能开发,而在多语种发行时,时区显示的问题就会浮出水面。拿韩国版本举例:
- 服务器固定放在首尔机房;
- 玩家的位置不确定,可能在韩国,也可能出差在其他国家或地区。
需求:
无论玩家身处哪个国家或地区,游戏内统一显示服务器的当前时间。也就是说,在PC上查看时,即便在国内测试,显示的也是韩国首尔的时间(比北京时间快1小时)。
实现:
-- 北京时间
local serverTime = 1536722753 -- 2018/09/12 11:25
function getTimeZone()
local now = os.time()
return os.difftime(now, os.time(os.date("!*t", now)))
end
-- 8 hour * 3600 seconds = 28800 seconds
local timeZone = getTimeZone()/ 3600
print("timeZone : " .. timeZone)
local timeInterval = os.time(os.date("!*t", serverTime)) + timeZone * 3600 + (os.date("*t", time).isdst and -1 or 0) * 3600
local timeTable = os.date("*t", timeInterval)
--[[
for k, v in pairs(timeTable) do
print(k .. ":" .. tostring(v))
end
]]
print(timeTable.year .. "/" .. timeTable.month .. "/" .. timeTable.day .. " " .. timeTable.hour .. ":" .. timeTable.min .. ":" .. timeTable.sec)
关键在这个方法:os.date("!*t", now),其中以“!”为关键。
看Lua源码,loslib.c Line 283:
static int os_date (lua_State *L) {
size_t slen;
const char *s = luaL_optlstring(L, 1, "%c", &slen);
time_t t = luaL_opt(L, l_checktime, 2, time(NULL));
const char *se = s + slen; /* 's' end */
struct tm tmr, *stm;
if (*s == '!') { /* UTC? */
stm = l_gmtime(&t, &tmr);
s++; /* skip '!' */
}
else
stm = l_localtime(&t, &tmr);
if (stm == NULL) /* invalid date? */
luaL_error(L, "time result cannot be represented in this installation");
if (strcmp(s, "*t") == 0) {
lua_createtable(L, 0, 9); /* 9 = number of fields */
setallfields(L, stm);
}
else {
char cc[4]; /* buffer for individual conversion specifiers */
luaL_Buffer b;
cc[0] = '%';
luaL_buffinit(L, &b);
while (s < se) {
if (*s != '%') /* not a conversion specifier? */
luaL_addchar(&b, *s++);
else {
size_t reslen;
char *buff = luaL_prepbuffsize(&b, SIZETIMEFMT);
s++; /* skip '%' */
s = checkoption(L, s, se - s, cc + 1); /* copy specifier to 'cc' */
reslen = strftime(buff, SIZETIMEFMT, cc, stm);
luaL_addsize(&b, reslen);
}
}
luaL_pushresult(&b);
}
return 1;
}
从源码可以看到,“!”调用了:
#define l_gmtime(t,r) gmtime_r(t,r)
gmtime_r 是标准的POSIX函数,线程安全,它将日历时间转换为UTC时间表示的时间。
注:UTC —— 协调世界时,又称世界统一时间、世界标准时间。
也就是说,“!*t”得到的是一个UTC时间,对应0度经线(本初子午线)。通常将UTC与GMT视作等同(但UTC更科学精确)。


首尔位于东9区,实际时间就是UTC+9,9就是时区差(9个小时)。北京位于东8区,即UTC+8。
如何保证游戏内全部统一为服务器的时间呢?
服务器需要返回给客户端当前的时区差值。比如韩国返回9,国内返回8,越南返回7,北美返回-16,记为serverTimeZone。
服务端返回当前服务器时间serverTime(即首尔当前时间),我们只需要将服务器时间转为UTC时间,然后再加上serverTimeZone即可:
os.time(os.date("!*t", serverTime)) + serverTimeZone * 3600
这样,无论在哪个地区或国家,显示的都是首尔时间,与服务器上的时间完全同步。
为什么要一直显示服务器的时间呢?
游戏中有很多功能有时间限制,比如运营活动或功能开启。如果用本地时间,很难统一控制。统一用服务器时间,就能避免很多麻烦。
不过这里容易踩一个坑。运营配置的活动时间都是针对当前服务器时间的,例如某个活动的截止时间是2018-10-08 00:00:00,游戏需要显示活动截止倒计时。
通常的做法是:ployEndTime – serverTime,得到一个秒数,再转成“xx天xx小时xx分xx秒”。
serverTime是固定的,但ployEndTime容易出错。为什么?
serverTime在东9区(首尔),而os.time({year=…})是根据本地时间计算的,这中间有时差问题。之前计算一直用serverTimeZone——一个固定值,但当前所在地区或国家相对于UTC的时区是不确定的。怎么办?
用(currTimeZone – serverTimeZone) * 3600 秒,os.time()之后再加上这个时区差,就是首尔当前的时间戳了。国内东8-东9=-1,也就是要减去1个时区,最终得到首尔地区的时间戳,再减去serverTime就是剩下的秒数,然后转成“xx天xx小时xx分xx秒”。
最后小结一下:
os.time({year=xx})算出来的是针对当前所处时区的那个时间戳。os.date("!*t", 时间戳)得到的是UTC(时区为0)的时间戳。- 获取当前时区的值,可以通过文章开头的
getTimeZone方法。 - 想显示固定时区的时间(例如无论在哪都显示服务器的时间),只需要将服务器时间戳(秒),通过第2步的方法得到UTC,再加上固定的时区差。
- 计算倒计时时,需要考虑到
os.time取的是当前时区,需要再将当前时区减去目标时区,再计算时间戳。 - 夏令时本身已经拨快了一个小时,当需要显示为固定时区的时间时,需要减去一个小时。
总结
以上就是在游戏开发中用Lua处理时区显示问题的完整思路和实现。从服务器统一时间到倒计时计算,关键在于理解os.date的“!”前缀和时区差的转换。掌握这些细节,多语种发行时的时区问题就不再是拦路虎了。
Windows 10 是一款微软推出的经典操作系统,拥有硬件兼容性与多任务处理能力。它更偏向把系统状态查看和常用调节动作放在一起,适合需要持续观察和微调设备状态的场景。
极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。
















