当前位置:

首页 > 编程开发 > Lua游戏开发教程之时区问题详解

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秒”。

    最后小结一下:

    1. os.time({year=xx})算出来的是针对当前所处时区的那个时间戳。
    2. os.date("!*t", 时间戳)得到的是UTC(时区为0)的时间戳。
    3. 获取当前时区的值,可以通过文章开头的getTimeZone方法。
    4. 想显示固定时区的时间(例如无论在哪都显示服务器的时间),只需要将服务器时间戳(秒),通过第2步的方法得到UTC,再加上固定的时区差。
    5. 计算倒计时时,需要考虑到os.time取的是当前时区,需要再将当前时区减去目标时区,再计算时间戳。
    6. 夏令时本身已经拨快了一个小时,当需要显示为固定时区的时间时,需要减去一个小时。

    总结

    以上就是在游戏开发中用Lua处理时区显示问题的完整思路和实现。从服务器统一时间到倒计时计算,关键在于理解os.date的“!”前缀和时区差的转换。掌握这些细节,多语种发行时的时区问题就不再是拦路虎了。

    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
    作者最新文章
    编程开发
    相关文章 更多
    编程语言的编程模型
    编程语言的编程模型

    编程模型涵盖面向过程的C语言、面向对象的Ruby、基于栈的PostScript、基于规则的Prolog、基于原型的Lua、函数式的Erlang/Haskell及混合模型的Scala/Clojure。类型系统区分强类型与弱类型、动态类型与静态类型,元编程允许运行时操作代码,提升表达力。

    lua 如何获取指定路径下的所有文件夹
    lua 如何获取指定路径下的所有文件夹

    Lua编程语言轻量级且小巧,可用io.popen执行系统命令获取指定路径下所有文件夹:在Windows系统下使用dir/b/s命令,在Unix系统下使用find-typef命令。然而官方更推荐使用lfs库进行递归遍历,通过lfs.dir和lfs.attributes函数筛选目录,此法避免系统命令依赖,代码简洁且跨平台效果更佳。

    lua调用C/C++的方法详解
    lua调用C/C++的方法详解

    Lua通过虚拟栈与C/C++交互,将C函数封装为接口供其调用。调用时,C从栈中获取参数并返回结果,参数传递与结果返回均通过栈操作完成,从而实现了Lua对C能力的高效、便捷调用,使得Lua能无缝利用C语言的高性能库。

    详解Lua中的元表和元方法
    详解Lua中的元表和元方法

    一、元表 元表是Lua中一个挺有意思的机制——它允许我们在一个值遇到未知操作时,给出自定义的行为。本质上,元表也是用table来承载的,所以熟悉了table,也就掌握了元表的基本用法。 不过得注意,元表只能针对一组预先定义好的操作集合做扩展,比面向对象中的类要受限得多。最典型的一点:元表不支持继承。

    如何使用Vim搭建Lua开发环境详解
    如何使用Vim搭建Lua开发环境详解

    基于SpaceVim搭建Lua开发环境,需先安装Neovim、Nerd字体及支持真色的终端,启用lang#lua模块后,即可获得全面的代码补全、语法检查、工程文件跳转、一键运行、代码格式化及交互式编程等高效开发功能,大幅提升编程效率与体验。

    Luvit像Node.js一样写Lua应用
    Luvit像Node.js一样写Lua应用

    先问个问题:如果你是一个Lua开发者,手头刚好有异步I/O的需求,你会怎么办?传统方案当然有,但要说最接近Node.js体验的,还得是Luvit。简单来说,它就是Lua界的Node.js——把Lua和libUV撮合在一起,再加上JIT加持,用项目自己的话说:Lua + libUV + JIT = p

    Lua流程控制语句if else的使用示例
    Lua流程控制语句if else的使用示例

    Lua的流程控制,if和if else是基础。和C语言很像,if语句支持嵌套,也能和循环配合——比如在循环体内根据条件决定是否跳出,这在日常开发中非常实用。 1、if语句 先看最基本的if语法结构: if(布尔表达式) then --[ 在布尔表达式为 true 时执行的语句 --] end 来看一

    Lua中pairs与ipairs的区别总结
    Lua中pairs与ipairs的区别总结

    最近在写一个流量转发的东西,用到了 nginx_lua_module 这个模块,需要根据 Header、Body 和 Cookie,按照流量比例把请求转发到其他地方。翻看前人留下的代码,发现循环遍历时,有时候用 pairs,有时候用 ipairs,风格并不统一,读起来让人有点困惑。 好在 Lua 官

    深入谈谈lua中神奇的table
    深入谈谈lua中神奇的table

    Lua中的table可作为字典、数组、环境、模块、对象和类使用。字典支持任意非nil键类型,遍历无序;数组索引从1开始,#对空洞处理需谨慎。table掌管全局环境_G和局部环境_ENV,模块本质为table。对象通过闭包实现私有成员,冒号语法糖简化self传递;类借助元表__index实现继承。

    利用Lua定制Redis命令的方法详解
    利用Lua定制Redis命令的方法详解

    Redis单线程但多客户端无同步导致数据冲突,网络往返也制约效率。通过内嵌Lua脚本可将多个命令复合执行,实现原子操作并减少请求次数。需注意脚本中避免全局变量、控制耗时,且报错时已执行命令无法回滚。

    查看更多
    精品专题 更多
    装机必备
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows
    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    PDF教程
    PDF教程

    正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

    Mac软件 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    Aperty macOS版
    Aperty macOS版
    macOS

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    WINDOWS 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    PixTV 网页版
    PixTV 网页版
    Windows

    PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。