├── .gitignore ├── core ├── condition │ ├── __init__.gd │ ├── Condition.gd │ └── BuiltinConditions.gd ├── __init__.gd ├── Module.gd ├── ValueEvaluator.gd ├── Handler.gd ├── Messenger.gd ├── Serializer.gd ├── EventEmitter.gd └── SimpleGameFramework.gd ├── modules ├── achievement │ ├── __init__.gd │ ├── AchievementModule.gd │ └── Achievement.gd ├── buff │ ├── __init__.gd │ ├── StrengthBuff.gd │ ├── TimeOutBuff.gd │ ├── TimerBuff.gd │ ├── Buff.gd │ └── BuffModule.gd ├── __init__.gd ├── GamePoint.gd ├── DailyTask.gd ├── AutoIncreasedGamePoint.gd └── TimerModule.gd ├── .editorconfig ├── __init__.gd ├── plugin.cfg ├── utils.gd ├── README.md ├── LICENSE ├── plugin.gd └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode -------------------------------------------------------------------------------- /core/condition/__init__.gd: -------------------------------------------------------------------------------- 1 | 2 | tool 3 | 4 | const BuiltinConditions = preload("BuiltinConditions.gd") 5 | const Condition = preload("Condition.gd") 6 | -------------------------------------------------------------------------------- /modules/achievement/__init__.gd: -------------------------------------------------------------------------------- 1 | 2 | tool 3 | 4 | const Achievement = preload("Achievement.gd") 5 | const AchievementModule = preload("AchievementModule.gd") 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.gd] 2 | indent_style = tab 3 | indent_size = 4 4 | end_of_line = lf 5 | charset = utf-8 6 | trim_trailing_whitespace = false 7 | insert_final_newline = true 8 | -------------------------------------------------------------------------------- /__init__.gd: -------------------------------------------------------------------------------- 1 | 2 | tool 3 | extends Node 4 | 5 | const core = preload("core/__init__.gd") 6 | const modules = preload("modules/__init__.gd") 7 | const utils = preload("utils.gd") 8 | -------------------------------------------------------------------------------- /plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="Simple Game Framework" 4 | description="A modular game framework for godot engine" 5 | author="Geequlim" 6 | version="0.0.1" 7 | script="plugin.gd" -------------------------------------------------------------------------------- /modules/buff/__init__.gd: -------------------------------------------------------------------------------- 1 | 2 | tool 3 | 4 | const Buff = preload("Buff.gd") 5 | const BuffModule = preload("BuffModule.gd") 6 | const StrengthBuff = preload("StrengthBuff.gd") 7 | const TimeOutBuff = preload("TimeOutBuff.gd") 8 | const TimerBuff = preload("TimerBuff.gd") 9 | -------------------------------------------------------------------------------- /modules/buff/StrengthBuff.gd: -------------------------------------------------------------------------------- 1 | # 带强度的 Buff, `strength` 衰减小于等于0时失效 2 | 3 | tool 4 | extends Buff 5 | class_name StrengthBuff 6 | 7 | # 强度值 8 | var strength = 0 9 | 10 | # 衰减强度 11 | func attenuate(amount = 1): 12 | self.strength -= amount 13 | if strength <= 0: 14 | stop() 15 | 16 | func update(dt): 17 | if strength <= 0: 18 | stop() 19 | -------------------------------------------------------------------------------- /modules/__init__.gd: -------------------------------------------------------------------------------- 1 | 2 | tool 3 | 4 | const AutoIncreasedGamePoint = preload("AutoIncreasedGamePoint.gd") 5 | const DailyTask = preload("DailyTask.gd") 6 | const GamePoint = preload("GamePoint.gd") 7 | const TimerModule = preload("TimerModule.gd") 8 | const achievement = preload("achievement/__init__.gd") 9 | const buff = preload("buff/__init__.gd") 10 | -------------------------------------------------------------------------------- /core/__init__.gd: -------------------------------------------------------------------------------- 1 | 2 | tool 3 | 4 | const EventEmitter = preload("EventEmitter.gd") 5 | const Handler = preload("Handler.gd") 6 | const Messenger = preload("Messenger.gd") 7 | const Module = preload("Module.gd") 8 | const Serializer = preload("Serializer.gd") 9 | const SimpleGameFramework = preload("SimpleGameFramework.gd") 10 | const ValueEvaluator = preload("ValueEvaluator.gd") 11 | const condition = preload("condition/__init__.gd") 12 | -------------------------------------------------------------------------------- /core/Module.gd: -------------------------------------------------------------------------------- 1 | # 逻辑模块 2 | # 代表一个逻辑功能,例如成就、商店、战斗等 3 | 4 | tool 5 | extends EventEmitter 6 | class_name Module 7 | 8 | # 所有模块创建完毕后执行 9 | func setup() -> void: 10 | pass 11 | 12 | # 模块是否准备就绪 13 | func is_ready() -> bool: 14 | return true 15 | 16 | # 初始化模块 17 | func initialize() -> void: 18 | pass 19 | 20 | # 模块开始 21 | func start() -> void: 22 | pass 23 | 24 | # 恒更新,不考虑逻辑是否初始化完毕或暂停等逻辑,固定每帧调用 25 | func process(dt: float) -> void: 26 | pass 27 | 28 | # 逻辑更新 29 | func update(dt: float) -> void: 30 | pass 31 | 32 | # 存档 33 | func save() -> Dictionary : 34 | return {} 35 | 36 | # 读档 37 | func load(data: Dictionary) -> void: 38 | pass 39 | 40 | -------------------------------------------------------------------------------- /modules/GamePoint.gd: -------------------------------------------------------------------------------- 1 | # 游戏点数,可用于如 体力 金币 等相关的数值记录 2 | 3 | tool 4 | extends Module 5 | class_name GamePoint 6 | 7 | signal point_changed(amount) # 点数变动 8 | var point = 0 setget set_point # 点数 9 | 10 | # 消耗点数 11 | func cost(amount) -> bool: 12 | if point >= amount: 13 | set_point(point - amount) 14 | return true 15 | return false 16 | 17 | # 充值点数 18 | func charge(amount): 19 | set_point(point + amount) 20 | 21 | # 设置点数 22 | func set_point(value): 23 | if value != point: 24 | var amount = value - point 25 | point = value 26 | emit_signal("point_changed", amount) 27 | 28 | func save() -> Dictionary: 29 | return { "point": point } 30 | 31 | func load(data: Dictionary): 32 | point = data.point 33 | -------------------------------------------------------------------------------- /core/condition/Condition.gd: -------------------------------------------------------------------------------- 1 | # 条件类,表示一些条件 2 | # 如任务是否完成、条件是否能够解锁等 3 | 4 | tool 5 | extends EventEmitter 6 | class_name Condition 7 | 8 | # Array 条件的参数 9 | var params = [] 10 | 11 | # 寄存取值器**引用**, 用于从取参数的具体值 12 | # 默认为 框架中提供的 `evaluator` 取值器 13 | var register: ValueEvaluator.Register = null 14 | 15 | func is_true(): 16 | return false 17 | 18 | # 获取参数值 19 | func get_param(idx: int): 20 | if idx >= len(params): 21 | if OS.is_debug_build(): 22 | assert(false) # 参数数量不足 23 | return null 24 | else: 25 | var raw = self.params[idx] 26 | if register and register.has_value(raw): 27 | return register.get_value(raw) 28 | return (SimpleGameFramework.get_singeleton() as SimpleGameFramework).evaluator.evalute(raw) 29 | -------------------------------------------------------------------------------- /core/ValueEvaluator.gd: -------------------------------------------------------------------------------- 1 | # 数值取值器 2 | 3 | tool 4 | extends EventEmitter 5 | class_name ValueEvaluator 6 | 7 | const utils = preload("../utils.gd") 8 | 9 | # 求值公式接口 10 | class IEvolutor: 11 | func has_value(prop) -> bool: 12 | return false 13 | func get_value(prop): 14 | return null 15 | 16 | # 寄存器 17 | class Register extends IEvolutor: 18 | var values = {} 19 | func has_value(prop) -> bool: 20 | return prop in values 21 | func get_value(prop): 22 | return values[prop] 23 | 24 | # 求值公式表 25 | # Array 26 | var evolutors = [] 27 | 28 | # 计算值 29 | func evalute(input): 30 | for e in evolutors: 31 | if e.has_value(input): 32 | return e.get_value(input) 33 | return input 34 | 35 | # 添加公式 36 | func add_evolutor(evolutor): 37 | if utils.implements(evolutor, IEvolutor): 38 | evolutors.append(evolutor) 39 | -------------------------------------------------------------------------------- /modules/buff/TimeOutBuff.gd: -------------------------------------------------------------------------------- 1 | # 限时Buff, `duration` 时间内有效,超时自动停止效果 2 | # 时间限制指的是逻辑更新时间,因此游戏暂停或玩家离线时时间是静止的,逻辑恢复更新后继续计时 3 | 4 | tool 5 | extends Buff 6 | class_name TimeOutBuff 7 | 8 | # 超时时长(秒) 9 | var duration = 5 10 | # 剩余时间(秒) 11 | var time_left = 0 setget set_time_left, get_time_left 12 | # 已经开始的时间(秒) 13 | var _started_duration = 0 14 | # 设置剩余时间(秒) 15 | func set_time_left(v: float): 16 | self._started_duration = duration - v 17 | # 获取剩余时间(秒) 18 | func get_time_left() -> float: 19 | return duration - self._started_duration 20 | 21 | func on_start(): 22 | self.time_left = duration 23 | 24 | func update(dt: float): 25 | _started_duration += dt 26 | if _started_duration >= duration: 27 | stop() 28 | 29 | func save() -> Dictionary: 30 | return { started_duration = self._started_duration } 31 | 32 | func load(data: Dictionary): 33 | self._started_duration = data.started_duration 34 | -------------------------------------------------------------------------------- /modules/buff/TimerBuff.gd: -------------------------------------------------------------------------------- 1 | # 限时Buff, 指定时刻之前有效,超时自动停止效果 2 | # 时间指的是真实时间,因此不受游戏暂停或玩家离线等条件的影响,超过指定时间后失效 3 | 4 | tool 5 | extends Buff 6 | class_name TimerBuff 7 | 8 | # 剩余时间(秒) 9 | var time_left = 0 setget set_time_left, get_time_left 10 | 11 | # 设定时长,启动后修改无效 12 | var duration = 5 13 | 14 | # 结束时刻 15 | var _timeout: float = 0 16 | 17 | # 设置剩余时间(秒) 18 | func set_time_left(v: float): 19 | self._timeout = now() + v 20 | 21 | # 获取剩余时间(秒) 22 | func get_time_left() -> float: 23 | return self._timeout - now() 24 | 25 | func on_start(): 26 | self.time_left = duration 27 | 28 | func update(dt: float): 29 | if now() >= self._timeout: 30 | stop() 31 | 32 | # 当前时间点(秒) 33 | func now() -> float: 34 | return OS.get_system_time_msecs() / 1000.0 35 | 36 | func save() -> Dictionary: 37 | return { timeout = self._timeout } 38 | 39 | func load(data: Dictionary): 40 | self._timeout = data.timeout 41 | -------------------------------------------------------------------------------- /core/Handler.gd: -------------------------------------------------------------------------------- 1 | # 回调函数 2 | 3 | tool 4 | extends Reference 5 | class_name Handler 6 | 7 | var method: String = "" # 回调方法 8 | var arguments = [] # 回调附加参数 9 | var target: Object = null setget _set_target, _get_target # 回调对象 10 | 11 | var _target_instance_id = 0 12 | func _set_target(target: Object): 13 | _target_instance_id = 0 if target == null else target.get_instance_id() 14 | func _get_target() -> Object: 15 | return instance_from_id(_target_instance_id) 16 | 17 | # 调用回调函数,参数形式同 `callv` 18 | func call_func(params = []): 19 | var args = [] 20 | if typeof(params) == TYPE_ARRAY: 21 | for p in params: args.append(p) 22 | else: 23 | args.append(params) 24 | for p in self.arguments: 25 | args.append(p) 26 | var obj = self.target 27 | if obj: 28 | return obj.callv(self.method, args) 29 | elif OS.is_debug_build(): 30 | assert(false) # The caller of this handler is already released 31 | return null 32 | -------------------------------------------------------------------------------- /utils.gd: -------------------------------------------------------------------------------- 1 | # 常用工具模块 2 | 3 | tool 4 | 5 | # Check does an object has all of script methods of target class 6 | # - - - - - - - - - - 7 | # *Parameters* 8 | # * [obj:Object] The object to check 9 | # * [interface:GDScript] The interface to check with 10 | # - - - - - - - - - - 11 | # *Returns* bool 12 | static func implements(obj: Object, interface:GDScript) -> bool: 13 | if obj == null or interface == null: 14 | return false 15 | if typeof(obj) != TYPE_OBJECT: 16 | return false 17 | if obj is interface: 18 | return true 19 | var interface_obj = interface.new() 20 | var required_methods = [] 21 | for m in interface_obj.get_method_list(): 22 | if m.flags & METHOD_FLAG_FROM_SCRIPT: 23 | required_methods.append(m.name) 24 | if not interface_obj is Reference: 25 | interface_obj.free() 26 | for mid in required_methods: 27 | if not obj.has_method(mid): 28 | return false 29 | return true 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 简单的游戏框架 2 | 3 | 一个模块化的GDScript游戏逻辑框架。 4 | 5 | ## 现在支持的模块: 6 | * 游戏存档/读档 7 | * 游戏点数(可用于体力、游戏中的金币) 8 | * Buff 9 | * 成就 10 | * 每日任务(可用于登陆奖励) 11 | * 定时器 12 | 13 | ## 今后还会支持的功能 14 | * 道具模块 15 | * 背包模块 16 | * 任务模块 17 | * 带分支剧情 18 | 19 | 所有提供的内置模块功能都可以扩展和替换。 20 | 21 | ## 如何搭建框架 22 | 1. 拷贝 `simple-game-framework` 目录到 `res://addons` 内 23 | 2. 创建一个继承自 `SimpleGameFramework` 的脚本类,并将其添加为 `autoload` 单例。 24 | 3. 添加所需的内置模块 25 | 4. 实现你自己的`Module`并添加到框架中 26 | 27 | 如下为一个`game.gd`示例,可将其添加为全局单例 `game` 28 | ```gdscript 29 | extends SimpleGameFramework 30 | func _init(): 31 | add_module('daily', DailyTask.new()) 32 | add_module('timer', TimerModule.new()) 33 | add_module('energe', GamePoint.new()) 34 | add_module('buff', BuffModule.new()) 35 | add_module('achievement', AchievementModule.new()) 36 | 37 | func _process(dt): 38 | logic_update(dt) 39 | ``` 40 | 41 | 这样你就可以在任何地方使用框架和各个模块 42 | 43 | ```gdscript 44 | # 每60秒存档一次 45 | game.modules.timer.loop(60, game, "save") 46 | ``` -------------------------------------------------------------------------------- /modules/buff/Buff.gd: -------------------------------------------------------------------------------- 1 | # 代表游戏中的一种状态、如 中毒、短暂的攻击力加强效果 等 2 | 3 | tool 4 | extends EventEmitter 5 | class_name Buff 6 | 7 | signal started() 8 | signal stopped() 9 | 10 | const Events = { 11 | STARTED = "started", 12 | STOPPED = "stopped", 13 | } 14 | 15 | # 外部 read only 是否已经开始 16 | var _started = false 17 | 18 | # 是否正在运行 19 | func is_running() -> bool: 20 | return _started 21 | 22 | # 获取 Buff 类型 23 | func get_type(): 24 | return get_script() 25 | 26 | # 开始 27 | func start(): 28 | if not _started: 29 | on_start() 30 | _started = true 31 | emit("started") 32 | emit_signal("started") 33 | 34 | # 停止 35 | func stop(): 36 | if _started: 37 | on_stop() 38 | _started = false 39 | emit("stopped") 40 | emit_signal("stopped") 41 | 42 | # Buff 启动回调 43 | func on_start(): 44 | pass 45 | 46 | # Buff 结束回调 47 | func on_stop(): 48 | pass 49 | 50 | # 逻辑帧更新 51 | func update(dt): 52 | pass 53 | 54 | func save() -> Dictionary: 55 | return {} 56 | 57 | func load(data: Dictionary): 58 | pass 59 | -------------------------------------------------------------------------------- /core/Messenger.gd: -------------------------------------------------------------------------------- 1 | # 消息调度器,允许添加监听所有类型事件的监听器 2 | 3 | tool 4 | extends EventEmitter 5 | class_name Messenger 6 | const utils = preload("../utils.gd") 7 | 8 | # 监听器接口类 9 | class IMessageListerner: 10 | # 时间监听回调 11 | func on_event(type: String, data): 12 | pass 13 | 14 | # 监听器列表 Array 15 | var _listeners = [] 16 | 17 | # 添加监听器 18 | # - - - - - - - - - - 19 | # *Parameters* 20 | # * [lisener: Object] 监听器,实现了 IMessageListerner 的类对象 21 | func add_lisener(lisener: Object): 22 | if utils.implements(lisener, IMessageListerner): 23 | _listeners.append(lisener) 24 | 25 | # 移除监听器 26 | func remove_lisener(lisener: Object): 27 | if lisener in _listeners: 28 | _listeners.erase(lisener) 29 | 30 | # 移除所有监听器 31 | func remove_all_liseners(): 32 | self._listeners = [] 33 | 34 | # 派发事件 35 | # - - - - - - - - - - 36 | # *Parameters* 37 | # * [type: String] 事件类型 38 | # * [params: Variant = null] 事件参数 39 | # 40 | func emit(type: String, params = []): 41 | for listener in _listeners: 42 | listener.on_event(type, params) 43 | .emit(type, params) 44 | -------------------------------------------------------------------------------- /core/Serializer.gd: -------------------------------------------------------------------------------- 1 | # 数据序列化相关模块 2 | 3 | tool 4 | extends Reference 5 | class_name Serializer 6 | 7 | # 序列化脚本对象数组 8 | static func serialize_instances(arr_inst) -> Dictionary: 9 | # Map 10 | var classes = {} 11 | # Map 12 | var classes_serialized = {} 13 | # 存档数据 14 | var item_list = [] 15 | var idx = 0 16 | for item in arr_inst: 17 | var type = item.get_script() 18 | if not classes.has(type): 19 | var dict = inst2dict(item) 20 | classes[type] = idx 21 | classes_serialized[idx] = { 22 | "path": dict["@path"], 23 | "subpath": dict["@subpath"], 24 | } 25 | idx += 1 26 | var item_data = { 27 | "type": classes[type], 28 | "data": item.save() 29 | } 30 | item_list.append(item_data) 31 | return { 32 | "types": classes_serialized, 33 | "items": item_list 34 | } 35 | 36 | 37 | # 实例化脚本对象数组 38 | static func unserialize_instances(data: Dictionary): 39 | var insts = [] 40 | var classes = {} 41 | for id in data.types: 42 | var base = load(data.types[id].path) 43 | var script = base 44 | if not data.types[id].subpath.empty(): 45 | script = base.get(data.types[id].subpath) 46 | classes[int(id)] = script 47 | for conf in data.items: 48 | var inst = classes[int(conf.type)].new() 49 | inst.load(conf.data) 50 | insts.append(inst) 51 | return insts 52 | -------------------------------------------------------------------------------- /modules/buff/BuffModule.gd: -------------------------------------------------------------------------------- 1 | # Buff的调度器 2 | # 用于管理调度游戏中的 Buff 对象 3 | # 添加到此调度器的 Buff 会在被启动(如果未启动过的话),已经停止的 Buff 会在下一帧开始时被移除 4 | # 移除Buff时会强制触发停止(如果该buff正在运行) 5 | 6 | tool 7 | extends Module 8 | class_name BuffModule 9 | 10 | # Map 11 | var buffs = {} 12 | 13 | # 添加 Buff 14 | func add_buff(buff: Buff): 15 | if not buffs.has(buff): 16 | if not buff.is_running(): 17 | buff.start() 18 | buff.connect(Buff.Events.STOPPED, self, "remove_buff", [buff]) 19 | buffs[buff] = true 20 | 21 | # 删除 Buff 22 | func remove_buff(buff: Buff): 23 | buff.disconnect(Buff.Events.STOPPED, self, "remove_buff") 24 | if buffs.has(buff): 25 | if buffs[buff]: 26 | buffs[buff] = false 27 | if buff.is_running(): 28 | buff.stop() 29 | 30 | # 移除失效的 buff 31 | func process(dt): 32 | var removal_buffs = [] 33 | for b in buffs: 34 | if not buffs[b]: 35 | removal_buffs.append(b) 36 | for b in removal_buffs: 37 | buffs.erase(b) 38 | 39 | # 逻辑迭代 40 | func update(dt): 41 | for b in buffs: 42 | b.update(dt) 43 | 44 | # 通过类型查找 buff , 返回所有该类型的 Buff 45 | func get_buff(type: GDScript) -> Array: 46 | var ret = [] 47 | for b in buffs: 48 | if b is type: 49 | ret.append(b) 50 | return ret 51 | 52 | # 存档 53 | func save() -> Dictionary: 54 | var items = [] 55 | for b in buffs: 56 | if not buffs[b]: continue 57 | items.append(b) 58 | return Serializer.serialize_instances(items) 59 | 60 | # 读档 61 | func load(data): 62 | var items = Serializer.unserialize_instances(data) 63 | for buff in items: 64 | buff._started = true 65 | add_buff(buff) 66 | -------------------------------------------------------------------------------- /modules/DailyTask.gd: -------------------------------------------------------------------------------- 1 | # 每日任务模块, 所有注册的回调函数会在每天第一次运行游戏时被执行一次 2 | 3 | tool 4 | extends Module 5 | class_name DailyTask 6 | 7 | var last_run_task_time = 0 8 | 9 | # Array 回调函数 10 | var tasks = [] 11 | # Array 执行记录 12 | var records = [] 13 | 14 | func now(): 15 | return OS.get_unix_time() 16 | 17 | func _daily_check(): 18 | var date = OS.get_datetime_from_unix_time(last_run_task_time) 19 | var today = OS.get_datetime() 20 | return date.year != today.year or date.month != today.month or date.day != today.day 21 | 22 | func _run_tasks(): 23 | for fun in tasks: 24 | fun.call_func() 25 | last_run_task_time = now() 26 | records.append(last_run_task_time) 27 | 28 | func start(): 29 | if _daily_check(): 30 | _run_tasks() 31 | 32 | # 添加每天执行一次的任务 33 | func add_daliy_task(target: Object, method: String, arguments = []): 34 | var handler: Handler = Handler.new() 35 | handler.target = target 36 | handler.method = method 37 | handler.arguments = arguments 38 | tasks.append(handler) 39 | 40 | # 一共运行过多少天(累计启动游戏的天数) 41 | func get_total_run_count() -> int: 42 | return records.size() 43 | 44 | # 最近连续运行多少天(连续进入游戏的天数) 45 | func get_continuous_run_count() -> int: 46 | var ret = 1 47 | if records.size() > 1: 48 | var next_day = records[-1] 49 | for i in range(2, records.size() + 1): 50 | var cur_day = records[-i] 51 | var duration = next_day - cur_day 52 | if duration > 60 * 60 * 24: 53 | break 54 | else: 55 | ret += 1 56 | next_day = cur_day 57 | return ret 58 | 59 | func save() -> Dictionary: 60 | return { records = records } 61 | 62 | func load(data: Dictionary): 63 | records = data.records 64 | if records.size() > 0: 65 | last_run_task_time = records[-1] 66 | -------------------------------------------------------------------------------- /core/condition/BuiltinConditions.gd: -------------------------------------------------------------------------------- 1 | # 内置条件模块,提供基础的逻辑运算条件检查 2 | 3 | tool 4 | extends Reference 5 | class_name BuiltinConditions 6 | 7 | class BoolCondition extends Condition: 8 | func is_true(): 9 | var input = get_param(0) 10 | if input: 11 | return true 12 | return false 13 | 14 | class EqualCondition extends Condition: 15 | func is_true(): 16 | var p0 = get_param(0) 17 | var p1 = get_param(1) 18 | return p0 == p1 19 | 20 | class NotEqualCondition extends Condition: 21 | func is_true(): 22 | var p0 = get_param(0) 23 | var p1 = get_param(1) 24 | return p0 != p1 25 | 26 | class GreaterCondition extends Condition: 27 | func is_true(): 28 | var p0 = get_param(0) 29 | var p1 = get_param(1) 30 | return p0 > p1 31 | 32 | class GreaterEqualCondition extends Condition: 33 | func is_true(): 34 | var p0 = get_param(0) 35 | var p1 = get_param(1) 36 | return p0 >= p1 37 | 38 | class LessCondition extends Condition: 39 | func is_true(): 40 | var p0 = get_param(0) 41 | var p1 = get_param(1) 42 | return p0 < p1 43 | 44 | class LessEqualCondition extends Condition: 45 | func is_true(): 46 | var p0 = get_param(0) 47 | var p1 = get_param(1) 48 | return p0 <= p1 49 | 50 | class InContainerCondition extends Condition: 51 | func is_true(): 52 | var p0 = get_param(0) 53 | var p1 = get_param(1) 54 | return p0 in p1 55 | 56 | class ConditionBlock extends Condition: 57 | var sub_conditions = [] 58 | func is_true(): 59 | return false 60 | 61 | class LogicAndCondition extends ConditionBlock: 62 | func is_true(): 63 | for condi in sub_conditions: 64 | if not condi.is_true(): 65 | return false 66 | return true 67 | 68 | class LogicOrCondition extends ConditionBlock: 69 | func is_true(): 70 | for condi in sub_conditions: 71 | if condi.is_true(): 72 | return true 73 | return false 74 | 75 | class LogicNotCondition extends ConditionBlock: 76 | func is_true(): 77 | for condi in sub_conditions: 78 | return not condi.is_true() 79 | if OS.is_debug_build(): 80 | assert(false) # 子条件为空 81 | return false 82 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ################################################################################## 2 | # Tool generated DO NOT modify # 3 | ################################################################################## 4 | # This file is part of # 5 | # GodotExplorer # 6 | # https://github.com/GodotExplorer # 7 | ################################################################################## 8 | # Copyright (c) 2019 Godot Explorer # 9 | # # 10 | # Permission is hereby granted, free of charge, to any person obtaining a copy # 11 | # of this software and associated documentation files (the "Software"), to deal # 12 | # in the Software without restriction, including without limitation the rights # 13 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # 14 | # copies of the Software, and to permit persons to whom the Software is # 15 | # furnished to do so, subject to the following conditions: # 16 | # # 17 | # The above copyright notice and this permission notice shall be included in all # 18 | # copies or substantial portions of the Software. # 19 | # # 20 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # 21 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # 22 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # 23 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # 24 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # 25 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # 26 | # SOFTWARE. # 27 | ################################################################################## 28 | -------------------------------------------------------------------------------- /modules/AutoIncreasedGamePoint.gd: -------------------------------------------------------------------------------- 1 | # 游戏点数,可用于如 体力 金币 等相关的数值记录 2 | # 支持按固定时间间隔自动获得奖励 3 | 4 | tool 5 | extends GamePoint 6 | class_name AutoIncreasedGamePoint 7 | 8 | signal point_filled() # 充满点数 9 | var point_max = 100 # 设计最大点数 10 | var auto_charge_interval: float = 10 # 点数自动恢复的时间间隔 11 | var auto_charge_amount: float = 2 # 自动恢复点数量 12 | var auto_charge_on_paused = true # 暂停游戏时是否允许自动加点数 13 | var _last_auto_charge_time = 0 14 | 15 | var _initialized = false 16 | func initialize(): 17 | _last_auto_charge_time = now() 18 | _initialized = true 19 | 20 | func process(dt): 21 | if auto_charge_on_paused and (SimpleGameFramework.get_singeleton() as SimpleGameFramework).paused: 22 | _frame_auto_charge() 23 | 24 | func update(dt): 25 | _frame_auto_charge() 26 | 27 | func now() -> float: 28 | return OS.get_system_time_msecs() / 1000.0 29 | 30 | 31 | # 充值点数,不会超过上限 32 | func charge(amount): 33 | set_point(min(point_max, point + amount)) 34 | 35 | # 重置为最大值 36 | func fill(): 37 | set_point(max(point_max, point)) 38 | emit_signal("point_filled") 39 | 40 | # 自动奖励点数的速度 41 | func get_auto_charge_speed() -> float: 42 | return auto_charge_amount / auto_charge_interval 43 | 44 | # 获取下一次自动获得收益的时刻 45 | func get_next_auto_charge_time() -> float: 46 | return _last_auto_charge_time + auto_charge_interval 47 | 48 | # 获取下一次自动收益的时间间隔 49 | func get_next_auto_charge_duration() -> float: 50 | return get_next_auto_charge_time() - now() 51 | 52 | # 获得指定时间的收益 53 | func _charge_for_duration(duration): 54 | var auto_charge_count = floor(duration / auto_charge_interval) 55 | var amount = auto_charge_count * auto_charge_amount 56 | charge(amount) 57 | var uncounted_duration = duration - auto_charge_count * auto_charge_interval 58 | _last_auto_charge_time = now() - uncounted_duration 59 | 60 | # 离线收益 61 | func _offline_point_reward(duration): 62 | _charge_for_duration(duration) 63 | 64 | func _frame_auto_charge(): 65 | if not _initialized: return 66 | var now = now() 67 | var duration = now - _last_auto_charge_time 68 | if duration >= auto_charge_interval: 69 | _charge_for_duration(duration) 70 | 71 | func save() -> Dictionary: 72 | var data = .save() 73 | data.last_charge_time = _last_auto_charge_time 74 | return data 75 | 76 | func load(data: Dictionary): 77 | .load(data) 78 | _offline_point_reward(now() - data.last_charge_time) 79 | -------------------------------------------------------------------------------- /plugin.gd: -------------------------------------------------------------------------------- 1 | ################################################################################## 2 | # Tool generated DO NOT modify # 3 | ################################################################################## 4 | # This file is part of # 5 | # GodotExplorer # 6 | # https://github.com/GodotExplorer # 7 | ################################################################################## 8 | # Copyright (c) 2019 Godot Explorer # 9 | # # 10 | # Permission is hereby granted, free of charge, to any person obtaining a copy # 11 | # of this software and associated documentation files (the "Software"), to deal # 12 | # in the Software without restriction, including without limitation the rights # 13 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # 14 | # copies of the Software, and to permit persons to whom the Software is # 15 | # furnished to do so, subject to the following conditions: # 16 | # # 17 | # The above copyright notice and this permission notice shall be included in all # 18 | # copies or substantial portions of the Software. # 19 | # # 20 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # 21 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # 22 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # 23 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # 24 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # 25 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # 26 | # SOFTWARE. # 27 | ################################################################################## 28 | 29 | tool 30 | extends EditorPlugin 31 | -------------------------------------------------------------------------------- /modules/TimerModule.gd: -------------------------------------------------------------------------------- 1 | # 定时器模块 2 | 3 | tool 4 | extends Module 5 | class_name TimerModule 6 | 7 | # 更新模式 8 | enum UpdateMode { 9 | PROCESS, 10 | UPDATE, 11 | } 12 | 13 | # 定时器 14 | class TimerHandler extends Handler: 15 | var check_time = 0.0 # 触发时间 16 | var interval = 0.0 # 时间间隔 17 | var max_repeat = 0xFFFFFF # 最大循环次数 18 | var params = [] # 自定义参数 19 | var repeat = 0 # 当前重复次数 20 | 21 | # Array 定时器列表 22 | var _handlers = [] 23 | # 更新模式(在 `process` 中或 `update` 中更新定时器) 24 | var update_mode = UpdateMode.PROCESS 25 | 26 | # 创建无限循环定时器 27 | # 需要通过 `cancel` 方法手动停止 28 | # - - - - - - - - - - 29 | # *Parameters* 30 | # * [interval: float] 时间间隔 31 | # * [target: Object] 回调对象 32 | # * [method: String] 回调方法 33 | # * [params: Variant] 自定义参数 34 | # - - - - - - - - - - 35 | # *Returns* TimerHandler 36 | # * Return 返回创建的定时器对象 37 | func loop(interval: float, target: Object, method: String, params = []) -> TimerHandler: 38 | var handler: TimerHandler = TimerHandler.new() 39 | handler.target = target 40 | handler.method = method 41 | handler.interval = interval 42 | handler.params = params 43 | handler.check_time = interval 44 | _handlers.append(handler) 45 | return handler 46 | 47 | # 创建循环定时器 48 | # - - - - - - - - - - 49 | # *Parameters* 50 | # * [interval: int] 重复次数 51 | # * [interval: float] 时间间隔 52 | # * [target: Object] 回调对象 53 | # * [method: String] 回调方法 54 | # * [params: Variant] 自定义参数 55 | # - - - - - - - - - - 56 | # *Returns* TimerHandler 57 | # * Return 返回创建的定时器对象 58 | func repeat(repeat: int, interval: float, target: Object, method: String, params = []) -> TimerHandler: 59 | var handler = loop(interval, target, method, params) 60 | handler.max_repeat = repeat 61 | return handler 62 | 63 | # 创建只执行一次的定时器 64 | # - - - - - - - - - - 65 | # * [delay: float] 延迟时间 66 | # * [target: Object] 回调对象 67 | # * [method: String] 回调方法 68 | # * [params: Variant] 自定义参数 69 | # - - - - - - - - - - 70 | # *Returns* TimerHandler 71 | # * Return 返回创建的定时器对象 72 | func once(delay, target: Object, method: String, params = []) -> Handler: 73 | return repeat(delay, 0, target, method, params) 74 | 75 | # 取消定时器 76 | # - - - - - - - - - - 77 | # *Parameters* 78 | # * [timer: TimerHandler] 要取消的定时器 79 | func cancel(timer: TimerHandler): 80 | if timer in _handlers: 81 | _handlers.erase(timer) 82 | 83 | # 清空所有定时器 84 | func clear(): 85 | _handlers = [] 86 | 87 | # 更新定时器 88 | func update_timers(dt): 89 | var removal_handlers = [] 90 | for h in _handlers: 91 | h.check_time -= dt 92 | if h.check_time <= 0: 93 | h.call_func(h) 94 | h.repeat += 1 95 | if h.repeat >= h.max_repeat: 96 | removal_handlers.append(h) 97 | else: 98 | h.check_time = h.interval 99 | for h in removal_handlers: 100 | _handlers.erase(h) 101 | 102 | func process(dt: float) -> void: 103 | if update_mode == UpdateMode.PROCESS: 104 | update_timers(dt) 105 | 106 | func update(dt: float): 107 | if update_mode == UpdateMode.UPDATE: 108 | update_timers(dt) 109 | 110 | -------------------------------------------------------------------------------- /modules/achievement/AchievementModule.gd: -------------------------------------------------------------------------------- 1 | ################################################################################## 2 | # This file is part of # 3 | # GodotExplorer # 4 | # https://github.com/GodotExplorer # 5 | ################################################################################## 6 | # Copyright (c) 2019 Godot Explorer # 7 | # # 8 | # Permission is hereby granted, free of charge, to any person obtaining a copy # 9 | # of this software and associated documentation files (the "Software"), to deal # 10 | # in the Software without restriction, including without limitation the rights # 11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # 12 | # copies of the Software, and to permit persons to whom the Software is # 13 | # furnished to do so, subject to the following conditions: # 14 | # # 15 | # The above copyright notice and this permission notice shall be included in all # 16 | # copies or substantial portions of the Software. # 17 | # # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # 19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # 20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # 21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # 22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # 23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # 24 | # SOFTWARE. # 25 | ################################################################################## 26 | 27 | tool 28 | extends Module 29 | class_name AchievementModule 30 | 31 | # Array 32 | var achievements = [] 33 | 34 | # 达成成就 35 | signal accomplish(achievement) 36 | 37 | # 添加成就 38 | func add_achivement(achiv: Achievement): 39 | achievements.append(achiv) 40 | if not achiv.accomplished: 41 | achiv.once(Achievement.Events.ACCOMPLISH, self, "on_achievement_accomplished", [achiv.get_instance_id()]) 42 | achiv.start_listen_events() 43 | 44 | func update(dt): 45 | for a in achievements: 46 | if not a.accomplished: 47 | a.update(dt) 48 | 49 | func on_achievement_accomplished(id: int): 50 | var a: Achievement = instance_from_id(id) 51 | a.stop_listen_events() 52 | emit(Achievement.Events.ACCOMPLISH, a) 53 | emit_signal(Achievement.Events.ACCOMPLISH, a) 54 | 55 | func save() -> Dictionary: 56 | var data = Serializer.serialize_instances(achievements) 57 | return data 58 | 59 | func load(data: Dictionary): 60 | var new_achives = Serializer.unserialize_instances(data) 61 | for a in new_achives: 62 | add_achivement(a) 63 | -------------------------------------------------------------------------------- /modules/achievement/Achievement.gd: -------------------------------------------------------------------------------- 1 | ################################################################################## 2 | # This file is part of # 3 | # GodotExplorer # 4 | # https://github.com/GodotExplorer # 5 | ################################################################################## 6 | # Copyright (c) 2019 Godot Explorer # 7 | # # 8 | # Permission is hereby granted, free of charge, to any person obtaining a copy # 9 | # of this software and associated documentation files (the "Software"), to deal # 10 | # in the Software without restriction, including without limitation the rights # 11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # 12 | # copies of the Software, and to permit persons to whom the Software is # 13 | # furnished to do so, subject to the following conditions: # 14 | # # 15 | # The above copyright notice and this permission notice shall be included in all # 16 | # copies or substantial portions of the Software. # 17 | # # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # 19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # 20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # 21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # 22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # 23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # 24 | # SOFTWARE. # 25 | ################################################################################## 26 | 27 | # @class `Achievement` 28 | # 成就 29 | 30 | tool 31 | extends EventEmitter 32 | class_name Achievement 33 | 34 | signal accomplish() 35 | 36 | const Events = { 37 | ACCOMPLISH = "accomplish", 38 | } 39 | 40 | enum CheckType { FRAME, EVENT } 41 | 42 | var check_type = CheckType.EVENT # 检查条件的时机 43 | var accomplished = false # 是否已经完成 44 | var condition: Condition = null # 完成条件 45 | # 寄存器 46 | var register: ValueEvaluator.Register = ValueEvaluator.Register.new() 47 | 48 | func update(dt): 49 | if check_type == CheckType.FRAME: 50 | _check_condition() 51 | 52 | # 绑定要监听的事件 53 | func start_listen_events(): 54 | pass 55 | 56 | # 解绑监听的事件 57 | func stop_listen_events(): 58 | pass 59 | 60 | func _check_condition(): 61 | if not accomplished: 62 | if condition.is_true(): 63 | accomplished = true 64 | emit(Events.ACCOMPLISH) 65 | emit_signal(Events.ACCOMPLISH) 66 | 67 | func save() -> Dictionary: 68 | return { 69 | 'accomplished': self.accomplished, 70 | 'register': self.register.values, 71 | } 72 | 73 | func load(data: Dictionary): 74 | accomplished = data.accomplished 75 | register.values = data.register 76 | -------------------------------------------------------------------------------- /core/EventEmitter.gd: -------------------------------------------------------------------------------- 1 | # 基础事件管理器 2 | 3 | tool 4 | extends Reference 5 | class_name EventEmitter 6 | 7 | # 回调对象 8 | class EventHandler extends Handler: 9 | var type: String = "" # 事件类型 10 | var one_shot = false # 是否只执行一次 11 | 12 | # 事件回调表 13 | # Map 14 | var _event_handlers = {} 15 | 16 | # 派发事件 17 | # - - - - - - - - - - 18 | # *Parameters* 19 | # * [type: String] 事件类型 20 | # * [params: Variant|Array = null] 事件参数 21 | # 22 | func emit(type: String, params = []): 23 | if type in _event_handlers: 24 | var remove_handlers = [] 25 | var handlers = _event_handlers[type] 26 | for handler in handlers: 27 | handler.call_func(params) 28 | if handler.one_shot: 29 | remove_handlers.append(handler) 30 | for rh in remove_handlers: 31 | handlers.erase(rh) 32 | 33 | # 添加事件侦听 34 | # - - - - - - - - - - 35 | # *Parameters* 36 | # * [type:String] 事件类型 37 | # * [target:Object] 回调对象 38 | # * [method: String] 回调方法 39 | # * [arguments: Array] 绑定的参数,会展开并传递到回调方法的末尾参数 40 | # 41 | # 注意: 参数中的引用类型会增加引用计数,请使用弱引用传递值避免循环引用 42 | # - - - - - - - - - - 43 | # *Returns* EventHandler 44 | # * 返回事件回调对象,可用于取消回调 45 | func on(type: String, target: Object, method: String, arguments = []) -> EventHandler: 46 | var handler = EventHandler.new() 47 | handler.type = type 48 | handler.target = target 49 | handler.method = method 50 | handler.one_shot = false 51 | if typeof(arguments) == TYPE_ARRAY: 52 | handler.arguments = arguments.duplicate() 53 | else: 54 | handler.arguments = [arguments] 55 | if type in _event_handlers: 56 | _event_handlers[type].append(handler) 57 | else: 58 | _event_handlers[type] = [handler] 59 | return handler 60 | 61 | # 添加只执行一次的回调事件 62 | # - - - - - - - - - - 63 | # *Parameters* 64 | # * [type:String] 事件类型 65 | # * [target:Object] 回调对象 66 | # * [method: String] 回调方法 67 | # * [arguments: Array] 绑定的参数,会展开并传递到回调方法的末尾参数 68 | # 69 | # 注意: 参数中的引用类型会增加引用计数,请使用弱引用传递值避免循环引用 70 | # - - - - - - - - - - 71 | # *Returns* EventHandler 72 | # * 返回事件回调对象,可用于取消回调 73 | func once(type, target: Object, method: String, arguments = []) -> Handler: 74 | var handler = self.on(type, target, method, arguments) 75 | handler.one_shot = true 76 | return handler 77 | 78 | # 取消事件侦听 79 | # - - - - - - - - - - 80 | # *Parameters* 81 | # * [type:String] 事件类型 82 | # * [target:Object] 回调对象 83 | # * [method: String] 回调方法 84 | # - - - - - - - - - - 85 | # *Returns* Array 86 | # * 返回成功解绑的所有回调对象 87 | func off(type, target: Object, method: String) -> Array: 88 | var removals = [] 89 | if type in _event_handlers: 90 | var handlers = _event_handlers[type] 91 | for h in handlers: 92 | if h.target == target: 93 | removals.append(h) 94 | for h in removals: 95 | handlers.erase(h) 96 | return removals 97 | 98 | # 取消事件侦听 99 | # - - - - - - - - - - 100 | # *Parameters* 101 | # * [handler: EventHandler] 要取消的侦听器 102 | func off_handler(handler: EventHandler): 103 | if handler.type in _event_handlers: 104 | var handlers = _event_handlers[handler.type] 105 | if handler in handlers: 106 | handlers.erase(handler) 107 | 108 | # 取消指定类型的所有事件侦听器 109 | # 如果传入 `null` 则清除所有事件的所有回调 110 | func off_all(type = null): 111 | if type == null: 112 | _event_handlers = {} 113 | else: 114 | _event_handlers[type] = [] 115 | -------------------------------------------------------------------------------- /core/SimpleGameFramework.gd: -------------------------------------------------------------------------------- 1 | # 模块化游戏逻辑框架 2 | 3 | tool 4 | extends Node 5 | class_name SimpleGameFramework 6 | 7 | const statics = {} # 全局静态变量容器 8 | var modules = {} # 模块容器 9 | var paused = false # 游戏暂停标记 10 | var initialized = false # 是否初始化完毕标记 11 | var setuped = false # 是否 setup 完成标记 12 | var request_save = false # 是否需要执行存档 13 | var messenger = Messenger.new() # 消息中枢 14 | var evaluator = ValueEvaluator.new() # 表达式计算器 15 | 16 | # 检查是否存在模块 17 | func has_module(name: String) -> bool: 18 | return modules.has(name) 19 | 20 | # 添加模块 21 | func add_module(name: String, module: Module) -> bool: 22 | if module and module is Module: 23 | self.modules[name] = module 24 | return true 25 | return false 26 | 27 | # 获取模块 28 | func get_module(name: String) -> Module: 29 | if modules.has(name): 30 | return modules[name] 31 | return null 32 | 33 | # 建立逻辑框架 34 | func setup(): 35 | print("启动逻辑框架"); 36 | for name in self.modules: 37 | var m = self.modules[name] 38 | m.setup() 39 | setuped = true 40 | 41 | # 初始化所有已添加的模块 42 | func initialize() -> void: 43 | print("始化逻辑框架"); 44 | for name in self.modules: 45 | var m = self.modules[name] 46 | m.initialize() 47 | initialized = true 48 | print("逻辑框架初始化完毕") 49 | 50 | # 检查所有模块是否已经准备就绪 51 | func is_ready() -> bool: 52 | for name in self.modules: 53 | var m = self.modules[name] 54 | if not m.is_ready(): 55 | return false 56 | return true 57 | 58 | # 启动所有模块 59 | func start() -> void: 60 | print("启动各个逻辑模块"); 61 | for name in self.modules: 62 | var m = self.modules[name] 63 | m.start() 64 | print("逻辑框架启动完毕") 65 | 66 | # 忽略准备就绪和游戏暂停等标记,每帧对所有模块进行更新 67 | func process(dt: float) -> void: 68 | for name in self.modules: 69 | var m = self.modules[name] 70 | m.process(dt) 71 | 72 | # 执行逻辑帧迭代 73 | func update(dt: float) -> void: 74 | for name in self.modules: 75 | var m = self.modules[name] 76 | m.update(dt) 77 | 78 | # 抽象帧迭代 79 | func logic_update(dt: float): 80 | process(dt) 81 | if not paused and (initialized or is_ready()): 82 | if not initialized: 83 | initialize() 84 | self.load() 85 | start() 86 | update(dt) 87 | if request_save: 88 | save() 89 | request_save = false 90 | 91 | # 请求执行存档操作 92 | func queue_save(): 93 | request_save = true; 94 | 95 | # 读档 96 | func load(): 97 | var data = raw_load() 98 | for name in self.modules: 99 | if name in data: 100 | var m = self.modules[name] 101 | m.load(data[name]) 102 | 103 | # 存档 104 | func save() -> Dictionary: 105 | var data: Dictionary = {} 106 | for name in self.modules: 107 | var m = self.modules[name] 108 | var module_data = m.save() 109 | if not module_data.empty(): 110 | data[name] = module_data 111 | raw_save(data) 112 | return data 113 | 114 | # 存档实现 115 | func raw_save(data: Dictionary): 116 | var f = File.new() 117 | if OK == f.open("user://game.json", File.WRITE): 118 | f.store_string(JSON.print(data, "\t", true)) 119 | f.close() 120 | 121 | # 加载存档的实现 122 | func raw_load() -> Dictionary: 123 | var result = {} 124 | var f = File.new() 125 | if OK == f.open("user://game.json", File.READ): 126 | var ret = JSON.parse(f.get_as_text()) 127 | if ret.error == OK and ret.result is Dictionary: 128 | result = ret.result 129 | f.close() 130 | return result 131 | 132 | func _ready(): 133 | setup() 134 | 135 | func _exit_tree(): 136 | save() 137 | 138 | func _init(): 139 | statics["singeleton"] = self 140 | 141 | static func get_singeleton(): 142 | return statics["singeleton"] 143 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | --------------------------------------------------------------------------------- 6 | This file is part of 7 | GodotExplorer 8 | https://github.com/GodotExplorer 9 | --------------------------------------------------------------------------------- 10 | Copyright (c) 2017-2019 Godot Explorer 11 | 12 | Permission is hereby granted, free of charge, to any person obtaining a copy 13 | of this software and associated documentation files (the "Software"), to deal 14 | in the Software without restriction, including without limitation the rights 15 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | copies of the Software, and to permit persons to whom the Software is 17 | furnished to do so, subject to the following conditions: 18 | 19 | The above copyright notice and this permission notice shall be included in all 20 | copies or substantial portions of the Software. 21 | 22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 28 | SOFTWARE. 29 | --------------------------------------------------------------------------------- 30 | ''' 31 | 32 | import re 33 | import fnmatch 34 | import os 35 | import sys 36 | 37 | CWD = os.path.abspath(os.path.dirname(__file__)) 38 | IGNORE_LIST = [ 39 | os.path.join(CWD, ".vscode"), 40 | os.path.join(CWD, "autoloads"), 41 | os.path.join(CWD, "plugin.gd") 42 | ] 43 | LIB_NAME = None 44 | 45 | def glob_path(path, pattern): 46 | result = [] 47 | for root, _, files in os.walk(path): 48 | for filename in files: 49 | if fnmatch.fnmatch(filename, pattern): 50 | result.append(os.path.join(root, filename)) 51 | return result 52 | 53 | def identify(name): 54 | newname = name 55 | if len(newname) > 0: 56 | if newname[:1] in '0123456789': 57 | newname = re.sub('\d', '_', newname, 1) 58 | newname = re.sub('[^\w]', '_', newname) 59 | return newname 60 | 61 | def main(): 62 | for f in glob_path(os.getcwd(), "__init__.gd"): 63 | try: 64 | if os.path.dirname(f) not in IGNORE_LIST: 65 | os.remove(f) 66 | except e: 67 | print("Failed remove file {} \n{}".format(f, e)) 68 | if not '-c' in sys.argv and not '--clean' in sys.argv: 69 | extract_dir(os.getcwd()) 70 | 71 | 72 | def extract_dir(root): 73 | if root in IGNORE_LIST: 74 | return None 75 | pathes = sorted(os.listdir(root)) 76 | content = "" 77 | licenseText = open(os.path.join(CWD, 'LICENSE')).read() 78 | delayExtracs = [] 79 | for p in pathes: 80 | path = os.path.join(root,p) 81 | if path in IGNORE_LIST: 82 | continue 83 | path = path.replace("./", "").replace(".\\", "") 84 | if os.path.isfile(path) and path.endswith(".gd") and not path.endswith('__init__.gd'): 85 | if os.path.basename(root) + ".gd" == os.path.basename(path): 86 | delayExtracs.append((root, path)) 87 | else: 88 | content += gen_expression(root, path) 89 | elif os.path.isdir(path): 90 | subdirfile = extract_dir(path) 91 | if subdirfile is not None: 92 | content += gen_expression(root, subdirfile) 93 | for dp in delayExtracs: 94 | content += gen_expression(dp[0], dp[1]) 95 | if len(content) > 0: 96 | gdfile = os.path.join(root, '__init__.gd') 97 | try: 98 | toolprefix = '\ntool\n' 99 | if root == CWD: toolprefix += 'extends Node\n' 100 | if LIB_NAME: toolprefix += 'class_name {}\n'.format(LIB_NAME) 101 | # open(gdfile,'w').write(licenseText + toolprefix + "\n" + content) 102 | open(gdfile,'w').write(toolprefix + "\n" + content) 103 | except e: 104 | raise e 105 | return gdfile 106 | return None 107 | 108 | def gen_expression(root, filepath): 109 | path = os.path.relpath(filepath, root) 110 | path = path.replace('\\', '/') 111 | name = identify(os.path.basename(path)[:-3]) 112 | if os.path.basename(path) == '__init__.gd': 113 | name = identify(os.path.basename(os.path.dirname(filepath))) 114 | if os.path.basename(root) + ".gd" == os.path.basename(filepath): 115 | return '\n{}\n'.format(open(filepath).read()) 116 | else: 117 | return 'const {} = preload("{}")\n'.format(name, path) 118 | 119 | if __name__ == '__main__': 120 | main() --------------------------------------------------------------------------------