zoukankan      html  css  js  c++  java
  • CDDA 源码解析

    一.编译

    A.MinGW
    1:https://github.com/CleverRaven/Cataclysm-DDA 下载源码
    2:下载IDE CodeBlocks,http://pan.baidu.com/s/1qYNcKZ6,解压到随便哪个目录,再下载TDM-GCC-64,完整安装64位,

    然后设置CodeBlocks的编译器为TDM-GCC:

    3:下载 http://dev.narc.ro/cataclysm/cdda-win64-codeblocks.7z  里的WinDepend解压到CDDA的根目录,这些是依赖的静态库跟动态库

    4:下载LUA 5.1 For Win并安装(需要先装有VC++ 2005)
    5:在CDDA根目录下找到CataclysmWin.cbp打开工程,右键项目(Cataclysm)-> Properties -> Build targets ->
    双击要编译的类型(如Relase(Lua)),然后在Pre/post build steps标签下,将Pre-build steps里的lua5.1 改为 lua

    因为第6步安装好Lua,默认在系统中的环境变量名是lua而不是lua5.1,不然会找不到该命令。

    6:选择对应的编译类型,然后编译。

    7: 如果报错 ISSUE - "winapifamily.h" no such file or directoyr

    复制这里的内容覆盖掉MinGW/include/SDL2/SDL_platform.h的内容 https://hg.libsdl.org/SDL/raw-file/e217ed463f25/include/SDL_platform.h 

    8:编译好后,将exe文件以及data拷贝到同一目录下(如果有多语言,贴图以及LUA,还要拷贝对应的文件夹lang,gfx以及依赖的dll到运 行目录下)

     http://dev.narc.ro/cataclysm/cdda-win64-codeblocks.7z 这里有已经编译好的dll,下载直接拷贝到游戏根目录即可。


    *如果不需要LUA,6、7步骤可以省略

    B.VS 2015

    1: 下载安装VS 2015学习免费版

    2: 从 https://github.com/CleverRaven/Cataclysm-DDA 下载源码

    3: 下载VS专用版WinDepend,同样解压到CDDA根目录

    4:打开CDDA->msvc-full-features->Cataclysm.sln,启动VS工程

    5:开始编译项目,编译完后,运行WinDepend里的copy_dll_to_bin.bat提取出所需的dll,然后在WinDepend目录下会生成个bin文件夹,将里面对应平台的dll文件全部拷贝到CDDA根目录,否则直接运行CDDA根目录下的EXE文件会找不到连接库报错。

    6:如果要调试:编译完成后,将VS的DEBUG工作目录设置为CDDA根目录(因为默认工作目录是工程所在目录即msvc-full-features,但我们的EXE生成目录是在CDDA根目录,所以需要手动设置调试目录),右键目录->属性-〉调试,将$(ProjectDir)改为$(ProjectDir)..,两个..表示上一级目录的意思。


    二.LUA调用C++
    CDDA项目里支持LUA脚本调用C++代码,具体的做法是:

    调用函数
    1.在catalua.cpp里写一个你新建的函数,例如void game_test(int x, inty)
    2.在class_definitions.lua的global_functions下注册这个函数,

    global_functions = {
    [...]
    test = {
    cpp_name = "game_test",
    args = {"int", "int"},
    rval = nil
    },
    [...]
    }

    3.然后编译的时候,如果有选LUA,则会执行命令脚本,调用generate_bindings.lua将lass_definitions.lua注册的函数warp到catabindings.cpp文件里,生成一个gamelib栈用来存放global_test的函数,添加项{"test", global_test}

    4.catalua.cpp在初始化的时候,会初始化lua,将gamelib里的全局函数名注册到lua里一个叫'game'的table下面

    5.LUA脚本里调用test的时候,CDDA的LUA引擎会通过'game.test'这个函数名在catabindings.cpp的gamelib寻找与之对应的c++ warp函数(global_test),然后执行global_test,而global_test里又去调用最原先在catalua.cpp里创建的game_test达到LUA调用C++的目的

    game.test();

    调用类
    1.先在c++文件里创建一个类,例如myClass
    2.在class_definitions.lua的class里注册,注意各种名字必须与c++里的一一对应

    myClass = {
    // 构造函数
    new = {
    { "string" },
    { "int" },
    },
    // 变量
    attributes = {
    name = {type = "string", writable = true},
    },
    // 函数
    functions = {
    {name = "fuck", rval = nil, args = {"int", "string"}},
    },
    },

    3.编译的时候,会在catabindings.cpp生成warp方法,然后在LUA里调用的时候,再从catabindings.cpp里调用对应的函数,在调用到具体的类去

    三.C++调用LUA

    CDDA里C++可以调用在LUA里写的函数(说白了就是在LUA脚本里写on_xx类的回调函数注册监听某种事件,在C++里触发了某种条件后,C++再调用LUA脚本里注册的对应函数

    目前官方仅放出4个回调注册支持,分别是:

    分别是在新玩家创建完毕、一天过去了、一分钟过去了、技能升级时触发,以"on_day_passed"为例分析这套回调的过程:

    1.首先,在LUA脚本里的MOD table里注册"on_day_passed"回调函数

    mods["your mod name"] = MOD
    
    function MOD.on_day_passed()
        // dosome
    end

    2.在C++脚本里一天过去触发时的地方调用lua_callback来调用lua脚本里的这个回调

    四.CDDA MOD模块执行过程

    1.一个MOD的基本属性

    2.初始化过程

    main循环:在主菜单选完角色 -> 按开始游戏 -> 加载角色表

    -> 调用game::setup()进行一些游戏的设置

      -> 读取核心数据game:load_core_data

        -> 初始化LUA

          -> 注册gamelib和global_funcs到lua里的game table下,作为Lua里的全局函数

          -> lua_dofile执行CDDA根目录下的autoexeclua等函数,用于初始化lua数据

        -> load_data_from_dir 执行 data/core 目录下的核心mod

      -> load_world_modfiles 读取当前世界所设定的mod文件

        -> load_packs 遍历读取mods文件夹下的所有mod

          -> load_data_from_dir  一个MOD的完整读取过程

            -> 检查mod目录下是否存preload.lua,若存在则luadofile执行它

            -> 获取并加载mod目录下的json文件

            -> 检查mod目录下是否存main.lua,若存在则luadofile执行它

        -> load_data_from_dir 执行save目录下当前世界的存档文件夹(save/mods)里的自定义mod(即世界创建完后,我们还可以动态地在存档文件夹里添加mod,但只能由一个mod)

          -> 重复以上mod读取过程

    3.MOD中的LUA脚本部分

    这方面其实就是二、三里提到的LUA与C++交互的部分了

    4.MOD中的JSON数据部分

    五.主菜单界面的循环

    menu.openging_screen的主循环在选择角色后跳到new_character_tab或者load_character_tab里,等到下一步操作。

    六.游戏内战斗初始化过程

    main_menu里的new_character_tab或load_character_tab在监听到选择完角色并开始游戏后:

    -> world_generator->set_active_world( world );  设置当前世界为所选的世界

    -> game->setup();    游戏初始化设置

      -> load and init mod

        -> DynamicDataLoader::unload_data(); 将init里的finalized置为false,然后卸载重置所有动态读取的json数据

        -> load_core_data后再load_world_modfiles,加载所有mod并读取运行lua脚本,加载json数据

        -> load_world_modfiles完后调用DynamicDataLoader::finalize_loaded_data(); 将init里的finalized置为true,并调用所有json对象的类的finalize()

      -> 初始化各种其他的属性,比如天气,怪物之类的

    -> game->load();  读取存档,主要是将上一步初始化的那些数据(比如天气,玩家)进行赋值存档数据

    -> 初始化完毕,跳到战斗内循环

    七.游戏内战斗主循环过程

    main的主while里的g->do_turn便是游戏的主逻辑循环了

    -> g::do_turn()  一回合跑一次

      -> calendar::turn.increment()  游戏时间系统,让游戏过去一回合,同时更新游戏内时间

      -> if (calendar::turn.seconds() == xx) lua_callback("on_xx_passed"); LUA的各种时间类的回调便是在这里

      -> u.update_body() && update_weather(); 各种状态的更新

      -> handle_action();  游戏最重要的一部分,所有操作处理集中在这里处理,包括玩家的各种按键输入

        -> game::get_player_input()

          -> while( handle_mouseview(ctxt, action) )   这里阻塞循环,等待玩家操作,如果玩家没有任何操作,那么一直卡这里面

            -> if( action == "TIMEOUT" ) break; 如果游戏设置为实时模式,那么就算玩家不进行任何操作,到了设定的时间后,也会强制跳出循环进行下一回合

            -> draw_weather(wPrint); && draw_pixel_minimap(); 游戏实时更新不受回合影响的内容放这执行,比如播放天气动画

        -> case ACTION_XX: xx();   上一步捕获按键输入后,这一步判断要执行什么动作(比如是移动还是使用物品)

      *注: 游戏的主循环并不是每帧都运行,因为这是回合制游戏,只有在上一步的handle玩家执行了操作后,才会继续新一轮循环,否则是阻塞在那等待的,除非设置为即时模式,那么每次倒计时完都会强制下一回合

    八.游戏的时间系统

    重要概念:

    1.回合:每次g::do_turn()都算为一回合,一回合消耗游戏时间6秒,按下"."游戏便会调用player:pause()强制过去一回合,如果是实时模式,那么现实时间每隔一段时间(设定的实时频率值,比如0.5秒)就会强制调用player:pause(),可以理解为游戏在固定的时间后自动帮你按一下"."。

    2.游戏时间:游戏内部有一套"日历"时间系统,用于记录游戏内部的时间流逝,与现实时间不同,只有每经过一回合,时间才会向前流动,游戏的时间,比如时、分、秒,都是根据回合来算的,秒 = (回合数 * 6) % 60

    3.现实时间:现实系统时间

    *注:游戏虽然属于回合制,但与传统的回合制不同,不是你打一回合,我打一回合,其实回合这个概念在游戏中也可以忽略,这个游戏应该算“半即时”制,游戏中应该只算时间概念,即所有的操作都只与时间有关,比如我挥刀10秒,敌人挥刀耗时5秒,那么我砍一次需要差不多2回合,而对方只需要1回合,当然,回合的概念是只存在于代码内部,不会在游戏里表现出来,所以你不会看到“我挥刀,敌人挥刀,等一回合,敌人打中你,再等一回合你才打中敌人”的现象。因为游戏是以你为准心,所以你一挥刀,你立刻就砍中了敌人,但此时游戏里面g::do_turn()跑了两遍,已经悄悄过去两回合了,敌人已经砍了你两刀了。

    九.物品相关

    1.物品初始化流程

    game:setup
      -> load_core_data
        ...
      -> load_world
        -> load_mods
          -> load_all_mod
            -> load_frome_file -> load_from_json -> load_object()    从json文件读取数据
              -> load_comestible -> load_basic_info    读食谱、合成表啥的
                -> set_use_methods_from_json    从json里获取物品的使用Action方法
                  -> actor:load      从json文件初始化action的其他属性,例如transform的msg,target等就是在这个时候读表的
          -> load_map_mod
            ...
          -> init:finalize_load
            -> item_factory:finalize
              -> all use_methods:finalize

    每种JSON文件都有对应的读取函数
    程序的开头会调用这个来初始化读取函数
    init.DynamicDataLoader:initialize
      -> add("skill", &Skill::load_skill)
        -> type_function_map.add
      -> add("item_action", &item_action)
        ...
    然后在 load_from_json -> load_object时,会从type_function_map里寻找当前该json的type对应的加载函数

    2.物品使用流程

    game_turn

      -> use_action -> game:use_item   玩家使用物品触发Action
        -> player:use(item_index)
          -> set item as last_use_item
          -> switch item type      根据物品类型决定使用方式
            tool  -> invoke_item
                -> item:use_fun_call  如果是item,则在item的方法表里找到对应的物品的调用函数并执行
                  -> iuse_funname()
                -> consume_charges  如果物品是会消耗能量的(比如手电筒),则进行能力是耗损计算
            food -> consume
            book -> red

    关于物品的使用,比如头灯,使用完后变成头灯(开),CDDA并没有用什么来控制物品状态的变化,也没有记录物品的状态属性,而是用了一个小技巧,

    用两个物品分别来表示物品的“开/关”状态,比如“头灯”和“头灯(开)”,然后在他们两者USE时执行一个Action,这个Action用来将转换他们。

    "use_action": {
      "type": "transform",  动作类型为转换,即表示该物品在“使用”后会转变为另一个物品
      "msg": "You turn the head torch on.",  使用时会提示的信息
      "target": "wearable_light_on",
      "active": true,
      "need_charges": 1,
      "need_charges_msg": "The head torch batteries are dead."  能量不足时提示的信息
    }
    “头灯”在使用后,会触发transform转换函数,将他变为“头灯(开)”

    3.增加并注册物品使用函数

    三种注册物品使用函数的方法(最终结果都是注册到iuse_function_list里)
    注册的地方在:Item_factory::init()
    1.添加类
      -> add_actor
        -> iuse_function_list:add (new xx_actor 继承 actor)
    2.直接在C++里写静态函数,然后给个名字丢到list里
      -> add_iuse
        -> iuse_function_list:add ("xx", &iuse:xx -> iuse_function_wrapper 继承 actor)

    3.LUA注册,在LUA脚本里调用C++的game_register_iuse,然后在C++里再register_iuse_lua添加
    ..lua.dofile()
      -> game_register_iuse
        -> item_controller:register_iuse_lua
          -> iuse_function_list:add (new lua_iuse_wrapper 继承 actor)

    // 调用
    iuse_function_list[name] -> use_function (iuse_actor)
    {
    or heal_actor // 1.
    ...
    or iuse_function_wrapper // 2.
    or lua_iuse_wrapper // 3.
    }
    list[name].call -> use_fun.call -> actor.use

    *注1:创建新的item_action,必须在 item_actions.json里注册这个action,否则会读取不到。

    在初始化的时候,load_from_json -> load_object时会读到item_action.json,然后用它来初始化item_actions列表,

    比如打火机的打火动作的描述:

    {
      "type" : "item_action",
      "id" : "firestarter",
      "name" : "Start a fire quickly"
    },

    然后在游戏中就会在物品描述中看到使用这个物品的使用说明:"Start a fire quickly"


    *注2:item_actions.json文件有一个全局的,但MOD没必要修改这个文件,创建一个同名文件丢到
    MOD目录下即可,游戏会自动把MOD目录下的此文件识别并加载,并附加到全局的列表里

    4.替换原有物品

    data里的默认物品可以替换,只要在MOD文件夹里创建同名json对象,就会自动替换,因为MOD比核心基础data后加载
    eg:
    {
      "id": "survivor_light",
      "name": "survior light"
    }
    在自己的MOD里也创建一个
    {
      "id": "survivor_light",
      "name": "幸存者头灯"
    }
    那么就会将原来的"survior light"替换为"幸存者头灯"

    5.物品组

    {
      "type": "item_group",
      "id": "guns_pistol_common",
      "//": "Pistols commonly owned by citizens and found in many locations.",
      "items": [
        [ "glock_19", 85 ],
        [ "glock_22", 35 ]
      ]
    }
    将现有的一组物品注册为一个物品组,供地图上显示物品用,比如指定地图上某个点掉落物品组中的某个物品
    后面的数字表示该物品出现的概率,比如"glock_19"出现的概率是 85/(85+35) 

    十.地图

    1.地形定义放在terrain.json
    {
      "type" : "terrain",
      "id" : "t_brick_wall_line",
      "name": "brick wall"
    }

    2.家具定义放在furniture.json
    {
      "type" : "furniture",
      "id" : "f_file_cabinet",
      "name": "filing cabinet"
    }

    3.房间定义
    {
      "type": "mapgen",
      "om_terrain": "combogarageA_first",
      "weight": 0,
      "method": "json",   可通过json数据定义房间,或者通过lua脚本动态生成房间,这里是通过json来描述房间内的布局
      "object": {
        "fill_ter": "t_floor",  表示房间里没有定义砖块属性的地方,默认由什么terrain来填充
        "rows": [
            ...      一个矩阵,用来表示房间的形成,符号的意义参加以下地形、家具等的符号描述

          ".|-----+--+-| | |.",
          ".| | --- |.",
          ".|d | |.",
          ".|d + |.",
          ".| | | h |.",

            ...
        ],

        "terrain": {        地形的描述,比如"-"就表示rows中的该符号位置位置表示是墙
          "#": "t_shrub",
          "+": "t_door_c",
          "-": "t_wall",
          ".": "t_grass",

        },

        "furniture": {      家具的描述,比如"."就表示rows中该符号位置有个沙发,当然,该符号也是上诉地形中草地的符号,所以最终结果表示在草地上有个沙发
          "0": "f_fireplace",
          ".": "f_sofa",

        },

        "toilets": {    厕所,rows中的"t"表示该位置有厕所,因为厕所比较特殊(比如厕所里可以有东西),所以当独放一块
          "t": {}
        },


        "place_items": [
          { "item": "cannedfood", "x": [ 6, 20 ], "y": 5, "chance": 10 },   地图上掉落的物品,名字是物品组的名字,[6, 20]指x轴6到20中随机一个,chance表示概率
          { "item": "guns_pistol_common", "x": 8, "y": 4, "chance": 100 }
        ],

        "place_monsters": [
          { "monster": "GROUP_ZOMBIE", "x": [ 2, 21 ], "y": [ 2, 21 ], "chance": 2 }  地图上刷僵尸,名字是僵尸组的名字
        ],

        "lua": "game.add_msg("这是你第一次来到这间屋子")"   第一次进入此房间就会触发的LUA脚本,与上面的method:lua用来生成房间的Lua脚本不同
      }
    }
    这里会引用1、2里定义的terrain和furniture内容来拼凑房间
    *注2:可以多个房间使用同一个om_terrain,那么在生成房间时,会随机使用同一om_terrain中的某一个
    其中生成某个房间的概率,是看该房间的权重(weight)来决定的(默认是1000,500是1/3的概率)

    4.以上步骤只是定义了房间,还需要注册房间,才可以在游戏里被引用到
    {
      "type" : "overmap_terrain",
      "id" : "combogarageA_first",
      "name" : "房间在游戏里显示的名字",
      "rotate" : true,  如果rotate为false,则在地图中该房间默认朝北,并且不能旋转
      "sym" : 94,  貌似是该房间在大地图上显示的符号的ASCII码符号?
    }

    5.注册房子(房间的组合),这里注册的房子将在游戏地图里随机刷出,这才是正在的房子注册
    可以通过第4里注册的房间来拼凑成一个房子,[x,y,z]表示房间出现的位置。
    {
      "type" : "overmap_special",
      "id" : "combohouseA",
      "overmaps" : [
        { "point":[0,0,0], "overmap": "combohouseA_first_north"},

        { "point":[1,0,0], "overmap": "combohouseA_second_north"},

      ...

    }
    房间名字最后面的"_north"表示该房间相对该房子的朝向,如果房间的rotate为false,则这里不能加上方向修饰

    以上的json定义表示,注册这样一个房子:房子id为combohouseA,由两个房间组成,其中在[0,0,0]处有一个朝向北的combohouseA_first,

    在它的右边,也就是[1,0,0]处有一个朝向北的combohouseA_second房间

    更多的属性描述参见“MAPGEN.md”

    十一.Effect与Flag

  • 相关阅读:
    Win7升Windows10有获取通知,但是就不推送的解决方法
    使用git@osc管理现有项目
    暗黑符文之语1.10
    springcloud干活之服务消费者(feign)
    springcloud干货之服务消费者(ribbon)
    springcloud干货之服务注册与发现(Eureka)
    java对redis的基本操作
    微信公众号开发模式中文乱码
    Java 验证代理ip
    maven将项目及第三方jar打成一个jar包
  • 原文地址:https://www.cnblogs.com/jeason1997/p/6254020.html
Copyright © 2011-2022 走看看