├── .gitattributes ├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── common ├── BOD.lua ├── BaseClass.lua ├── MathUtil.lua ├── TableUtil.lua ├── helper.lua ├── print_r.lua ├── sproto.lua ├── sprotoloader.lua └── util.lua ├── config ├── data ├── UnityMMOAccount.sql └── UnityMMOGame.sql ├── game ├── account │ └── Account.lua ├── bag │ ├── BagConst.lua │ └── BagMgr.lua ├── chat │ ├── ChatConst.lua │ └── ChatMgr.lua ├── config │ ├── ConfigConversation.lua │ ├── ConfigDungeon.lua │ ├── ConfigGoods.lua │ ├── ConfigMonster.lua │ ├── ConfigNPC.lua │ ├── ConfigTask.lua │ ├── ErrorCode.lua │ └── scene │ │ ├── config_drop.lua │ │ ├── config_monster.lua │ │ ├── config_npc.lua │ │ ├── config_scene_1001.lua │ │ ├── config_scene_2001.lua │ │ └── config_skill.lua ├── gm │ ├── GM.lua │ └── GMConst.lua ├── proto │ ├── proto_100_scene.lua │ ├── proto_1_account.lua │ ├── proto_200_task.lua │ ├── proto_300_bag.lua │ ├── proto_400_gm.lua │ └── proto_500_chat.lua ├── scene │ ├── ECSSystemMgr.lua │ ├── EventMgr.lua │ ├── FightHelper.lua │ ├── FightMgr.lua │ ├── Global.lua │ ├── MonsterConst.lua │ ├── MonsterMgr.lua │ ├── NPCMgr.lua │ ├── RoleMgr.lua │ ├── SceneConst.lua │ ├── SceneHelper.lua │ ├── SceneMgr.lua │ ├── Time.lua │ ├── ai │ │ ├── BlueprintRegister.lua │ │ ├── MonsterFSM.lua │ │ └── state │ │ │ ├── DeadState.lua │ │ │ ├── FightState.lua │ │ │ └── PatrolState.lua │ ├── aoi.lua │ ├── aoi_test.lua │ ├── com │ │ ├── Components.lua │ │ └── SpeedData.lua │ ├── fight │ │ ├── Ability.lua │ │ ├── Attr.lua │ │ ├── Buff.lua │ │ ├── BuffActions.lua │ │ ├── BuffSys.lua │ │ ├── CheckAttr.lua │ │ ├── CheckHP.lua │ │ ├── ClearBuff.lua │ │ ├── HP.lua │ │ ├── HasBuff.lua │ │ ├── Hurt.lua │ │ ├── NotifyBuff.lua │ │ ├── PickTarget.lua │ │ ├── SkillActions.lua │ │ ├── Speed.lua │ │ └── SuckHP.lua │ └── system │ │ ├── AISystem.lua │ │ ├── ActionSys.lua │ │ ├── DamageSystem.lua │ │ ├── MovementUpdateSystem.lua │ │ └── SkillSys.lua ├── service │ ├── chat.lua │ ├── id_service.lua │ ├── scene.lua │ └── world.lua ├── task │ ├── Task.lua │ └── TaskConst.lua └── util │ ├── Dispatcher.lua │ ├── TablePool.lua │ └── Vector3.lua ├── lualib ├── Action │ ├── Action.lua │ ├── Common │ │ ├── Importer.lua │ │ ├── LuaOO.lua │ │ └── luaunit.lua │ ├── README.md │ ├── Src │ │ ├── ActionMgr.lua │ │ ├── And.lua │ │ ├── CallFunc.lua │ │ ├── Delay.lua │ │ ├── If.lua │ │ ├── Or.lua │ │ ├── Random.lua │ │ ├── Repeat.lua │ │ └── Sequence.lua │ └── Tests │ │ ├── Test.lua │ │ ├── TestActions.lua │ │ └── TestLuaOO.lua ├── Blueprint │ ├── BT │ │ └── BTNode.lua │ ├── Blueprint.lua │ ├── Core │ │ ├── BaseClass.lua │ │ ├── Blackboard.lua │ │ ├── Graph.lua │ │ ├── GraphsOwner.lua │ │ ├── Importer.lua │ │ ├── Node.lua │ │ ├── Time.lua │ │ └── TypeManager.lua │ ├── FSM │ │ ├── FSMGraph.lua │ │ └── FSMState.lua │ ├── Flow │ │ ├── Control │ │ │ ├── Branch.lua │ │ │ ├── Delay.lua │ │ │ ├── DoN.lua │ │ │ ├── DoOnce.lua │ │ │ ├── FlipFlop.lua │ │ │ ├── FlowControll.lua │ │ │ ├── ForLoop.lua │ │ │ ├── ForLoopWithBreak.lua │ │ │ ├── Gate.lua │ │ │ ├── MultiGate.lua │ │ │ ├── Sequence.lua │ │ │ └── WhileLoop.lua │ │ ├── Event │ │ │ └── UpdateEvent.lua │ │ └── Variables │ │ │ ├── GetVariable.lua │ │ │ └── Variables.lua │ ├── README.md │ └── Tests │ │ ├── FSMSampleState.lua │ │ ├── TestBlueprintGraph.lua │ │ ├── TestFSM.lua │ │ ├── TestGraphsOwner.lua │ │ ├── luaunit.lua │ │ └── test_blueprint_all.lua └── ECS │ ├── Common │ ├── BaseClass.lua │ ├── Importer.lua │ ├── SortingUtilities.lua │ ├── UnsafeLinkedListNode.lua │ └── luaunit.lua │ ├── ECS.lua │ ├── README.md │ ├── Src │ ├── ArchetypeManager.lua │ ├── Chunk.lua │ ├── ChunkDataUtility.lua │ ├── ComponentChunkIterator.lua │ ├── ComponentDataArray.lua │ ├── ComponentGroup.lua │ ├── ComponentSystem.lua │ ├── ComponentSystemInjection.lua │ ├── ComponentType.lua │ ├── ComponentTypeInArchetype.lua │ ├── Entity.lua │ ├── EntityArray.lua │ ├── EntityDataManager.lua │ ├── EntityGroupManager.lua │ ├── EntityManager.lua │ ├── InjectComponentGroupData.lua │ ├── ScriptBehaviourManager.lua │ ├── SharedComponentDataManager.lua │ ├── TypeManager.lua │ └── World.lua │ └── Tests │ ├── TestBaseClass.lua │ ├── TestComponentSystem.lua │ ├── TestEntityManager.lua │ ├── TestPerformance.lua │ └── test_all.lua ├── run.sh ├── service ├── dbserver.lua ├── gated.lua ├── logind.lua ├── main.lua ├── msgagent.lua └── protoloader.lua ├── skynet.log ├── skynet.pid └── stop.sh /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | log/ -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "skynet"] 2 | path = skynet 3 | url = https://github.com/cloudwu/skynet.git 4 | ignore = all 5 | [submodule "lsocket"] 6 | path = lsocket 7 | url = https://github.com/cloudwu/lsocket.git 8 | ignore = all -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 千空 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SkynetMMO 2 | a skynet implementation of MMO, server side of [UnityMMO](https://github.com/liuhaopen/UnityMMO "UnityMMO") 3 | 4 | # Usage 5 | 1. git clone https://github.com/liuhaopen/SkynetMMO.git --recurse 6 | 2. compile skynet : 7 | cd SkynetMMO/skynet 8 | make linux 9 | 3. import database, assume the password is 123456, if not, you need to change password in main.lua: 10 | mysql -uroot -p 11 | create database UnityMMOAccount; 12 | use UnityMMOAccount; 13 | source data/UnityMMOAccount.sql; 14 | create database UnityMMOGame; 15 | use UnityMMOGame; 16 | source data/UnityMMOGame.sql; 17 | 4. ./run.sh -------------------------------------------------------------------------------- /common/BOD.lua: -------------------------------------------------------------------------------- 1 | local BOD = BaseClass() 2 | 3 | BOD.Type = { 4 | Veto = 1,--一票否决权,即只要有个 false 就结果为 false 5 | Half = 2,--过半数通过 6 | } 7 | 8 | function BOD:Constructor( default, type ) 9 | self.type = type or BOD.Type.Veto 10 | self.bod = {} 11 | if default == nil then 12 | default = true 13 | end 14 | self.default = default 15 | self.value = default 16 | end 17 | 18 | function BOD:Update( ) 19 | local newValue = self.default 20 | if self.type == BOD.Type.Veto then 21 | for k,v in pairs(self.bod) do 22 | if not v then 23 | newValue = false 24 | break 25 | end 26 | end 27 | elseif self.type == BOD.Type.Half then 28 | --useless for now 29 | end 30 | self.value = newValue 31 | end 32 | 33 | function BOD:Change( bodName, isSet, value ) 34 | -- print('Cat:BOD.lua[34] bodName, isSet, value', bodName, isSet, value) 35 | if isSet then 36 | self.bod[bodName] = value 37 | else 38 | self.bod[bodName] = nil 39 | end 40 | self:Update() 41 | end 42 | 43 | return BOD -------------------------------------------------------------------------------- /common/BaseClass.lua: -------------------------------------------------------------------------------- 1 | local function destroyFunc(self) 2 | if self.is_destroyed then --是否已经调过一次DeleteMe 3 | return 4 | end 5 | self.is_destroyed = true 6 | 7 | local now_super = self.__class_type 8 | while now_super ~= nil do 9 | local ondestroy = rawget(now_super, "OnDestroy") 10 | print('Cat:BaseClass.lua[10] ondestroy', ondestroy) 11 | if ondestroy then --每一个类调OnDestroy方法 12 | ondestroy(self) 13 | end 14 | now_super = now_super.super 15 | end 16 | end 17 | 18 | function BaseClass(super) 19 | local class_type={} 20 | class_type.Constructor=false 21 | class_type.DefaultVar=false 22 | class_type.super=super 23 | class_type.New=function(...) 24 | local obj=nil 25 | local create 26 | create = function(c, obj, ...) 27 | if c.super then 28 | create(c.super, obj, ...) 29 | end 30 | if c.Constructor then 31 | c.Constructor(obj,...) 32 | end 33 | end 34 | if class_type.DefaultVar then 35 | obj = class_type.DefaultVar(obj) 36 | else 37 | obj = {} 38 | end 39 | local function meta_func(t, k) 40 | local ret = class_type[k] 41 | obj[k] = ret 42 | return ret 43 | end 44 | setmetatable(obj, { __index=meta_func }) 45 | create(class_type, obj, ...) 46 | obj.__class_type = class_type 47 | obj.Destroy = destroyFunc 48 | return obj 49 | end 50 | 51 | if super then 52 | setmetatable(class_type,{__index= 53 | function(t,k) 54 | local ret=super[k] 55 | class_type[k]=ret 56 | return ret 57 | end 58 | }) 59 | end 60 | 61 | return class_type 62 | end 63 | 64 | return BaseClass -------------------------------------------------------------------------------- /common/MathUtil.lua: -------------------------------------------------------------------------------- 1 | local clamp = function ( value, min, max ) 2 | if value > max then 3 | value = max 4 | end 5 | if value < min then 6 | value = min 7 | end 8 | return value 9 | end 10 | 11 | math.clamp = clamp 12 | -------------------------------------------------------------------------------- /common/helper.lua: -------------------------------------------------------------------------------- 1 | function RequireAllLuaFileInFolder( folder, ignore_list ) 2 | -- print('Cat:helper.lua[2] folder', folder) 3 | local s = io.popen("ls "..folder)--for linux 4 | -- local s = io.popen("dir /b Tests")--for windows 5 | local fileNames = s:read("*all") 6 | fileNames = Split(fileNames, "\n") 7 | for k,v in pairs(fileNames or {}) do 8 | local is_ignore = false 9 | if ignore_list then 10 | for ii,vv in ipairs(ignore_list) do 11 | if v == vv then 12 | is_ignore = true 13 | break 14 | end 15 | end 16 | end 17 | if v~="" and not is_ignore then 18 | local dot_index = string.find(v, ".", 1, true) 19 | local is_lua_file = string.find(v, ".lua", -4, true) 20 | if dot_index ~= nil and is_lua_file then 21 | local name_without_ex = string.sub(v, 1, dot_index-1) 22 | -- print('test_all.lua init test file name : ', name_without_ex) 23 | require(name_without_ex) 24 | end 25 | end 26 | end 27 | end -------------------------------------------------------------------------------- /common/print_r.lua: -------------------------------------------------------------------------------- 1 | local print = print 2 | local tconcat = table.concat 3 | local tinsert = table.insert 4 | local srep = string.rep 5 | local type = type 6 | local pairs = pairs 7 | local tostring = tostring 8 | local next = next 9 | 10 | local function print_r(root) 11 | local cache = { [root] = "." } 12 | local function _dump(t,space,name) 13 | local temp = {} 14 | for k,v in pairs(t) do 15 | local key = tostring(k) 16 | if cache[v] then 17 | tinsert(temp,"+" .. key .. " {" .. cache[v].."}") 18 | elseif type(v) == "table" then 19 | local new_key = name .. "." .. key 20 | cache[v] = new_key 21 | tinsert(temp,"+" .. key .. _dump(v,space .. (next(t,k) and "|" or " " ).. srep(" ",#key),new_key)) 22 | else 23 | tinsert(temp,"+" .. key .. " [" .. tostring(v).."]") 24 | end 25 | end 26 | return tconcat(temp,"\n"..space) 27 | end 28 | print(_dump(root, "","")) 29 | end 30 | 31 | return print_r -------------------------------------------------------------------------------- /common/sprotoloader.lua: -------------------------------------------------------------------------------- 1 | local parser = require "sprotoparser" 2 | local core = require "sproto.core" 3 | local sproto = require "common.sproto" 4 | 5 | local loader = {} 6 | 7 | function loader.register(filename, index) 8 | local f = assert(io.open(filename), "Can't open sproto file") 9 | local data = f:read "a" 10 | f:close() 11 | local sp = core.newproto(parser.parse(data)) 12 | core.saveproto(sp, index) 13 | end 14 | 15 | function loader.save(bin, index) 16 | local sp = core.newproto(bin) 17 | core.saveproto(sp, index) 18 | end 19 | 20 | function loader.load(index) 21 | local sp = core.loadproto(index) 22 | -- no __gc in metatable 23 | return sproto.sharenew(sp) 24 | end 25 | 26 | return loader 27 | 28 | -------------------------------------------------------------------------------- /common/util.lua: -------------------------------------------------------------------------------- 1 | function PrintTable( tbl, level, return_counter ) 2 | if tbl == nil or type(tbl) ~= "table" then 3 | return 4 | end 5 | return_counter = return_counter or 7 --剩下多少层就返回,防止无限打印 6 | if return_counter <= 0 then 7 | -- print('Cat:util.lua PrintTable return_counter empty') 8 | return 9 | end 10 | return_counter = return_counter - 1 11 | level = level or 1 12 | 13 | local indent_str = "" 14 | for i = 1, level do 15 | indent_str = indent_str.." " 16 | end 17 | print(indent_str .. "{") 18 | for k,v in pairs(tbl) do 19 | 20 | local item_str = string.format("%s%s = %s", indent_str .. " ",tostring(k), tostring(v)) 21 | print(item_str) 22 | if type(v) == "table" then 23 | PrintTable(v, level + 1, return_counter) 24 | end 25 | end 26 | print(indent_str .. "}") 27 | end 28 | 29 | --将 szFullString 对象拆分为一个子字符串表 30 | function Split(szFullString, szSeparator, start_pos) 31 | local nFindStartIndex = start_pos or 1 32 | local nSplitIndex = 1 33 | local nSplitArray = {} 34 | while true do 35 | local nFindLastIndex = string.find(szFullString, szSeparator, nFindStartIndex) 36 | if not nFindLastIndex then 37 | nSplitArray[nSplitIndex] = string.sub(szFullString, nFindStartIndex, string.len(szFullString)) 38 | break 39 | end 40 | table.insert(nSplitArray, string.sub(szFullString, nFindStartIndex, nFindLastIndex - 1)) 41 | nFindStartIndex = nFindLastIndex + string.len(szSeparator) 42 | nSplitIndex = nSplitIndex + 1 43 | end 44 | return nSplitArray 45 | end 46 | 47 | function Trim(str) 48 | if str == nil or type(str) == "table" then return "" end 49 | str = string.gsub(str, "^[ \t\n\r]+", "") 50 | return string.gsub(str, "[ \t\n\r]+$", "") 51 | end 52 | 53 | function Round(number) 54 | local intNum = math.floor(number) 55 | if number >= (intNum + 0.5) then 56 | return intNum + 1 57 | else 58 | return intNum 59 | end 60 | end 61 | 62 | -------------------------------------------------------------------------------- /config: -------------------------------------------------------------------------------- 1 | root = "$ROOT/" 2 | thread = 8 3 | logpath = root 4 | harbor = 1 5 | address = "127.0.0.1:2526" 6 | master = "127.0.0.1:2013" 7 | standalone = "0.0.0.0:2013" 8 | start = "main" 9 | luaservice = root.."service/?.lua;"..root.."skynet/service/?.lua;"..root.."game/service/?.lua;" 10 | lualoader = root .. "skynet/lualib/loader.lua" 11 | lua_path = root .. "lualib/?.lua;"..root.."lualib/?/?.lua;" .. root .. "skynet/lualib/?.lua;" .. root .. "skynet/lualib/?/init.lua;" .. root.."?.lua;"..root.."game/?.lua;"..root .. "../Lua/?.lua;"..root.."game/scene/system/?.lua;"..root.."../Lua/Common/?.lua;"..root.."lualib/ECS/?.lua;" 12 | lua_cpath = root .. "skynet/luaclib/?.so;" .. root.."lualib/ECS/?.so" 13 | cpath = root .. "/cservice/?.so;"..root.."/skynet/cservice/?.so" 14 | 15 | if $DAEMON then 16 | logger = root .. "skynet.log" 17 | daemon = root .. "skynet.pid" 18 | end 19 | -------------------------------------------------------------------------------- /data/UnityMMOAccount.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Navicat MySQL Data Transfer 3 | 4 | Source Server : UnityMMO 5 | Source Server Version : 50640 6 | Source Host : 192.168.5.132:3306 7 | Source Database : UnityMMOAccount 8 | 9 | Target Server Type : MYSQL 10 | Target Server Version : 50640 11 | File Encoding : 65001 12 | 13 | Date: 2019-08-24 16:33:50 14 | */ 15 | 16 | SET FOREIGN_KEY_CHECKS=0; 17 | 18 | -- ---------------------------- 19 | -- Table structure for Account 20 | -- ---------------------------- 21 | DROP TABLE IF EXISTS `Account`; 22 | CREATE TABLE `Account` ( 23 | `account_id` bigint(20) NOT NULL, 24 | `password` char(20) DEFAULT NULL, 25 | PRIMARY KEY (`account_id`) 26 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 27 | -------------------------------------------------------------------------------- /data/UnityMMOGame.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Navicat MySQL Data Transfer 3 | 4 | Source Server : UnityMMO 5 | Source Server Version : 50640 6 | Source Host : 192.168.5.132:3306 7 | Source Database : UnityMMOGame 8 | 9 | Target Server Type : MYSQL 10 | Target Server Version : 50640 11 | File Encoding : 65001 12 | 13 | Date: 2019-08-24 16:33:42 14 | */ 15 | 16 | SET FOREIGN_KEY_CHECKS=0; 17 | 18 | -- ---------------------------- 19 | -- Table structure for AttrInfo 20 | -- ---------------------------- 21 | DROP TABLE IF EXISTS `AttrInfo`; 22 | CREATE TABLE `AttrInfo` ( 23 | `role_id` bigint(60) unsigned NOT NULL, 24 | `att` int(255) unsigned zerofill DEFAULT NULL, 25 | `hp` int(255) unsigned zerofill DEFAULT NULL, 26 | `def` int(255) unsigned zerofill DEFAULT NULL, 27 | `crit` int(255) unsigned zerofill DEFAULT NULL, 28 | `hit` int(255) unsigned zerofill DEFAULT NULL, 29 | `dodge` int(255) unsigned zerofill DEFAULT NULL, 30 | PRIMARY KEY (`role_id`) 31 | ) ENGINE=InnoDB DEFAULT CHARSET=latin1; 32 | 33 | -- ---------------------------- 34 | -- Table structure for Bag 35 | -- ---------------------------- 36 | DROP TABLE IF EXISTS `Bag`; 37 | CREATE TABLE `Bag` ( 38 | `uid` bigint(20) unsigned NOT NULL, 39 | `typeID` int(10) unsigned NOT NULL, 40 | `roleID` bigint(20) unsigned NOT NULL, 41 | `pos` tinyint(10) unsigned NOT NULL, 42 | `cell` smallint(10) unsigned NOT NULL, 43 | `num` int(10) unsigned NOT NULL, 44 | PRIMARY KEY (`uid`), 45 | KEY `role_id` (`roleID`,`pos`) USING BTREE 46 | ) ENGINE=InnoDB DEFAULT CHARSET=latin1; 47 | 48 | -- ---------------------------- 49 | -- Table structure for BagInfo 50 | -- ---------------------------- 51 | DROP TABLE IF EXISTS `BagInfo`; 52 | CREATE TABLE `BagInfo` ( 53 | `role_id` bigint(20) unsigned zerofill NOT NULL, 54 | `pos` varchar(255) DEFAULT NULL, 55 | `cell_num` mediumint(10) unsigned zerofill NOT NULL, 56 | KEY `role_id` (`role_id`,`pos`) USING BTREE 57 | ) ENGINE=InnoDB DEFAULT CHARSET=latin1; 58 | 59 | -- ---------------------------- 60 | -- Table structure for RoleBaseInfo 61 | -- ---------------------------- 62 | DROP TABLE IF EXISTS `RoleBaseInfo`; 63 | CREATE TABLE `RoleBaseInfo` ( 64 | `role_id` bigint(20) unsigned NOT NULL, 65 | `name` varchar(18) NOT NULL, 66 | `career` tinyint(4) NOT NULL, 67 | `level` smallint(5) unsigned DEFAULT '0', 68 | `scene_id` int(11) DEFAULT NULL, 69 | `pos_x` int(11) DEFAULT NULL, 70 | `pos_y` int(11) DEFAULT NULL, 71 | `pos_z` int(11) DEFAULT NULL, 72 | `coin` int(11) unsigned zerofill DEFAULT NULL, 73 | `diamond` int(11) unsigned zerofill DEFAULT NULL, 74 | `hp` int(11) unsigned zerofill NOT NULL, 75 | PRIMARY KEY (`role_id`) 76 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 77 | 78 | -- ---------------------------- 79 | -- Table structure for RoleList 80 | -- ---------------------------- 81 | DROP TABLE IF EXISTS `RoleList`; 82 | CREATE TABLE `RoleList` ( 83 | `account_id` bigint(20) unsigned NOT NULL, 84 | `role_id` bigint(20) unsigned NOT NULL, 85 | `create_time` bigint(20) unsigned NOT NULL, 86 | KEY `account_id` (`account_id`) USING BTREE 87 | ) ENGINE=InnoDB DEFAULT CHARSET=latin1; 88 | 89 | -- ---------------------------- 90 | -- Table structure for RoleLooksInfo 91 | -- ---------------------------- 92 | DROP TABLE IF EXISTS `RoleLooksInfo`; 93 | CREATE TABLE `RoleLooksInfo` ( 94 | `role_id` bigint(64) NOT NULL, 95 | `body` int(10) DEFAULT NULL, 96 | `hair` int(10) DEFAULT NULL, 97 | `weapon` int(10) DEFAULT NULL, 98 | `wing` int(10) DEFAULT NULL, 99 | PRIMARY KEY (`role_id`) 100 | ) ENGINE=InnoDB DEFAULT CHARSET=latin1; 101 | 102 | -- ---------------------------- 103 | -- Table structure for TaskList 104 | -- ---------------------------- 105 | DROP TABLE IF EXISTS `TaskList`; 106 | CREATE TABLE `TaskList` ( 107 | `id` bigint(60) NOT NULL AUTO_INCREMENT, 108 | `roleID` bigint(60) NOT NULL, 109 | `taskID` int(11) NOT NULL, 110 | `status` tinyint(5) DEFAULT NULL, 111 | `subTaskIndex` tinyint(10) unsigned zerofill DEFAULT NULL, 112 | `subType` int(10) DEFAULT NULL, 113 | `curProgress` int(8) unsigned zerofill DEFAULT NULL, 114 | `maxProgress` int(8) unsigned DEFAULT NULL, 115 | `contentID` int(8) DEFAULT NULL, 116 | PRIMARY KEY (`id`), 117 | KEY `role_id` (`roleID`) 118 | ) ENGINE=InnoDB AUTO_INCREMENT=34 DEFAULT CHARSET=latin1; 119 | -------------------------------------------------------------------------------- /game/account/Account.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynet" 2 | require "common.util" 3 | local queue = require "skynet.queue" 4 | local cs = queue() 5 | 6 | local id_service 7 | local gameDBServer 8 | local account = {} 9 | 10 | function account.account_get_role_list(user_info, req_data) 11 | --先不考虑性能吧,优先把前端搞定再优化 12 | gameDBServer = gameDBServer or skynet.localname(".GameDBServer") 13 | local is_succeed, role_list = skynet.call(gameDBServer, "lua", "select_by_key", "RoleList", "account_id", user_info.user_id) 14 | if is_succeed and role_list and #role_list > 0 then 15 | local sort_func = function ( a, b ) 16 | return a.create_time < b.create_time 17 | end 18 | table.sort(role_list, sort_func) 19 | local ack_data = {role_list={}} 20 | for i,v in ipairs(role_list) do 21 | local role_id = v.role_id 22 | if role_id then 23 | local is_ok, role_info = skynet.call(gameDBServer, "lua", "select_one_by_key", "RoleBaseInfo", "role_id", role_id) 24 | if is_ok and role_info then 25 | local is_ok, looks_info = skynet.call(gameDBServer, "lua", "select_one_by_key", "RoleLooksInfo", "role_id", role_id) 26 | if is_ok then 27 | for k,v in pairs(looks_info) do 28 | role_info[k] = v 29 | end 30 | end 31 | table.insert(ack_data.role_list, role_info) 32 | end 33 | else 34 | skynet.error("account_get_role_list error : role id nil!") 35 | end 36 | end 37 | return ack_data 38 | end 39 | return {} 40 | end 41 | 42 | local gen_random_role_looks = function ( role_id ) 43 | return {role_id=role_id, body=math.random(0,1), hair=math.random(0,1)} 44 | end 45 | 46 | local gen_role_base_attr = function ( role_id ) 47 | return {role_id=role_id, att=100, hp=10000, def=100, hit=100, dodge=120, crit=100} 48 | end 49 | 50 | function account.account_create_role( user_info, req_data ) 51 | local create_role = function ( ) 52 | gameDBServer = gameDBServer or skynet.localname(".GameDBServer") 53 | local is_succeed, role_list = skynet.call(gameDBServer, "lua", "select_by_key", "RoleList", "account_id", user_info.user_id) 54 | local is_full_role = role_list and #role_list >= 3 55 | if is_full_role then 56 | return {result = ErrorCode.FullCreateRoleNum} 57 | end 58 | id_service = id_service or skynet.localname(".id_service") 59 | local new_role_id = skynet.call(id_service, "lua", "gen_uid", "role") 60 | assert(new_role_id, "gen role id failed!") 61 | local is_create_succeed = false 62 | if new_role_id then 63 | is_succeed = skynet.call(gameDBServer, "lua", "insert", "RoleList", {account_id=user_info.user_id, role_id=new_role_id, create_time=math.floor(skynet.time()*1000+0.5)}) 64 | if is_succeed then 65 | local attr_info = gen_role_base_attr(new_role_id) 66 | is_succeed = skynet.call(gameDBServer, "lua", "insert", "RoleBaseInfo", {role_id=new_role_id, name=req_data.name, career=req_data.career, level=1, hp=attr_info.hp}) 67 | is_succeed = is_succeed and skynet.call(gameDBServer, "lua", "insert", "RoleLooksInfo", gen_random_role_looks(new_role_id)) 68 | is_succeed = is_succeed and skynet.call(gameDBServer, "lua", "insert", "AttrInfo", attr_info) 69 | end 70 | if is_succeed then 71 | is_create_succeed = true 72 | end 73 | end 74 | return {result=is_create_succeed and ErrorCode.Succeed or ErrorCode.Unknow, role_id=new_role_id} 75 | end 76 | --防止同时进入 77 | return cs(create_role) 78 | end 79 | 80 | function account.account_get_server_time( user_info, req_data ) 81 | local cur_time = skynet.time() 82 | return {server_time = math.floor(cur_time*1000+0.5)} 83 | end 84 | 85 | function account.account_select_role_enter_game( user_info, req_data ) 86 | if req_data and req_data.role_id and req_data.role_id ~= 0 then 87 | user_info.cur_role_id = req_data.role_id 88 | --角色进入游戏场景 89 | local world = skynet.uniqueservice ("world") 90 | local result_code = skynet.call(world, "lua", "role_enter_game", user_info, req_data.role_id) 91 | return {result = result_code} 92 | else 93 | skynet.error("wrong req_data for proto account_select_role_enter_game") 94 | return {result = ErrorCode.WrongRoleIDForEnterGame} 95 | end 96 | end 97 | 98 | return account -------------------------------------------------------------------------------- /game/bag/BagConst.lua: -------------------------------------------------------------------------------- 1 | local BagConst = { 2 | --BagConst.Pos.Bag 3 | Pos = { 4 | Bag = 1, 5 | Warehouse = 2, 6 | Equip = 3, 7 | }, 8 | --BagConst.MaxCell 9 | MaxCell = 50, 10 | } 11 | return BagConst -------------------------------------------------------------------------------- /game/chat/ChatConst.lua: -------------------------------------------------------------------------------- 1 | local ChatConst = { 2 | -- ChatConst.Channel.Count 3 | Channel = { 4 | World = 1,--世界频道 5 | Notify = 2,--系统通知 6 | Private = 3,--私人 7 | Team = 4,--队伍 8 | CS = 5,--跨服 9 | Count = 6 10 | }, 11 | MaxHistoryNum = 50, 12 | } 13 | return ChatConst -------------------------------------------------------------------------------- /game/chat/ChatMgr.lua: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuhaopen/SkynetMMO/cc08a82c6e8e53d16f0ace3ab8ca306068c2887b/game/chat/ChatMgr.lua -------------------------------------------------------------------------------- /game/config/ConfigConversation.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | btnName:1继续 2完成 3领取 3 | --]] 4 | local config = { 5 | [1] = {content=[[第一个对话,哈哈哈]], flag=1}, 6 | [2] = {content=[[2有没搞错]], flag=2}, 7 | [3] = {content=[[不服来战]], flag=1}, 8 | [4] = {content=[[有种打我呀]], flag=1}, 9 | [5] = {content=[[大佬别生气]], flag=1}, 10 | } 11 | 12 | return config -------------------------------------------------------------------------------- /game/config/ConfigDungeon.lua: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuhaopen/SkynetMMO/cc08a82c6e8e53d16f0ace3ab8ca306068c2887b/game/config/ConfigDungeon.lua -------------------------------------------------------------------------------- /game/config/ConfigGoods.lua: -------------------------------------------------------------------------------- 1 | local config = { 2 | [100000] = { 3 | typeID = 100000, name = [[钻石]], icon = 100000, type = 1, color = 1, overlap = 5, level = 0, intro = [[ 充值获得的珍贵货币,可用于购买物品 ]], career = 1, use_way = [[ y ]], use_intro = [[ ]] 4 | }, 5 | [100001] = { 6 | typeID = 100001, name = [[铜币]], icon = 100001, type = 1, color = 1, overlap = 5, level = 0,intro = [[ 游戏内产出的珍贵货币,可用来购买物品 ]], career = 1, use_way = [[ y ]], use_intro = [[ ]] 7 | }, 8 | [100002] = { 9 | typeID = 100002, name = [[经验丹(小)]], icon = 100002, type = 2, color = 2, overlap = 5, level = 0, intro = [[ 使用后固定增加小量经验 ]], career = 1, use_way = [[ y ]], use_intro = [[ 固定经验丹,早用早升级 ]] 10 | }, 11 | [100003] = { 12 | typeID = 100003, name = [[经验丹(中)]], icon = 100003, type = 2, color = 3, overlap = 5, level = 10, intro = [[ 使用后固定增加中量经验 ]], career = 1, use_way = [[ y ]], use_intro = [[ 固定经验丹,早用早升级 ]] 13 | }, 14 | [100004] = { 15 | typeID = 100004, name = [[经验丹(大)]], icon = 100004, type = 2, color = 4, overlap = 5, level = 20, intro = [[ 使用后固定增加大量经验 ]], career = 1, use_way = [[ y ]], use_intro = [[ 固定经验丹,早用早升级 ]] 16 | }, 17 | } 18 | return config -------------------------------------------------------------------------------- /game/config/ConfigMonster.lua: -------------------------------------------------------------------------------- 1 | local config = { 2 | [2000] = { 3 | id = 2000, name = "小灰狼", 4 | }, 5 | [2001] = { 6 | id = 2001, name = "大灰", 7 | }, 8 | [2002] = { 9 | id = 2002, name = "金钱猫", 10 | }, 11 | [2003] = { 12 | id = 2003, name = "红袍妖女", 13 | }, 14 | [2004] = { 15 | id = 2004, name = "断头鬼", 16 | }, 17 | [2005] = { 18 | id = 2005, name = "唱大戏的", 19 | }, 20 | } 21 | return config -------------------------------------------------------------------------------- /game/config/ConfigNPC.lua: -------------------------------------------------------------------------------- 1 | local config = { 2 | [3000] = { 3 | id = 3000, name = "流浪剑客", defaultChat = {1}, 4 | }, 5 | [3001] = { 6 | id = 3001, name = "风车男孩", defaultChat = {2}, 7 | }, 8 | [3002] = { 9 | id = 3002, name = "猫女", defaultChat = {1,2,3}, 10 | }, 11 | } 12 | return config -------------------------------------------------------------------------------- /game/config/ErrorCode.lua: -------------------------------------------------------------------------------- 1 | --错误码:前3位是模块号,后三位自增数 2 | local ErrorCode = { 3 | Succeed = 0, 4 | Unknow = 1, 5 | FullCreateRoleNum = 2, 6 | WrongRoleIDForEnterGame = 3, 7 | SkillCastFail = 200000, 8 | SkillCfgNotFind = 200001, 9 | SkillInCD = 200002, 10 | UIDErrorOnCastSkill = 200003, 11 | CannotFindGoods = 300000, 12 | GMArgeWrong = 400000, 13 | GMUnknow = 400001, 14 | } 15 | 16 | return ErrorCode -------------------------------------------------------------------------------- /game/config/scene/config_drop.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | key:怪物类型id 3 | belong:归属:1大家都可捡,2最大伤害者,3最后一击者 4 | goods:类型为数组,元素结构为:[1]=道具类型id,[2]=数量,[3]=概率, 5 | --]] 6 | local config_drop = { 7 | [2000] = { 8 | belong = 1, goods = {{100000,1,1200}}, 9 | }, 10 | } 11 | 12 | return config_drop -------------------------------------------------------------------------------- /game/config/scene/config_monster.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | move_spped:移动速度 3 | skill_list:技能列表 4 | attr_list:属性列表 5 | ai.fight_back:是否会反击 6 | ai.reborn_time:复活时间(毫秒) 7 | ai.remove_after_dead:死后隔多久清理尸体(毫秒) 8 | ai.attack_area:攻击范围,max_distance为最远攻击距离,min_distance用于一些弓箭手或魔法师,要离敌人最少一定距离才攻击 9 | ai.patrol.type:巡逻类型,1为随机在活动范围里找个点然后走过去;2是路点巡逻,读取way_points字段 10 | ai.patrol.auto_attack_radius:主动攻击半径,为nil或0时就不主动找人打 11 | ai.patrol.way_points:巡逻路点,如{{1,2},{3,5}} 12 | ai.patrol.idle_min和ai.patrol.idle_max:发呆的时间范围,怪物走一会就会发呆一会 13 | ai.hunt_radius:追捕半径,超过此半径就不再追了,切换到巡逻状态 14 | --]] 15 | local config = { 16 | [2000] = { 17 | type_id = 2000, name = "小灰狼", 18 | attr_list = {[1]=110,[2]=1000,[3]=20,[4]=20}, move_speed=500, ai={ 19 | fight_back = true, reborn_time = 2000, remove_after_dead = 500, patrol={type=1, idle_min=3000, idle_max=7000, auto_attack_radius=1000}, attack_area = {min_distance=70, max_distance=150}, hunt_radius=1000, skill_list = { 20 | {skill_id=200000, random=60}, {skill_id=200001, random=40} 21 | }, 22 | }, 23 | }, 24 | [2001] = { 25 | type_id = 2001, name = "大灰", attr_list = {[1]=150,[2]=3000,[3]=50,[4]=100}, move_speed=500, skill_list={}, 26 | ai={ 27 | fight_back = true, reborn_time = 2000, remove_after_dead = 500, patrol={type=1, idle_min=3000, idle_max=7000, auto_attack_radius=1000}, attack_area = {min_distance=140, max_distance=300}, hunt_radius=1000, skill_list = { 28 | {skill_id=200100, random=50}, {skill_id=200101, random=50} 29 | }, 30 | }, 31 | }, 32 | [2002] = { 33 | type_id = 2002, name = "金钱猫", 34 | attr_list = {[1]=150,[2]=1000,[3]=20,[4]=30}, move_speed=500, skill_list={}, ai={ 35 | fight_back = true, reborn_time = 3000, remove_after_dead = 500, patrol={type=1, idle_min=3000, idle_max=7000, auto_attack_radius=1000}, attack_area = {min_distance=60, max_distance=150}, hunt_radius=1000, skill_list = { 36 | {skill_id=200200, random=50}, {skill_id=200201, random=50} 37 | }, 38 | }, 39 | }, 40 | [2003] = { 41 | type_id = 2003, name = "红袍妖女", 42 | attr_list = {[1]=150,[2]=3000,[3]=20,[4]=120}, move_speed=500, skill_list={}, ai={ 43 | fight_back = true, reborn_time = 3000, remove_after_dead = 500, patrol={type=1, idle_min=3000, idle_max=7000, auto_attack_radius=1000}, attack_area = {min_distance=60, max_distance=200}, hunt_radius=1000, skill_list = { 44 | {skill_id=200300, random=50}, {skill_id=200301, random=50} 45 | }, 46 | }, 47 | }, 48 | [2004] = { 49 | type_id = 2004, name = "断头鬼", 50 | attr_list = {[1]=150,[2]=1000,[3]=20,[4]=30}, move_speed=500, skill_list={}, ai={ 51 | fight_back = true, reborn_time = 3000, remove_after_dead = 500, patrol={type=1, idle_min=3000, idle_max=7000, auto_attack_radius=1000}, attack_area = {min_distance=120, max_distance=200}, hunt_radius=1000, skill_list = { 52 | {skill_id=200400, random=50}, {skill_id=200401, random=50} 53 | }, 54 | }, 55 | }, 56 | [2005] = { 57 | type_id = 2005, name = "唱大戏的", 58 | attr_list = {[1]=150,[2]=3000,[3]=20,[4]=150}, move_speed=500, skill_list={}, ai={ 59 | fight_back = true, reborn_time = 3000, remove_after_dead = 500, patrol={type=1, idle_min=3000, idle_max=7000, auto_attack_radius=1000}, attack_area = {min_distance=80, max_distance=300}, hunt_radius=1000, skill_list = { 60 | {skill_id=200500, random=50}, {skill_id=200501, random=50} 61 | }, 62 | }, 63 | }, 64 | } 65 | return config -------------------------------------------------------------------------------- /game/config/scene/config_npc.lua: -------------------------------------------------------------------------------- 1 | local config = { 2 | [3000] = { 3 | id = 3000 4 | }, 5 | [3001] = { 6 | id = 3001 7 | }, 8 | [3002] = { 9 | id = 3002 10 | }, 11 | } 12 | return config -------------------------------------------------------------------------------- /game/config/scene/config_scene_1001.lua: -------------------------------------------------------------------------------- 1 | local config = { 2 | scene_id = 1001, 3 | scene_name = "新手村", 4 | door_list = {{ 5 | pos_x = 85500, 6 | pos_y = 16507, 7 | pos_z = 118100, 8 | door_id = 1, 9 | target_scene_id = 1002, 10 | target_x = 1, 11 | target_y = 2, 12 | target_z = 3, 13 | }, 14 | { 15 | pos_x = 67500, 16 | pos_y = 16478, 17 | pos_z = 116900, 18 | door_id = 2, 19 | target_scene_id = 1003, 20 | target_x = 11, 21 | target_y = 22, 22 | target_z = 33, 23 | }, 24 | }, 25 | born_list = {{ 26 | pos_x = 73670, 27 | pos_y = 16261, 28 | pos_z = 114380, 29 | born_id = 1, 30 | }, 31 | { 32 | pos_x = 65528, 33 | pos_y = 16478, 34 | pos_z = 117550, 35 | born_id = 2, 36 | }, 37 | }, 38 | npc_list = {{ 39 | pos_x = 78790, 40 | pos_y = 16619, 41 | pos_z = 107390, 42 | npc_id = 3000, 43 | }, 44 | { 45 | pos_x = 64309, 46 | pos_y = 16886, 47 | pos_z = 123830, 48 | npc_id = 3001, 49 | }, 50 | { 51 | pos_x = 77271, 52 | pos_y = 16460, 53 | pos_z = 120828, 54 | npc_id = 3002, 55 | }, 56 | }, 57 | monster_list = {{ 58 | pos_x = 84990, 59 | pos_y = 16538, 60 | pos_z = 116869, 61 | monster_type_id = 2000, 62 | monster_num = 7, 63 | radius = 1000, 64 | }, 65 | { 66 | pos_x = 84990, 67 | pos_y = 16538, 68 | pos_z = 116869, 69 | monster_type_id = 2001, 70 | monster_num = 1, 71 | radius = 800, 72 | }, 73 | { 74 | pos_x = 69300, 75 | pos_y = 16450, 76 | pos_z = 124200, 77 | monster_type_id = 2002, 78 | monster_num = 6, 79 | radius = 1200, 80 | }, 81 | { 82 | pos_x = 69300, 83 | pos_y = 16450, 84 | pos_z = 124200, 85 | monster_type_id = 2003, 86 | monster_num = 1, 87 | radius = 1000, 88 | }, 89 | { 90 | pos_x = 63300, 91 | pos_y = 16541, 92 | pos_z = 108900, 93 | monster_type_id = 2004, 94 | monster_num = 7, 95 | radius = 1400, 96 | }, 97 | { 98 | pos_x = 63300, 99 | pos_y = 16541, 100 | pos_z = 108900, 101 | monster_type_id = 2005, 102 | monster_num = 1, 103 | radius = 1200, 104 | }, 105 | { 106 | pos_x = 79920, 107 | pos_y = 16438, 108 | pos_z = 110400, 109 | monster_type_id = 2002, 110 | monster_num = 7, 111 | radius = 1400, 112 | }, 113 | { 114 | pos_x = 79920, 115 | pos_y = 16438, 116 | pos_z = 110400, 117 | monster_type_id = 2001, 118 | monster_num = 1, 119 | radius = 1200, 120 | }, 121 | { 122 | pos_x = 85959, 123 | pos_y = 16450, 124 | pos_z = 129530, 125 | monster_type_id = 2002, 126 | monster_num = 8, 127 | radius = 1600, 128 | }, 129 | { 130 | pos_x = 85959, 131 | pos_y = 16450, 132 | pos_z = 129530, 133 | monster_type_id = 2005, 134 | monster_num = 1, 135 | radius = 1000, 136 | }, 137 | { 138 | pos_x = 76340, 139 | pos_y = 16538, 140 | pos_z = 119490, 141 | monster_type_id = 2000, 142 | monster_num = 7, 143 | radius = 1300, 144 | }, 145 | { 146 | pos_x = 76340, 147 | pos_y = 16538, 148 | pos_z = 119490, 149 | monster_type_id = 2001, 150 | monster_num = 1, 151 | radius = 800, 152 | }, 153 | { 154 | pos_x = 79479, 155 | pos_y = 16538, 156 | pos_z = 133530, 157 | monster_type_id = 2000, 158 | monster_num = 6, 159 | radius = 1000, 160 | }, 161 | { 162 | pos_x = 79479, 163 | pos_y = 16538, 164 | pos_z = 133530, 165 | monster_type_id = 2003, 166 | monster_num = 1, 167 | radius = 800, 168 | }, 169 | }, 170 | collectable_list = {{ 171 | pos_x = 85764, 172 | pos_y = 16385, 173 | pos_z = 129069, 174 | collectable_type_id = 4000, 175 | }, 176 | { 177 | pos_x = 80685, 178 | pos_y = 16470, 179 | pos_z = 108673, 180 | collectable_type_id = 4001, 181 | }, 182 | }, 183 | } 184 | return config -------------------------------------------------------------------------------- /game/config/scene/config_scene_2001.lua: -------------------------------------------------------------------------------- 1 | local config = { 2 | scene_id = 2001, 3 | scene_name = "boss副本", 4 | door_list = {}, 5 | born_list = {{ 6 | pos_x = 8700, 7 | pos_y = -1400, 8 | pos_z = -8300, 9 | born_id = 1, 10 | }, 11 | }, 12 | npc_list = {{ 13 | pos_x = -1019, 14 | pos_y = -1431, 15 | pos_z = -8300, 16 | npc_id = 3002, 17 | }, 18 | { 19 | pos_x = 964, 20 | pos_y = -901, 21 | pos_z = 2979, 22 | npc_id = 3001, 23 | }, 24 | }, 25 | monster_list = {{ 26 | pos_x = -2139, 27 | pos_y = -1929, 28 | pos_z = 13800, 29 | monster_type_id = 2005, 30 | monster_num = 1, 31 | radius = 400, 32 | }, 33 | { 34 | pos_x = -2139, 35 | pos_y = -1929, 36 | pos_z = 13800, 37 | monster_type_id = 2004, 38 | monster_num = 4, 39 | radius = 1500, 40 | }, 41 | { 42 | pos_x = -2139, 43 | pos_y = -1929, 44 | pos_z = 13800, 45 | monster_type_id = 2002, 46 | monster_num = 3, 47 | radius = 1500, 48 | }, 49 | { 50 | pos_x = -2139, 51 | pos_y = -1929, 52 | pos_z = 13800, 53 | monster_type_id = 2000, 54 | monster_num = 3, 55 | radius = 1500, 56 | }, 57 | }, 58 | collectable_list = {}, 59 | } 60 | return config -------------------------------------------------------------------------------- /game/gm/GM.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynet" 2 | local BagConst = require "game.gm.GMConst" 3 | 4 | local this = { 5 | user_info = nil, 6 | gmList = { 7 | {gmName="增加道具", defaultGMStr="Goods,100000,1"}, 8 | {gmName="清空背包", defaultGMStr="ClearAllGoods"}, 9 | {gmName="切换场景", defaultGMStr="Scene,1001"}, 10 | {gmName="改变属性", defaultGMStr="Attr,1,100"}, 11 | {gmName="设置金钱", defaultGMStr="Money,100000"}, 12 | {gmName="升级", defaultGMStr="LvUp,10"}, 13 | {gmName="改变速度", defaultGMStr="Speed,10"}, 14 | {gmName="设置等级", defaultGMStr="Lv,10"}, 15 | {gmName="增加经验", defaultGMStr="Exp,10000"}, 16 | {gmName="发送协议", defaultGMStr="Proto,Bag_Change,123456"}, 17 | {gmName="完成任务", defaultGMStr="Task,1000000"}, 18 | {gmName="重置任务", defaultGMStr="TaskReset"}, 19 | }, 20 | } 21 | 22 | local gmHandler = {} 23 | function gmHandler.Goods( gmParts ) 24 | if #gmParts ~= 3 then 25 | return ErrorCode.GMArgeWrong 26 | end 27 | local func = this.dispatcher:GetPublicFunc("Bag", "ChangeBagGoods") 28 | print('Cat:GM.lua[14] func', func) 29 | if func then 30 | func(tonumber(gmParts[2]), tonumber(gmParts[3])) 31 | return ErrorCode.Succeed 32 | end 33 | return ErrorCode.GMUnknow 34 | end 35 | 36 | function gmHandler.ClearAllGoods( gmParts ) 37 | local func = this.dispatcher:GetPublicFunc("Bag", "ClearAllGoods") 38 | print('Cat:GM.lua[clearAllGoods] func', func) 39 | if func then 40 | func() 41 | return ErrorCode.Succeed 42 | end 43 | return ErrorCode.GMUnknow 44 | end 45 | 46 | function gmHandler.Attr( gmParts ) 47 | local world = skynet.uniqueservice("world") 48 | local scene_service = skynet.call(world, "lua", "get_role_scene_service", this.user_info.cur_role_id) 49 | skynet.error("gmHandler.Attr scene_service : "+scene_service) 50 | if scene_service then 51 | local attrList = {} 52 | for i = 2, #gmParts, 2 do 53 | table.insert(attrList, {gmParts[i], gmParts[i+1]}) 54 | end 55 | skynet.send(scene_service, "lua", "change_attr", this.user_info.cur_role_id, attrList) 56 | end 57 | end 58 | 59 | local SprotoHandlers = {} 60 | function SprotoHandlers.GM_GetList( reqData ) 61 | return {gmList=this.gmList} 62 | end 63 | 64 | function SprotoHandlers.GM_Excute( reqData ) 65 | print("Cat:GM [start:31] reqData: ,", reqData) 66 | PrintTable(reqData) 67 | print("Cat:GM [end]") 68 | local gmParts = Split(reqData.gmStr, ",") 69 | local code = ErrorCode.GMUnknow 70 | if gmParts then 71 | local handleFunc = gmHandler[(gmParts[1] or "")] 72 | if handleFunc then 73 | code = handleFunc(gmParts) 74 | end 75 | end 76 | return {ret=code, gmStr=reqData.gmStr} 77 | end 78 | 79 | local PublicFuncs = {} 80 | function PublicFuncs.Init( user_info, dispatcher ) 81 | this.user_info = user_info 82 | this.dispatcher = dispatcher 83 | end 84 | 85 | SprotoHandlers.PublicClassName = "GM" 86 | SprotoHandlers.PublicFuncs = PublicFuncs 87 | return SprotoHandlers -------------------------------------------------------------------------------- /game/gm/GMConst.lua: -------------------------------------------------------------------------------- 1 | local GMConst = { 2 | 3 | } 4 | return GMConst -------------------------------------------------------------------------------- /game/proto/proto_100_scene.lua: -------------------------------------------------------------------------------- 1 | return [[ 2 | 3 | .scene_main_role_info { 4 | scene_uid 0 : integer 5 | role_id 1 : integer 6 | career 2 : integer 7 | name 3 : string 8 | scene_id 4 : integer 9 | pos_x 5 : integer 10 | pos_y 6 : integer 11 | pos_z 7 : integer 12 | cur_hp 8 : integer 13 | max_hp 9 : integer 14 | base_info 10 : scene_role_base_info 15 | coin 11 : integer 16 | diamond 12 : integer 17 | } 18 | 19 | #key 对应前端SceneInfoKey.cs里的SceneInfoKey或后端SceneConst.lua里的SceneConst.InfoKey 20 | #1:EnterView即有场景节点(角色、怪物或NPC)进入视角,value为scene_uid,type_id,pos_x,pos_y,pos_z 21 | #2:LeaveView场景节点离开视角 22 | #3:PosChange场景节点的坐标变更 23 | #4:TargetPos场景节点的目标坐标变更 24 | .info_item { 25 | key 0 : integer 26 | value 1 : string 27 | time 2 : integer 28 | } 29 | 30 | .scene_obj_info { 31 | scene_obj_uid 0 : integer 32 | info_list 1 : *info_item 33 | } 34 | 35 | .scene_role_base_info { 36 | level 0 : integer 37 | career 1 : integer 38 | } 39 | 40 | .scene_role_looks_info { 41 | career 0 : integer 42 | body 1 : integer 43 | hair 2 : integer 44 | weapon 3 : integer 45 | wing 4 : integer 46 | horse 5 : integer 47 | name 6 : string 48 | hp 7 : integer 49 | max_hp 8 : integer 50 | } 51 | .scene_monster_info { 52 | monster_id 0 : integer 53 | monster_type_id 1 : integer 54 | hp 2 : integer 55 | maxhp 3 : integer 56 | } 57 | 58 | #flag:1时技能被打断 59 | .scene_skill_event_info { 60 | attacker_uid 0 : integer 61 | skill_id 1 : integer 62 | skill_lv 2 : integer 63 | attacker_pos_x 3 : integer 64 | attacker_pos_y 4 : integer 65 | attacker_pos_z 5 : integer 66 | target_pos_x 6 : integer 67 | target_pos_y 7 : integer 68 | target_pos_z 8 : integer 69 | direction 9 : integer 70 | time 10 : integer 71 | flag 11 : integer 72 | } 73 | 74 | #flag: 0普通扣血 1暴击 2Miss 3穿刺 4死亡 75 | .scene_hurt_defender_info { 76 | uid 0 : integer 77 | change_num 1 : integer 78 | cur_hp 2 : integer 79 | flag 3 : integer 80 | } 81 | 82 | .scene_hurt_event_info { 83 | attacker_uid 0 : integer 84 | time 1 : integer 85 | defenders 2 : *scene_hurt_defender_info 86 | } 87 | 88 | .scene_npc_info { 89 | npc_id 0 : integer 90 | npc_type_id 1 : integer 91 | } 92 | 93 | scene_get_main_role_info 100 { 94 | response { 95 | role_info 0 : scene_main_role_info 96 | } 97 | } 98 | 99 | #走路协议 100 | scene_walk 101 { 101 | request { 102 | start_x 0 : integer 103 | start_y 1 : integer 104 | start_z 2 : integer 105 | end_x 3 : integer 106 | end_z 4 : integer 107 | time 5 : integer 108 | jump_state 6 : integer 109 | } 110 | response { 111 | } 112 | } 113 | 114 | #通用状态变更协议,具体内容见上面的scene_obj_info.info_item结构体注释 115 | scene_get_objs_info_change 102 { 116 | request { 117 | } 118 | response { 119 | obj_infos 0 : *scene_obj_info 120 | } 121 | } 122 | 123 | scene_change_aoi_radius 103 { 124 | request { 125 | radius 0 : integer 126 | } 127 | } 128 | 129 | scene_get_role_look_info 104 { 130 | request { 131 | uid 0 : integer 132 | } 133 | response { 134 | result 0 : integer 135 | role_looks_info 1 : scene_role_looks_info 136 | } 137 | } 138 | 139 | scene_cast_skill 105 { 140 | request { 141 | skill_id 0 : integer 142 | cur_pos_x 1 : integer 143 | cur_pos_y 2 : integer 144 | cur_pos_z 3 : integer 145 | target_pos_x 4 : integer 146 | target_pos_y 5 : integer 147 | target_pos_z 6 : integer 148 | direction 7 : integer 149 | } 150 | response { 151 | result 0 : integer 152 | skill_id 1 : integer 153 | cd_end_time 2 : integer 154 | } 155 | } 156 | 157 | #一有别的玩家或怪物出招,自己就会收到此事件 158 | scene_listen_skill_event 106 { 159 | request { 160 | } 161 | response { 162 | events 0 : *scene_skill_event_info 163 | } 164 | } 165 | 166 | #监听扣血伤害相关的事件 167 | scene_listen_hurt_event 107 { 168 | request { 169 | } 170 | response { 171 | events 0 : *scene_hurt_event_info 172 | } 173 | } 174 | 175 | scene_enter_to 108 { 176 | request { 177 | scene_id 0 : integer 178 | door_id 1 : integer 179 | } 180 | response { 181 | result 0 : integer 182 | } 183 | } 184 | 185 | scene_relive 109 { 186 | request { 187 | relive_type 0 : integer 188 | } 189 | response { 190 | result 0 : integer 191 | relive_type 1 : integer 192 | } 193 | } 194 | 195 | ]] 196 | 197 | -------------------------------------------------------------------------------- /game/proto/proto_1_account.lua: -------------------------------------------------------------------------------- 1 | return [[ 2 | .role_figure { 3 | role_id 0 : integer 4 | career 1 : integer 5 | name 2 : string 6 | body 3 : integer 7 | hair 4 : integer 8 | } 9 | 10 | account_get_server_time 1 { 11 | response { 12 | server_time 0 : integer 13 | } 14 | } 15 | 16 | account_get_role_list 2 { 17 | response { 18 | role_list 0 : *role_figure 19 | } 20 | } 21 | 22 | account_create_role 3 { 23 | request { 24 | career 0 : integer 25 | name 1 : string 26 | } 27 | response { 28 | result 0 : integer 29 | role_id 1 : integer 30 | } 31 | } 32 | 33 | account_delete_role 4 { 34 | request { 35 | role_id 0 : integer 36 | } 37 | response { 38 | result 0 : integer 39 | } 40 | } 41 | 42 | account_select_role_enter_game 5 { 43 | request { 44 | role_id 0 : integer 45 | } 46 | response { 47 | result 0 : integer 48 | } 49 | } 50 | 51 | ]] 52 | 53 | -------------------------------------------------------------------------------- /game/proto/proto_200_task.lua: -------------------------------------------------------------------------------- 1 | return [[ 2 | 3 | #status:见TaskConst.Status:0条件不足不可接取 1可接取 2进行中 3已完成 4已领取奖励 4 | 5 | #maxProgress要求数量 6 | #curProgress已完成数量 7 | #contentID:根据不同子任务类型可为npcID或怪物id或物品id 8 | .taskInfo { 9 | taskID 0 : integer 10 | status 1 : integer 11 | subTaskIndex 2 : integer 12 | subType 3 : integer 13 | curProgress 4 : integer 14 | maxProgress 5 : integer 15 | addition 6 : string 16 | } 17 | 18 | .taskNPCTaskInfo { 19 | taskID 0 : integer 20 | } 21 | 22 | #获取任务列表 23 | Task_GetInfoList 200 { 24 | response { 25 | taskList 0 : *taskInfo 26 | } 27 | } 28 | 29 | #获取该NPC身上的任务列表 30 | Task_GetInfoListInNPC 201 { 31 | request { 32 | npcID 0 : integer 33 | } 34 | response { 35 | npcID 0 : integer 36 | taskIDList 1 : *integer 37 | content 2 : string 38 | } 39 | } 40 | 41 | Task_TakeTask 202 { 42 | request { 43 | taskID 0 : integer 44 | } 45 | response { 46 | result 0 : integer 47 | } 48 | } 49 | 50 | #有些任务阶段需要前端自己完成的,比如npc对话 51 | Task_DoTask 203 { 52 | request { 53 | taskID 0 : integer 54 | } 55 | response { 56 | result 0 : integer 57 | } 58 | } 59 | 60 | Task_ProgressChanged 204 { 61 | response { 62 | taskInfo 0 : taskInfo 63 | } 64 | } 65 | 66 | 67 | ]] -------------------------------------------------------------------------------- /game/proto/proto_300_bag.lua: -------------------------------------------------------------------------------- 1 | return [[ 2 | 3 | #goods_uid:全宇宙唯一的道具id,由服务器标识和自增数组合而成 4 | #goods_type_id:道具的类型id 5 | #pos:1普通背包 2仓库 3玩家身上装备 6 | #cell:道具在哪个格子 7 | #num:数量 8 | .goodsInfo { 9 | uid 0 : integer 10 | typeID 1 : integer 11 | pos 2 : integer 12 | cell 3 : integer 13 | num 4 : integer 14 | } 15 | 16 | .goodsDetail { 17 | 18 | } 19 | 20 | #获取背包列表,pos:1普通背包 2仓库 3玩家身上装备 21 | Bag_GetInfo 300 { 22 | request { 23 | pos 0 : integer 24 | } 25 | response { 26 | pos 0 : integer 27 | cellNum 1 : integer 28 | goodsList 2 : *goodsInfo 29 | } 30 | } 31 | 32 | #获取背包变更列表 33 | Bag_GetChangeList 301 { 34 | response { 35 | goodsList 0 : *goodsInfo 36 | } 37 | } 38 | 39 | #获取道具的详细信息 40 | Bag_GetGoodsDetail 302 { 41 | request { 42 | uid 0 : integer 43 | } 44 | response { 45 | detail 0 : goodsDetail 46 | } 47 | } 48 | 49 | #使用道具 50 | Bag_UseGoods 303 { 51 | request { 52 | uid 0 : integer 53 | num 1 : integer 54 | } 55 | response { 56 | result 0 : integer 57 | } 58 | } 59 | 60 | #出售道具 61 | Bag_SellGoods 304 { 62 | request { 63 | uid 0 : integer 64 | num 1 : integer 65 | } 66 | response { 67 | result 0 : integer 68 | } 69 | } 70 | 71 | #丢掉道具 72 | Bag_DropGoods 305 { 73 | request { 74 | uid 0 : integer 75 | } 76 | response { 77 | result 0 : integer 78 | } 79 | } 80 | 81 | #整理背包 82 | Bag_Sort 306 { 83 | request { 84 | pos 0 : integer 85 | } 86 | response { 87 | result 0 : integer 88 | } 89 | } 90 | ]] 91 | 92 | -------------------------------------------------------------------------------- /game/proto/proto_400_gm.lua: -------------------------------------------------------------------------------- 1 | return [[ 2 | 3 | #gmName:在前端显示的按钮名字 4 | #defaultGMStr:默认的gm字符串 5 | .gmInfo { 6 | gmName 0 : string 7 | defaultGMStr 1 : string 8 | } 9 | #获取可用的GM列表 10 | GM_GetList 400 { 11 | response { 12 | gmList 0 : *gmInfo 13 | } 14 | } 15 | 16 | #获取可用的GM列表 17 | GM_Excute 401 { 18 | request { 19 | gmStr 0 : string 20 | } 21 | response { 22 | ret 0 : integer 23 | gmStr 1 : string 24 | } 25 | } 26 | 27 | ]] 28 | 29 | -------------------------------------------------------------------------------- /game/proto/proto_500_chat.lua: -------------------------------------------------------------------------------- 1 | return [[ 2 | 3 | .chatInfo { 4 | roleID 0 : integer 5 | name 1 : string 6 | content 2 : string 7 | career 3 : integer 8 | lv 4 : integer 9 | vip 5 : integer 10 | headID 6 : integer 11 | headUrl 7 : string 12 | bubbleID 8 : integer 13 | time 9 : integer 14 | channel 10 : integer 15 | extra 11 : string 16 | id 12 : integer 17 | } 18 | 19 | #获取某频道的历史聊天记录 20 | Chat_GetHistory 500 { 21 | request { 22 | channel 0 : integer 23 | } 24 | response { 25 | channel 0 : integer 26 | list 1 : *chatInfo 27 | } 28 | } 29 | 30 | #发送聊天 31 | Chat_Send 501 { 32 | request { 33 | channel 0 : integer 34 | content 1 : string 35 | extra 2 : string 36 | } 37 | response { 38 | ret 0 : integer 39 | } 40 | } 41 | 42 | #监听新聊天信息 43 | Chat_GetNew 502 { 44 | response { 45 | list 0 : *chatInfo 46 | } 47 | } 48 | ]] 49 | 50 | -------------------------------------------------------------------------------- /game/scene/ECSSystemMgr.lua: -------------------------------------------------------------------------------- 1 | local ECSSystemMgr = BaseClass() 2 | 3 | function ECSSystemMgr:Init( world, sceneMgr ) 4 | self.ecsSystemList = {} 5 | self.delayDestroyList = {} 6 | self.entityMgr = sceneMgr.entityMgr 7 | local arge = { 8 | sceneMgr=sceneMgr 9 | } 10 | local systems = { 11 | -- "UMO.DamageSystem", 12 | "UMO.AISystem", 13 | "UMO.MovementUpdateSystem", 14 | --skill sys 15 | -- "UMO.SkillSys", 16 | } 17 | 18 | for i,v in ipairs(systems) do 19 | local system = world:CreateManager(v, arge) 20 | table.insert(self.ecsSystemList, system) 21 | end 22 | end 23 | 24 | function ECSSystemMgr:Update( delta_time ) 25 | for i,v in ipairs(self.ecsSystemList) do 26 | v:Update() 27 | end 28 | for i,v in ipairs(self.delayDestroyList) do 29 | -- print('Cat:ECSSystemMgr.lua[29]destroy v', v) 30 | self.entityMgr:DestroyEntity(v) 31 | end 32 | self.delayDestroyList = {} 33 | end 34 | 35 | function ECSSystemMgr:AddDestroyEntity( entity ) 36 | -- print('Cat:ECSSystemMgr.lua[36]AddDestroyEntity entity', entity) 37 | table.insert(self.delayDestroyList, entity) 38 | end 39 | 40 | return ECSSystemMgr -------------------------------------------------------------------------------- /game/scene/EventMgr.lua: -------------------------------------------------------------------------------- 1 | local SceneEventMgr = BaseClass() 2 | local table_insert = table.insert 3 | 4 | function SceneEventMgr:Init( sceneMgr ) 5 | self.sceneMgr = sceneMgr 6 | self.event_list = {} 7 | self.skill_event_list = {} 8 | self.hurt_event_list = {} 9 | end 10 | 11 | function SceneEventMgr:AddSceneEvent( uid, eventInfo ) 12 | self.event_list[uid] = self.event_list[uid] or {} 13 | table_insert(self.event_list[uid], eventInfo) 14 | end 15 | 16 | function SceneEventMgr:GetSceneEvent( uid ) 17 | return self.event_list[uid] 18 | end 19 | 20 | function SceneEventMgr:ClearAllSceneEvents( ) 21 | self.event_list = {} 22 | end 23 | 24 | function SceneEventMgr:AddSkillEvent( uid, eventInfo ) 25 | self.skill_event_list[uid] = self.skill_event_list[uid] or {} 26 | table_insert(self.skill_event_list[uid], eventInfo) 27 | end 28 | 29 | function SceneEventMgr:GetSkillEvent( uid ) 30 | return self.skill_event_list[uid] 31 | end 32 | 33 | function SceneEventMgr:ClearAllSkillEvents( ) 34 | self.skill_event_list = {} 35 | end 36 | 37 | function SceneEventMgr:AddHurtEvent( uid, eventInfo ) 38 | self.hurt_event_list[uid] = self.hurt_event_list[uid] or {} 39 | table_insert(self.hurt_event_list[uid], eventInfo) 40 | end 41 | 42 | function SceneEventMgr:GetHurtEvent( uid ) 43 | return self.hurt_event_list[uid] 44 | end 45 | 46 | function SceneEventMgr:ClearAllHurtEvents( ) 47 | self.hurt_event_list = {} 48 | end 49 | 50 | return SceneEventMgr -------------------------------------------------------------------------------- /game/scene/FightHelper.lua: -------------------------------------------------------------------------------- 1 | local ECS = require "ECS" 2 | local skynet = require "skynet" 3 | local skill_cfg = require "game.config.scene.config_skill" 4 | local math_random = math.random 5 | local FightHelper = {} 6 | 7 | function FightHelper:Init( sceneMgr ) 8 | self.sceneMgr = sceneMgr 9 | self.entityMgr = sceneMgr.entityMgr 10 | end 11 | 12 | local randomAddOrMinus = function ( ) 13 | local isAdd = math_random(1,2)==1 14 | return isAdd and 1 or -1 15 | end 16 | 17 | --获取可攻击的坐标 18 | function FightHelper:GetAssailablePos( curPos, targetPos, minDistance, maxDistance ) 19 | local xAddOrMinus = randomAddOrMinus() 20 | local xRandomDis = math_random(0, (maxDistance-minDistance)) 21 | local pos_x = targetPos.x + minDistance*xAddOrMinus + xRandomDis*xAddOrMinus 22 | local pos_y = targetPos.y 23 | local zAddOrMinus = randomAddOrMinus() 24 | local zRandomDis = math_random(0, (maxDistance-minDistance)) 25 | local pos_z = targetPos.z + minDistance*zAddOrMinus + zRandomDis*zAddOrMinus 26 | local newPos = {x=pos_x, y=pos_y, z=pos_z} 27 | return newPos 28 | end 29 | 30 | function FightHelper:IsSkillInCD( entity, skillID ) 31 | local hasCD = self.entityMgr:HasComponent(entity, "UMO.CD") 32 | if hasCD then 33 | local cdData = self.entityMgr:GetComponentData(entity, "UMO.CD") 34 | local cdEndTime = cdData[skillID] 35 | return cdEndTime and Time.timeMS <= cdEndTime 36 | end 37 | return false 38 | end 39 | 40 | function FightHelper:GetSkillCD( skillID, lv ) 41 | lv = lv or 1 42 | local cfg = skill_cfg[skillID] 43 | if cfg and cfg.detail[lv] then 44 | return cfg.detail[lv].cd 45 | end 46 | return 0 47 | end 48 | 49 | function FightHelper:ApplySkillCD( entity, skillID, lv ) 50 | local hasCD = self.entityMgr:HasComponent(entity, "UMO.CD") 51 | local endTime = 0 52 | if hasCD then 53 | local cdData = self.entityMgr:GetComponentData(entity, "UMO.CD") 54 | local cd = self:GetSkillCD(skillID, lv) 55 | endTime = Time.timeMS + cd 56 | cdData[skillID] = endTime 57 | end 58 | return endTime 59 | end 60 | 61 | function FightHelper:IsLive( entity ) 62 | if entity and self.entityMgr:Exists(entity) and self.entityMgr:HasComponent(entity, "UMO.HP") then 63 | local hpData = self.entityMgr:GetComponentData(entity, "UMO.HP") 64 | return hpData.cur > 0 65 | end 66 | return false 67 | end 68 | 69 | function FightHelper:ChangeHP( entity, hp, offsetValue, attacker ) 70 | if hp.cur <= 0 then return end 71 | hp.cur = hp.cur - offsetValue 72 | if hp.cur <= 0 then 73 | hp.cur = 0 74 | hp.killedBy = attacker 75 | hp.deathTime = Time.time 76 | end 77 | local uid = self.entityMgr:GetComponentData(entity, "UMO.UID") 78 | if hp.cur <= 0 then 79 | --enter dead state 80 | if self.entityMgr:HasComponent(entity, "UMO.MonsterAI") then 81 | self.sceneMgr.monsterMgr:TriggerState(uid, "DeadState") 82 | local killer = self.sceneMgr:GetEntity(hp.killedBy) 83 | if self.entityMgr:HasComponent(killer, "UMO.MsgAgent") then 84 | local agent = self.entityMgr:GetComponentData(killer, "UMO.MsgAgent") 85 | local roleID = self.entityMgr:GetComponentData(killer, "UMO.TypeID") 86 | local monsterID = self.entityMgr:GetComponentData(entity, "UMO.TypeID") 87 | skynet.send(agent, "lua", "execute", "Task", "KillMonster", roleID, monsterID, 1) 88 | end 89 | end 90 | end 91 | end 92 | 93 | function FightHelper:ChangeSpeed( entity, victim_uid, caster_uid, bodName, isSet, speed ) 94 | if not entity then return end 95 | local isExist = self.entityMgr:Exists(entity) 96 | if not isExist then return end 97 | local speedData = self.entityMgr:GetComponentData(entity, "UMO.MoveSpeed") 98 | speedData:ChangeSpeed(bodName, isSet, speed) 99 | local buffEvent = { 100 | key = SceneConst.InfoKey.Speed, 101 | value = string.format("%s,%s,%s,%s", bodName, isSet and 1 or 0, speed and math.floor(speed) or 0, caster_uid), 102 | } 103 | self.sceneMgr.eventMgr:AddSceneEvent(victim_uid, buffEvent) 104 | end 105 | 106 | function FightHelper:ChangeTargetPos( entity, pos ) 107 | local speed = self.entityMgr:GetComponentData(entity, "UMO.MoveSpeed") 108 | if speed.curSpeed <= 0 then 109 | return 110 | end 111 | self.entityMgr:SetComponentData(entity, "UMO.TargetPos", pos) 112 | local uid = self.entityMgr:GetComponentData(entity, "UMO.UID") 113 | local change_target_pos_event_info = {key=SceneConst.InfoKey.TargetPos, value=math.floor(pos.x)..","..math.floor(pos.z), time=Time.timeMS} 114 | self.sceneMgr.eventMgr:AddSceneEvent(uid, change_target_pos_event_info) 115 | end 116 | 117 | return FightHelper -------------------------------------------------------------------------------- /game/scene/Global.lua: -------------------------------------------------------------------------------- 1 | ErrorCode = require "game.config.ErrorCode" 2 | SceneConst = require "game.scene.SceneConst" 3 | require "common.TableUtil" 4 | require "common.util" 5 | require "common.BaseClass" 6 | Time = require "game.scene.Time" 7 | Vector3 = require "game.util.Vector3" 8 | TablePool = require "game.util.TablePool" 9 | BOD = require "common.BOD" 10 | require "common.helper" 11 | require "common.MathUtil" 12 | -------------------------------------------------------------------------------- /game/scene/MonsterConst.lua: -------------------------------------------------------------------------------- 1 | local const = { 2 | -- monster_state = { 3 | -- patrol = 1,--巡逻 4 | -- chase = 2,--追逐 5 | -- fighting = 3,--战斗 6 | -- dead = 4,--死亡 7 | -- }, 8 | -- monster_sub_state = { 9 | -- enter = 1, 10 | -- update = 2, 11 | -- leave = 3, 12 | -- }, 13 | } 14 | return const -------------------------------------------------------------------------------- /game/scene/NPCMgr.lua: -------------------------------------------------------------------------------- 1 | local npc_cfg = require "game.config.scene.config_npc" 2 | local SceneHelper = require "game.scene.SceneHelper" 3 | local SceneConst = require "game.scene.SceneConst" 4 | -- local BP = require("Blueprint") 5 | local NPCMgr = BaseClass() 6 | local test_info = { 7 | } 8 | 9 | function NPCMgr:Init( sceneMgr, npcCfgList ) 10 | self.sceneMgr = sceneMgr 11 | self.entityMgr = sceneMgr.entityMgr 12 | self.aoi = sceneMgr.aoi 13 | self.npcCfgList = npcCfgList 14 | 15 | self:InitArchetype() 16 | self:InitNPC() 17 | end 18 | 19 | function NPCMgr:InitArchetype( ) 20 | self.npc_archetype = self.entityMgr:CreateArchetype({ 21 | "UMO.Position", "UMO.UID", "UMO.TypeID", "UMO.SceneObjType", "UMO.AOIHandle", 22 | }) 23 | end 24 | 25 | function NPCMgr:InitNPC( ) 26 | for i,v in ipairs(self.npcCfgList) do 27 | self:CreateNPC(v.npc_id, v.pos_x, v.pos_y, v.pos_z) 28 | end 29 | end 30 | 31 | function NPCMgr:CreateNPC( type_id, pos_x, pos_y, pos_z ) 32 | local cfg = npc_cfg[type_id] 33 | if not cfg then return end 34 | 35 | local entity = self.entityMgr:CreateEntityByArcheType(self.npc_archetype) 36 | self.entityMgr:SetComponentData(entity, "UMO.Position", {x=pos_x, y=pos_y, z=pos_z}) 37 | local scene_uid = SceneHelper:NewSceneUID(SceneConst.ObjectType.NPC) 38 | self.entityMgr:SetComponentData(entity, "UMO.UID", scene_uid) 39 | self.entityMgr:SetComponentData(entity, "UMO.TypeID", type_id) 40 | self.entityMgr:SetComponentData(entity, "UMO.SceneObjType", {value=SceneConst.ObjectType.NPC}) 41 | 42 | local handle = self.aoi:add() 43 | self.aoi:set_user_data(handle, "uid", scene_uid) 44 | -- self.aoi:set_user_data(handle, "entity", entity) 45 | self.sceneMgr:SetAOI(handle, scene_uid) 46 | self.aoi:set_pos(handle, pos_x, pos_y, pos_z) 47 | self.entityMgr:SetComponentData(entity, "UMO.AOIHandle", {value=handle}) 48 | 49 | self.sceneMgr:SetEntity(scene_uid, entity) 50 | 51 | return entity 52 | end 53 | 54 | return NPCMgr -------------------------------------------------------------------------------- /game/scene/SceneConst.lua: -------------------------------------------------------------------------------- 1 | local SceneConst = { 2 | --SceneConst.SceneConst.ObjectType.Role 3 | ObjectType={ 4 | Role=1,Monster=2,NPC=3, 5 | }, 6 | --SceneConst.InfoKey.EnterView 7 | InfoKey = { 8 | EnterView=1, 9 | LeaveView=2, 10 | PosChange=3, 11 | TargetPos=4, 12 | JumpState=5, 13 | HPChange=6, 14 | NPCState=7, 15 | SceneChange=8, 16 | Buff=9, 17 | Speed=10, 18 | Exp=11, 19 | }, 20 | --有些类型的事件,就算是自己的也要发给前端 21 | InterestSelfEvent = { 22 | [3] = true, [6] = true, 23 | }, 24 | --SceneConst.Attr.Att 25 | Attr = { 26 | Att = 1,--攻击 27 | HP = 2,--血量 28 | Def = 3,--防御 29 | Crit = 4,--暴击 30 | Hit = 5,--命中 31 | Dodge = 6,--闪避 32 | }, 33 | --SceneConst.AttrStrMap["att"] 34 | AttrStrMap = { 35 | ["att"] = 1, 36 | ["hp"] = 2, 37 | ["def"] = 3, 38 | ["crit"] = 4, 39 | ["hit"] = 5, 40 | ["dodge"] = 6, 41 | }, 42 | --SceneConst.Buff.Attr 43 | Buff = { 44 | Attr = 1,--属性buff,改变某属性值,比如攻击,暴击,防御等 45 | Fire = 2,--火,定时扣血类型 46 | Poison = 3,--毒,定时扣血类型 47 | Forzen = 4,--冰冻,动不了加定时扣血类型 48 | Dizzy = 5,--晕眩,动不了 49 | HpToMp = 6,--用MP代替HP伤害 50 | Silence = 7,--沉默,发不出技能 51 | Speed = 8,--控制速度 52 | ClearBadBuff = 9,--消除不良buff 53 | HurtPower = 10,--伤害加成 54 | Suck = 11,--吸过来 55 | HPShield = 12,--血量盾牌 56 | Chaos = 13,--混乱,到处跑 57 | Sneer = 14,--嘲讽,只能普攻 58 | Shapeshift = 15,--变身 59 | Invisible = 16,--隐身 60 | FilpControl = 17,--反转控制,比如按前即向后走 61 | SkillTargetMaxNum = 19,--改变发出技能的最大攻击数量 62 | Exp = 20,--经验加成 63 | GoodsDrop = 21,--道具掉率 64 | Rebound = 22,--反弹 65 | SuckHP = 23,--吸血 66 | }, 67 | --SceneConst.SkillTargetType.Enemy 68 | SkillTargetType = { 69 | Enemy = 1,--敌方 70 | Me = 2,--自己 71 | Our = 3,--我方 72 | }, 73 | --SceneConst.ReliveType.SafeArea 74 | ReliveType = { 75 | SafeArea = 1, 76 | Inplace = 2, 77 | }, 78 | --SceneConst.ClearBuffType.All 79 | ClearBuffType = { 80 | All = 1, 81 | Bad = 2, 82 | Good = 3, 83 | }, 84 | } 85 | 86 | return SceneConst -------------------------------------------------------------------------------- /game/scene/SceneHelper.lua: -------------------------------------------------------------------------------- 1 | local SceneConst = require "game.scene.SceneConst" 2 | local Time = Time 3 | local SceneHelper = { 4 | scene_uid_counter = {0, 0, 0}, 5 | } 6 | 7 | function SceneHelper:NewSceneUID( scene_obj_type ) 8 | self.scene_uid_counter[scene_obj_type] = self.scene_uid_counter[scene_obj_type] + 1 9 | return scene_obj_type*10000000000 + self.scene_uid_counter[scene_obj_type] 10 | end 11 | 12 | --返回SceneConst.ObjectType枚举类型 13 | function SceneHelper:GetSceneObjTypeByUID( scene_uid ) 14 | local type = math.floor(scene_uid/10000000000) 15 | return type 16 | end 17 | 18 | function SceneHelper.AddInfoItem( change_obj_infos, scene_uid, info_item ) 19 | change_obj_infos = change_obj_infos or {obj_infos={}} 20 | local cur_info = nil 21 | for i,v in ipairs(change_obj_infos.obj_infos) do 22 | if v.scene_obj_uid == scene_uid then 23 | cur_info = v 24 | end 25 | end 26 | if not cur_info then 27 | cur_info = {scene_obj_uid=scene_uid, info_list={}} 28 | table.insert(change_obj_infos.obj_infos, cur_info) 29 | end 30 | table.insert(cur_info.info_list, info_item) 31 | return change_obj_infos 32 | end 33 | 34 | function SceneHelper.ChangePos( entity, pos, entityMgr, eventMgr ) 35 | entityMgr:SetComponentData(entity, "UMO.Position", pos) 36 | entityMgr:SetComponentData(entity, "UMO.TargetPos", pos) 37 | local uid = entityMgr:GetComponentData(entity, "UMO.UID") 38 | local change_pos_event_info = {key=SceneConst.InfoKey.PosChange, value=math.floor(pos.x)..","..math.floor(pos.y)..","..math.floor(pos.z), time=Time.timeMS} 39 | eventMgr:AddSceneEvent(uid, change_pos_event_info) 40 | end 41 | 42 | return SceneHelper -------------------------------------------------------------------------------- /game/scene/Time.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynet" 2 | 3 | local Time = { 4 | time = 0, 5 | deltaTime = 0, 6 | lastUpdateTime = 0, 7 | } 8 | 9 | function Time:update( curTime, deltaTime ) 10 | local skynetTime = skynet.time() 11 | self.time = skynetTime 12 | self.timeMS = math.floor(skynetTime*1000+0.5)--Cat_Todo : 应该加一个每次都取最新值的,这样更加精确 13 | self.deltaTime = (self.time-(self.lastUpdateTime or self.time)) 14 | self.deltaTimeMS = math.floor(self.deltaTime*1000+0.5) 15 | self.lastUpdateTime = self.time 16 | end 17 | 18 | return Time -------------------------------------------------------------------------------- /game/scene/ai/BlueprintRegister.lua: -------------------------------------------------------------------------------- 1 | local BP = require("Blueprint") 2 | 3 | local BlueprintRegister = {} 4 | 5 | function BlueprintRegister:register_all( ) 6 | BP.TypeManager:RegisterType("Blueprint.State.PatrolState", require("game.scene.ai.state.PatrolState")) 7 | BP.TypeManager:RegisterType("Blueprint.State.FightState", require("game.scene.ai.state.FightState")) 8 | BP.TypeManager:RegisterType("Blueprint.State.DeadState", require("game.scene.ai.state.DeadState")) 9 | end 10 | 11 | return BlueprintRegister -------------------------------------------------------------------------------- /game/scene/ai/MonsterFSM.lua: -------------------------------------------------------------------------------- 1 | local MonsterFSM = { 2 | nodes = { 3 | { 4 | id = 1, 5 | type = "Blueprint.State.PatrolState", 6 | name = "PatrolState", 7 | }, 8 | { 9 | id = 2, 10 | type = "Blueprint.State.FightState", 11 | name = "FightState", 12 | }, 13 | { 14 | id = 3, 15 | type = "Blueprint.State.DeadState", 16 | name = "DeadState", 17 | }, 18 | }, 19 | } 20 | return MonsterFSM -------------------------------------------------------------------------------- /game/scene/ai/state/DeadState.lua: -------------------------------------------------------------------------------- 1 | local BP = require("Blueprint") 2 | local SceneConst = require "game.scene.SceneConst" 3 | local SceneHelper = require "game.scene.SceneHelper" 4 | local Time = Time 5 | local DeadState = BP.BaseClass(BP.FSM.FSMState) 6 | 7 | local SubState = { 8 | Catch = 1,--追捕 9 | Fighting = 2,--攻击 10 | } 11 | function DeadState:OnInit( ) 12 | -- print('Cat:DeadState.lua[OnInit]') 13 | self.entity = self.blackboard:GetVariable("entity") 14 | self.sceneMgr = self.blackboard:GetVariable("sceneMgr") 15 | self.entityMgr = self.sceneMgr.entityMgr 16 | self.monsterMgr = self.sceneMgr.monsterMgr 17 | self.cfg = self.blackboard:GetVariable("cfg") 18 | self.patrolInfo = self.entityMgr:GetComponentData(self.entity, "UMO.PatrolInfo") 19 | 20 | self.uid = self.entityMgr:GetComponentData(self.entity, "UMO.UID") 21 | end 22 | 23 | function DeadState:OnEnter( ) 24 | -- print('Cat:DeadState.lua[OnEnter]') 25 | self.dead_time = Time.time 26 | self.wait_for_relive = self.cfg.ai.reborn_time/1000 27 | end 28 | 29 | function DeadState:Relive( ) 30 | local hpData = self.entityMgr:GetComponentData(self.entity, "UMO.HP") 31 | hpData.cur = hpData.max 32 | local change_target_pos_event_info = {key=SceneConst.InfoKey.HPChange, value=math.floor(hpData.cur)..",relive", time=Time.timeMS} 33 | self.sceneMgr.eventMgr:AddSceneEvent(self.uid, change_target_pos_event_info) 34 | --set a new position 35 | local radius = self.patrolInfo.radius/2 36 | local randomPos = { 37 | x=self.patrolInfo.x + math.random(-radius, radius), 38 | y=self.patrolInfo.y + math.random(-radius, radius), 39 | z=self.patrolInfo.z + math.random(-radius, radius) 40 | } 41 | SceneHelper.ChangePos(self.entity, randomPos, self.entityMgr, self.sceneMgr.eventMgr) 42 | 43 | self.fsm:TriggerState("PatrolState") 44 | end 45 | 46 | function DeadState:OnUpdate( ) 47 | if Time.time - self.dead_time > self.wait_for_relive then 48 | self:Relive() 49 | end 50 | end 51 | 52 | function DeadState:OnExit( ) 53 | end 54 | 55 | function DeadState:OnPause( ) 56 | end 57 | 58 | return DeadState -------------------------------------------------------------------------------- /game/scene/ai/state/FightState.lua: -------------------------------------------------------------------------------- 1 | local BP = require("Blueprint") 2 | local FightHelper = require("game.scene.FightHelper") 3 | local Time = Time 4 | local FightState = BP.BaseClass(BP.FSM.FSMState) 5 | 6 | local SubState = { 7 | Chase = 1,--追捕 8 | Fighting = 2,--攻击 9 | GoBack = 3,--回到巡逻点 10 | } 11 | function FightState:OnInit( ) 12 | self.aoi_handle = self.blackboard:GetVariable("aoi_handle") 13 | self.aoi_area = self.blackboard:GetVariable("aoi_area") 14 | self.entity = self.blackboard:GetVariable("entity") 15 | self.sceneMgr = self.blackboard:GetVariable("sceneMgr") 16 | self.aoi = self.sceneMgr.aoi 17 | self.entityMgr = self.sceneMgr.entityMgr 18 | self.monsterMgr = self.sceneMgr.monsterMgr 19 | self.cfg = self.blackboard:GetVariable("cfg") 20 | 21 | self.uid = self.entityMgr:GetComponentData(self.entity, "UMO.UID") 22 | end 23 | 24 | function FightState:OnEnter( ) 25 | self.targetEnemyEntity = self.blackboard:GetVariable("targetEnemyEntity") 26 | self:SetSubState(SubState.Chase) 27 | end 28 | 29 | function FightState:OnUpdate( ) 30 | if self.sub_state == SubState.Chase then 31 | local isLive = FightHelper:IsLive(self.targetEnemyEntity) 32 | if isLive then 33 | if not self.last_retarget_time or Time.time-self.last_retarget_time > 0.5 then 34 | self.last_retarget_time = Time.time 35 | --判断是否进入攻击范围,是则发起攻击,否则追上去 36 | local myPos = self.entityMgr:GetComponentData(self.entity, "UMO.Position") 37 | local enemyPos = self.entityMgr:GetComponentData(self.targetEnemyEntity, "UMO.Position") 38 | local distanceFromTargetSqrt = Vector3.Distance(myPos, enemyPos) 39 | local isMaxOk = distanceFromTargetSqrt <= self.cfg.ai.attack_area.max_distance 40 | local isMinOk = distanceFromTargetSqrt >= self.cfg.ai.attack_area.min_distance 41 | local isOverHuntDis = distanceFromTargetSqrt > self.cfg.ai.hunt_radius 42 | if isOverHuntDis then 43 | self.targetEnemyEntity = nil 44 | self.blackboard:SetVariable("targetEnemyEntity", nil) 45 | self.fsm:TriggerState("PatrolState") 46 | elseif isMaxOk and isMinOk then 47 | --离敌人距离刚好,发动攻击 48 | self:Attack() 49 | else 50 | --离敌人太远或太近了,走位移动到可攻击的目标点 51 | local newPos = FightHelper:GetAssailablePos(myPos, enemyPos, self.cfg.ai.attack_area.min_distance, self.cfg.ai.attack_area.max_distance) 52 | FightHelper:ChangeTargetPos(self.entity, newPos) 53 | end 54 | end 55 | else 56 | --没有攻击目标了,回去耕田吧 57 | self.targetEnemyEntity = nil 58 | self.fsm:TriggerState("PatrolState") 59 | end 60 | end 61 | end 62 | 63 | function FightState:Attack( ) 64 | if self.last_attack_time and Time.time-self.last_attack_time < self.last_attack_duration then 65 | return 66 | end 67 | self.last_attack_time = Time.time 68 | self.last_attack_duration = math.random(2, 5) 69 | --先随机挑个技能 70 | if not self.cfg or not self.cfg.ai.skill_list then return end 71 | local ability = self.entityMgr:GetComponentData(self.entity, "UMO.Ability") 72 | local canCastSkill = ability.CastSkill.value 73 | if not canCastSkill then return end 74 | 75 | local skill_id = nil 76 | local randomNum = math.random(1,100) 77 | local elapsedNum = 0 78 | for i,v in ipairs(self.cfg.ai.skill_list) do 79 | if randomNum <= v.random+elapsedNum then 80 | skill_id = v.skill_id 81 | break 82 | end 83 | elapsedNum = elapsedNum + v.random 84 | end 85 | local pos = self.entityMgr:GetComponentData(self.entity, "UMO.Position") 86 | local enemyPos = self.entityMgr:GetComponentData(self.targetEnemyEntity, "UMO.Position") 87 | local defender_uid = self.entityMgr:GetComponentData(self.targetEnemyEntity, "UMO.UID") 88 | 89 | self.sceneMgr.fightMgr:CastSkill(self.uid, { 90 | skill_id = skill_id, 91 | cur_pos_x = math.floor(pos.x), 92 | cur_pos_y = math.floor(pos.y), 93 | cur_pos_z = math.floor(pos.z), 94 | target_pos_x = math.floor(enemyPos.x), 95 | target_pos_y = math.floor(enemyPos.y), 96 | target_pos_z = math.floor(enemyPos.z), 97 | targets = {[defender_uid]=true}, 98 | }) 99 | 100 | end 101 | 102 | function FightState:SetSubState( sub_state ) 103 | self.sub_state = sub_state 104 | self.sub_elapsed_time = Time.time 105 | if sub_state == SubState.Chase then 106 | end 107 | end 108 | 109 | function FightState:OnExit( ) 110 | end 111 | 112 | function FightState:OnPause( ) 113 | end 114 | 115 | return FightState -------------------------------------------------------------------------------- /game/scene/com/Components.lua: -------------------------------------------------------------------------------- 1 | local ECS = require "ECS" 2 | 3 | ECS.TypeManager.RegisterType("UMO.Position", {x=0, y=0, z=0}) 4 | ECS.TypeManager.RegisterType("UMO.TargetPos", {x=0, y=0, z=0}) 5 | ECS.TypeManager.RegisterType("UMO.UID", 0) 6 | ECS.TypeManager.RegisterType("UMO.TypeID", 0)--Role类型的话对应RoleID,Monster和NPC类型对应TypeID 7 | ECS.TypeManager.RegisterType("UMO.SceneObjType", {value=0}) 8 | ECS.TypeManager.RegisterType("UMO.HP", {cur=0, max=0, deathTime=0, killedBy}) 9 | ECS.TypeManager.RegisterType("UMO.MoveSpeed", nil) 10 | ECS.TypeManager.RegisterType("UMO.PatrolInfo", {x=0, y=0, z=0, radius=0})--怪物有一个中心点,以此点划圆巡逻 11 | ECS.TypeManager.RegisterType("UMO.AOIHandle", {value=0}) 12 | --CD数组,元素结构为{[skill_id]=end_time} 13 | ECS.TypeManager.RegisterType("UMO.CD", {}) 14 | ECS.TypeManager.RegisterType("UMO.Skill", {caster_uid=0, cast_time=0, skill_id=0, skill_lv=0, targets={}, max_target_num=0}) 15 | ECS.TypeManager.RegisterType("UMO.Buff", {}) 16 | ECS.TypeManager.RegisterType("UMO.BaseAttr", nil) 17 | ECS.TypeManager.RegisterType("UMO.FightAttr", nil) 18 | 19 | --里面是个数组,元素结构:attacker攻击者,damage伤害值,direction攻击方向,impulse推力 20 | ECS.TypeManager.RegisterType("UMO.DamageEvents", {}) 21 | 22 | ECS.TypeManager.RegisterType("UMO.MonsterAI", 0) 23 | ECS.TypeManager.RegisterType("UMO.RoleInfo", {name="", base_info=nil}) 24 | --0活着的,1死了,2幽灵 25 | -- ECS.TypeManager.RegisterType("UMO.DeadState", 0) 26 | ECS.TypeManager.RegisterType("UMO.Beatable", true)--是否可被打的 27 | ECS.TypeManager.RegisterType("UMO.Ability", {NormalAtk=BOD.New(), CastSkill=BOD.New(), Jump=BOD.New()})--各种能力 28 | ECS.TypeManager.RegisterType("UMO.MsgAgent", nil) 29 | -------------------------------------------------------------------------------- /game/scene/com/SpeedData.lua: -------------------------------------------------------------------------------- 1 | local SpeedData = BaseClass() 2 | --因为速度会有多方同时改变到,所以获取当前速度时应该要考虑各方的设置,比如 A 状态下设置速度为0,状态结束后设回原来速度,但如果 A 状态期间又发生 B 状况需要把速度改成其它值,这时是不让改的,所以必须记录各种状态的速度设置。 3 | function SpeedData:Constructor( baseSpeed, curSpeed ) 4 | self.baseSpeed = baseSpeed 5 | self.curSpeed = curSpeed or baseSpeed 6 | self.bod = {}--董事会缩写:board of directors 7 | end 8 | 9 | --bodName : 每次更改速度都要指定是哪个董事会成员的意见, 10 | --isSet: true 时即发表意见,false 时为撤消意见 11 | --offset:变更万分比,0时即不动 12 | function SpeedData:ChangeSpeed( bodName, isSet, offset ) 13 | if isSet then 14 | self.bod[bodName] = offset 15 | else 16 | self.bod[bodName] = nil 17 | end 18 | self:UpdateSpeed() 19 | end 20 | 21 | function SpeedData:UpdateSpeed( ) 22 | local hasZero = self:HasZero() 23 | if hasZero then 24 | self.curSpeed = 0 25 | else 26 | local factor = 0 27 | for k,v in pairs(self.bod) do 28 | factor = factor + v 29 | end 30 | self.curSpeed = self.baseSpeed + self.baseSpeed*factor 31 | end 32 | end 33 | 34 | function SpeedData:HasZero( ) 35 | local hasZero = false 36 | for k,v in pairs(self.bod) do 37 | if v == 0 then 38 | hasZero = true 39 | break 40 | end 41 | end 42 | return hasZero 43 | end 44 | 45 | return SpeedData -------------------------------------------------------------------------------- /game/scene/fight/Ability.lua: -------------------------------------------------------------------------------- 1 | local Ac = require "Action" 2 | -- local FightHelper = require("game.scene.FightHelper") 3 | -- local SceneConst = require "game.scene.SceneConst" 4 | local Ability = Ac.OO.Class { 5 | type = "Ability", 6 | __index = { 7 | Start = function(self, buffData) 8 | self.buffData = buffData 9 | self.sceneMgr = buffData.sceneMgr 10 | self.entityMgr = self.sceneMgr.entityMgr 11 | end, 12 | IsDone = function(self) 13 | return true 14 | end, 15 | Update = function(self, deltaTime) 16 | if not self.buffData.victim_entity then return end 17 | local isExist = self.entityMgr:Exists(self.buffData.victim_entity) 18 | if not isExist then return end 19 | local ability = self.entityMgr:GetComponentData(self.buffData.victim_entity, "UMO.Ability") 20 | ability.NormalAtk:Change(self.bod, self.is_set, self.value) 21 | ability.CastSkill:Change(self.bod, self.is_set, self.value) 22 | end, 23 | }, 24 | } 25 | return Ability -------------------------------------------------------------------------------- /game/scene/fight/Attr.lua: -------------------------------------------------------------------------------- 1 | local Ac = require "Action" 2 | --[[ 3 | 用法: Attr {duration=持续毫秒,attr_id=属性索引,value=偏移数值,is_percent=是否是绝对值,否则就是万份比} 4 | e.g. Attr {duration=1200, attr_id=3, value=-1000} 扣万份之1000的防御(防御索引是3,见SceneConst.Attr),持续1200毫秒 5 | Attr {duration=2000, attr_id=1, value=500, is_percent=true} 加500点攻击,持续2000毫秒 6 | --]] 7 | local ChangeAttr = function ( self, additionValue ) 8 | local attr_id = self.attr_id 9 | local fightAttr = self.entityMgr:GetComponentData(self.buffData.victim_entity, "UMO.FightAttr") 10 | local curValue = fightAttr[attr_id] 11 | if self.is_percent then 12 | --绝对值 13 | fightAttr[attr_id] = math.max(0, curValue + additionValue) 14 | else 15 | --默认是万分比 16 | local baseAttr = self.entityMgr:GetComponentData(self.buffData.victim_entity, "UMO.BaseAttr") 17 | local baseAttrValue = baseAttr[attr_id] or 0 18 | local additionAbsValue = baseAttrValue*additionValue/10000 19 | fightAttr[attr_id] = math.max(0, curValue+additionAbsValue) 20 | end 21 | return fightAttr[attr_id]-curValue 22 | end 23 | local Attr = Ac.OO.Class { 24 | type = "Attr", 25 | __index = { 26 | Start = function(self, buffData) 27 | self.buffData = buffData 28 | self.entityMgr = buffData.sceneMgr.entityMgr 29 | self.elapsed = 0 30 | self.isDone = false 31 | self.state = "start" 32 | end, 33 | IsDone = function(self) 34 | return self.isDone, true 35 | end, 36 | Update = function(self, deltaTime) 37 | self.elapsed = self.elapsed + deltaTime 38 | self.isDone = self.elapsed >= self.duration 39 | if self.state == "start" and not self.isDone then 40 | local additionValue = self.value 41 | self.lastAdditionValue = ChangeAttr(self, additionValue) 42 | self.state = "have set" 43 | end 44 | if self.isDone then 45 | self.is_percent = true 46 | ChangeAttr(self, -self.lastAdditionValue) 47 | end 48 | end, 49 | }, 50 | } 51 | return Attr -------------------------------------------------------------------------------- /game/scene/fight/Buff.lua: -------------------------------------------------------------------------------- 1 | local Ac = require "Action" 2 | local BuffActions = require("game.scene.fight.BuffActions") 3 | 4 | local GetBuffs = function ( ) 5 | 6 | end 7 | local Buff = Ac.OO.Class { 8 | type = "Buff", 9 | __index = { 10 | Start = function(self, skillData) 11 | self.skillData = skillData 12 | self.sceneMgr = self.skillData.sceneMgr 13 | self.entityMgr = self.sceneMgr.entityMgr 14 | end, 15 | IsDone = function(self) 16 | return true 17 | end, 18 | Update = function(self, deltaTime) 19 | local buff_id = self[1] 20 | local buff_arge = self[2] or self.skillData.cfg.detail[self.skillData.skill_lv].buff 21 | local curTime = Time.timeMS 22 | for uid,_ in pairs(self.skillData.targets) do 23 | local targetEntity = self.sceneMgr:GetEntity(uid) 24 | local buffList = self.entityMgr:GetComponentData(targetEntity, "UMO.Buff") 25 | local buffActionCreator = BuffActions:GetActionCreator(buff_id) 26 | local buffAction = buffActionCreator(buff_arge) 27 | local buffData = TablePool:Get("BuffComData") or {} 28 | buffData.buff_id = buff_id 29 | buffData.victim_uid = uid 30 | buffData.victim_entity = targetEntity 31 | buffData.caster_uid = self.skillData.caster_uid 32 | buffData.caster_entity = self.skillData.caster_entity 33 | buffData.cast_time = curTime 34 | buffData.sceneMgr = self.skillData.sceneMgr 35 | buffData.arge = buff_arge 36 | buffData.action = buffAction 37 | buffAction:Start(buffData) 38 | table.insert(buffList, buffData) 39 | self.sceneMgr.actionMgr:AutoUpdate(buffAction) 40 | end 41 | end 42 | }, 43 | } 44 | return Buff -------------------------------------------------------------------------------- /game/scene/fight/BuffSys.lua: -------------------------------------------------------------------------------- 1 | --handle buff lifetime 2 | local ECS = require "ECS" 3 | local skynet = require "skynet" 4 | local BuffSys = ECS.BaseClass(ECS.ComponentSystem) 5 | ECS.TypeManager.RegisterScriptMgr("UMO.BuffSys", BuffSys) 6 | 7 | function BuffSys:OnCreate( ) 8 | ECS.ComponentSystem.OnCreate(self) 9 | self.group = self:GetComponentGroup({"UMO.Buff"}) 10 | end 11 | 12 | function BuffSys:OnUpdate( ) 13 | local buffs = self.group:ToComponentDataArray("UMO.Buff") 14 | local entities = self.group:ToEntityArray() 15 | local deltaTime = Time.deltaTime 16 | for i=1,buffs.Length do 17 | local buffList = buffs[i] 18 | for i,v in ipairs(buffList) do 19 | v.action:Update(deltaTime) 20 | local isDone = v.action:IsDone() 21 | if isDone then 22 | --Cat_Todo : 加载移除队列 23 | end 24 | end 25 | end 26 | end 27 | 28 | return BuffSys 29 | -------------------------------------------------------------------------------- /game/scene/fight/CheckAttr.lua: -------------------------------------------------------------------------------- 1 | local OperatorMap = { 2 | [">"] = function(a, b) return a>b end, 3 | ["<"] = function(a, b) return a="] = function(a, b) return a>=b end, 5 | ["<="] = function(a, b) return a<=b end, 6 | ["=="] = function(a, b) return a==b end, 7 | } 8 | local CheckAttr = Ac.OO.Class { 9 | type = "CheckAttr", 10 | __call = function(self, data) 11 | local sceneMgr = self.skillData.sceneMgr 12 | local entityMgr = sceneMgr.entityMgr 13 | --检查属性的目标有两种,在Buff时用target_uid,在Skill时就是检查施法者caster_uid 14 | local target_uid = data.target_uid or data.caster_uid 15 | local targetEntity = sceneMgr:GetEntity(target_uid) 16 | local curAttrList = entityMgr:GetComponentData(targetEntity, "UMO.BaseAttr") 17 | local curAttrValue = curAttrList[self[1]] 18 | local operator = self[2] 19 | local compareValue = self[3] 20 | local compareFunc = OperatorMap[operator] 21 | return compareFunc(curAttrValue, compareValue) 22 | end 23 | } 24 | return CheckAttr -------------------------------------------------------------------------------- /game/scene/fight/CheckHP.lua: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuhaopen/SkynetMMO/cc08a82c6e8e53d16f0ace3ab8ca306068c2887b/game/scene/fight/CheckHP.lua -------------------------------------------------------------------------------- /game/scene/fight/ClearBuff.lua: -------------------------------------------------------------------------------- 1 | --清除掉目标身上所有的 buff, target参数指定消除哪个目标身上的 buff,clear_type 指定消除哪些类型的 buff,见:SceneConst.ClearBuffType 枚举,有三种:所有,坏的和好的 buff. 2 | local Ac = require "Action" 3 | local FightHelper = require("game.scene.FightHelper") 4 | local SceneConst = require "game.scene.SceneConst" 5 | local BuffActions = require("game.scene.fight.BuffActions") 6 | 7 | --对于造成临时效果,比如晕眩或一定时间内降低属性的 buff,只需要快速播放至结束就好了 8 | local HandleDurationBuff = function ( self, buffAction ) 9 | while true do 10 | buffAction:Update(100000000000) 11 | if buffAction:IsDone() then 12 | self.actionMgr:RemoveAction(buffAction) 13 | break 14 | end 15 | end 16 | end 17 | 18 | --对于造成不可恢复效果的,直接结束播放就行了 19 | local HandleUnrecoverableBuff = function ( self, buffAction ) 20 | self.actionMgr:RemoveAction(buffAction) 21 | end 22 | 23 | --有些 buff 虽然造成不可恢复的效果,但不能直接删掉该 action,比如冰冻,它会每隔一定时间就扣血,所以不能快速把它播放完。但它还会减你速度,直到播放结束时才把速度还原,所以不能直接把它给删了,不然就跳过了还原速度的处理。所以这种 buff 需要特殊处理 24 | local HandleSpecialBuff = function ( self, buffAction ) 25 | local clearer = BuffActions:GetActionClearer(buffAction.buffData.buff_id) 26 | if clearer then 27 | clearer() 28 | end 29 | end 30 | 31 | local BuffTypeMap = { 32 | [400000] = HandleDurationBuff, 33 | [400001] = HandleUnrecoverableBuff, 34 | [400002] = HandleDurationBuff, 35 | [400003] = HandleSpecialBuff, 36 | } 37 | 38 | local BadBuffDic = { 39 | [400000] = true, 40 | [400001] = true, 41 | [400002] = true, 42 | [400003] = true, 43 | } 44 | 45 | local ClearBuff = Ac.OO.Class { 46 | type = "ClearBuff", 47 | __index = { 48 | Start = function(self, buffData) 49 | self.buffData = buffData 50 | self.sceneMgr = buffData.sceneMgr 51 | self.actionMgr = self.sceneMgr.actionMgr 52 | end, 53 | IsDone = function(self) 54 | return true 55 | end, 56 | Update = function(self, deltaTime) 57 | local target = self[1] 58 | local clearType = self[2] 59 | --针对不同的 buff 类型做处理 60 | local targetEntity 61 | if target == SceneConst.SkillTargetType.Enemy then 62 | targetEntity = self.buffData.victim_entity 63 | elseif target == SceneConst.SkillTargetType.Me then 64 | targetEntity = self.buffData.caster_entity 65 | end 66 | local buffList = self.entityMgr:GetComponentData(targetEntity, "UMO.Buff") 67 | for i,v in ipairs(buffList) do 68 | local needClear = false 69 | if clearType == SceneConst.ClearBuffType.All then 70 | needClear = true 71 | elseif clearType == SceneConst.ClearBuffType.Bad then 72 | needClear = BadBuffDic[v.buff_id] 73 | else 74 | needClear = true 75 | end 76 | if needClear then 77 | local handler = BuffTypeMap[v.buff_id] 78 | if handler then 79 | handler(v.action) 80 | end 81 | end 82 | end 83 | end, 84 | }, 85 | } 86 | return ClearBuff -------------------------------------------------------------------------------- /game/scene/fight/HP.lua: -------------------------------------------------------------------------------- 1 | local Ac = require "Action" 2 | --[[ 3 | 用法: HP {target=目标, value=偏移数值, is_percent=是否万分比} 4 | e.g. HP {1200, 3, -1000} 扣万份之1000的防御(防御索引是3,见SceneConst.HP),持续1200毫秒 5 | HP {2000, 1, 500, true} 加500点攻击,持续2000毫秒 6 | --]] 7 | 8 | local HP = Ac.OO.Class { 9 | type = "HP", 10 | __index = { 11 | Start = function(self, buffData) 12 | self.buffData = buffData 13 | self.entityMgr = buffData.sceneMgr.entityMgr 14 | end, 15 | IsDone = function(self) 16 | return true 17 | end, 18 | Update = function(self, deltaTime) 19 | 20 | end, 21 | }, 22 | TargetType = { 23 | Caster = 1,--施法者 24 | Victim = 2,--受害者 25 | }, 26 | } 27 | return HP -------------------------------------------------------------------------------- /game/scene/fight/HasBuff.lua: -------------------------------------------------------------------------------- 1 | --检查身上是否有某种 buff 2 | local Ac = require "Action" 3 | 4 | local HasBuff = Ac.OO.Class { 5 | type = "HasBuff", 6 | Start = function(self, data) 7 | self.data = data 8 | self.entityMgr = data.sceneMgr.entityMgr 9 | end, 10 | __call = function(self) 11 | local targetEntity = self.data.victim_entity 12 | local buffList = self.entityMgr:GetComponentData(targetEntity, "UMO.Buff") 13 | local buff_id = self[1] 14 | for i,v in ipairs(buffList) do 15 | if v.buff_id == buff_id then 16 | return true 17 | end 18 | end 19 | return false 20 | end 21 | } 22 | return HasBuff -------------------------------------------------------------------------------- /game/scene/fight/Hurt.lua: -------------------------------------------------------------------------------- 1 | local Ac = require "Action" 2 | local skynet = require "skynet" 3 | local SceneConst = require "game.scene.SceneConst" 4 | local FightHelper = require("game.scene.FightHelper") 5 | 6 | local CalDamage = function( self, targetEntity ) 7 | local targetFightAttr = self.entityMgr:GetComponentData(targetEntity, "UMO.FightAttr") 8 | local attackerAtt = self.attackerFightAttr[SceneConst.Attr.Att] or 0 9 | local defenderDef = targetFightAttr[SceneConst.Attr.Def] or 0 10 | local baseDamage = math.max(attackerAtt-defenderDef, 0)*self.damageRate/10000 11 | local attackerCrit = self.attackerFightAttr[SceneConst.Attr.Crit] or 0 12 | local critDamage = attackerCrit*math.random(1,100)/100 13 | return math.floor(baseDamage+critDamage) 14 | end 15 | 16 | local Update = function ( self ) 17 | local hurtEvent = { 18 | attacker_uid = self.skillData.caster_uid, 19 | time = Time.timeMS, 20 | defenders = {}, 21 | } 22 | local attackerEntity = self.sceneMgr:GetEntity(self.skillData.caster_uid) 23 | -- print('Cat:Hurt.lua[23] self.skillData.caster_uid, attackerEntity', self.skillData.caster_uid, attackerEntity) 24 | if not attackerEntity then 25 | print('Cat:Hurt.lua attackerEntity no exist uid : ', self.skillData.caster_uid) 26 | return 27 | end 28 | 29 | local isExist = self.entityMgr:Exists(attackerEntity) 30 | if not isExist then 31 | print('Cat:Hurt.lua attackerEntity no exist') 32 | return 33 | end 34 | self.attackerFightAttr = self.entityMgr:GetComponentData(attackerEntity, "UMO.FightAttr") 35 | 36 | for uid,_ in pairs(self.skillData.targets) do 37 | local entity = self.sceneMgr:GetEntity(uid) 38 | if entity then 39 | local hp = self.entityMgr:GetComponentData(entity, "UMO.HP") 40 | local damage_value = CalDamage(self, entity) 41 | FightHelper:ChangeHP(entity, hp, damage_value, self.skillData.caster_uid) 42 | table.insert(hurtEvent.defenders, {uid=uid, cur_hp=hp.cur, change_num=damage_value, flag=math.random(0, 2)}) 43 | end 44 | end 45 | if hurtEvent.defenders and #hurtEvent.defenders > 0 then 46 | self.sceneMgr.eventMgr:AddHurtEvent(self.skillData.caster_uid, hurtEvent) 47 | end 48 | end 49 | 50 | local Hurt = Ac.OO.Class { 51 | type = "Hurt", 52 | __index = { 53 | Start = function(self, skillData) 54 | self.skillData = skillData 55 | self.sceneMgr = skillData.sceneMgr 56 | self.entityMgr = self.sceneMgr.entityMgr 57 | self.damageRate = self[1] or self.skillData.cfg.detail[self.skillData.skill_lv].damage_rate 58 | end, 59 | IsDone = function(self) 60 | return true 61 | end, 62 | Update = Update, 63 | }, 64 | } 65 | 66 | return Hurt -------------------------------------------------------------------------------- /game/scene/fight/NotifyBuff.lua: -------------------------------------------------------------------------------- 1 | local Ac = require "Action" 2 | local SceneConst = require "game.scene.SceneConst" 3 | 4 | local NotifyBuff = Ac.OO.Class { 5 | type = "NotifyBuff", 6 | __index = { 7 | Start = function(self, buffData) 8 | self.buffData = buffData 9 | self.sceneMgr = buffData.sceneMgr 10 | end, 11 | IsDone = function(self) 12 | return true 13 | end, 14 | Update = function(self, deltaTime) 15 | local buffID = self[1] 16 | local duration = self[2] 17 | local endTime = Time.timeMS + duration 18 | local buffEvent = { 19 | key = SceneConst.InfoKey.Buff, 20 | value = string.format("%s,%s,%s", buffID, endTime, self.buffData.caster_uid), 21 | } 22 | self.sceneMgr.eventMgr:AddSceneEvent(self.buffData.victim_uid, buffEvent) 23 | end, 24 | }, 25 | } 26 | return NotifyBuff -------------------------------------------------------------------------------- /game/scene/fight/PickTarget.lua: -------------------------------------------------------------------------------- 1 | local Ac = require "Action" 2 | local ECS = require "ECS" 3 | local SkillCfg = require "game.config.scene.config_skill" 4 | local SceneConst = require "game.scene.SceneConst" 5 | 6 | --计算受击者列表 7 | local Update = function ( self ) 8 | local cfg = SkillCfg[self.skillData.skill_id] 9 | if not cfg then return end 10 | 11 | local target_type = self[1] or cfg.target_type 12 | local maxTargetNum = self[2] or cfg.detail[self.skillData.skill_lv].attack_max_num 13 | self.skillData.targets = nil 14 | local isPickEnemy = target_type == SceneConst.SkillTargetType.Enemy 15 | if isPickEnemy or target_type == SceneConst.SkillTargetType.Our then 16 | local skill_bomb = self.aoi:add() 17 | self.aoi:set_pos(skill_bomb, self.skillData.target_pos_x, self.skillData.target_pos_y, self.skillData.target_pos_z) 18 | 19 | local area = cfg.detail[self.skillData.skill_lv].area 20 | local around = self.aoi:get_around_offset(skill_bomb, area, area) 21 | if around then 22 | self.skillData.targets = {} 23 | --Cat_Todo : 区分我方和敌方阵营 24 | if isPickEnemy then 25 | local isExist = self.EntityManager:Exists(self.skillData.caster_entity) 26 | if isExist then 27 | local attacker_aoi_handle = self.EntityManager:GetComponentData(self.skillData.caster_entity, "UMO.AOIHandle") 28 | around[attacker_aoi_handle.value] = nil--把攻击者自己去掉 29 | else 30 | print("caster entity unexist on pick target "..tostring(self.skillData.caster_entity).." skill id : "..self.skillData.skill_id) 31 | end 32 | end 33 | local targetNums = 0 34 | for aoi_handle,v in pairs(around) do 35 | local uid = self.aoi:get_user_data(aoi_handle, "uid") 36 | local entity = self.sceneMgr:GetEntity(uid) 37 | local isBeatable = self.EntityManager:HasComponent(entity, "UMO.Beatable") 38 | -- print('Cat:PickTarget.lua[36] uid, entity, isBeatable', uid, entity, isBeatable) 39 | if isBeatable then 40 | local hpData = self.EntityManager:GetComponentData(entity, "UMO.HP") 41 | if hpData.cur > 0 then 42 | self.skillData.targets[uid] = true 43 | targetNums = targetNums + 1 44 | if targetNums >= maxTargetNum then 45 | break 46 | end 47 | end 48 | end 49 | end 50 | end 51 | self.aoi:remove(skill_bomb) 52 | elseif target_type == SceneConst.SkillTargetType.Me then 53 | self.skillData.targets[self.skillData.caster_uid] = true 54 | end 55 | --Cat_Todo : 控制最大受击数量 56 | end 57 | 58 | local PickTarget = Ac.OO.Class { 59 | type = "PickTarget", 60 | __index = { 61 | Start = function(self, skillData) 62 | self.skillData = skillData 63 | self.sceneMgr = skillData.sceneMgr 64 | self.EntityManager = self.sceneMgr.entityMgr 65 | self.aoi = self.sceneMgr.aoi 66 | end, 67 | IsDone = function(self) 68 | return true 69 | end, 70 | Update = Update, 71 | }, 72 | } 73 | 74 | return PickTarget -------------------------------------------------------------------------------- /game/scene/fight/SkillActions.lua: -------------------------------------------------------------------------------- 1 | local Ac = require "Action" 2 | local Hurt = require "game.scene.fight.Hurt" 3 | local PickTarget = require "game.scene.fight.PickTarget" 4 | local Buff = require "game.scene.fight.Buff" 5 | local SceneConst = require "game.scene.SceneConst" 6 | local If = Ac.If 7 | local Delay = Ac.Delay 8 | local Random = Ac.Random 9 | local Sequence = Ac.Sequence 10 | local CallFunc = Ac.CallFunc 11 | local And = Ac.And 12 | local Or = Ac.Or 13 | local Repeat = Ac.Repeat 14 | 15 | local SkillActions = {} 16 | function SkillActions:Init( ) 17 | self.actions = {} 18 | --[[ 19 | Sequence: 20 | 队列,后面可带不定数量的Action 21 | Delay: 22 | 延迟,参数为延迟的毫秒 23 | PickTarget: 24 | 选取目标,必须要有的,运行后会更新目标列表,大部分的Action都会用到该目标列表,参数有目标类型(敌方、自己、我方等等)、选取形状(圆、直线等)、最大目标数量等,为空时就用该技能的默认配置 25 | If: 26 | 第一参数是个条件Action,true则运行第二参数的Action否则跑第三参数的 27 | Hurt: 28 | 直接扣血,第一参数为伤害比率,不传的话将读取技能配置里的damage_rate字段 29 | Buff: 30 | 添加Buff,当然每个Buff也像技能一样是个Action,里面也有触发条件等判定 31 | 条件Action: 32 | 可以是个function或者重写了__call的table 33 | Random: 34 | 条件Action,参数为概率万分比 35 | CheckAttr: 36 | 条件Action,判断某属性和某个数值的关系 37 | BreakSkill: 38 | 打断目标的技能 39 | 注:之所以每个技能都做成一个 function 的形式提供是因为一个技能可能有多个等级,不同等级会有不同效果,所以创建一个技能 action 时需要传入该技能的特定等级的配置即 cfg,其是从 config_skill.lua 里该技能的 detail 字段里当前等级的那条 40 | --]] 41 | local normal_skill_list = { 42 | 110000, 110001, 110002, 110003, 110012, 43 | 120000, 120001, 120002, 120003, 120012, 44 | } 45 | --普通的技能,选中攻击目标,然后直接扣血 46 | for i,v in ipairs(normal_skill_list) do 47 | self.actions[v] = function( cfg ) 48 | return Sequence { 49 | PickTarget {}, 50 | Hurt {} 51 | } 52 | end 53 | end 54 | 55 | local skill_list_with_delay = { 56 | [200000]=300, [200001]=800, 57 | [200100]=600, [200101]=600, 58 | [200200]=500, [200201]=600, 59 | [200300]=350, [200301]=400, 60 | [200400]=400, [200401]=300, 61 | [200500]=500, [200501]=300, 62 | } 63 | --怪物的普通技能,因为是单体攻击所以就从调用 FightMgr:CastSkill 时就已经传入攻击目标了,直接扣血 64 | for k,v in pairs(skill_list_with_delay) do 65 | self.actions[k] = function( cfg ) 66 | return Sequence { 67 | Delay {v}, 68 | Hurt {} 69 | } 70 | end 71 | end 72 | 73 | --扣血后有万份之 x 概率触发400000buff(降低防御),具体降低的数值根据技能等级读取配置的 74 | self.actions[110010] = function( cfg ) 75 | return Sequence { 76 | Delay {100}, 77 | PickTarget {}, 78 | Hurt {}, 79 | If { Random{cfg.buff.probability}, Buff{400000} } 80 | } 81 | end 82 | self.actions[120010] = self.actions[110010] 83 | 84 | --扣血后有万份之 x 概率触发400002buff(晕眩) 85 | self.actions[110011] = function( cfg ) 86 | return Sequence { 87 | Delay {500}, 88 | PickTarget {}, 89 | Hurt {}, 90 | If { Random{cfg.buff.probability}, Buff{400002} } 91 | } 92 | end 93 | self.actions[120011] = self.actions[110011] 94 | 95 | --男主大招,300毫秒后,重复5次攻击,每次间隔700毫秒 96 | self.actions[110013] = function( cfg ) 97 | return Sequence { 98 | Delay {300}, 99 | Repeat {5, 100 | Sequence { 101 | PickTarget {}, 102 | Hurt {}, 103 | If { Random{cfg.buff.probability}, Buff{400001} }, 104 | Delay {700} 105 | } 106 | } 107 | } 108 | end 109 | self.actions[120013] = self.actions[110013] 110 | 111 | --女主大招,重复5次攻击,每次间隔700毫秒,每次都有一定概率加吸血 buff 112 | self.actions[120013] = function( cfg ) 113 | return Sequence { 114 | Delay {300}, 115 | Repeat {5, 116 | Sequence { 117 | PickTarget {}, 118 | Hurt {}, 119 | If { Random{cfg.buff.probability}, Buff{400001} }, 120 | Delay {700} 121 | } 122 | } 123 | } 124 | end 125 | end 126 | 127 | function SkillActions:GetActionCreator( skillID ) 128 | return self.actions[skillID] 129 | end 130 | 131 | return SkillActions -------------------------------------------------------------------------------- /game/scene/fight/Speed.lua: -------------------------------------------------------------------------------- 1 | local Ac = require "Action" 2 | local FightHelper = require("game.scene.FightHelper") 3 | local SceneConst = require "game.scene.SceneConst" 4 | 5 | local Speed = Ac.OO.Class { 6 | type = "Speed", 7 | __index = { 8 | Start = function(self, buffData) 9 | self.buffData = buffData 10 | self.sceneMgr = buffData.sceneMgr 11 | self.entityMgr = self.sceneMgr.entityMgr 12 | end, 13 | IsDone = function(self) 14 | return true 15 | end, 16 | Update = function(self, deltaTime) 17 | FightHelper:ChangeSpeed(self.buffData.victim_entity, self.buffData.victim_uid, self.buffData.caster_uid, self.bod, self.is_set, self.value) 18 | end, 19 | }, 20 | } 21 | return Speed -------------------------------------------------------------------------------- /game/scene/fight/SuckHP.lua: -------------------------------------------------------------------------------- 1 | local Ac = require "Action" 2 | local FightHelper = require("game.scene.FightHelper") 3 | local SceneConst = require "game.scene.SceneConst" 4 | 5 | local SuckHP = Ac.OO.Class { 6 | type = "SuckHP", 7 | __index = { 8 | Start = function(self, buffData) 9 | self.buffData = buffData 10 | self.sceneMgr = buffData.sceneMgr 11 | self.entityMgr = self.sceneMgr.entityMgr 12 | end, 13 | IsDone = function(self) 14 | return true 15 | end, 16 | Update = function(self, deltaTime) 17 | local hp = self.entityMgr:GetComponentData(self.buffData.victim_entity, "UMO.HP") 18 | local suckHP = 0 19 | if self.is_percent then 20 | local baseAttr = self.entityMgr:GetComponentData(self.buffData.victim_entity, "UMO.BaseAttr") 21 | local maxHP = baseAttr[SceneConst.Attr.HP] or 0 22 | suckHP = maxHP * self.value / 10000 23 | else 24 | suckHP = self.value 25 | end 26 | suckHP = math.min(hp.cur, suckHP) 27 | FightHelper:ChangeHP(self.buffData.victim_entity, hp, suckHP, self.buffData.caster_uid) 28 | local buffEvent = { 29 | key = SceneConst.InfoKey.Buff, 30 | value = string.format("%s,%s,%s", SceneConst.Buff.SuckHP, math.floor(suckHP), self.buffData.caster_uid), 31 | } 32 | self.sceneMgr.eventMgr:AddSceneEvent(self.buffData.victim_uid, buffEvent) 33 | end, 34 | }, 35 | } 36 | return SuckHP -------------------------------------------------------------------------------- /game/scene/system/AISystem.lua: -------------------------------------------------------------------------------- 1 | local ECS = require "ECS" 2 | 3 | local AISystem = ECS.BaseClass(ECS.ComponentSystem) 4 | ECS.TypeManager.RegisterScriptMgr("UMO.AISystem", AISystem) 5 | 6 | function AISystem:Constructor( ) 7 | end 8 | 9 | function AISystem:OnCreate( ) 10 | ECS.ComponentSystem.OnCreate(self) 11 | 12 | self.monsterMgr = self.sceneMgr.monsterMgr 13 | 14 | self.group = self:GetComponentGroup({"UMO.MonsterAI", "UMO.UID"}) 15 | end 16 | 17 | function AISystem:OnUpdate( ) 18 | local deltaTime = Time.deltaTime 19 | -- local entities = self.group:ToEntityArray() 20 | local uids = self.group:ToComponentDataArray("UMO.UID") 21 | for i=1,uids.Length do 22 | local graphsowner = self.monsterMgr:GetGraphsOwner(uids[i]) 23 | graphsowner:Update(deltaTime) 24 | end 25 | end 26 | 27 | return AISystem -------------------------------------------------------------------------------- /game/scene/system/ActionSys.lua: -------------------------------------------------------------------------------- 1 | --handle action lifetime 2 | local ECS = require "ECS" 3 | local skynet = require "skynet" 4 | local ActionSys = ECS.BaseClass(ECS.ComponentSystem) 5 | ECS.TypeManager.RegisterScriptMgr("UMO.ActionSys", ActionSys) 6 | 7 | function ActionSys:OnCreate( ) 8 | ECS.ComponentSystem.OnCreate(self) 9 | self.ecsSystemMgr = self.sceneMgr.ecsSystemMgr 10 | self.group = self:GetComponentGroup({"UMO.Action"}) 11 | end 12 | 13 | function ActionSys:OnUpdate( ) 14 | local actions = self.group:ToComponentDataArray("UMO.Action") 15 | local entities = self.group:ToEntityArray() 16 | for i=1,actions.Length do 17 | self:HandleAction(actions[i], entities[i]) 18 | end 19 | end 20 | 21 | function ActionSys:HandleAction( skillData, entity ) 22 | skillData.action:Update(Time.deltaTimeMS) 23 | local isDone = skillData.action:IsDone() 24 | if isDone then 25 | print('Cat:ActionSys.lua[29] isDone', isDone, entity) 26 | self.ecsSystemMgr:AddDestroyEntity(entity) 27 | end 28 | end 29 | 30 | return ActionSys 31 | -------------------------------------------------------------------------------- /game/scene/system/DamageSystem.lua: -------------------------------------------------------------------------------- 1 | local ECS = require "ECS" 2 | local skynet = require "skynet" 3 | local SceneConst = require "game.scene.SceneConst" 4 | local Time = Time 5 | local DamageSystem = ECS.BaseClass(ECS.ComponentSystem) 6 | ECS.TypeManager.RegisterScriptMgr("UMO.DamageSystem", DamageSystem) 7 | 8 | function DamageSystem:OnCreate( ) 9 | ECS.ComponentSystem.OnCreate(self) 10 | self.fightMgr = self.sceneMgr.fightMgr 11 | 12 | self.group = self:GetComponentGroup({"UMO.DamageEvents", "UMO.AOIHandle", "UMO.HP"}) 13 | end 14 | 15 | function DamageSystem:OnUpdate( ) 16 | local damages = self.group:ToComponentDataArray("UMO.DamageEvents") 17 | local hps = self.group:ToComponentDataArray("UMO.HP") 18 | local entities = self.group:ToEntityArray() 19 | for i=1,damages.Length do 20 | self:HandleDamage(entities[i], damages[i], hps[i]) 21 | end 22 | end 23 | 24 | function DamageSystem:HandleDamage( entity, dEvents, hp ) 25 | if not dEvents or #dEvents <= 0 or hp.cur <= 0 then 26 | return 27 | end 28 | local isDamaged = false 29 | local sumDamage = 0 30 | for i,damageEvent in ipairs(dEvents) do 31 | isDamaged = true 32 | if hp.cur > 0 then 33 | hp.cur = hp.cur - damageEvent.damage 34 | if hp.cur <= 0 then 35 | hp.cur = 0 36 | hp.killedBy = damageEvent.attacker 37 | hp.deathTime = Time.time 38 | end 39 | end 40 | sumDamage = sumDamage + damageEvent.damage 41 | dEvents[i] = nil--逐个置nil,不需要重新分配table 42 | end 43 | if isDamaged then 44 | local uid = self.m_EntityManager:GetComponentData(entity, "UMO.UID") 45 | local change_target_pos_event_info 46 | if hp.cur > 0 then 47 | change_target_pos_event_info = {key=SceneConst.InfoKey.HPChange, value=math.floor(hp.cur), time=Time.timeMS} 48 | else 49 | --enter dead state 50 | if self.m_EntityManager:HasComponent(entity, "UMO.MonsterAI") then 51 | self.sceneMgr.monsterMgr:TriggerState(uid, "DeadState") 52 | local killer = self.sceneMgr:GetEntity(hp.killedBy) 53 | if self.m_EntityManager:HasComponent(killer, "UMO.MsgAgent") then 54 | local agent = self.m_EntityManager:GetComponentData(killer, "UMO.MsgAgent") 55 | local roleID = self.m_EntityManager:GetComponentData(killer, "UMO.TypeID") 56 | local monsterID = self.m_EntityManager:GetComponentData(entity, "UMO.TypeID") 57 | skynet.send(agent, "lua", "execute", "Task", "KillMonster", roleID, monsterID, 1) 58 | end 59 | end 60 | change_target_pos_event_info = {key=SceneConst.InfoKey.HPChange, value=math.floor(hp.cur)..",dead", time=Time.timeMS} 61 | end 62 | self.sceneMgr.eventMgr:AddSceneEvent(uid, change_target_pos_event_info) 63 | end 64 | end 65 | 66 | return DamageSystem -------------------------------------------------------------------------------- /game/scene/system/MovementUpdateSystem.lua: -------------------------------------------------------------------------------- 1 | local ECS = require "ECS" 2 | 3 | local MovementUpdateSystem = ECS.BaseClass(ECS.ComponentSystem) 4 | ECS.TypeManager.RegisterScriptMgr("UMO.MovementUpdateSystem", MovementUpdateSystem) 5 | 6 | function MovementUpdateSystem:Constructor( ) 7 | end 8 | 9 | function MovementUpdateSystem:OnCreate( ) 10 | ECS.ComponentSystem.OnCreate(self) 11 | 12 | self.group = self:GetComponentGroup({"UMO.Position", "UMO.TargetPos", "UMO.MoveSpeed", "UMO.AOIHandle"}) 13 | end 14 | 15 | function MovementUpdateSystem:OnUpdate( ) 16 | local deltaTime = Time.deltaTime 17 | local positions = self.group:ToComponentDataArray("UMO.Position") 18 | local targetPositions = self.group:ToComponentDataArray("UMO.TargetPos") 19 | local speeds = self.group:ToComponentDataArray("UMO.MoveSpeed") 20 | local aoi_handles = self.group:ToComponentDataArray("UMO.AOIHandle") 21 | local dt = Time.deltaTime 22 | for i=1,positions.Length do 23 | local startPos = positions[i] 24 | startPos = Vector3(startPos.x, startPos.y, startPos.z) 25 | local targetPos = targetPositions[i] 26 | targetPos = Vector3(targetPos.x, targetPos.y, targetPos.z) 27 | local speed = speeds[i].curSpeed 28 | if speed > 0 then 29 | local moveDir = Vector3.Sub(targetPos, startPos) 30 | local groundDir = moveDir 31 | groundDir.y = 0 32 | local moveDistance = Vector3.Magnitude(groundDir) 33 | groundDir = Vector3.Normalize(groundDir) 34 | local isMoveWanted = moveDistance>0.01 35 | local newPos 36 | -- print('Cat:MovementUpdateSystem.lua[33] moveDistance, dt', moveDistance, dt) 37 | if (moveDistance < speed*dt) then 38 | --目标已经离得很近了 39 | newPos = targetPos 40 | else 41 | newPos = startPos+groundDir*speed*dt 42 | end 43 | positions[i] = {x=newPos.x, y=newPos.y, z=newPos.z} 44 | self.sceneMgr.aoi:set_pos(aoi_handles[i].value, newPos.x, newPos.y, newPos.z) 45 | end 46 | end 47 | end 48 | 49 | return MovementUpdateSystem -------------------------------------------------------------------------------- /game/scene/system/SkillSys.lua: -------------------------------------------------------------------------------- 1 | --handle skill lifetime 2 | local ECS = require "ECS" 3 | local skynet = require "skynet" 4 | -- local SkillCfg = require "game.config.scene.config_skill" 5 | local SkillSys = ECS.BaseClass(ECS.ComponentSystem) 6 | ECS.TypeManager.RegisterScriptMgr("UMO.SkillSys", SkillSys) 7 | 8 | function SkillSys:OnCreate( ) 9 | ECS.ComponentSystem.OnCreate(self) 10 | self.ecsSystemMgr = self.sceneMgr.ecsSystemMgr 11 | self.group = self:GetComponentGroup({"UMO.Skill"}) 12 | end 13 | 14 | function SkillSys:OnUpdate( ) 15 | local skills = self.group:ToComponentDataArray("UMO.Skill") 16 | local entities = self.group:ToEntityArray() 17 | for i=1,skills.Length do 18 | self:HandleSkill(skills[i], entities[i]) 19 | end 20 | end 21 | 22 | function SkillSys:HandleSkill( skillData, entity ) 23 | skillData.action:Update(Time.deltaTimeMS) 24 | local isDone = skillData.action:IsDone() 25 | if isDone then 26 | print('Cat:SkillSys.lua[29] isDone', isDone, entity) 27 | self.ecsSystemMgr:AddDestroyEntity(entity) 28 | end 29 | end 30 | 31 | return SkillSys 32 | -------------------------------------------------------------------------------- /game/service/chat.lua: -------------------------------------------------------------------------------- 1 | 2 | local skynet = require "skynet" 3 | local ChatConst = require "game.chat.ChatConst" 4 | local table_insert = table.insert 5 | local ErrorCode = require "game.config.ErrorCode" 6 | local succeed = {ret=ErrorCode.Succeed} 7 | local unknow = {ret=ErrorCode.Unknow} 8 | local NORET = {} 9 | local CMD = {} 10 | local this = {} 11 | 12 | local getRoleInfo = function ( roleID ) 13 | local info = this.cacheRoleInfos[roleID] 14 | print("Cat:chat [start:10] ", info, roleID) 15 | if not info then 16 | local is_ok, role_info = skynet.call(this.db, "lua", "select_one_by_key", "RoleBaseInfo", "role_id", roleID) 17 | print("Cat:chat [start:12] ", is_ok, role_info) 18 | if is_ok then 19 | info = role_info 20 | this.cacheRoleInfos[roleID] = info 21 | end 22 | end 23 | return info 24 | end 25 | 26 | function CMD.get_handlers() 27 | local handlers = {} 28 | for k,v in pairs(CMD) do 29 | table.insert(handlers, k) 30 | end 31 | return handlers 32 | end 33 | 34 | function CMD.Chat_GetHistory( user_info, req_data ) 35 | print("Cat:chat [start:11] ", user_info, req_data) 36 | return {channel=req_data.channel, list={}} 37 | end 38 | 39 | function CMD.Chat_Send( user_info, req_data ) 40 | local roleInfo = getRoleInfo(user_info.cur_role_id) 41 | if not roleInfo then 42 | return unknow 43 | end 44 | local chatInfo = { 45 | roleID = user_info.cur_role_id, 46 | name = roleInfo.name, 47 | career = roleInfo.career, 48 | vip = roleInfo.vip or 0, 49 | headID = roleInfo.headID or 0, 50 | headUrl = roleInfo.headUrl or "", 51 | bubbleID = roleInfo.bubbleID or 0, 52 | channel = req_data.channel, 53 | content = req_data.content, 54 | extra = req_data.extra, 55 | time = Time.timeMS, 56 | id = this.ids[req_data.channel], 57 | } 58 | this.ids[req_data.channel] = this.ids[req_data.channel] + 1 59 | local hisy = this.history[req_data.channel] 60 | table_insert(hisy, chatInfo) 61 | table_insert(this.newChatList, chatInfo) 62 | return succeed 63 | end 64 | 65 | function CMD.Chat_GetNew( user_info, req_data ) 66 | local ack = this.ackGetNews[user_info.cur_role_id] 67 | if not ack then 68 | this.ackGetNews[user_info.cur_role_id] = skynet.response() 69 | end 70 | return NORET 71 | end 72 | 73 | --Cat_Todo : 要不要改成组播的形式 74 | local boardcast = function ( ) 75 | 76 | end 77 | 78 | local init = function ( ) 79 | this.history = {} 80 | this.ids = {} 81 | for i = ChatConst.Channel.World, ChatConst.Channel.Count do 82 | this.history[i] = {} 83 | this.ids[i] = 0 84 | end 85 | this.newChatList = {} 86 | this.cacheRoleInfos = {} 87 | setmetatable(this.cacheRoleInfos, {__mode = "v"}) 88 | this.db = skynet.localname(".GameDBServer") 89 | this.ackGetNews = {} 90 | this.interestChannel = {} 91 | end 92 | 93 | skynet.start (function () 94 | init() 95 | skynet.fork(function() 96 | while true do 97 | boardcast() 98 | skynet.sleep(50) 99 | end 100 | end) 101 | skynet.dispatch ("lua", function (_, _, command, ...) 102 | local f = assert(CMD[command]) 103 | local r = f(...) 104 | if r ~= NORET then 105 | skynet.retpack(r) 106 | end 107 | end) 108 | end) 109 | -------------------------------------------------------------------------------- /game/service/id_service.lua: -------------------------------------------------------------------------------- 1 | require "common.util" 2 | local skynet = require "skynet" 3 | require "skynet.manager" 4 | 5 | --使用snowflake算法生成唯一id 6 | local start_use_time = 1563482410*100 --注:单位为10毫秒,该值正式使用后就不能改了,因为每次id的时间部分存储的是和此值的差 7 | 8 | local this = { 9 | increase_info = {}, 10 | server_id = 0, 11 | platform_id = 0, 12 | } 13 | local increase_bit = 11--每10毫秒里自增1024个 14 | local server_bit = 5--16个平台id 15 | local platform_bit = 9--512个服务器id 16 | local time_bit = 38--2199023255552 17 | local left_shift_for_server = increase_bit 18 | local left_shift_for_platform = increase_bit+server_bit 19 | local left_shift_for_time = increase_bit+server_bit+platform_bit 20 | local sequenceMask = -1 ~ (-1 << increase_bit) 21 | 22 | local CMD = {} 23 | 24 | local get_cur_time = function ( ) 25 | local curTime = math.floor(skynet.time()*100) 26 | return curTime 27 | end 28 | 29 | local test_gen_uid = function ( ) 30 | print('Cat:id_service.lua[start test uid generator]') 31 | local test_uids = {} 32 | for i=1,100000 do 33 | local uid = CMD.gen_uid("goods") 34 | assert(test_uids[uid]==nil, "repetitive uid!") 35 | test_uids[uid] = true 36 | end 37 | print('Cat:id_service.lua[end test uid generator]') 38 | end 39 | 40 | function CMD.open( conf ) 41 | this.server_id = conf.server_id 42 | this.platform_id = conf.platform_id 43 | skynet.register(conf.name) 44 | -- test_gen_uid() 45 | end 46 | 47 | local tilNextTime = function ( lastTimestamp ) 48 | local timestamp = get_cur_time() 49 | while timestamp <= lastTimestamp do 50 | timestamp = get_cur_time() 51 | end 52 | return timestamp 53 | end 54 | 55 | local init_increase_info = function ( type_name ) 56 | local curTime = get_cur_time() 57 | local deltaTime = curTime-start_use_time 58 | this.increase_info[type_name] = {deltaTime=deltaTime, value=0} 59 | return this.increase_info[type_name] 60 | end 61 | 62 | function CMD.gen_uid( type_name ) 63 | type_name = type_name or "empty string" 64 | local increase_info = this.increase_info[type_name] 65 | if not increase_info then 66 | increase_info = init_increase_info(type_name) 67 | end 68 | local curTime = get_cur_time() 69 | local deltaTime = curTime - start_use_time 70 | if deltaTime == increase_info.deltaTime then 71 | increase_info.value = (increase_info.value + 1) & sequenceMask 72 | if increase_info.value == 0 then 73 | --自增序列用满了,阻塞到下一毫秒 74 | curTime = tilNextTime(curTime) 75 | deltaTime = curTime - start_use_time 76 | increase_info.deltaTime = deltaTime 77 | end 78 | elseif deltaTime < increase_info.deltaTime then 79 | skynet.error("gen uid error : time back?") 80 | return 81 | else 82 | increase_info.value = 0 83 | increase_info.deltaTime = deltaTime 84 | end 85 | local increase_num = increase_info.value 86 | 87 | local uid = (deltaTime< 0 then 10 | return table.remove(pool, #pool) 11 | else 12 | return nil 13 | end 14 | end 15 | 16 | function TablePool:Recycle( name, tbl ) 17 | local pool = self.pools[name] 18 | pool = pool or {} 19 | pool[#pool+1] = tbl 20 | self.pools[name] = pool 21 | end 22 | 23 | return TablePool -------------------------------------------------------------------------------- /lualib/Action/Action.lua: -------------------------------------------------------------------------------- 1 | local Ac = Ac or {} 2 | 3 | local importer = require("Action.Common.Importer") 4 | importer.enable() 5 | 6 | --让本框架里的文件都有Ac这个全局变量 7 | local AcEnv = { 8 | Ac = Ac 9 | } 10 | setmetatable(AcEnv, { 11 | __index = _ENV, 12 | __newindex = function (t,k,v) 13 | --本框架内不允许新增和修改全局变量,实在想要的也可以使用_ENV.xx = yy这种形式,但我像是这种没节操的人吗?! 14 | error("attempt to set a global value", 2) 15 | end, 16 | }) 17 | 18 | Ac.OO = importer.require("Action.Common.LuaOO", AcEnv) 19 | Ac.Sequence = importer.require("Action.Src.Sequence", AcEnv) 20 | Ac.CallFunc = importer.require("Action.Src.CallFunc", AcEnv) 21 | Ac.Delay = importer.require("Action.Src.Delay", AcEnv) 22 | Ac.Random = importer.require("Action.Src.Random", AcEnv) 23 | Ac.If = importer.require("Action.Src.If", AcEnv) 24 | Ac.And = importer.require("Action.Src.And", AcEnv) 25 | Ac.Or = importer.require("Action.Src.Or", AcEnv) 26 | Ac.Repeat = importer.require("Action.Src.Repeat", AcEnv) 27 | Ac.ActionMgr = importer.require("Action.Src.ActionMgr", AcEnv) 28 | 29 | --为了不影响全局,这里要还原一下package.searchers 30 | importer.disable() 31 | 32 | return Ac -------------------------------------------------------------------------------- /lualib/Action/Common/Importer.lua: -------------------------------------------------------------------------------- 1 | --本源码文件主要摘自云风的blog:https://blog.codingnow.com/2015/10/lua_require_env.html 2 | local loaded = package.loaded 3 | local searchpath = package.searchpath 4 | 5 | local function load_env(filename) 6 | local f,err = loadfile(filename) 7 | if f == nil then 8 | return err 9 | end 10 | return function() 11 | return function(env) 12 | if env then 13 | debug.setupvalue(f, 1, env) 14 | end 15 | return f(filename) 16 | end 17 | end 18 | end 19 | 20 | local function searcher_env(name) 21 | local filename, err = package.searchpath(name, package.path) 22 | if filename == nil then 23 | return err 24 | else 25 | return load_env(filename) 26 | end 27 | end 28 | 29 | local function import(modname, env) 30 | return require(modname)(env) 31 | end 32 | 33 | local function enable() 34 | table.insert(package.searchers, 2, searcher_env) 35 | end 36 | 37 | local function disable() 38 | table.remove(package.searchers, 2) 39 | end 40 | 41 | return {enable=enable, disable=disable, require=import} 42 | -------------------------------------------------------------------------------- /lualib/Action/Common/LuaOO.lua: -------------------------------------------------------------------------------- 1 | -- See details for additional information : http://angg.twu.net/LATEX/dednat6/eoo.lua.html 2 | local OO = {} 3 | 4 | OO.Class = { 5 | type = "Class", 6 | __call = function (class, o) return setmetatable(o, class) end, 7 | } 8 | setmetatable(OO.Class, OO.Class) 9 | 10 | OO.Type = function (o) 11 | local mt = getmetatable(o) 12 | return mt and mt.type or type(o) 13 | end 14 | 15 | OO.Over = function (uppertable) 16 | return function (lowertable) 17 | setmetatable(uppertable, {__index=lowertable}) 18 | return uppertable 19 | end 20 | end 21 | 22 | OO.ClassOver = function (upperclassmt) 23 | return function (lowerclass) 24 | setmetatable(upperclassmt.__index, {__index=lowerclass.__index}) 25 | return OO.Class(upperclassmt) 26 | end 27 | end 28 | 29 | return OO -------------------------------------------------------------------------------- /lualib/Action/README.md: -------------------------------------------------------------------------------- 1 | # Action 2 | 在实现技能和 buff 系统时,最简单粗暴的方法是为每个技能写一个函数甚至类,这样每个技能间必定会存在大量的重复代码。 3 | 稍好点的方案是为技能分类,比如 a 类是一次性攻击,b 类是持续每间隔 n 秒攻击1次,c 类是把 n 只怪吸过来然后攻击...最后会发现还是分了很多类且很多重复逻辑。 4 | 用上继承吧,按技能的复杂度你肯定是逃不过“鸭嘴兽”效应的,比如你创建了一个动物世界的继承体系:哺乳动物->猪,猫,狗;鸟类->啄木鸟;爬行动物;无脊椎动物等等。但当鸭嘴兽出现时你就蛋疼了,你是该继承哪个类?还是为它创建一个新的分类? 5 | 鲁迅就曾经说过:组合优于继承。许多框架都用此指导思想做了不少高复用的设计,比如 unity 的基于组件开发,cocos 里的 Action 系统。 6 | 具体到 GamePlay 上的有守望先锋里的 Statescript 系统(暴雪自研的脚本系统),虚幻的 blueprint 或 unity 上的 FlowCanvas,NodeCanvas。实质都是把逻辑划分为多个节点,包括流程逻辑如 if,else,for 等都有对应的节点,然后通过节点间的排列组合实现各种逻辑,这样可以极大地提高复用率,而且还可以做个界面进行可视化编程。 7 | 在 MMO 项目弄怪物 AI 时我就想弄一套差不多的,就是 UnityMMO/Server/lualib/Blueprint。但目前还不想在后端里做太多复杂的 AI,而且自由也是有代价的,所以就先抑制住了做这把牛刀的冲动,先用状态机凑合着,所以目前只有一种怪物 AI,等以后需求养肥了再考虑换成行为树。 8 | 但技能系统就不能将就了,前期弄不好,后期就改不动或不想改了。 9 | 所以还是得花点时间好好捋一捋。技能的逻辑大多都不需要像 AI 逻辑那么长,所以应该可以砍掉许多行为树和节点流的功能,我想弄得越简单越好。 10 | 为了和我的 Blueprint 区分,这里的逻辑节点就叫 Action 吧,就是一个 lua table,只要有三个函数:Start, Update, IsDone 就能成为一个 Action。框架内提供了几个 Action 用作粘合剂: 11 | 12 | ## Sequence 13 | 可以包含 n 个 Action,下面的代码就是先一直调用 Action1 的 Update 函数直到其 IsDone 返回 true,然后就是轮到 Action2 和 Action3,最后 Action3 的 IsDone 也返回 true 后就结束循环了。: 14 | ``` 15 | local Ac = require "Action" 16 | local action = Ac.Sequence {Action1, Action2, Action3} 17 | while not action:IsDone() do 18 | action:Update(Time.deltaTime) 19 | end 20 | ``` 21 | 22 | ## Repeat 23 | 重复跑某个 Action,下面的代码就是创建一个 Action,将重复 Done Action1 5次: 24 | ``` 25 | local action = Ac.Repeat {5, Action1} 26 | ``` 27 | 28 | ## Delay 29 | 延迟 Action: 30 | ``` 31 | local action = Ac.Sequence {Action1, Ac.Delay{500}, Action2} 32 | ``` 33 | 34 | ## If 35 | 判定 Action,第一参数是个条件 Action(要求有 \__call 元函数的 table,可参与 Random 这个 Action),如果该 Action 返回 true 就运行第二参数传入的 Action,否则跑第三参数的 Action。下面的代码就是30%概率跑 Action1,70%概率跑 Action2: 36 | ``` 37 | local action = Ac.If {Ac.Random{30}, Action1, Action2} 38 | ``` 39 | 当然你可以自己定制条件 Action,比如可以做个 CheckAttr 的条件 Action,用于判断角色的某个属性是否大于或小于某个值: 40 | ``` 41 | local action = Ac.If {CheckAttr{"hp","<=",10}, Action1, Action2} 42 | ``` 43 | 44 | # Usage 45 | ``` 46 | local Ac = require "Action" 47 | --该技能仅有两个步骤:PickTarget 选取攻击目标(把结果保存在 targets 里),Hurt 把目标列表 (targets) 里的角色造成100点伤害 48 | local skill1 = Sequence { PickTarget{}, Hurt{100} } 49 | local skillData = { targets = {} } 50 | skill1:Start(skillData)--从 Start 函数传入要处理的 table,然后所有的 Action 针对它进行某些处理,相当于行为树里的黑板 51 | skill1:Update() 52 | 53 | --复杂一点的技能如:一共触发5次,每次有20%的概率暴发(伤害500点)并触发 Buff1,另外的80%可能造成100点伤害但没有 Buff,然后延迟1000毫秒后再继续下次循环。 54 | local skill2 = Repeat { 5, If { Random{2000}, 55 | Sequence{ PickTarget{}, Hurt{500}, Buff{1}, Delay{1000} } , 56 | Sequence{ PickTarget{}, Hurt{100}, Delay{1000} } , 57 | } 58 | } 59 | skill2:Start(skillData) 60 | while true do 61 | --每次 Update 都要传入每帧间隔的时间,用于计算延迟 Action 62 | skill2:Update(Time.deltaTime) 63 | if skill2:IsDone() then 64 | break 65 | end 66 | end 67 | --上面的 PickTarget、Hurt 和 Buff 三个 Action都是根据项目需要自定义的,各自实现了 Action 三函数,用 Start 函数接收要处理的 table,然后在 Update 函数里添加业务逻辑,因为都是一次性完成的,所以 IsDone 可以直接返回 true。 68 | } 69 | ``` 70 | 71 | # Test 72 | 可以在windows或linux上运行测试用例,Lua5.2以上都是可以的 : 73 | lua ./Tests/Test.lua -v 74 | -------------------------------------------------------------------------------- /lualib/Action/Src/ActionMgr.lua: -------------------------------------------------------------------------------- 1 | --提取简单的action管理器仅供参与,可以自己另外去定制更加丰富的版本,比如加上缓冲对象池功能什么的 2 | local table_insert = table.insert 3 | local table_remove = table.remove 4 | 5 | local function Update( self, deltaTimeMS ) 6 | self.isUpdating = true 7 | local i = 1 8 | while i <= #self.actions do 9 | local action = self.actions[i] 10 | if not action.__removed__ then 11 | action:Update(deltaTimeMS) 12 | if action:IsDone() then 13 | table_remove(self.actions, i) 14 | if self.doneCallBack then 15 | self.doneCallBack(action) 16 | end 17 | else 18 | i = i + 1 19 | end 20 | else 21 | i = i + 1 22 | end 23 | end 24 | self.isUpdating = false 25 | for i,v in ipairs(self.waitForDelete) do 26 | self:RemoveAction(v) 27 | end 28 | end 29 | 30 | local function RemoveAction( self, action ) 31 | if self.isUpdating then 32 | action.__removed__ = true 33 | table_insert(self.waitForDelete, action) 34 | return 35 | end 36 | for i,v in ipairs(self.actions) do 37 | if v == action then 38 | table_remove(self.actions, i) 39 | if self.doneCallBack then 40 | self.doneCallBack(action) 41 | end 42 | end 43 | end 44 | end 45 | 46 | local ActionMgr = Ac.OO.Class { 47 | type = "ActionMgr", 48 | __index = { 49 | Init = function(self) 50 | self.actions = {} 51 | self.waitForDelete = {} 52 | end, 53 | SetDoneCallBack = function(self, doneCallBack) 54 | self.doneCallBack = doneCallBack 55 | end, 56 | AutoUpdate = function(self, action) 57 | table_insert(self.actions, action) 58 | end, 59 | Update = Update, 60 | RemoveAction = RemoveAction, 61 | }, 62 | } 63 | 64 | return ActionMgr -------------------------------------------------------------------------------- /lualib/Action/Src/And.lua: -------------------------------------------------------------------------------- 1 | local And = Ac.OO.Class { 2 | type = "And", 3 | __index = { 4 | Start = function(self, data) 5 | self.data = data 6 | end, 7 | }, 8 | __call = function(self) 9 | local conditionA = self[1] 10 | local conditionB = self[2] 11 | if type(conditionA)=="table" and conditionA.Start then 12 | conditionA:Start(self.data) 13 | end 14 | if type(conditionB)=="table" and conditionB.Start then 15 | conditionB:Start(self.data) 16 | end 17 | return conditionA(self.data) and conditionB(self.data) 18 | end 19 | } 20 | return And -------------------------------------------------------------------------------- /lualib/Action/Src/CallFunc.lua: -------------------------------------------------------------------------------- 1 | local CallFunc = Ac.OO.Class { 2 | type = "CallFunc", 3 | __index = { 4 | Start = function(self, data) 5 | self.data = data 6 | end, 7 | IsDone = function(self) 8 | return true 9 | end, 10 | Update = function(self, deltaTime) 11 | self[1](self.data) 12 | end 13 | }, 14 | } 15 | return CallFunc -------------------------------------------------------------------------------- /lualib/Action/Src/Delay.lua: -------------------------------------------------------------------------------- 1 | local Delay = Ac.OO.Class { 2 | type = "Delay", 3 | __index = { 4 | Start = function(self) 5 | self.elapsed = 0 6 | -- self.updateNum = 0 7 | end, 8 | IsDone = function(self) 9 | return self.elapsed >= self[1], true-- and self.updateNum>1 10 | end, 11 | Update = function(self, deltaTime) 12 | self.elapsed = self.elapsed + deltaTime 13 | -- self.updateNum = self.updateNum + 1 14 | end 15 | }, 16 | } 17 | return Delay -------------------------------------------------------------------------------- /lualib/Action/Src/If.lua: -------------------------------------------------------------------------------- 1 | local If = Ac.OO.Class { 2 | type = "If", 3 | __index = { 4 | Start = function(self, data) 5 | self.data = data 6 | self.action = nil 7 | end, 8 | IsDone = function(self) 9 | local isDone, isTimeAction 10 | if self.action then 11 | isDone, isTimeAction = self.action:IsDone() 12 | else 13 | isDone = true 14 | end 15 | return isDone, isTimeAction 16 | end, 17 | Update = function(self, deltaTime) 18 | if self.action == nil then 19 | local conditionFunctor = self[1] 20 | if type(conditionFunctor)=="table" and conditionFunctor.Start then 21 | conditionFunctor:Start(self.data) 22 | end 23 | local isTrue = conditionFunctor(self.data) 24 | if isTrue then 25 | self.action = self[2] 26 | else 27 | self.action = self[3] 28 | end 29 | if self.action then 30 | self.action:Start(self.data) 31 | end 32 | end 33 | if self.action then 34 | self.action:Update(deltaTime) 35 | end 36 | end 37 | }, 38 | } 39 | return If -------------------------------------------------------------------------------- /lualib/Action/Src/Or.lua: -------------------------------------------------------------------------------- 1 | local Or = Ac.OO.Class { 2 | type = "Or", 3 | __index = { 4 | Start = function(self, data) 5 | self.data = data 6 | end, 7 | }, 8 | __call = function(self) 9 | local conditionA = self[1] 10 | if type(conditionA)=="table" and conditionA.Start then 11 | conditionA:Start(self.data) 12 | end 13 | if conditionA(self.data) then 14 | return true 15 | end 16 | local conditionB = self[2] 17 | if type(conditionB)=="table" and conditionB.Start then 18 | conditionB:Start(self.data) 19 | end 20 | return conditionB(self.data) 21 | end 22 | } 23 | return Or -------------------------------------------------------------------------------- /lualib/Action/Src/Random.lua: -------------------------------------------------------------------------------- 1 | local Random = Ac.OO.Class { 2 | type = "Random", 3 | __call = function(self) 4 | local random = math.random(1, 10000) 5 | return random <= self[1] 6 | end 7 | } 8 | return Random -------------------------------------------------------------------------------- /lualib/Action/Src/Repeat.lua: -------------------------------------------------------------------------------- 1 | local Repeat = Ac.OO.Class { 2 | type = "Repeat", 3 | __index = { 4 | Start = function(self, data) 5 | self.data = data 6 | self.loop = self[1] 7 | self.hadLoop = 0 8 | self.action = self[2] 9 | self.startedActionLoopIndex = 0 10 | end, 11 | IsDone = function(self) 12 | return self.hadLoop >= self.loop 13 | end, 14 | Update = function(self, deltaTime) 15 | local leftLoop = self.loop - self.hadLoop 16 | if leftLoop > 0 then 17 | if self.hadLoop == self.startedActionLoopIndex then 18 | self.startedActionLoopIndex = self.startedActionLoopIndex + 1 19 | self.action:Start(self.data) 20 | end 21 | self.action:Update(deltaTime) 22 | local isDone = self.action:IsDone() 23 | if isDone then 24 | self.hadLoop = self.hadLoop + 1 25 | end 26 | end 27 | -- for i=1,leftLoop do 28 | -- if self.hadLoop == self.startedActionLoopIndex then 29 | -- self.startedActionLoopIndex = self.startedActionLoopIndex + 1 30 | -- self.action:Start(self.data) 31 | -- end 32 | -- self.action:Update(deltaTime) 33 | -- local isDone = self.action:IsDone() 34 | -- if isDone then 35 | -- self.hadLoop = self.hadLoop + 1 36 | -- end 37 | -- break 38 | -- end 39 | end 40 | }, 41 | } 42 | return Repeat -------------------------------------------------------------------------------- /lualib/Action/Src/Sequence.lua: -------------------------------------------------------------------------------- 1 | local Update = function(self, deltaTime) 2 | local actionNum = #self 3 | local hasTimeAct = nil 4 | for i=self.curActionIndex,actionNum do 5 | self.curActionIndex = i 6 | local action = self[i] 7 | if i > self.startedActionIndex then 8 | action:Start(self.data) 9 | self.startedActionIndex = i 10 | end 11 | action:Update(deltaTime) 12 | local isDone, isTimeAction = action:IsDone() 13 | if isDone then 14 | if i == actionNum then 15 | self.isDone = true 16 | end 17 | if isTimeAction then 18 | --一次Update里不让连着完成两次时间相关的action 19 | if hasTimeAct then 20 | break 21 | else 22 | hasTimeAct = true 23 | end 24 | end 25 | else 26 | break 27 | end 28 | end 29 | end 30 | 31 | local Sequence = Ac.OO.Class { 32 | type = "Sequence", 33 | __index = { 34 | Start = function(self, data) 35 | self.data = data 36 | self.curActionIndex = 1 37 | self.startedActionIndex = 0 38 | self.isDone = false 39 | end, 40 | IsDone = function(self) 41 | return self.isDone 42 | end, 43 | Update = Update, 44 | }, 45 | } 46 | 47 | return Sequence -------------------------------------------------------------------------------- /lualib/Action/Tests/Test.lua: -------------------------------------------------------------------------------- 1 | package.path = package.path ..'?/?.lua;../?.lua;../../?.lua;Tests/?.lua'; 2 | lu = require('Common.luaunit') 3 | 4 | --在上级目录运行本文件即可:lua Tests/Test.lua 5 | 6 | --将 szFullString 对象拆分为一个子字符串表 7 | function Split(szFullString, szSeparator, start_pos) 8 | local nFindStartIndex = start_pos or 1 9 | local nSplitIndex = 1 10 | local nSplitArray = {} 11 | while true do 12 | local nFindLastIndex = string.find(szFullString, szSeparator, nFindStartIndex) 13 | if not nFindLastIndex then 14 | nSplitArray[nSplitIndex] = string.sub(szFullString, nFindStartIndex, string.len(szFullString)) 15 | break 16 | end 17 | table.insert(nSplitArray, string.sub(szFullString, nFindStartIndex, nFindLastIndex - 1)) 18 | nFindStartIndex = nFindLastIndex + string.len(szSeparator) 19 | nSplitIndex = nSplitIndex + 1 20 | end 21 | return nSplitArray 22 | end 23 | 24 | function PrintTable( tbl, level, return_counter ) 25 | if tbl == nil or type(tbl) ~= "table" then 26 | return 27 | end 28 | return_counter = return_counter or 5 --剩下多少层就返回,防止无限打印 29 | if return_counter <= 0 then 30 | -- print('Cat:util.lua PrintTable return_counter empty') 31 | return 32 | end 33 | return_counter = return_counter - 1 34 | level = level or 1 35 | 36 | local indent_str = "" 37 | for i = 1, level do 38 | indent_str = indent_str.." " 39 | end 40 | print(indent_str .. "{") 41 | for k,v in pairs(tbl) do 42 | 43 | local item_str = string.format("%s%s = %s", indent_str .. " ",tostring(k), tostring(v)) 44 | print(item_str) 45 | if type(v) == "table" then 46 | PrintTable(v, level + 1, return_counter) 47 | end 48 | end 49 | print(indent_str .. "}") 50 | 51 | end 52 | local s = io.popen("ls ./Tests")--for linux 53 | -- local s = io.popen("dir /b Tests")--for windows 54 | local fileNames = s:read("*all") 55 | fileNames = Split(fileNames, "\n") 56 | for k,v in pairs(fileNames or {}) do 57 | if v~="" and v~="Test.lua" and v~="luaunit.lua" then 58 | local dot_index = string.find(v, ".", 1, true) 59 | local is_lua_file = string.find(v, ".lua", -4, true) 60 | if dot_index ~= nil and is_lua_file then 61 | local name_without_ex = string.sub(v, 1, dot_index-1) 62 | -- print('test_all.lua init test file name : ', name_without_ex) 63 | require(name_without_ex) 64 | end 65 | end 66 | end 67 | 68 | os.exit( lu.LuaUnit.run() ) -------------------------------------------------------------------------------- /lualib/Action/Tests/TestLuaOO.lua: -------------------------------------------------------------------------------- 1 | local Ac = require("Action") 2 | 3 | TestLuaOO = {} 4 | 5 | function TestLuaOO:setUp( ) 6 | end 7 | 8 | function TestLuaOO:tearDown( ) 9 | end 10 | 11 | function TestLuaOO:TestLuaOOClassKV( ) 12 | local Object = Ac.OO.Class { 13 | type = "Object", 14 | __tostring = function (o) return "("..o.x..","..o.y..","..o.z..")" end, 15 | __index = { 16 | x=1, y=2, z=3 17 | }, 18 | } 19 | local obj = Object {} 20 | lu.assertNotNil(obj) 21 | lu.assertEquals(obj.x, 1) 22 | lu.assertEquals(obj.y, 2) 23 | lu.assertEquals(obj.z, 3) 24 | lu.assertNil(obj.nothing) 25 | lu.assertEquals(tostring(obj), "(1,2,3)") 26 | 27 | local obj2 = Object {x=11, other=22} 28 | lu.assertNotNil(obj2) 29 | lu.assertEquals(obj2.x, 11) 30 | lu.assertEquals(obj.x, 1) 31 | lu.assertEquals(obj2.y, 2) 32 | lu.assertEquals(obj2.z, 3) 33 | lu.assertEquals(obj2.other, 22) 34 | lu.assertNil(obj2.nothing) 35 | lu.assertEquals(tostring(obj2), "(11,2,3)") 36 | 37 | local obj3 = Object {x=1111, other=444} 38 | lu.assertNotNil(obj3) 39 | lu.assertEquals(obj3.x, 1111) 40 | lu.assertEquals(obj.x, 1) 41 | lu.assertEquals(obj2.x, 11) 42 | lu.assertEquals(obj3.y, 2) 43 | lu.assertEquals(obj3.z, 3) 44 | lu.assertEquals(obj3.other, 444) 45 | lu.assertEquals(obj2.other, 22) 46 | lu.assertEquals(tostring(obj3), "(1111,2,3)") 47 | 48 | end 49 | 50 | function TestLuaOO:TestLuaOOClassIV( ) 51 | local Object = Ac.OO.Class { 52 | type = "Object", 53 | __tostring = function (o) return "("..o[1].." "..o[2].." "..o[3]..")" end, 54 | __index = { 55 | 11, 22, 33 56 | }, 57 | } 58 | local obj = Object {} 59 | lu.assertNotNil(obj) 60 | lu.assertEquals(obj[1], 11) 61 | lu.assertEquals(obj[2], 22) 62 | lu.assertEquals(obj[3], 33) 63 | lu.assertNil(obj[4]) 64 | lu.assertEquals(tostring(obj), "(11 22 33)") 65 | 66 | local obj2 = Object {[2]=2, [3]=333, [5]=-1} 67 | lu.assertNotNil(obj2) 68 | lu.assertEquals(obj2[1], 11) 69 | lu.assertEquals(obj2[2], 2) 70 | lu.assertEquals(obj[2], 22) 71 | lu.assertEquals(obj2[3], 333) 72 | lu.assertEquals(obj[3], 33) 73 | lu.assertNil(obj2[4]) 74 | lu.assertEquals(obj2[5], -1) 75 | lu.assertEquals(tostring(obj2), "(11 2 333)") 76 | 77 | local obj3 = Object {nil, 22222, nil, 44444} 78 | lu.assertNotNil(obj3) 79 | lu.assertEquals(obj3[1], 11) 80 | lu.assertEquals(obj3[2], 22222) 81 | lu.assertEquals(obj3[3], 33) 82 | lu.assertEquals(obj3[4], 44444) 83 | lu.assertEquals(tostring(obj3), "(11 22222 33)") 84 | end 85 | 86 | function TestLuaOO:TestLuaOOType( ) 87 | end 88 | 89 | function TestLuaOO:TestLuaOOClassOver( ) 90 | end -------------------------------------------------------------------------------- /lualib/Blueprint/BT/BTNode.lua: -------------------------------------------------------------------------------- 1 | local BTNodee = BP.BaseClass(BP.Node) 2 | 3 | function BTNodee:Constructor( graph ) 4 | self.is_updatable_bp_node = true 5 | self.graph = graph 6 | print('Cat:BTNodee.lua[5] graph', graph) 7 | end 8 | 9 | function BTNodee:Update( deltaTime ) 10 | print('Cat:BTNodee.lua[8] update') 11 | end 12 | 13 | return BTNodee -------------------------------------------------------------------------------- /lualib/Blueprint/Blueprint.lua: -------------------------------------------------------------------------------- 1 | local BP = BP or {} 2 | 3 | local importer = require("Blueprint.Core.Importer") 4 | importer.enable() 5 | 6 | --让本框架里的文件都有BP这个全局变量 7 | local BPEnv = { 8 | BP = BP 9 | } 10 | setmetatable(BPEnv, { 11 | __index = _ENV, 12 | __newindex = function (t,k,v) 13 | --本框架内不允许新增和修改全局变量,实在想要的也可以使用_ENV.xx = yy这种形式,但我像是这种没节操的人吗?! 14 | error("attempt to set a global value", 2) 15 | end, 16 | }) 17 | 18 | BP.BaseClass = importer.require("Blueprint.Core.BaseClass", BPEnv) 19 | BP.Time = importer.require("Blueprint.Core.Time", BPEnv) 20 | BP.Blackboard = importer.require("Blueprint.Core.Blackboard", BPEnv) 21 | BP.GraphsOwner = importer.require("Blueprint.Core.GraphsOwner", BPEnv) 22 | BP.Node = importer.require("Blueprint.Core.Node", BPEnv) 23 | BP.Graph = importer.require("Blueprint.Core.Graph", BPEnv) 24 | 25 | BP.FSM = BP.FSM or {} 26 | BP.FSM.FSMState = importer.require("Blueprint.FSM.FSMState", BPEnv) 27 | BP.FSM.FSMGraph = importer.require("Blueprint.FSM.FSMGraph", BPEnv) 28 | 29 | BP.Flow = BP.Flow or {} 30 | BP.Flow.UpdateEvent = importer.require("Blueprint.Flow.Event.UpdateEvent", BPEnv) 31 | -- BP.Flow.Delay = importer.require("Blueprint.Flow.Control.Delay", BPEnv) 32 | -- BP.Flow.GetVariable = importer.require("Blueprint.Flow.Variables.GetVariable", BPEnv) 33 | 34 | BP.TypeManager = importer.require("Blueprint.Core.TypeManager", BPEnv) 35 | BP.TypeManager:InitDefaultTypes() 36 | 37 | --为了不影响全局,这里要还原一下package.searchers 38 | importer.disable() 39 | 40 | return BP -------------------------------------------------------------------------------- /lualib/Blueprint/Core/BaseClass.lua: -------------------------------------------------------------------------------- 1 | local function BaseClass(super) 2 | local class_type={} 3 | class_type.Constructor=false 4 | class_type.DefaultVar=false 5 | class_type.super=super 6 | class_type.New=function(...) 7 | local obj=nil 8 | local create 9 | create = function(c, obj, ...) 10 | if c.super then 11 | create(c.super, obj, ...) 12 | end 13 | if c.Constructor then 14 | c.Constructor(obj,...) 15 | end 16 | end 17 | -- if class_type.DefaultVar then 18 | -- obj = class_type.DefaultVar(obj) 19 | -- else 20 | obj = {} 21 | -- end 22 | local function meta_func(t, k) 23 | local ret = class_type[k] 24 | obj[k] = ret 25 | return ret 26 | end 27 | setmetatable(obj, { __index=meta_func }) 28 | create(class_type, obj, ...) 29 | return obj 30 | end 31 | 32 | if super then 33 | setmetatable(class_type,{__index= 34 | function(t,k) 35 | local ret=super[k] 36 | class_type[k]=ret 37 | return ret 38 | end 39 | }) 40 | end 41 | 42 | return class_type 43 | end 44 | 45 | return BaseClass -------------------------------------------------------------------------------- /lualib/Blueprint/Core/Blackboard.lua: -------------------------------------------------------------------------------- 1 | local Blackboard = BP.BaseClass() 2 | 3 | function Blackboard:Constructor( ) 4 | self.variables = {} 5 | end 6 | 7 | function Blackboard:Init( luaData ) 8 | end 9 | 10 | function Blackboard:SetVariable( name, val ) 11 | self.variables[name] = val 12 | end 13 | 14 | function Blackboard:GetVariable( name ) 15 | local val = self.variables[name] 16 | if type(val) ~= "function" then 17 | return val 18 | else 19 | return val() 20 | end 21 | end 22 | 23 | return Blackboard -------------------------------------------------------------------------------- /lualib/Blueprint/Core/GraphsOwner.lua: -------------------------------------------------------------------------------- 1 | local GraphsOwner = BP.BaseClass() 2 | --负责管理黑板和持有一些graph集合,方便统一调配 3 | function GraphsOwner:Constructor( ) 4 | self.graphs = {} 5 | self.blackboard = BP.Blackboard.New() 6 | end 7 | 8 | function GraphsOwner.Create( ) 9 | local owner = GraphsOwner.New() 10 | return owner 11 | end 12 | 13 | function GraphsOwner:GetBlackboard( ) 14 | return self.blackboard 15 | end 16 | 17 | function GraphsOwner:AddGraph( graph ) 18 | table.insert(self.graphs, graph) 19 | -- graph:SetOwner(self) 20 | end 21 | 22 | function GraphsOwner:Start( ) 23 | for i,v in ipairs(self.graphs) do 24 | v:StartGraph(self) 25 | end 26 | end 27 | 28 | function GraphsOwner:Update( deltaTime ) 29 | for i,v in ipairs(self.graphs) do 30 | v:UpdateGraph(deltaTime) 31 | end 32 | end 33 | 34 | function GraphsOwner:Pause( ) 35 | for i,v in ipairs(self.graphs) do 36 | v:Pause() 37 | end 38 | end 39 | 40 | function GraphsOwner:Stop( ) 41 | for i,v in ipairs(self.graphs) do 42 | v:Stop() 43 | end 44 | end 45 | 46 | return GraphsOwner -------------------------------------------------------------------------------- /lualib/Blueprint/Core/Importer.lua: -------------------------------------------------------------------------------- 1 | --本源码文件主要摘自云风的blog:https://blog.codingnow.com/2015/10/lua_require_env.html 2 | local loaded = package.loaded 3 | local searchpath = package.searchpath 4 | 5 | local function load_env(filename) 6 | local f,err = loadfile(filename) 7 | if f == nil then 8 | return err 9 | end 10 | return function() 11 | return function(env) 12 | if env then 13 | debug.setupvalue(f, 1, env) 14 | end 15 | return f(filename) 16 | end 17 | end 18 | end 19 | 20 | local function searcher_env(name) 21 | local filename, err = package.searchpath(name, package.path) 22 | if filename == nil then 23 | return err 24 | else 25 | return load_env(filename) 26 | end 27 | end 28 | 29 | local function import(modname, env) 30 | return require(modname)(env) 31 | end 32 | 33 | local function enable() 34 | table.insert(package.searchers, 2, searcher_env) 35 | end 36 | 37 | local function disable() 38 | table.remove(package.searchers, 2) 39 | end 40 | 41 | return {enable=enable, disable=disable, require=import} 42 | -------------------------------------------------------------------------------- /lualib/Blueprint/Core/Node.lua: -------------------------------------------------------------------------------- 1 | local Node = BP.BaseClass() 2 | 3 | BP.Status = { 4 | Failure = 0, 5 | Success = 1, 6 | Running = 2, 7 | Resting = 3, 8 | Error = 4, 9 | Optional = 5, 10 | } 11 | 12 | function Node:Constructor( ) 13 | self.id = 0 14 | self.name = "node" 15 | self.typeName = "Node" 16 | self.graph = nil 17 | self.inSlots = {} 18 | self.outSlots = {} 19 | self.status = BP.Status.Resting 20 | end 21 | 22 | function Node.Create( targetGraph, nodeType ) 23 | if targetGraph == nil then 24 | error("Can't Create a Node without providing a Target Graph", 2) 25 | return nil 26 | end 27 | 28 | local classTbl = nodeType and BP.TypeManager:GetType(nodeType) 29 | local newNode = nil 30 | if classTbl then 31 | newNode = classTbl.New() 32 | else 33 | error("try to get an unexist type name : "..nodeType, 2) 34 | return nil 35 | end 36 | 37 | newNode.graph = targetGraph 38 | newNode:OnValidate(targetGraph) 39 | newNode:OnCreate(targetGraph) 40 | return newNode 41 | end 42 | 43 | function Node:OnValidate( graph ) 44 | --override me 45 | end 46 | 47 | function Node:OnCreate( graph ) 48 | --override me 49 | end 50 | 51 | function Node:SetInSlot( slotName, node ) 52 | self.inSlots[slotName] = node 53 | end 54 | 55 | function Node:SetOutSlot( slotName, node ) 56 | self.outSlots[slotName] = node 57 | end 58 | 59 | function Node:OnGraphStarted( ) 60 | --override me 61 | end 62 | 63 | function Node:OnGraphUnpaused( ) 64 | --override me 65 | end 66 | 67 | function Node:OnGraphPaused( ) 68 | --override me 69 | end 70 | 71 | function Node:OnGraphStoped( ) 72 | --override me 73 | end 74 | 75 | function Node:Execute( owner ) 76 | self.status = self:OnExecute(owner) 77 | return self.status 78 | end 79 | 80 | function Node:OnExecute( owner ) 81 | --override me 82 | return self.status 83 | end 84 | 85 | function Node:OnReset( ) 86 | --override me 87 | end 88 | 89 | function Node:Reset( recursively ) 90 | -- if recursively == nil then 91 | -- recursively = true 92 | -- end 93 | if self.status == BP.Status.Resting or self.isChecked then 94 | return 95 | end 96 | self:OnReset() 97 | self.status = BP.Status.Resting 98 | self.isChecked = true 99 | --handle outConnections 100 | self.isChecked = false 101 | end 102 | 103 | return Node -------------------------------------------------------------------------------- /lualib/Blueprint/Core/Time.lua: -------------------------------------------------------------------------------- 1 | local Time = {} 2 | 3 | --每帧更新最新时间,秒为单位 4 | function Time:Update( curTime ) 5 | self.time = curTime 6 | self.deltaTime = (self.time-(self.lastUpdateTime or self.time)) 7 | self.lastUpdateTime = self.time 8 | end 9 | 10 | return Time -------------------------------------------------------------------------------- /lualib/Blueprint/Core/TypeManager.lua: -------------------------------------------------------------------------------- 1 | local TypeManager = { 2 | types = {}, 3 | } 4 | 5 | function TypeManager:RegisterType( typeName, classTbl ) 6 | if not typeName then return end 7 | 8 | if self.types[typeName] then 9 | error("already register type name : "..typeName, 2) 10 | end 11 | self.types[typeName] = classTbl 12 | end 13 | 14 | function TypeManager:GetType( typeName ) 15 | return self.types[typeName] 16 | end 17 | 18 | function TypeManager:InitDefaultTypes( ) 19 | --以下是默认的一些node 20 | self:RegisterType("BP.Node", BP.Node) 21 | 22 | self:RegisterType("BP.FSM.FSMGraph", BP.FSM.FSMGraph) 23 | self:RegisterType("BP.FSM.FSMState", BP.FSM.FSMState) 24 | 25 | 26 | self:RegisterType("BP.Flow.UpdateEvent", BP.Flow.UpdateEvent) 27 | -- self:RegisterType("BP.Flow.GetVariable", BP.Flow.GetVariable) 28 | -- self:RegisterType("BP.Flow.Delay", BP.Flow.Delay) 29 | 30 | end 31 | 32 | return TypeManager -------------------------------------------------------------------------------- /lualib/Blueprint/FSM/FSMGraph.lua: -------------------------------------------------------------------------------- 1 | local FSMGraph = BP.BaseClass(BP.Graph) 2 | 3 | function FSMGraph:Constructor( ) 4 | self.currentState = nil 5 | self.previousState = nil 6 | self.hasInitialized = false 7 | end 8 | 9 | function FSMGraph.Create( luaData ) 10 | local graph = FSMGraph.New() 11 | local isOk = graph:LoadFromLuaData(luaData) 12 | if isOk then 13 | return graph 14 | end 15 | return nil 16 | end 17 | 18 | function FSMGraph:OnGraphStarted( ) 19 | if not self.hasInitialized then 20 | self.hasInitialized = true 21 | end 22 | self:EnterState(self.previousState == nil and self.primeNode or self.previousState) 23 | end 24 | 25 | function FSMGraph:OnGraphUpdate( deltaTime ) 26 | if self.currentState == nil then 27 | self:Stop(false) 28 | return 29 | end 30 | 31 | if self.currentState ~= nil then 32 | self.currentState:Update(deltaTime) 33 | end 34 | end 35 | 36 | function FSMGraph:GetStateWithName( stateName ) 37 | if not self.nodes then return end 38 | 39 | for k,v in pairs(self.nodes) do 40 | if v.name and v.name == stateName then 41 | return v 42 | end 43 | end 44 | return nil 45 | end 46 | 47 | function FSMGraph:TriggerState( stateName ) 48 | local state = self:GetStateWithName(stateName) 49 | if state ~= nil then 50 | self:EnterState(state) 51 | return state 52 | end 53 | error("No State with name '"..stateName.."' found on FSM '"..name.."'", 2) 54 | return nil 55 | end 56 | 57 | function FSMGraph:EnterState( newState ) 58 | if not self.isRunning then 59 | print("Tried to EnterState on an FSM that was not running") 60 | return false 61 | end 62 | 63 | if newState == nil then 64 | print("Tried to Enter nil State") 65 | return false 66 | end 67 | 68 | if self.currentState ~= nil then 69 | self.currentState:Finish() 70 | self.currentState:Reset() 71 | end 72 | 73 | self.previousState = self.currentState 74 | self.currentState = newState 75 | 76 | self.currentState:Execute(self.owner) 77 | return true 78 | end 79 | 80 | function FSMGraph:OnGraphStoped( ) 81 | if self.currentState ~= nil then 82 | self.currentState:Finish() 83 | self.currentState:Reset() 84 | end 85 | self.previousState = nil 86 | self.currentState = nil 87 | end 88 | 89 | function FSMGraph:OnGraphPaused( ) 90 | self.previousState = self.currentState 91 | self.currentState = nil 92 | end 93 | 94 | function FSMGraph:OnGraphUnpaused( ) 95 | self:EnterState(self.previousState == nil and self.primeNode or self.previousState) 96 | end 97 | 98 | function FSMGraph:GetCurrentState( ) 99 | return self.currentState 100 | end 101 | 102 | function FSMGraph:GetPreviousState( ) 103 | return self.previousState 104 | end 105 | 106 | return FSMGraph -------------------------------------------------------------------------------- /lualib/Blueprint/FSM/FSMState.lua: -------------------------------------------------------------------------------- 1 | local FSMState = BP.BaseClass(BP.Node) 2 | 3 | function FSMState:Constructor( ) 4 | self.elapsedTime = 0 5 | self.hasInit = false 6 | end 7 | 8 | function FSMState:OnExecute( owner ) 9 | if not self.hasInit then 10 | self.hasInit = true 11 | self.fsm = self.graph 12 | self.owner = owner 13 | self.blackboard = owner:GetBlackboard() 14 | self:OnInit() 15 | end 16 | 17 | if self.status == BP.Status.Resting or status == BP.Status.Running then 18 | self.status = BP.Status.Running 19 | self:OnEnter() 20 | end 21 | return self.status 22 | end 23 | 24 | function FSMState:OnGraphPaused( ) 25 | if self.status == BP.Status.Running then 26 | self:OnPause() 27 | end 28 | end 29 | 30 | function FSMState:Update( ) 31 | -- self.elapsedTime += Time.deltaTime; 32 | if self.status == BP.Status.Running then 33 | self:OnUpdate() 34 | end 35 | end 36 | 37 | function FSMState:OnReset( ) 38 | self.status = BP.Status.Resting 39 | self.elapsedTime = 0 40 | self:OnExit() 41 | end 42 | 43 | function FSMState:OnInit( ) 44 | --override me 45 | end 46 | 47 | function FSMState:OnEnter( ) 48 | --override me 49 | end 50 | 51 | function FSMState:OnUpdate( deltaTime ) 52 | --override me 53 | end 54 | 55 | function FSMState:OnExit( ) 56 | --override me 57 | end 58 | 59 | function FSMState:OnPause( ) 60 | --override me 61 | end 62 | 63 | function FSMState:Finish( inSuccess ) 64 | if inSuccess == nil then 65 | inSuccess = true 66 | end 67 | self.status = inSuccess and BP.Status.Success or BP.Status.Failure 68 | end 69 | 70 | return FSMState -------------------------------------------------------------------------------- /lualib/Blueprint/Flow/Control/Branch.lua: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuhaopen/SkynetMMO/cc08a82c6e8e53d16f0ace3ab8ca306068c2887b/lualib/Blueprint/Flow/Control/Branch.lua -------------------------------------------------------------------------------- /lualib/Blueprint/Flow/Control/Delay.lua: -------------------------------------------------------------------------------- 1 | local Delay = BP.BaseClass(BP.Node) 2 | 3 | function Delay:Constructor( graph ) 4 | self.is_updatable_bp_node = true 5 | self.graph = graph 6 | print('Cat:Delay.lua[5] graph', graph) 7 | end 8 | 9 | function Delay:Update( deltaTime ) 10 | print('Cat:Delay.lua[8] update') 11 | end 12 | 13 | return Delay -------------------------------------------------------------------------------- /lualib/Blueprint/Flow/Control/DoN.lua: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuhaopen/SkynetMMO/cc08a82c6e8e53d16f0ace3ab8ca306068c2887b/lualib/Blueprint/Flow/Control/DoN.lua -------------------------------------------------------------------------------- /lualib/Blueprint/Flow/Control/DoOnce.lua: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuhaopen/SkynetMMO/cc08a82c6e8e53d16f0ace3ab8ca306068c2887b/lualib/Blueprint/Flow/Control/DoOnce.lua -------------------------------------------------------------------------------- /lualib/Blueprint/Flow/Control/FlipFlop.lua: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuhaopen/SkynetMMO/cc08a82c6e8e53d16f0ace3ab8ca306068c2887b/lualib/Blueprint/Flow/Control/FlipFlop.lua -------------------------------------------------------------------------------- /lualib/Blueprint/Flow/Control/FlowControll.lua: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuhaopen/SkynetMMO/cc08a82c6e8e53d16f0ace3ab8ca306068c2887b/lualib/Blueprint/Flow/Control/FlowControll.lua -------------------------------------------------------------------------------- /lualib/Blueprint/Flow/Control/ForLoop.lua: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuhaopen/SkynetMMO/cc08a82c6e8e53d16f0ace3ab8ca306068c2887b/lualib/Blueprint/Flow/Control/ForLoop.lua -------------------------------------------------------------------------------- /lualib/Blueprint/Flow/Control/ForLoopWithBreak.lua: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuhaopen/SkynetMMO/cc08a82c6e8e53d16f0ace3ab8ca306068c2887b/lualib/Blueprint/Flow/Control/ForLoopWithBreak.lua -------------------------------------------------------------------------------- /lualib/Blueprint/Flow/Control/Gate.lua: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuhaopen/SkynetMMO/cc08a82c6e8e53d16f0ace3ab8ca306068c2887b/lualib/Blueprint/Flow/Control/Gate.lua -------------------------------------------------------------------------------- /lualib/Blueprint/Flow/Control/MultiGate.lua: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuhaopen/SkynetMMO/cc08a82c6e8e53d16f0ace3ab8ca306068c2887b/lualib/Blueprint/Flow/Control/MultiGate.lua -------------------------------------------------------------------------------- /lualib/Blueprint/Flow/Control/Sequence.lua: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuhaopen/SkynetMMO/cc08a82c6e8e53d16f0ace3ab8ca306068c2887b/lualib/Blueprint/Flow/Control/Sequence.lua -------------------------------------------------------------------------------- /lualib/Blueprint/Flow/Control/WhileLoop.lua: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuhaopen/SkynetMMO/cc08a82c6e8e53d16f0ace3ab8ca306068c2887b/lualib/Blueprint/Flow/Control/WhileLoop.lua -------------------------------------------------------------------------------- /lualib/Blueprint/Flow/Event/UpdateEvent.lua: -------------------------------------------------------------------------------- 1 | local UpdateEvent = BP.BaseClass(BP.Node) 2 | 3 | function UpdateEvent:Constructor( ) 4 | self.is_updatable_bp_node = true 5 | self.typeName = "UpdateEvent" 6 | end 7 | 8 | function UpdateEvent:OnValidate( ) 9 | self.outNode = self.outSlots["out"] 10 | assert(self.outNode, "empty out node!") 11 | end 12 | 13 | function UpdateEvent:Update( deltaTime ) 14 | self.outNode:Run() 15 | end 16 | 17 | return UpdateEvent -------------------------------------------------------------------------------- /lualib/Blueprint/Flow/Variables/GetVariable.lua: -------------------------------------------------------------------------------- 1 | local GetVariable = BP.BaseClass(BP.Node) 2 | 3 | function GetVariable:Constructor( graph ) 4 | self.is_updatable_bp_node = true 5 | self.graph = graph 6 | print('Cat:GetVariable.lua[5] graph', graph) 7 | end 8 | 9 | function GetVariable:Update( deltaTime ) 10 | print('Cat:GetVariable.lua[8] update') 11 | end 12 | 13 | return GetVariable -------------------------------------------------------------------------------- /lualib/Blueprint/Flow/Variables/Variables.lua: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuhaopen/SkynetMMO/cc08a82c6e8e53d16f0ace3ab8ca306068c2887b/lualib/Blueprint/Flow/Variables/Variables.lua -------------------------------------------------------------------------------- /lualib/Blueprint/README.md: -------------------------------------------------------------------------------- 1 | 前方正在施工.... 2 | 3 | 19.05.02:本项目为 lua 实现的 FlowCanvas,BehaviourTree,StateMachine 集合,支持此三大类的相互嵌套。可以在 unity 的 FlowCanvas+NodeCanvas插件编辑后导出 lua 脚本直接在本项目使用,当然需要用到本项目提供的导出脚本。尽管项目名叫 blueprint(参考UE的蓝图 blueprint),但和 UE 没半毛钱关系。 4 | 实现上只是命名和 FlowCanvas 插件差不多,因为语言差异和动态语言的便利,lua 可以少许多中间类。比如行为树就是相当于把n个逻辑函数拆分成n个节点(类,在 lua 里就是 table)然后分别调用,这样就为了可视化编程而牺牲了性能,本来就几个函数调用的,偏要弄成几个 table,这尤其在服务端是不可接受的,几百只怪身上都挂次一个实例。所以本项目会尽量实现为不产生额外 table。 5 | 19.05.06:我忘记了当年选择 skynet 就是为了简洁,所以想了下是否有必要引入这么复杂的 blueprint,用在前端还好,但后端对性能敏感很多。目前只是杀鸡阶段还不需要用到牛刀,所以还是先做个简单的状态机吧。 6 | 19.05.07:先完成了黑板和有限状态机基础部分,暂不使用 NodeCanvas 插件导出的数据,因为目前就几个状态,而且跳转关系不复杂,所以全写在代码里更好阅读。以后需求越来越复杂时再使用可视化编程也行,数据结构是一样的。 -------------------------------------------------------------------------------- /lualib/Blueprint/Tests/FSMSampleState.lua: -------------------------------------------------------------------------------- 1 | local BP = require("Blueprint") 2 | local FSMSampleState = BP.BaseClass(BP.FSM.FSMState) 3 | 4 | function FSMSampleState:OnInit( ) 5 | print('Cat:FSMSampleState.lua[OnInit]') 6 | end 7 | 8 | function FSMSampleState:OnEnter( ) 9 | print('Cat:FSMSampleState.lua[OnEnter]') 10 | end 11 | 12 | function FSMSampleState:OnUpdate( deltaTime ) 13 | print('Cat:FSMSampleState.lua[24] deltaTime', deltaTime) 14 | end 15 | 16 | function FSMSampleState:OnExit( ) 17 | print('Cat:FSMSampleState.lua[OnExit]') 18 | end 19 | 20 | function FSMSampleState:OnPause( ) 21 | print('Cat:FSMSampleState.lua[OnPause]') 22 | end 23 | 24 | return FSMSampleState -------------------------------------------------------------------------------- /lualib/Blueprint/Tests/TestBlueprintGraph.lua: -------------------------------------------------------------------------------- 1 | local BP = require("Blueprint") 2 | do return {} end 3 | TestBlueprintGraph = {} 4 | 5 | function TestBlueprintGraph:setUp( ) 6 | end 7 | 8 | function TestBlueprintGraph:tearDown( ) 9 | end 10 | 11 | function TestBlueprintGraph:TestFlowGraph( ) 12 | local data = { 13 | --该蓝图的所有节点 14 | nodes = { 15 | { 16 | id = 1, 17 | type = "BP.Flow.UpdateEvent", 18 | }, 19 | { 20 | id = 2, 21 | type = "BP.Flow.GetVariable", 22 | arge = { 23 | key = "value", 24 | value = 123 25 | }, 26 | }, 27 | }, 28 | --该蓝图的所有线段,表示了哪两个节点相连 29 | wires = { 30 | { 31 | sourceID = 1, 32 | sourceSlotName = "out", 33 | targetID = 2, 34 | targetSlotName = "in", 35 | }, 36 | }, 37 | } 38 | local graph = BP.Graph.Create(data) 39 | lu.assertNotNil(graph) 40 | lu.assertEquals(TableSize(graph.nodes), 2) 41 | graph:Start() 42 | 43 | graph:Update() 44 | end 45 | 46 | function TestBlueprintGraph:TestFSMGraph( ) 47 | local data = { 48 | --该蓝图的所有节点 49 | nodes = { 50 | { 51 | id = 1, 52 | type = "BP.FSM.State", 53 | name = "idle", 54 | }, 55 | { 56 | id = 2, 57 | type = "BP.FSM.ActionState", 58 | arge = { 59 | key = "value", 60 | value = 123 61 | }, 62 | }, 63 | }, 64 | --该蓝图的所有线段,表示了哪两个节点相连 65 | wires = { 66 | { 67 | sourceID = 1, 68 | sourceSlotName = "out", 69 | targetID = 2, 70 | targetSlotName = "in", 71 | }, 72 | }, 73 | } 74 | local graph = BP.Graph.Create(data) 75 | lu.assertNotNil(graph) 76 | lu.assertEquals(TableSize(graph.nodes), 2) 77 | graph:Start() 78 | 79 | graph:Update() 80 | end 81 | 82 | function TestBlueprintGraph:TestBTGraph( ) 83 | local data = { 84 | --该蓝图的所有节点 85 | nodes = { 86 | { 87 | id = 1, 88 | type = "BP.BT.ConditionNode", 89 | arge = { 90 | 91 | }, 92 | }, 93 | { 94 | id = 2, 95 | type = "BP.BT.Sequencer", 96 | }, 97 | }, 98 | --该蓝图的所有线段,表示了哪两个节点相连 99 | wires = { 100 | { 101 | sourceID = 1, 102 | sourceSlotName = "out", 103 | targetID = 2, 104 | targetSlotName = "in", 105 | }, 106 | }, 107 | } 108 | local graph = BP.Graph.Create(data) 109 | lu.assertNotNil(graph) 110 | lu.assertEquals(TableSize(graph.nodes), 2) 111 | graph:Start() 112 | 113 | graph:Update() 114 | end 115 | 116 | return TestBlueprintGraph -------------------------------------------------------------------------------- /lualib/Blueprint/Tests/TestGraphsOwner.lua: -------------------------------------------------------------------------------- 1 | local BP = require("Blueprint") 2 | 3 | TestGraphsOwner = {} 4 | 5 | function TestGraphsOwner:setUp( ) 6 | end 7 | 8 | function TestGraphsOwner:tearDown( ) 9 | end 10 | 11 | function TestGraphsOwner:TestGraphsOwnerFSM( ) 12 | do return end 13 | local sampleClass = require("Blueprint.Tests.FSMSampleState") 14 | BP.TypeManager:RegisterType("Blueprint.State.TestGraphsOwnerFSMState", sampleClass) 15 | 16 | local data = { 17 | nodes = { 18 | { 19 | id = 1, 20 | type = "Blueprint.State.FSMSampleState", 21 | name = "PatrolState", 22 | }, 23 | { 24 | id = 2, 25 | type = "Blueprint.State.FSMSampleState", 26 | name = "FightState", 27 | }, 28 | }, 29 | } 30 | local graph = BP.FSM.FSMGraph.Create(data) 31 | lu.assertNotNil(graph) 32 | lu.assertEquals(TableSize(graph.nodes), 2) 33 | 34 | local owner = BP.GraphsOwner.Create() 35 | lu.assertNotNil(owner) 36 | owner:AddGraph(graph) 37 | owner:Start() 38 | owner:Update(1) 39 | end 40 | 41 | return TestGraphsOwner -------------------------------------------------------------------------------- /lualib/Blueprint/Tests/test_blueprint_all.lua: -------------------------------------------------------------------------------- 1 | package.path = package.path ..';../?.lua;../../?.lua;Tests/?.lua'; 2 | local BP = require('Blueprint') 3 | lu = require('Tests.luaunit') 4 | 5 | --在上级目录运行本文件即可:lua Tests/test_all.lua 6 | --目前只支持lua5.2及以上版本,如果你在windows系统的话可以自己编译最新的lua库并生成exe用来运行本测试 7 | 8 | --将 szFullString 对象拆分为一个子字符串表 9 | function Split(szFullString, szSeparator, start_pos) 10 | local nFindStartIndex = start_pos or 1 11 | local nSplitIndex = 1 12 | local nSplitArray = {} 13 | while true do 14 | local nFindLastIndex = string.find(szFullString, szSeparator, nFindStartIndex) 15 | if not nFindLastIndex then 16 | nSplitArray[nSplitIndex] = string.sub(szFullString, nFindStartIndex, string.len(szFullString)) 17 | break 18 | end 19 | table.insert(nSplitArray, string.sub(szFullString, nFindStartIndex, nFindLastIndex - 1)) 20 | nFindStartIndex = nFindLastIndex + string.len(szSeparator) 21 | nSplitIndex = nSplitIndex + 1 22 | end 23 | return nSplitArray 24 | end 25 | 26 | -- 检查table是否为空 27 | function IsTableEmpty(tbl) 28 | return not tbl or _G.next( tbl ) == nil 29 | end 30 | 31 | -- 获取table长度,当数据不连续时不能用# 32 | function TableSize(tbl) 33 | if IsTableEmpty(tbl) then 34 | return 0 35 | end 36 | 37 | local len = 0 38 | for _ in pairs(tbl) do 39 | len = len + 1 40 | end 41 | return len 42 | end 43 | 44 | function PrintTable( tbl, level, return_counter ) 45 | if tbl == nil or type(tbl) ~= "table" then 46 | return 47 | end 48 | return_counter = return_counter or 5 --剩下多少层就返回,防止无限打印 49 | if return_counter <= 0 then 50 | -- print('Cat:util.lua PrintTable return_counter empty') 51 | return 52 | end 53 | return_counter = return_counter - 1 54 | level = level or 1 55 | 56 | local indent_str = "" 57 | for i = 1, level do 58 | indent_str = indent_str.." " 59 | end 60 | print(indent_str .. "{") 61 | for k,v in pairs(tbl) do 62 | 63 | local item_str = string.format("%s%s = %s", indent_str .. " ",tostring(k), tostring(v)) 64 | print(item_str) 65 | if type(v) == "table" then 66 | PrintTable(v, level + 1, return_counter) 67 | end 68 | end 69 | print(indent_str .. "}") 70 | 71 | end 72 | 73 | local ignore_files = { 74 | ["test_blueprint_all.lua"]=true, 75 | ["luaunit.lua"]=true, 76 | ["FSMSampleState.lua"]=true, 77 | } 78 | -- local s = io.popen("ls ./Tests")--for linux 79 | local s = io.popen("dir /b Tests")--for windows 80 | local fileNames = s:read("*all") 81 | fileNames = Split(fileNames, "\n") 82 | for k,v in pairs(fileNames or {}) do 83 | if v~="" and not ignore_files[v] then 84 | local dot_index = string.find(v, ".", 1, true) 85 | local is_lua_file = string.find(v, ".lua", -4, true) 86 | if dot_index ~= nil and is_lua_file then 87 | local name_without_ex = string.sub(v, 1, dot_index-1) 88 | -- print('test_blueprint_all.lua init test file name : ', name_without_ex) 89 | require(name_without_ex) 90 | end 91 | end 92 | end 93 | 94 | os.exit( lu.LuaUnit.run() ) -------------------------------------------------------------------------------- /lualib/ECS/Common/BaseClass.lua: -------------------------------------------------------------------------------- 1 | local function BaseClass(super) 2 | local class_type={} 3 | class_type.Constructor=false 4 | class_type.DefaultVar=false 5 | class_type.super=super 6 | class_type.New=function(...) 7 | local obj=nil 8 | local create 9 | create = function(c, obj, ...) 10 | if c.super then 11 | create(c.super, obj, ...) 12 | end 13 | if c.Constructor then 14 | c.Constructor(obj,...) 15 | end 16 | end 17 | if class_type.DefaultVar then 18 | obj = class_type.DefaultVar(obj) 19 | else 20 | obj = {} 21 | end 22 | local function meta_func(t, k) 23 | local ret = class_type[k] 24 | obj[k] = ret 25 | return ret 26 | end 27 | setmetatable(obj, { __index=meta_func }) 28 | create(class_type, obj, ...) 29 | return obj 30 | end 31 | 32 | if super then 33 | setmetatable(class_type,{__index= 34 | function(t,k) 35 | local ret=super[k] 36 | class_type[k]=ret 37 | return ret 38 | end 39 | }) 40 | end 41 | 42 | return class_type 43 | end 44 | 45 | return BaseClass -------------------------------------------------------------------------------- /lualib/ECS/Common/Importer.lua: -------------------------------------------------------------------------------- 1 | --本源码文件主要摘自云风的blog:https://blog.codingnow.com/2015/10/lua_require_env.html 2 | local loaded = package.loaded 3 | local searchpath = package.searchpath 4 | 5 | local function load_env(filename) 6 | local f,err = loadfile(filename) 7 | if f == nil then 8 | return err 9 | end 10 | return function() 11 | return function(env) 12 | if env then 13 | debug.setupvalue(f, 1, env) 14 | end 15 | return f(filename) 16 | end 17 | end 18 | end 19 | 20 | local function searcher_env(name) 21 | local filename, err = package.searchpath(name, package.path) 22 | if filename == nil then 23 | return err 24 | else 25 | return load_env(filename) 26 | end 27 | end 28 | 29 | local function import(modname, env) 30 | return require(modname)(env) 31 | end 32 | 33 | local function enable() 34 | table.insert(package.searchers, 2, searcher_env) 35 | end 36 | 37 | local function disable() 38 | table.remove(package.searchers, 2) 39 | end 40 | 41 | return {enable=enable, disable=disable, require=import} 42 | -------------------------------------------------------------------------------- /lualib/ECS/Common/SortingUtilities.lua: -------------------------------------------------------------------------------- 1 | local SortingUtilities = {} 2 | ECS.SortingUtilities = SortingUtilities 3 | 4 | function SortingUtilities.InsertSorted( data, length, newValue ) 5 | while (length > 1 and newValue < data[length - 1]) do 6 | data[length] = data[length - 1] 7 | length = length - 1 8 | end 9 | data[length] = newValue 10 | end 11 | 12 | return SortingUtilities -------------------------------------------------------------------------------- /lualib/ECS/Common/UnsafeLinkedListNode.lua: -------------------------------------------------------------------------------- 1 | local UnsafeLinkedListNode = ECS.BaseClass() 2 | ECS.UnsafeLinkedListNode = UnsafeLinkedListNode 3 | 4 | function UnsafeLinkedListNode:Constructor( ) 5 | end 6 | 7 | function UnsafeLinkedListNode.InitializeList( list ) 8 | list.Prev = list 9 | list.Next = list 10 | end 11 | 12 | function UnsafeLinkedListNode:Begin( ) 13 | return self.Next 14 | end 15 | 16 | function UnsafeLinkedListNode:IsEmpty( ) 17 | return self == self.Next 18 | end 19 | 20 | function UnsafeLinkedListNode:GetChunk( ) 21 | return self.chunk 22 | end 23 | 24 | function UnsafeLinkedListNode:SetChunk( value ) 25 | self.chunk = value 26 | end 27 | 28 | function UnsafeLinkedListNode:Add( node ) 29 | UnsafeLinkedListNode.InsertBefore(self, node) 30 | end 31 | 32 | function UnsafeLinkedListNode:Remove( ) 33 | if (self.Prev == nil) then 34 | return 35 | end 36 | 37 | self.Prev.Next = self.Next 38 | self.Next.Prev = self.Prev 39 | self.Prev = nil 40 | self.Next = nil 41 | end 42 | 43 | function UnsafeLinkedListNode.InsertBefore( pos, node ) 44 | assert(node ~= pos, "cannot be same!") 45 | -- Assert.IsFalse(node.IsInList) 46 | node.Prev = pos.Prev 47 | node.Next = pos 48 | 49 | node.Prev.Next = node 50 | node.Next.Prev = node 51 | end 52 | 53 | return UnsafeLinkedListNode -------------------------------------------------------------------------------- /lualib/ECS/ECS.lua: -------------------------------------------------------------------------------- 1 | local ECS = ECS or {} 2 | 3 | local importer = require("ECS.Common.Importer") 4 | importer.enable() 5 | 6 | --让本框架里的文件都有ECS这个全局变量 7 | local ECSEnv = { 8 | ECS = ECS 9 | } 10 | setmetatable(ECSEnv, { 11 | __index = _ENV, 12 | __newindex = function (t,k,v) 13 | --本框架内不允许新增和修改全局变量,实在想要的也可以使用_ENV.xx = yy这种形式,但我像是这种没节操的人吗?! 14 | error("attempt to set a global value", 2) 15 | end, 16 | }) 17 | 18 | ECS.BaseClass = importer.require("ECS.Common.BaseClass", ECSEnv) 19 | ECS.TypeManager = importer.require("ECS.Src.TypeManager", ECSEnv) 20 | ECS.ScriptBehaviourManager = importer.require("ECS.Src.ScriptBehaviourManager", ECSEnv) 21 | ECS.World = importer.require("ECS.Src.World", ECSEnv) 22 | ECS.Entity = importer.require("ECS.Src.Entity", ECSEnv) 23 | ECS.EntityManager = importer.require("ECS.Src.EntityManager", ECSEnv) 24 | ECS.EntityDataManager = importer.require("ECS.Src.EntityDataManager", ECSEnv) 25 | ECS.ComponentGroup = importer.require("ECS.Src.ComponentGroup", ECSEnv) 26 | ECS.ComponentSystem = importer.require("ECS.Src.ComponentSystem", ECSEnv) 27 | ECS.SharedComponentDataManager = importer.require("ECS.Src.SharedComponentDataManager", ECSEnv) 28 | ECS.ArchetypeManager = importer.require("ECS.Src.ArchetypeManager", ECSEnv) 29 | ECS.EntityGroupManager = importer.require("ECS.Src.EntityGroupManager", ECSEnv) 30 | ECS.ComponentType = importer.require("ECS.Src.ComponentType", ECSEnv) 31 | ECS.ComponentTypeInArchetype = importer.require("ECS.Src.ComponentTypeInArchetype", ECSEnv) 32 | ECS.SortingUtilities = importer.require("ECS.Common.SortingUtilities", ECSEnv) 33 | ECS.Chunk = importer.require("ECS.Src.Chunk", ECSEnv) 34 | ECS.UnsafeLinkedListNode = importer.require("ECS.Common.UnsafeLinkedListNode", ECSEnv) 35 | ECS.ChunkDataUtility = importer.require("ECS.Src.ChunkDataUtility", ECSEnv) 36 | ECS.ComponentSystemInjection = importer.require("ECS.Src.ComponentSystemInjection", ECSEnv) 37 | ECS.InjectComponentGroupData = importer.require("ECS.Src.InjectComponentGroupData", ECSEnv) 38 | ECS.ComponentChunkIterator = importer.require("ECS.Src.ComponentChunkIterator", ECSEnv) 39 | ECS.ComponentDataArray = importer.require("ECS.Src.ComponentDataArray", ECSEnv) 40 | ECS.EntityArray = importer.require("ECS.Src.EntityArray", ECSEnv) 41 | 42 | local function InitWorld( worldName ) 43 | local world = ECS.World.New(worldName) 44 | ECS.World.Active = world 45 | 46 | world.EntityManager = world:GetOrCreateManager(ECS.EntityManager.Name) 47 | 48 | return world 49 | end 50 | 51 | ECS.InitWorld = InitWorld 52 | 53 | --为了不影响全局,这里要还原一下package.searchers 54 | importer.disable() 55 | 56 | return ECS -------------------------------------------------------------------------------- /lualib/ECS/README.md: -------------------------------------------------------------------------------- 1 | # LuaECS 2 | Unity ECS 框架 Entities 的 Lua 实现 3 | 对实现细节有兴趣的可以看我对 UnityECS 的框架源码分析:https://blog.csdn.net/yudianxia/column/info/31641 4 | Lua 版本的话基本上和 Unity 实现是一样的,除了数据的存放方式,在早期版本试过直接开辟一段内存然后把组件的数据逐字段地写入,读取时计算下该字段的指针偏移就可以了,但 lua 和 c 的交互消耗比连续存放数据带来的优化更大,所以就放弃了该方案,现在改成直接存放 table了。 5 | 6 | # 用例 7 | ``` 8 | local ECS = require "ECS" 9 | --ComponentType 10 | ECS.TypeManager.RegisterType("ECS.CustomCom", {x=0, y="", z=false}) 11 | 12 | --Entity 13 | local entityMgr = ECS.World.Active:GetOrCreateManager(ECS.EntityManager.Name) 14 | local archetype = entityMgr:CreateArchetype({"ECS.CustomCom"}) 15 | local entity = entityMgr:CreateEntityByArcheType(archetype) 16 | entityMgr:SetComponentData(entity, "ECS.CustomCom", {x=1.1, y="hello", z=true, tbl={a=1,b=false}}) 17 | local comp_data = entityMgr:GetComponentData(entity, "ECS.CustomCom") 18 | 19 | --ComponentSystem 20 | local TestInjectSystem = ECS.BaseClass(ECS.ComponentSystem) 21 | ECS.TypeManager.RegisterScriptMgr("TestInjectSystem", TestInjectSystem) 22 | function TestInjectSystem:OnCreate( ) 23 | ECS.ComponentSystem.OnCreate(self) 24 | self.group = self:GetComponentGroup({"ECS.CustomCom"}) 25 | end 26 | function TestInjectSystem:OnUpdate( ) 27 | local comps = self.group:ToComponentDataArray("ECS.CustomCom") 28 | for i=1,comps.Length do 29 | local comp = comps[i] 30 | if comp.tbl.b then 31 | do some thing... 32 | end 33 | end 34 | end 35 | ``` 36 | 37 | # 测试 38 | 可以在 windows 或 linux 上运行测试用例,Lua5.2以上都是可以的 : 39 | lua ./Tests/test_all.lua -v 40 | 41 | # Todo 42 | )增加 ShardComponent 43 | )支持多线程? 44 | )System 根据 UpdateBefore,After 等排序 45 | -------------------------------------------------------------------------------- /lualib/ECS/Src/Chunk.lua: -------------------------------------------------------------------------------- 1 | local Chunk = ECS.BaseClass() 2 | ECS.Chunk = Chunk 3 | ECS.Chunk.kChunkSize = 16 * 1024 4 | 5 | function Chunk:Constructor( ) 6 | -- self.Buffer = ECS.Core.CreateChunk(ECS.Chunk.kChunkSize) 7 | self.Buffer = {} 8 | self.Count = 0--当前Entity的数量 9 | self.Capacity = 0--能存放Entity的容量 10 | self.SharedComponentValueArray = {} 11 | self.Archetype = nil 12 | self.ChunkListNode = nil 13 | self.ChunkListWithEmptySlotsNode = nil 14 | 15 | end 16 | 17 | function Chunk.GetChunkBufferSize( numComponents, numSharedComponents ) 18 | local bufferSize = ECS.Chunk.kChunkSize - (numSharedComponents * 4 + numComponents * 4) 19 | return bufferSize 20 | end 21 | 22 | function Chunk.GetSharedComponentOffset( numSharedComponents ) 23 | return 0 24 | end 25 | 26 | function Chunk.GetChangedComponentOffset( numComponents, numSharedComponents ) 27 | return 0 28 | end 29 | 30 | return Chunk -------------------------------------------------------------------------------- /lualib/ECS/Src/ComponentChunkIterator.lua: -------------------------------------------------------------------------------- 1 | local ComponentChunkIterator = ECS.BaseClass() 2 | 3 | ECS.FilterType = { 4 | None = 1, SharedComponent=2, Changed=3, 5 | } 6 | function ComponentChunkIterator:Constructor( match, globalSystemVersion, filter ) 7 | self.m_FirstMatchingArchetype = match 8 | self.m_CurrentMatchingArchetype = match 9 | self.IndexInComponentGroup = -1 10 | self.m_CurrentChunk = nil 11 | self.m_CurrentArchetypeEntityIndex = math.huge 12 | self.m_CurrentArchetypeIndex = math.huge 13 | self.m_CurrentChunkEntityIndex = 1 14 | self.m_CurrentChunkIndex = 1 15 | self.m_GlobalSystemVersion = globalSystemVersion 16 | self.m_Filter = filter 17 | end 18 | 19 | function ComponentChunkIterator.Clone( iterator ) 20 | assert(iterator~=nil, "iterator should not be nil!") 21 | return ComponentChunkIterator.New(iterator.m_FirstMatchingArchetype, iterator.m_GlobalSystemVersion, iterator.m_Filter) 22 | end 23 | 24 | function ComponentChunkIterator:SetIndexInComponentGroup( indexInComponentGroup ) 25 | self.IndexInComponentGroup = indexInComponentGroup 26 | end 27 | 28 | function ComponentChunkIterator:MoveToEntityIndex( index ) 29 | if not self.m_Filter.RequiresMatchesFilter then 30 | if index < self.m_CurrentArchetypeEntityIndex then 31 | self.m_CurrentMatchingArchetype = self.m_FirstMatchingArchetype 32 | self.m_CurrentArchetypeEntityIndex = 1 33 | self.m_CurrentChunk = self.m_CurrentMatchingArchetype.Archetype.ChunkList:Begin():GetChunk() 34 | self.m_CurrentChunkEntityIndex = 1 35 | end 36 | 37 | while index >= self.m_CurrentArchetypeEntityIndex + self.m_CurrentMatchingArchetype.Archetype.EntityCount do 38 | self.m_CurrentArchetypeEntityIndex = self.m_CurrentArchetypeEntityIndex+self.m_CurrentMatchingArchetype.Archetype.EntityCount 39 | self.m_CurrentMatchingArchetype = self.m_CurrentMatchingArchetype.Next 40 | self.m_CurrentChunk = self.m_CurrentMatchingArchetype.Archetype.ChunkList:Begin():GetChunk() 41 | self.m_CurrentChunkEntityIndex = 1 42 | end 43 | 44 | index = index - self.m_CurrentArchetypeEntityIndex + 1 45 | if index < self.m_CurrentChunkEntityIndex then 46 | self.m_CurrentChunk = self.m_CurrentMatchingArchetype.Archetype.ChunkList:Begin():GetChunk() 47 | self.m_CurrentChunkEntityIndex = 1 48 | end 49 | 50 | while index >= self.m_CurrentChunkEntityIndex + self.m_CurrentChunk.Count do 51 | self.m_CurrentChunkEntityIndex = self.m_CurrentChunkEntityIndex + self.m_CurrentChunk.Count 52 | self.m_CurrentChunk = self.m_CurrentChunk.ChunkListNode.Next 53 | end 54 | end 55 | end 56 | 57 | function ComponentChunkIterator:UpdateCacheToCurrentChunk( cache, isWriting, indexInComponentGroup ) 58 | local archetype = self.m_CurrentMatchingArchetype.Archetype 59 | local indexInArchetype = self.m_CurrentMatchingArchetype.IndexInArchetype[indexInComponentGroup] 60 | cache.CachedBeginIndex = self.m_CurrentChunkEntityIndex + self.m_CurrentArchetypeEntityIndex - 2 61 | cache.CachedEndIndex = cache.CachedBeginIndex + self.m_CurrentChunk.Count 62 | cache.CurChunk = self.m_CurrentChunk 63 | end 64 | 65 | function ComponentChunkIterator:MoveToEntityIndexAndUpdateCache( index, cache, isWriting ) 66 | self:MoveToEntityIndex(index) 67 | self:UpdateCacheToCurrentChunk(cache, isWriting, self.IndexInComponentGroup) 68 | end 69 | 70 | function ComponentChunkIterator.CalculateLength( firstMatchingArchetype, filter ) 71 | local length = 0 72 | if not filter.RequiresMatchesFilter then 73 | local match = firstMatchingArchetype 74 | while match~=nil do 75 | length = length + match.Archetype.EntityCount 76 | match = match.Next 77 | end 78 | else 79 | local match = firstMatchingArchetype 80 | while match~=nil do 81 | length = length + match.Archetype.EntityCount 82 | if match.Archetype.EntityCount > 0 then 83 | local archeType = match.Archetype 84 | local c = archeType.ChunkList:Begin() 85 | while c ~= archeType.ChunkList:End() do 86 | if c:MatchesFilter(match, filter) then 87 | length = length + c.Count 88 | end 89 | c = c.ChunkListNode.Next 90 | end 91 | end 92 | match = match.Next 93 | end 94 | end 95 | return length 96 | end 97 | 98 | return ComponentChunkIterator -------------------------------------------------------------------------------- /lualib/ECS/Src/ComponentDataArray.lua: -------------------------------------------------------------------------------- 1 | local ComponentDataArray = {} 2 | ECS.ComponentDataArray = ComponentDataArray 3 | 4 | function ComponentDataArray.Create( iterator, length, componentName ) 5 | assert(iterator~=nil, "iterator should not be nil!") 6 | assert(length~=nil, "length should not be nil!") 7 | assert(componentName~=nil, "componentName should not be nil!") 8 | local array = { 9 | m_Iterator=iterator, 10 | Length=length, 11 | m_ComponentTypeName=componentName, 12 | m_Data = {}, 13 | m_Cache = { 14 | CachedPtr=nil, CachedBeginIndex=0, CachedEndIndex=0, CachedSizeOf=0, IsWriting=false 15 | }, 16 | } 17 | ComponentDataArray.InitMetaTable(array) 18 | return array 19 | end 20 | 21 | local get_fun = function ( t, index ) 22 | if index < 1 or index > t.Length then 23 | return nil 24 | end 25 | if index < t.m_Cache.CachedBeginIndex or index >= t.m_Cache.CachedEndIndex then 26 | t.m_Iterator:MoveToEntityIndexAndUpdateCache(index, t.m_Cache, false) 27 | end 28 | local data = ECS.ChunkDataUtility.GetComponentDataWithTypeName(t.m_Cache.CurChunk, t.m_ComponentTypeName, index-t.m_Cache.CachedBeginIndex) 29 | return data 30 | end 31 | 32 | local set_fun = function ( t, index, value ) 33 | if index < t.m_Cache.CachedBeginIndex or index >= t.m_Cache.CachedEndIndex then 34 | t.m_Iterator:MoveToEntityIndexAndUpdateCache(index, t.m_Cache, true) 35 | -- elseif not t.m_Cache.IsWriting then 36 | -- t.m_Cache.IsWriting = true; 37 | -- t.m_Iterator:UpdateChangeVersion() 38 | end 39 | t.m_Cache.CurChunk.Buffer[t.m_ComponentTypeName][index-t.m_Cache.CachedBeginIndex] = value 40 | end 41 | 42 | local meta_tbl = { 43 | __index = get_fun, 44 | __newindex = set_fun, 45 | } 46 | function ComponentDataArray.InitMetaTable( array ) 47 | setmetatable(array, meta_tbl) 48 | end 49 | 50 | return ComponentDataArray -------------------------------------------------------------------------------- /lualib/ECS/Src/ComponentGroup.lua: -------------------------------------------------------------------------------- 1 | local ComponentGroup = ECS.BaseClass() 2 | ECS.ComponentGroup = ComponentGroup 3 | 4 | function ComponentGroup:Constructor( groupData, safetyManager, typeManager, entityDataManager ) 5 | self.m_GroupData = groupData 6 | self.m_EntityDataManager = entityDataManager 7 | self.m_Filter = {Type=ECS.FilterType.None, RequiredChangeVersion=0} 8 | self.ArchetypeManager = typeManager 9 | self.EntityDataManager = entityDataManager 10 | end 11 | 12 | function ComponentGroup:ToComponentDataArray( com_type ) 13 | local typeIndex = ECS.TypeManager.GetTypeIndexByName(com_type) 14 | local iterator, length = self:GetComponentChunkIterator() 15 | local indexInComponentGroup = self:GetIndexInComponentGroup(typeIndex) 16 | local res = self:ToComponentDataArrayByIterator(iterator, indexInComponentGroup, length, com_type) 17 | return res 18 | end 19 | 20 | function ComponentGroup:GetIndexInComponentGroup( componentType ) 21 | local componentIndex = 1 22 | while componentIndex <= self.m_GroupData.RequiredComponentsCount and self.m_GroupData.RequiredComponents[componentIndex].TypeIndex ~= componentType do 23 | componentIndex = componentIndex + 1 24 | end 25 | return componentIndex 26 | end 27 | 28 | function ComponentGroup:ToComponentDataArrayByIterator( iterator, indexInComponentGroup, length, com_type ) 29 | iterator:SetIndexInComponentGroup(indexInComponentGroup) 30 | local data = ECS.ComponentDataArray.Create(iterator, length, com_type) 31 | return data 32 | end 33 | 34 | function ComponentGroup:GetSharedComponentDataArray( shared_com_type ) 35 | local iterator, length = self:GetComponentChunkIterator() 36 | local indexInComponentGroup = self:GetIndexInComponentGroup(TypeManager.GetTypeIndex(shared_com_type)) 37 | local res = {} 38 | self:GetSharedComponentDataArray(iterator, indexInComponentGroup, length, res) 39 | return res 40 | end 41 | 42 | function ComponentGroup:ToEntityArray( ) 43 | local iterator, length = self:GetComponentChunkIterator() 44 | iterator:SetIndexInComponentGroup(1) 45 | local data = ECS.EntityArray.Create(iterator, length) 46 | return data 47 | end 48 | 49 | function ComponentGroup:GetComponentChunkIterator( ) 50 | local length = ECS.ComponentChunkIterator.CalculateLength(self.m_GroupData.FirstMatchingArchetype, self.m_Filter) 51 | local iterator = ECS.ComponentChunkIterator.New(self.m_GroupData.FirstMatchingArchetype, self.m_EntityDataManager.GlobalSystemVersion, self.m_Filter) 52 | return iterator, length 53 | end 54 | 55 | function ComponentGroup:ResetFilter( ) 56 | if self.m_Filter.Type == ECS.FilterType.SharedComponent then 57 | local filteredCount = self.m_Filter.Shared.Count 58 | local sm = self.ArchetypeManager.GetSharedComponentDataManager() 59 | local sharedComponentIndexPtr = self.m_Filter.Shared.SharedComponentIndex 60 | for var=1,filteredCount do 61 | sm:RemoveReference(sharedComponentIndexPtr[i]) 62 | end 63 | end 64 | self.m_Filter.Type = FilterType.None 65 | end 66 | 67 | function ComponentGroup:SetFilter( ) 68 | end 69 | 70 | function ComponentGroup:CompareComponents( componentTypes ) 71 | return ECS.EntityGroupManager.CompareComponents(componentTypes, self.m_GroupData) 72 | end 73 | 74 | return ComponentGroup -------------------------------------------------------------------------------- /lualib/ECS/Src/ComponentSystemInjection.lua: -------------------------------------------------------------------------------- 1 | local ComponentSystemInjection = {} 2 | 3 | local function Split(szFullString, szSeparator, start_pos) 4 | local nFindStartIndex = start_pos or 1 5 | local nSplitIndex = 1 6 | local nSplitArray = {} 7 | while true do 8 | local nFindLastIndex = string.find(szFullString, szSeparator, nFindStartIndex) 9 | if not nFindLastIndex then 10 | nSplitArray[nSplitIndex] = string.sub(szFullString, nFindStartIndex, string.len(szFullString)) 11 | break 12 | end 13 | table.insert(nSplitArray, string.sub(szFullString, nFindStartIndex, nFindLastIndex - 1)) 14 | nFindStartIndex = nFindLastIndex + string.len(szSeparator) 15 | nSplitIndex = nSplitIndex + 1 16 | end 17 | return nSplitArray 18 | end 19 | 20 | local IsFindInStrList = function ( str_list, find_str ) 21 | if not str_list then return false end 22 | 23 | for k,v in pairs(str_list) do 24 | if v == find_str then 25 | return true 26 | end 27 | end 28 | return false 29 | end 30 | 31 | function ComponentSystemInjection.Inject( componentSystem, world, entityManager, 32 | outInjectGroups, outInjectFromEntityData ) 33 | local inject_info_list = componentSystem:GetInjectInfoList() 34 | for i,v in ipairs(inject_info_list) do 35 | local inject_field_name = v[1] 36 | local inject_info = v[2] 37 | 38 | local group = ECS.InjectComponentGroupData.CreateInjection(inject_field_name, inject_info, componentSystem) 39 | table.insert(outInjectGroups, group) 40 | end 41 | end 42 | 43 | function ComponentSystemInjection:InjectConstructorDependencies( manager, world, field_info, inject_field_name ) 44 | manager[inject_field_name] = world:GetOrCreateManager(field_info[2]) 45 | end 46 | 47 | return ComponentSystemInjection -------------------------------------------------------------------------------- /lualib/ECS/Src/ComponentType.lua: -------------------------------------------------------------------------------- 1 | local ComponentType = ECS.BaseClass() 2 | ECS.ComponentType = ComponentType 3 | 4 | ComponentType.AccessMode = { 5 | ReadWrite = 1, 6 | ReadOnly = 2, 7 | Subtractive = 3, 8 | } 9 | 10 | function ComponentType:Constructor( ) 11 | self.TypeIndex = 0 12 | self.AccessModeType = ComponentType.AccessMode.ReadWrite 13 | end 14 | 15 | function ComponentType.Create( type_name ) 16 | local ctype = ComponentType.FromTypeIndex(ECS.TypeManager.GetTypeIndexByName(type_name)) 17 | ComponentType.InitMetaTable(ctype) 18 | return ctype 19 | end 20 | 21 | function ComponentType.FromTypeIndex( typeIndex ) 22 | local ct = ECS.TypeManager.GetTypeInfoByIndex(typeIndex) 23 | local type = ComponentType.New() 24 | type.TypeIndex = typeIndex 25 | type.AccessModeType = ComponentType.AccessMode.ReadWrite 26 | type.BufferCapacity = ct.BufferCapacity 27 | return type 28 | end 29 | 30 | local is_equal = function ( lhs, rhs ) 31 | return lhs.TypeIndex == rhs.TypeIndex and lhs.BufferCapacity == rhs.BufferCapacity and lhs.AccessModeType == rhs.AccessModeType 32 | end 33 | 34 | local less_than = function ( lhs, rhs ) 35 | if lhs.TypeIndex == rhs.TypeIndex then 36 | return lhs.BufferCapacity ~= rhs.BufferCapacity 37 | and lhs.BufferCapacity < rhs.BufferCapacity 38 | or lhs.AccessModeType < rhs.AccessModeType 39 | end 40 | return lhs.TypeIndex < rhs.TypeIndex 41 | end 42 | 43 | local big_than = function ( lhs, rhs ) 44 | return less_than(rhs, lhs) 45 | end 46 | 47 | local less_equal = function ( lhs, rhs ) 48 | return not big_than(lhs, rhs) 49 | end 50 | 51 | function ComponentType.InitMetaTable( ctype ) 52 | local meta_tbl = getmetatable(ctype) 53 | meta_tbl.__eq = is_equal 54 | meta_tbl.__lt = less_than 55 | meta_tbl.__le = less_equal 56 | setmetatable(ctype, meta_tbl) 57 | end 58 | 59 | return ComponentType -------------------------------------------------------------------------------- /lualib/ECS/Src/ComponentTypeInArchetype.lua: -------------------------------------------------------------------------------- 1 | local ComponentTypeInArchetype = ECS.BaseClass() 2 | ECS.ComponentTypeInArchetype = ComponentTypeInArchetype 3 | 4 | function ComponentTypeInArchetype:Constructor( type ) 5 | self.TypeIndex = type.TypeIndex 6 | self.BufferCapacity = type.BufferCapacity 7 | end 8 | 9 | function ComponentTypeInArchetype.Create( type ) 10 | local arche = ComponentTypeInArchetype.New(type) 11 | ComponentTypeInArchetype.InitMetaTable(arche) 12 | return arche 13 | end 14 | 15 | local is_equal = function ( lhs, rhs ) 16 | return lhs.TypeIndex == rhs.TypeIndex and lhs.BufferCapacity == rhs.BufferCapacity 17 | end 18 | 19 | local less_than = function ( lhs, rhs ) 20 | return lhs.TypeIndex ~= rhs.TypeIndex and lhs.TypeIndex < rhs.TypeIndex or lhs.BufferCapacity < rhs.BufferCapacity 21 | end 22 | 23 | local big_than = function ( lhs, rhs ) 24 | return lhs.TypeIndex ~= rhs.TypeIndex and lhs.TypeIndex > rhs.TypeIndex or lhs.BufferCapacity > rhs.BufferCapacity 25 | end 26 | 27 | local less_equal = function ( lhs, rhs ) 28 | return not big_than(lhs, rhs) 29 | end 30 | 31 | function ComponentTypeInArchetype.InitMetaTable( arche ) 32 | local meta_tbl = getmetatable(arche) 33 | meta_tbl.__eq = is_equal 34 | meta_tbl.__lt = less_than 35 | meta_tbl.__le = less_equal 36 | setmetatable(arche, meta_tbl) 37 | end 38 | 39 | return ComponentTypeInArchetype -------------------------------------------------------------------------------- /lualib/ECS/Src/Entity.lua: -------------------------------------------------------------------------------- 1 | local Entity = ECS.BaseClass() 2 | ECS.Entity = Entity 3 | ECS.Entity.Name = "ECS.Entity" 4 | ECS.Entity.Size = nil --Init In CoreHelper 5 | function Entity:Constructor( ) 6 | self.Index = 0 7 | self.Version = 0 8 | setmetatable(self, {__tostring=function(o) 9 | return "Entity:"..o.Index.." V:"..o.Version 10 | end}) 11 | end 12 | 13 | return Entity -------------------------------------------------------------------------------- /lualib/ECS/Src/EntityArray.lua: -------------------------------------------------------------------------------- 1 | local EntityArray = {} 2 | ECS.EntityArray = EntityArray 3 | function EntityArray.Create( iterator, length ) 4 | assert(iterator~=nil, "iterator should not be nil!") 5 | assert(length~=nil, "length should not be nil!") 6 | local array = { 7 | m_Iterator=iterator, 8 | Length=length, 9 | m_Data = {}, 10 | m_Cache = { 11 | CachedPtr=nil, CachedBeginIndex=0, CachedEndIndex=0, CachedSizeOf=0, IsWriting=false 12 | }, 13 | } 14 | EntityArray.InitMetaTable(array) 15 | return array 16 | end 17 | 18 | local get_fun = function ( t, index ) 19 | if index < 1 or index > t.Length then 20 | return nil 21 | end 22 | if index < t.m_Cache.CachedBeginIndex or index >= t.m_Cache.CachedEndIndex then 23 | t.m_Iterator:MoveToEntityIndexAndUpdateCache(index, t.m_Cache, false) 24 | end 25 | -- return ECS.ChunkDataUtility.ReadComponentFromArray(t.m_Cache.CachedPtr, index, ECS.Entity.Name, t.m_Data) 26 | local data = ECS.ChunkDataUtility.GetComponentDataWithTypeName(t.m_Cache.CurChunk, ECS.Entity.Name, index-t.m_Cache.CachedBeginIndex) 27 | return data 28 | end 29 | 30 | local set_fun = function ( t, index, value ) 31 | print("EntityArray setter is useless : ", debug.traceback()) 32 | end 33 | 34 | local meta_tbl = { 35 | __index = get_fun, 36 | __newindex = set_fun, 37 | } 38 | function EntityArray.InitMetaTable( array ) 39 | setmetatable(array, meta_tbl) 40 | end 41 | 42 | return EntityArray -------------------------------------------------------------------------------- /lualib/ECS/Src/InjectComponentGroupData.lua: -------------------------------------------------------------------------------- 1 | local InjectComponentGroupData = ECS.BaseClass() 2 | ECS.InjectComponentGroupData = InjectComponentGroupData 3 | 4 | function InjectComponentGroupData:Constructor( system, injectGroupName, componentRequirements, componentDataInjections, lengthFieldInfo ) 5 | self.system = system 6 | self.m_InjectGroupName = injectGroupName 7 | self.m_ComponentDataInjections = componentDataInjections 8 | self.m_EntityGroup = system:GetComponentGroup(componentRequirements) 9 | self.m_LengthFieldInfo = lengthFieldInfo 10 | 11 | self:PatchGetIndexInComponentGroup(self.m_ComponentDataInjections) 12 | -- self:PatchGetIndexInComponentGroup(m_BufferArrayInjections) 13 | -- self:PatchGetIndexInComponentGroup(m_SharedComponentInjections) 14 | end 15 | 16 | function InjectComponentGroupData.CreateInjection( injectGroupName, groupField, system ) 17 | local componentRequirements = {} 18 | local componentDataInjections = {} 19 | local lengthFieldInfo = {} 20 | InjectComponentGroupData.CollectInjectedGroup(system, groupField, componentRequirements, componentDataInjections, lengthFieldInfo) 21 | return InjectComponentGroupData.New(system, injectGroupName, componentRequirements, componentDataInjections, lengthFieldInfo) 22 | end 23 | 24 | function InjectComponentGroupData:UpdateInjection( ) 25 | local origin_iterator, length = self.m_EntityGroup:GetComponentChunkIterator() 26 | self.system[self.m_InjectGroupName] = {} 27 | for i,v in ipairs(self.m_ComponentDataInjections) do 28 | local iterator = ECS.ComponentChunkIterator.Clone(origin_iterator) 29 | iterator:SetIndexInComponentGroup(self.m_ComponentDataInjections[i].IndexInComponentGroup) 30 | local data = ECS.ComponentDataArray.Create(iterator, length, self.m_ComponentDataInjections[i].ComponentTypeName) 31 | self.system[self.m_InjectGroupName][self.m_ComponentDataInjections[i].InjectFieldName] = data 32 | end 33 | if self.m_LengthFieldInfo and self.m_LengthFieldInfo.InjectFieldName then 34 | self.system[self.m_InjectGroupName][self.m_LengthFieldInfo.InjectFieldName] = length 35 | end 36 | end 37 | 38 | function InjectComponentGroupData.CollectInjectedGroup( system, groupField, componentRequirements, componentDataInjections, lengthFieldInfo ) 39 | local field_info 40 | for field_name,v in pairs(groupField) do 41 | local field_info = Split(v, ":") 42 | if not field_info then return end 43 | 44 | local field_type = field_info and field_info[1] 45 | if field_type == "ComponentDataArray" or field_type == "Array" then 46 | local comp_type_name = field_info[2] 47 | table.insert(componentRequirements, comp_type_name) 48 | table.insert(componentDataInjections, {InjectFieldName=field_name, IndexInComponentGroup=0, ComponentTypeName=comp_type_name}) 49 | elseif field_type == "SubtractiveComponent" then 50 | elseif field_type == "BufferArray" then 51 | elseif field_type == "SharedComponentDataArray" then 52 | elseif field_type == "EntityArray" then 53 | elseif field_type == "Length" then 54 | lengthFieldInfo.InjectFieldName = field_name 55 | end 56 | end 57 | end 58 | 59 | function InjectComponentGroupData:PatchGetIndexInComponentGroup( componentInjections ) 60 | for i=1,#componentInjections do 61 | local type_index = ECS.TypeManager.GetTypeIndexByName(componentInjections[i].ComponentTypeName) 62 | componentInjections[i].IndexInComponentGroup = self.m_EntityGroup:GetIndexInComponentGroup(type_index) 63 | end 64 | end 65 | 66 | return InjectComponentGroupData -------------------------------------------------------------------------------- /lualib/ECS/Src/ScriptBehaviourManager.lua: -------------------------------------------------------------------------------- 1 | local ScriptBehaviourManager = ECS.BaseClass() 2 | 3 | function ScriptBehaviourManager:CreateInstance( world ) 4 | if self.OnBeforeCreateManagerInternal then 5 | self:OnBeforeCreateManagerInternal(world) 6 | end 7 | 8 | if self.OnCreate then 9 | self:OnCreate() 10 | end 11 | end 12 | 13 | function ScriptBehaviourManager:Update( ) 14 | if self.InternalUpdate then 15 | self:InternalUpdate() 16 | end 17 | end 18 | 19 | function ScriptBehaviourManager:DestroyInstance( ) 20 | if self.OnBeforeDestroyManagerInternal then 21 | self:OnBeforeDestroyManagerInternal() 22 | end 23 | if self.OnDestroyManager then 24 | self:OnDestroyManager() 25 | end 26 | if self.OnAfterDestroyManagerInternal then 27 | self:OnAfterDestroyManagerInternal() 28 | end 29 | end 30 | 31 | return ScriptBehaviourManager -------------------------------------------------------------------------------- /lualib/ECS/Src/SharedComponentDataManager.lua: -------------------------------------------------------------------------------- 1 | local SharedComponentDataManager = ECS.BaseClass() 2 | ECS.SharedComponentDataManager = SharedComponentDataManager 3 | 4 | function SharedComponentDataManager:Constructor( ) 5 | self.m_SharedComponentData = {} 6 | self.m_SharedComponentRefCount = {1} 7 | self.m_SharedComponentType = {-1} 8 | self.m_SharedComponentVersion = {1} 9 | self.m_FreeListIndex = -1 10 | end 11 | 12 | function SharedComponentDataManager:GetAllUniqueSharedComponents( com_type, sharedComponentValues ) 13 | sharedComponentValues.Add(com_type) 14 | for var=1,self.m_SharedComponentData.Count do 15 | local data = self.m_SharedComponentData[i] 16 | if (data ~= nil and data:GetType() == com_type) then 17 | sharedComponentValues.Add(self.m_SharedComponentData[i]) 18 | end 19 | end 20 | end 21 | 22 | return SharedComponentDataManager -------------------------------------------------------------------------------- /lualib/ECS/Src/TypeManager.lua: -------------------------------------------------------------------------------- 1 | local TypeManager = {} 2 | ECS.TypeManager = TypeManager 3 | 4 | TypeManager.TypeCategory = { 5 | ComponentData = 1, 6 | BufferData = 2, 7 | ISharedComponentData = 3, 8 | EntityData = 4, 9 | Class = 5, 10 | } 11 | TypeManager.s_Types = {} 12 | TypeManager.s_Systems = {} 13 | TypeManager.s_Count = 0 14 | TypeManager.StaticTypeLookup = {} 15 | 16 | local CalculateFieldInfo, CalculateMemoryOrdering 17 | 18 | function TypeManager.Initialize( ) 19 | TypeManager.RegisterType(ECS.Entity.Name, {Index=0, Version=0}) 20 | end 21 | 22 | function TypeManager.BuildComponentType( name, type_desc ) 23 | local memoryOrdering = CalculateMemoryOrdering(name) 24 | local type_info = { 25 | Name = name, 26 | Prototype = type_desc, 27 | TypeIndex = TypeManager.s_Count, 28 | BufferCapacity = -1, 29 | MemoryOrdering = memoryOrdering, 30 | } 31 | return type_info 32 | end 33 | 34 | function TypeManager.RegisterType( name, type_desc ) 35 | if TypeManager.StaticTypeLookup[name] then 36 | return TypeManager.s_Types[TypeManager.StaticTypeLookup[name]] 37 | end 38 | local type_info = TypeManager.BuildComponentType(name, type_desc) 39 | TypeManager.s_Types[TypeManager.s_Count] = type_info 40 | TypeManager.StaticTypeLookup[name] = TypeManager.s_Count 41 | TypeManager.s_Count = TypeManager.s_Count + 1 42 | return type_info 43 | end 44 | 45 | CalculateFieldInfo = function ( type_desc ) 46 | local field_names = {} 47 | for k,v in pairs(type_desc) do 48 | assert(type(k)=="string", "key type must be string!") 49 | table.insert(field_names, k) 50 | end 51 | table.sort(field_names) 52 | --Cat_Todo : 考虑字节对齐,提高读取性能 53 | local sum_size = 0 54 | local field_info_list = {} 55 | for i,v in ipairs(field_names) do 56 | local field_type = type_desc[v] 57 | local field_desc_type = type(field_type) 58 | if field_desc_type == "string" then 59 | local field_size = ECS.CoreHelper.GetNativeTypeSize(field_type) 60 | table.insert(field_info_list, {FieldName=v, FieldType=field_type, FieldSize=field_size, Offset=sum_size}) 61 | sum_size = sum_size + field_size 62 | elseif field_desc_type == "table" then 63 | local out_field_info_list, out_field_size = CalculateFieldInfo(field_type) 64 | table.insert(field_info_list, {FieldName=v, FieldType="table", FieldSize=out_field_size, Offset=sum_size, ChildFieldList=out_field_info_list}) 65 | sum_size = sum_size + out_field_size 66 | else 67 | assert(false, "wrong type : "..field_desc_type) 68 | end 69 | end 70 | return field_info_list, sum_size 71 | end 72 | 73 | CalculateMemoryOrdering = function ( type_name ) 74 | if type_name == ECS.Entity.Name then 75 | return 0 76 | end 77 | return 1 78 | end 79 | 80 | function TypeManager.GetTypeIndexByName( type_name ) 81 | assert(type_name and type_name ~= "", "wrong type name!") 82 | local index = TypeManager.StaticTypeLookup[type_name] 83 | assert(index, "had no register type : "..type_name) 84 | if index then 85 | return index 86 | end 87 | end 88 | 89 | function TypeManager.GetTypeInfoByIndex( typeIndex ) 90 | return TypeManager.s_Types[typeIndex] 91 | end 92 | 93 | function TypeManager.GetTypeInfoByName( typeName ) 94 | local index = TypeManager.GetTypeIndexByName(typeName) 95 | return TypeManager.s_Types[index] 96 | end 97 | 98 | function TypeManager.GetTypeNameByIndex( typeIndex ) 99 | local info = TypeManager.s_Types[typeIndex] 100 | return info and info.Name or "UnkownTypeName" 101 | end 102 | 103 | function TypeManager.RegisterScriptMgr( name, system ) 104 | assert(TypeManager.s_Systems[name]==nil, "had register system :"..name) 105 | TypeManager.s_Systems[name] = system 106 | end 107 | 108 | function TypeManager.GetScriptMgr( name ) 109 | return TypeManager.s_Systems[name] 110 | end 111 | 112 | function TypeManager.GetScriptMgrMap( ) 113 | return TypeManager.s_Systems 114 | end 115 | 116 | return TypeManager -------------------------------------------------------------------------------- /lualib/ECS/Src/World.lua: -------------------------------------------------------------------------------- 1 | local World = ECS.BaseClass() 2 | ECS.World = World 3 | ECS.World.Active = nil 4 | ECS.World.allWorlds = {} 5 | function World:Constructor( name ) 6 | self.name = name 7 | self.behaviour_mgrs = {} 8 | self.behaviour_mgrs_lookup = {} 9 | 10 | self.IsCreated = true 11 | table.insert(ECS.World.allWorlds, self) 12 | end 13 | 14 | function World:GetBehaviourManagers( ) 15 | return self.behaviour_mgrs 16 | end 17 | 18 | function World:GetOrCreateManager( script_behaviour_mgr_type ) 19 | local mgr = self:GetExistingManager(script_behaviour_mgr_type) 20 | if not mgr then 21 | mgr = self:CreateManager(script_behaviour_mgr_type) 22 | end 23 | return mgr 24 | end 25 | 26 | function World:CreateManager( script_behaviour_mgr_type, arge ) 27 | assert(script_behaviour_mgr_type, "nil mgr type : "..(script_behaviour_mgr_type or "nilstr")) 28 | -- local mgr_class = require(script_behaviour_mgr_type) 29 | local mgr_class = ECS.TypeManager.GetScriptMgr(script_behaviour_mgr_type) 30 | assert(mgr_class, script_behaviour_mgr_type.." file had not register by TypeManager!") 31 | local mgr = mgr_class.New() 32 | if arge then 33 | for k,v in pairs(arge) do 34 | mgr[k] = v 35 | end 36 | end 37 | mgr:CreateInstance(self) 38 | table.insert(self.behaviour_mgrs, mgr) 39 | self.behaviour_mgrs_lookup[script_behaviour_mgr_type] = mgr 40 | return mgr 41 | end 42 | 43 | function World:GetExistingManager( script_behaviour_mgr_type ) 44 | return self.behaviour_mgrs_lookup[script_behaviour_mgr_type] 45 | end 46 | 47 | function World:DestroyManager( manager_name ) 48 | if not self.behaviour_mgrs_lookup[manager_name] then 49 | assert(self.behaviour_mgrs_lookup[manager_name], manager_name.." manager does not exist in the world") 50 | end 51 | -- Version = Version + 1 52 | self.behaviour_mgrs_lookup[manager_name]:DestroyInstance() 53 | end 54 | 55 | return World -------------------------------------------------------------------------------- /lualib/ECS/Tests/TestBaseClass.lua: -------------------------------------------------------------------------------- 1 | local ECS = require "ECS" 2 | local TestBaseClass = ECS.BaseClass() 3 | 4 | function TestBaseClass:Constructor( ) 5 | 6 | end 7 | 8 | function TestBaseClass:setUp( ) 9 | -- print('Cat:TestBaseClass.lua[setUp]') 10 | self.m_PreviousWorld = ECS.World.Active 11 | ECS.World.Active = ECS.World.New("Test World") 12 | self.m_World = ECS.World.Active 13 | 14 | self.m_Manager = self.m_World:GetOrCreateManager("ECS.EntityManager") 15 | -- m_ManagerDebug = new EntityManager.EntityManagerDebug(self.m_Manager) 16 | end 17 | 18 | function TestBaseClass:tearDown( ) 19 | -- print('Cat:TestBaseClass.lua[tearDown]') 20 | if (m_Manager ~= nil) then 21 | -- self.m_World:Delete() 22 | self.m_World = nil 23 | ECS.World.Active = self.m_PreviousWorld 24 | self.m_PreviousWorld = nil 25 | self.m_Manager = nil 26 | end 27 | end 28 | 29 | return TestBaseClass -------------------------------------------------------------------------------- /lualib/ECS/Tests/TestPerformance.lua: -------------------------------------------------------------------------------- 1 | local ECS = require "ECS" 2 | TestPerformance = ECS.BaseClass(require("TestBaseClass")) 3 | 4 | local testTimes = 20000 5 | 6 | function TestPerformance:TestMany( ) 7 | local bt = os.clock() 8 | ECS.TypeManager.RegisterType("DataForTestPerformance1", {x=0, y=false, z=""}) 9 | ECS.TypeManager.RegisterType("DataForTestPerformance2", {x=false, b=false}) 10 | ECS.TypeManager.RegisterType("DataForTestPerformance3", {value=0}) 11 | 12 | local archetype = self.m_Manager:CreateArchetype({"DataForTestPerformance1", "DataForTestPerformance2", "DataForTestPerformance3"}) 13 | local entities = {} 14 | for i=1,testTimes do 15 | entities[i] = self.m_Manager:CreateEntityByArcheType(archetype) 16 | end 17 | local onWriteCost = os.clock() - bt 18 | print('\necs create cost : ', onWriteCost) 19 | 20 | local bt = os.clock() 21 | for i=1,testTimes do 22 | self.m_Manager:SetComponentData(entities[i], "DataForTestPerformance1", {x=1.123456, y=true, z="123"}) 23 | local a = self.m_Manager:GetComponentData(entities[i], "DataForTestPerformance1") 24 | end 25 | local readCost = os.clock() - bt 26 | print('ecs read write cost : ', readCost) 27 | end 28 | -------------------------------------------------------------------------------- /lualib/ECS/Tests/test_all.lua: -------------------------------------------------------------------------------- 1 | package.path = package.path ..'?/?.lua;../?.lua;../../?.lua;Tests/?.lua'; 2 | local ECS = require "ECS" 3 | lu = require('Common.luaunit') 4 | 5 | --在上级目录运行本文件即可:lua Tests/test_all.lua 6 | 7 | --将 szFullString 对象拆分为一个子字符串表 8 | function Split(szFullString, szSeparator, start_pos) 9 | local nFindStartIndex = start_pos or 1 10 | local nSplitIndex = 1 11 | local nSplitArray = {} 12 | while true do 13 | local nFindLastIndex = string.find(szFullString, szSeparator, nFindStartIndex) 14 | if not nFindLastIndex then 15 | nSplitArray[nSplitIndex] = string.sub(szFullString, nFindStartIndex, string.len(szFullString)) 16 | break 17 | end 18 | table.insert(nSplitArray, string.sub(szFullString, nFindStartIndex, nFindLastIndex - 1)) 19 | nFindStartIndex = nFindLastIndex + string.len(szSeparator) 20 | nSplitIndex = nSplitIndex + 1 21 | end 22 | return nSplitArray 23 | end 24 | 25 | function PrintTable( tbl, level, return_counter ) 26 | if tbl == nil or type(tbl) ~= "table" then 27 | return 28 | end 29 | return_counter = return_counter or 5 --剩下多少层就返回,防止无限打印 30 | if return_counter <= 0 then 31 | -- print('Cat:util.lua PrintTable return_counter empty') 32 | return 33 | end 34 | return_counter = return_counter - 1 35 | level = level or 1 36 | 37 | local indent_str = "" 38 | for i = 1, level do 39 | indent_str = indent_str.." " 40 | end 41 | print(indent_str .. "{") 42 | for k,v in pairs(tbl) do 43 | 44 | local item_str = string.format("%s%s = %s", indent_str .. " ",tostring(k), tostring(v)) 45 | print(item_str) 46 | if type(v) == "table" then 47 | PrintTable(v, level + 1, return_counter) 48 | end 49 | end 50 | print(indent_str .. "}") 51 | 52 | end 53 | local s = io.popen("ls ./Tests")--for linux 54 | -- local s = io.popen("dir /b Tests")--for windows 55 | local fileNames = s:read("*all") 56 | fileNames = Split(fileNames, "\n") 57 | for k,v in pairs(fileNames or {}) do 58 | if v~="" and v~="test_all.lua" and v~="luaunit.lua" then 59 | local dot_index = string.find(v, ".", 1, true) 60 | local is_lua_file = string.find(v, ".lua", -4, true) 61 | if dot_index ~= nil and is_lua_file then 62 | local name_without_ex = string.sub(v, 1, dot_index-1) 63 | -- print('test_all.lua init test file name : ', name_without_ex) 64 | require(name_without_ex) 65 | end 66 | end 67 | end 68 | 69 | os.exit( lu.LuaUnit.run() ) -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | export ROOT=$(cd `dirname $0`; pwd) 3 | export DAEMON=false 4 | if [ ! -d "log" ]; then 5 | mkdir log 6 | fi 7 | if [ $(ps e -u ${USER} | grep -v grep | grep $(pwd) | grep skynet | wc -l) != 0 ] 8 | then 9 | echo "server is already running, please execute ./stop.sh" 10 | else 11 | $ROOT/skynet/skynet $ROOT/config > log/$(date "+%Y%m%d-%H%M").log & 12 | fi 13 | -------------------------------------------------------------------------------- /service/dbserver.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynet" 2 | local mysql = require "skynet.db.mysql" 3 | require "skynet.manager" 4 | require "common.util" 5 | 6 | --[[ 7 | 用法: 8 | local dbserver = skynet.localname(".your db name") 9 | local is_succeed = skynet.call(dbserver, "lua", "insert", "Account", {account_id=7, password="123"}) 10 | print('Cat:main.lua[insert] is_succeed', is_succeed) 11 | local is_succeed, result = skynet.call(dbserver, "lua", "select_all", "Account") 12 | if is_succeed then 13 | print("Cat:main [start:30] result:", result) 14 | PrintTable(result) 15 | print("Cat:main [end]") 16 | end 17 | --]] 18 | local db 19 | 20 | local function ping() 21 | while true do 22 | if db then 23 | db:query("select l;") 24 | end 25 | skynet.sleep(3600*1000) 26 | end 27 | end 28 | 29 | local CMD = {} 30 | 31 | function CMD.open( conf ) 32 | -- print("Cat:dbserver [start:18] conf:", conf) 33 | -- PrintTable(conf) 34 | -- print("Cat:dbserver [end]") 35 | db = mysql.connect(conf) 36 | skynet.fork(ping) 37 | 38 | skynet.register(conf.name or "."..conf.database) 39 | end 40 | 41 | function CMD.close( conf ) 42 | if db then 43 | db:disconnect() 44 | db = nil 45 | end 46 | end 47 | 48 | function CMD.insert( tablename, rows ) 49 | local cols = {} 50 | local vals = {} 51 | for k, v in pairs(rows) do 52 | table.insert(cols, k) 53 | if type(v) == "string" then 54 | v = mysql.quote_sql_str(v) 55 | end 56 | table.insert(vals, v) 57 | end 58 | vals = table.concat(vals, ",") 59 | cols = table.concat(cols, ",") 60 | local sql = string.format("insert into %s(%s) values(%s);", tablename, cols, vals) 61 | local result = db:query(sql) 62 | if result.errno then 63 | skynet.error(result.err) 64 | return false 65 | end 66 | return true 67 | end 68 | 69 | function CMD.delete( tablename, key, value ) 70 | local sql = string.format("delete from %s where %s = %s;", tablename, key, mysql.quote_sql_str(tostring(value))) 71 | local result = db:query(sql) 72 | if result.errno then 73 | skynet.error(result.err) 74 | return false 75 | end 76 | return true 77 | end 78 | 79 | function CMD.query(command) 80 | local result = db:query(command) 81 | if result.errno then 82 | skynet.error(result.err) 83 | return false 84 | end 85 | return true, result 86 | end 87 | 88 | function CMD.update( tablename, key, value, row ) 89 | local t = {} 90 | for k,v in pairs(row) do 91 | if type(v) == "string" then 92 | v = mysql.quote_sql_str(v) 93 | end 94 | table.insert(t, k.."="..v) 95 | end 96 | local setvalues = table.concat(t, ",") 97 | local sql = string.format("update %s set %s where %s = '%s';", tablename, setvalues, key, value) 98 | local result = db:query(sql) 99 | if result.errno then 100 | skynet.error(result.err) 101 | return false 102 | end 103 | return true 104 | end 105 | 106 | function CMD.select_by_key( tablename, key, value ) 107 | local sql = string.format("select * from %s where %s = '%s';", tablename, key, value) 108 | local result = db:query(sql) 109 | if result.errno then 110 | skynet.error(result.err) 111 | return false 112 | end 113 | return true, result 114 | end 115 | 116 | function CMD.select_one_by_key( tablename, key, value ) 117 | local sql = string.format("select * from %s where %s = '%s';", tablename, key, value) 118 | local result = db:query(sql) 119 | if result.errno then 120 | skynet.error(result.err) 121 | return false 122 | end 123 | return true, result and result[1] 124 | end 125 | 126 | function CMD.select_by_condition( tablename, condition ) 127 | local sql = string.format("select * from %s where %s;", tablename, condition) 128 | local result = db:query(sql) 129 | if result.errno then 130 | skynet.error(result.err) 131 | return false 132 | end 133 | return true, result 134 | end 135 | 136 | function CMD.select_all( tablename ) 137 | local sql = string.format("select * from %s;", tablename) 138 | local result = db:query(sql) 139 | if result.errno then 140 | skynet.error(result.err) 141 | return false 142 | end 143 | return true, result 144 | end 145 | 146 | skynet.start(function() 147 | skynet.dispatch("lua", function(session, source, cmd, ...) 148 | local f = assert(CMD[cmd], "can't not find cmd :"..(cmd or "empty")) 149 | if session == 0 then 150 | f(...) 151 | else 152 | skynet.ret(skynet.pack(f(...))) 153 | end 154 | end) 155 | end) 156 | 157 | -------------------------------------------------------------------------------- /service/gated.lua: -------------------------------------------------------------------------------- 1 | local msgserver = require "snax.msgserver" 2 | local crypt = require "skynet.crypt" 3 | local skynet = require "skynet" 4 | 5 | local start_arge = {...} 6 | local loginservice = tonumber(start_arge[1]) 7 | local platform = tonumber(start_arge[2]) 8 | local server_id = tonumber(start_arge[3]) 9 | 10 | local server = {} 11 | local users = {} 12 | local username_map = {} 13 | local internal_id = 0 14 | local pool = {} 15 | 16 | -- login server disallow multi login, so login_handler never be reentry 17 | -- call by login server 18 | function server.login_handler(uid, secret) 19 | if users[uid] then 20 | error(string.format("%s is already login", uid)) 21 | end 22 | 23 | internal_id = internal_id + 1 24 | local id = internal_id -- don't use internal_id directly 25 | local username = msgserver.username(uid, id, servername) 26 | 27 | local agent 28 | if #pool == 0 then 29 | agent = skynet.newservice "msgagent" 30 | else 31 | agent = table.remove(pool, 1) 32 | end 33 | 34 | local u = { 35 | username = username, 36 | agent = agent, 37 | uid = uid, 38 | subid = id, 39 | } 40 | 41 | -- trash subid (no used) 42 | skynet.call(agent, "lua", "login", uid, id, secret, platform, server_id) 43 | 44 | users[uid] = u 45 | username_map[username] = u 46 | 47 | msgserver.login(username, secret) 48 | 49 | -- you should return unique subid 50 | return id 51 | end 52 | 53 | -- call by agent 54 | function server.logout_handler(uid, subid) 55 | local u = users[uid] 56 | if u then 57 | local username = msgserver.username(uid, subid, servername) 58 | assert(u.username == username) 59 | msgserver.logout(u.username) 60 | users[uid] = nil 61 | -- if username_map[u.username] and username_map[u.username].agent then 62 | -- table.insert(pool, username_map[u.username].agent) 63 | -- end 64 | username_map[u.username] = nil 65 | skynet.call(loginservice, "lua", "logout",uid, subid) 66 | end 67 | end 68 | 69 | -- call by login server 70 | function server.kick_handler(uid, subid) 71 | local u = users[uid] 72 | if u then 73 | local username = msgserver.username(uid, subid, servername) 74 | assert(u.username == username) 75 | -- NOTICE: logout may call skynet.exit, so you should use pcall. 76 | pcall(skynet.call, u.agent, "lua", "logout") 77 | end 78 | end 79 | 80 | -- call by self (when socket disconnect) 81 | function server.disconnect_handler(username) 82 | local u = username_map[username] 83 | if u then 84 | skynet.call(u.agent, "lua", "afk") 85 | end 86 | end 87 | 88 | -- call by self (when recv a request from client) 89 | function server.request_handler(username, msg) 90 | local u = username_map[username] 91 | return skynet.tostring(skynet.rawcall(u.agent, "client", msg)) 92 | end 93 | 94 | -- call by self (when gate open) 95 | function server.register_handler(name) 96 | servername = name 97 | skynet.call(loginservice, "lua", "register_gate", servername, skynet.self()) 98 | 99 | local n = 10 100 | for i = 1, n do 101 | table.insert (pool, skynet.newservice "msgagent") 102 | end 103 | --加载游戏场景配置信息和场景管理器 104 | skynet.uniqueservice("world") 105 | skynet.uniqueservice("chat") 106 | end 107 | 108 | msgserver.start(server) 109 | 110 | -------------------------------------------------------------------------------- /service/logind.lua: -------------------------------------------------------------------------------- 1 | local login = require "snax.loginserver" 2 | local crypt = require "skynet.crypt" 3 | local skynet = require "skynet" 4 | require "common.util" 5 | 6 | local server = { 7 | host = "0.0.0.0", 8 | port = 8001, 9 | multilogin = false, -- disallow multilogin 10 | name = "login_master", 11 | } 12 | 13 | local server_list = {} 14 | local user_online = {} 15 | local user_login = {} 16 | 17 | function server.auth_handler(token) 18 | -- the token is base64(user)@base64(server):base64(password) 19 | local user, server, password = token:match("([^@]+)@([^:]+):(.+)") 20 | user = crypt.base64decode(user) 21 | server = crypt.base64decode(server) 22 | password = crypt.base64decode(password) 23 | print('Cat:logind.lua[22] user, password', user, password) 24 | local accountServer = skynet.localname(".AccountDBServer") 25 | local is_succeed, result = skynet.call(accountServer, "lua", "select_by_key", "Account", "account_id", user) 26 | if is_succeed then 27 | local user_info = result and result[1] 28 | if user_info and user_info.account_id and user_info.password then 29 | assert(password == user_info.password, "Invalid password") 30 | elseif server == "DevelopServer" then 31 | --开发服的话直接创建帐号 32 | --Cat_Todo : 不要明文保存密码,随便加个固定前后缀再md5都好过明文啦 33 | skynet.call(accountServer, "lua", "insert", "Account", {account_id=user, password=password}) 34 | end 35 | else 36 | --数据库查询失败 37 | end 38 | return server, user 39 | end 40 | 41 | function server.login_handler(server, uid, secret) 42 | print(string.format("%s@%s is login, secret is %s", uid, server, crypt.hexencode(secret))) 43 | local gameserver = assert(server_list[server], "Unknown server") 44 | -- only one can login, because disallow multilogin 45 | local last = user_online[uid] 46 | if last then 47 | skynet.call(last.address, "lua", "kick", uid, last.subid) 48 | end 49 | if user_online[uid] then 50 | error(string.format("user %s is already online", uid)) 51 | end 52 | 53 | local subid = tostring(skynet.call(gameserver, "lua", "login", uid, secret)) 54 | user_online[uid] = { address = gameserver, subid = subid , server = server} 55 | return subid 56 | end 57 | 58 | local CMD = {} 59 | 60 | function CMD.register_gate(server, address) 61 | server_list[server] = address 62 | end 63 | 64 | function CMD.logout(uid, subid) 65 | local u = user_online[uid] 66 | if u then 67 | print(string.format("%s@%s is logout", uid, u.server)) 68 | user_online[uid] = nil 69 | end 70 | end 71 | 72 | function server.command_handler(command, ...) 73 | local f = assert(CMD[command]) 74 | return f(...) 75 | end 76 | 77 | login(server) 78 | -------------------------------------------------------------------------------- /service/main.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynet" 2 | require "common.util" 3 | 4 | skynet.start(function() 5 | skynet.error("Server start") 6 | skynet.uniqueservice("protoloader") 7 | -- if not skynet.getenv "daemon" then 8 | -- local console = skynet.newservice("console") 9 | -- end 10 | skynet.newservice("debug_console",8000) 11 | 12 | local loginserver = skynet.newservice("logind") 13 | local platform_id = 1 14 | local server_id = 1 15 | local gate = skynet.newservice("gated", loginserver, platform_id, server_id) 16 | skynet.call(gate, "lua", "open" , { 17 | port = 8888, 18 | maxclient = 512, 19 | servername = "DevelopServer", 20 | }) 21 | 22 | --Cat_Todo : 登录服务器需要配置到其它物理机器上 23 | local dbserver = skynet.newservice("dbserver") 24 | skynet.call(dbserver, "lua", "open", { 25 | host = "127.0.0.1", 26 | port = 3306, 27 | database = "UnityMMOAccount", 28 | user = "root", 29 | password = "123456", 30 | name = ".AccountDBServer", 31 | }) 32 | 33 | local gamedbserver = skynet.newservice("dbserver") 34 | skynet.call(gamedbserver, "lua", "open", { 35 | host = "127.0.0.1", 36 | port = 3306, 37 | database = "UnityMMOGame", 38 | user = "root", 39 | password = "123456", 40 | name = ".GameDBServer", 41 | }) 42 | 43 | local id_service = skynet.newservice("id_service") 44 | skynet.call(id_service, "lua", "open", { 45 | platform_id = platform_id, 46 | server_id = server_id, 47 | name = ".id_service", 48 | }) 49 | 50 | skynet.exit() 51 | end) 52 | -------------------------------------------------------------------------------- /service/protoloader.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynet" 2 | local sprotoparser = require "sprotoparser" 3 | local sprotoloader = require "sprotoloader" 4 | require "common.util" 5 | 6 | --某协议有语法错误时,只解析该协议文件,方便定位。不然报错中的行数很难定位,因为是所有协议文件合并成一个整体的行数:syntax error at [=text] line (210) 7 | -- local test_files = { 8 | -- "proto_200_299_task.lua", 9 | -- } 10 | skynet.start(function() 11 | --从协议目录里读取所有的lua文件,拼接其字符串生成sproto协议对象 12 | local s = io.popen("ls game/proto") 13 | local fileNames = s:read("*all") 14 | fileNames = Split(fileNames, "\n") 15 | if test_files and #test_files > 0 then 16 | fileNames = test_files 17 | end 18 | local proto_c2s_tb = {} 19 | for k,v in pairs(fileNames or {}) do 20 | local dot_index = string.find(v, ".", 1, true) 21 | local is_lua_file = string.find(v, ".lua", -4, true) 22 | if Trim(v) ~= "" and dot_index ~= nil and is_lua_file then 23 | local name_without_ex = string.sub(v, 1, dot_index-1) 24 | local proto_str = require("proto."..name_without_ex) 25 | if proto_str then 26 | table.insert(proto_c2s_tb, proto_str) 27 | end 28 | end 29 | end 30 | local c2s_spb = sprotoparser.parse(table.concat(proto_c2s_tb)) 31 | sprotoloader.save(c2s_spb, 1) 32 | -- don't call skynet.exit() , because sproto.core may unload and the global slot become invalid 33 | end) 34 | -------------------------------------------------------------------------------- /skynet.log: -------------------------------------------------------------------------------- 1 | [:01000001] LAUNCH logger /mnt/hgfs/UnityMMO/Server/skynet.log 2 | [:01000002] LAUNCH snlua bootstrap 3 | [:01000003] LAUNCH snlua launcher 4 | [:01000004] LAUNCH snlua cmaster 5 | [:01000004] master listen socket 0.0.0.0:2013 6 | [:01000005] LAUNCH snlua cslave 7 | [:01000005] slave connect to master 127.0.0.1:2013 8 | [:01000004] connect from 127.0.0.1:39890 4 9 | [:01000006] LAUNCH harbor 1 16777221 10 | [:01000004] Harbor 1 (fd=4) report 127.0.0.1:2526 11 | [:01000005] Waiting for 0 harbors 12 | [:01000005] Shakehand ready 13 | [:01000007] LAUNCH snlua datacenterd 14 | [:01000008] LAUNCH snlua service_mgr 15 | [:01000009] LAUNCH snlua main 16 | [:01000009] Server start 17 | [:0100000a] LAUNCH snlua test_socket 18 | [:01000009] KILL self 19 | [:01000002] KILL self 20 | [:0100000a] client say : ||| 21 | [:0100000a] lua call [0 to :100000a : 0 msgsz = 24] error : /mnt/hgfs/UnityMMO/Server/skynet/lualib/skynet.lua:615: /mnt/hgfs/UnityMMO/Server/skynet/lualib/skynet.lua:172: /mnt/hgfs/UnityMMO/Server/../Common/sproto.lua:218: session not found 22 | stack traceback: 23 | [C]: in function 'assert' 24 | /mnt/hgfs/UnityMMO/Server/../Common/sproto.lua:218: in method 'dispatch' 25 | /mnt/hgfs/UnityMMO/Server/service/test_socket.lua:21: in upvalue 'echo' 26 | /mnt/hgfs/UnityMMO/Server/service/test_socket.lua:44: in field 'callback' 27 | /mnt/hgfs/UnityMMO/Server/skynet/lualib/skynet/socket.lua:105: in field '?' 28 | /mnt/hgfs/UnityMMO/Server/skynet/lualib/skynet/socket.lua:161: in upvalue 'f' 29 | /mnt/hgfs/UnityMMO/Server/skynet/lualib/skynet.lua:121: in function 30 | stack traceback: 31 | [C]: in function 'assert' 32 | /mnt/hgfs/UnityMMO/Server/skynet/lualib/skynet.lua:615: in function 'skynet.dispatch_message' 33 | -------------------------------------------------------------------------------- /skynet.pid: -------------------------------------------------------------------------------- 1 | 2275 2 | -------------------------------------------------------------------------------- /stop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | PID=$(ps e -u ${USER} | grep -v grep | grep "$(pwd)" | grep skynet | awk '{print $1}') 3 | kill ${PID} 4 | --------------------------------------------------------------------------------