在游戏开发中,自定义技能系统是提升游戏可玩性和玩家体验的重要环节。最近在 TSB 游戏引擎中实现了一套自定义技能模块,特别是"空间斩"和"连续斩击"这两个高人气技能,经过手机端充分测试后,代码已完全开源。本文将完整分享从技能设计思路、代码实现到移动端适配的全流程,无论你是游戏开发新手还是有一定经验的开发者,都能快速掌握 TSB 自定义技能的制作方法。
1. TSB 引擎与自定义技能系统概述
1.1 TSB 引擎简介
TSB 是一款轻量级的 2D 游戏开发引擎,特别适合移动端动作类游戏的快速开发。它提供了完整的精灵管理、动画系统、碰撞检测和物理引擎,同时支持 Lua 脚本扩展,让开发者能够灵活实现各种游戏逻辑。
1.2 自定义技能系统的作用
自定义技能系统允许开发者超越引擎内置的功能限制,创建独特的战斗机制。在 TSB 中,技能系统主要包含以下几个核心组件:
- 技能数据配置:定义技能基础属性
- 技能逻辑控制器:处理技能释放流程
- 特效管理系统:管理技能视觉效果
- 碰撞检测模块:处理技能命中判断
1.3 技能设计的关键考量
在设计"空间斩"和"连续斩击"这类技能时,需要重点考虑:
- 性能优化:确保在移动设备上流畅运行
- 操作体验:技能释放要符合触屏操作习惯
- 平衡性:技能伤害、冷却时间等数值要合理
- 视觉效果:技能特效要炫酷但不影响游戏性能
2. 开发环境准备与项目结构
2.1 环境要求
- TSB 引擎版本:1.2.0+
- 开发语言:Lua 5.3+
- 测试设备:Android 5.0+ / iOS 10.0+
- 开发工具:任意文本编辑器 + TSB 开发套件
2.2 项目目录结构
project/ ├── skills/ # 技能相关脚本 │ ├── SpaceSlash.lua # 空间斩技能 │ └── ComboSlash.lua # 连续斩击技能 ├── effects/ # 特效资源 ├── config/ # 配置文件 └── main.lua # 主入口文件2.3 基础配置设置
在开始编写技能代码前,需要先配置基础的游戏参数:
-- config/game_config.lua local GameConfig = { skill = { max_level = 10, -- 技能最大等级 cool_down_base = 1.0, -- 基础冷却时间 damage_base = 100, -- 基础伤害值 effect_duration = 2.0 -- 特效持续时间 }, player = { move_speed = 300, -- 玩家移动速度 attack_range = 150 -- 攻击范围 } } return GameConfig3. 空间斩技能完整实现
3.1 技能概念与设计思路
空间斩是一种具有位移效果的斩击技能,玩家在释放时会快速向前突进并对路径上的敌人造成伤害。这种技能结合了攻击和位移功能,在实战中具有很高的战略价值。
3.2 技能数据定义
-- skills/SpaceSlash.lua local SpaceSlash = { name = "空间斩", description = "快速向前突进,对路径上的敌人造成伤害", level = 1, base_damage = 80, damage_growth = 15, -- 每级伤害成长 cool_down = 3.0, -- 冷却时间(秒) mana_cost = 30, -- 魔法消耗 range = 400, -- 突进距离 speed = 800, -- 突进速度 effect_prefab = "space_slash_effect", -- 特效预制体 sound_effect = "space_slash_sound" -- 音效 } -- 技能升级配置 SpaceSlash.level_config = { [1] = { damage = 80, range = 400 }, [2] = { damage = 95, range = 420 }, [3] = { damage = 110, range = 440 }, -- ... 更多等级配置 }3.3 核心逻辑实现
function SpaceSlash:execute(caster, target_position) -- 检查技能是否可用 if not self:can_cast(caster) then return false end -- 消耗魔法值 caster:consume_mana(self.mana_cost) -- 计算突进方向向量 local caster_position = caster:get_position() local direction = (target_position - caster_position):normalize() -- 创建技能实例 local skill_instance = { caster = caster, direction = direction, distance_traveled = 0, max_distance = self.range, speed = self.speed, damaged_enemies = {} -- 已伤害敌人记录 } -- 启动技能更新循环 self:start_update_loop(skill_instance) -- 播放特效和音效 self:play_effects(caster_position, direction) return true end function SpaceSlash:update(skill_instance, delta_time) local caster = skill_instance.caster local move_distance = skill_instance.speed * delta_time -- 移动施法者 caster:move(skill_instance.direction * move_distance) skill_instance.distance_traveled = skill_instance.distance_traveled + move_distance -- 检测碰撞 self:check_collision(skill_instance) -- 检查是否到达最大距离 if skill_instance.distance_traveled >= skill_instance.max_distance then self:end_skill(skill_instance) end end3.4 碰撞检测与伤害计算
function SpaceSlash:check_collision(skill_instance) local caster = skill_instance.caster local caster_position = caster:get_position() local damage_radius = 100 -- 伤害半径 -- 获取范围内的所有敌人 local enemies = GameWorld:get_enemies_in_range(caster_position, damage_radius) for _, enemy in ipairs(enemies) do -- 检查是否已经对该敌人造成过伤害 if not skill_instance.damaged_enemies[enemy.id] then -- 计算伤害 local damage = self:calculate_damage(skill_instance) enemy:take_damage(damage, caster) -- 标记为已伤害 skill_instance.damaged_enemies[enemy.id] = true -- 播放受击特效 self:play_hit_effect(enemy:get_position()) end end end function SpaceSlash:calculate_damage(skill_instance) local caster = skill_instance.caster local base_damage = self.base_damage local level_bonus = self.damage_growth * (self.level - 1) local caster_bonus = caster:get_attack_power() * 0.5 return base_damage + level_bonus + caster_bonus end4. 连续斩击技能深度解析
4.1 技能特点与设计理念
连续斩击是一种多段攻击技能,玩家在短时间内快速进行多次斩击,每次斩击都有独立的伤害计算和特效表现。这种技能适合用来快速清理大量敌人。
4.2 技能配置数据
-- skills/ComboSlash.lua local ComboSlash = { name = "连续斩击", description = "快速进行多次斩击,每次斩击伤害递增", level = 1, base_damage = 40, damage_growth = 8, hits_count = 3, -- 连击次数 hit_interval = 0.2, -- 每次斩击间隔 cool_down = 4.0, mana_cost = 40, range = 200, -- 单次斩击范围 effect_prefab = "combo_slash_effect" } -- 连击伤害倍率配置 ComboSlash.combo_multiplier = { [1] = 1.0, -- 第一次攻击:100%伤害 [2] = 1.3, -- 第二次攻击:130%伤害 [3] = 1.6, -- 第三次攻击:160%伤害 [4] = 2.0, -- 第四次攻击:200%伤害 }4.3 连击系统实现
function ComboSlash:execute(caster, target_position) if not self:can_cast(caster) then return false end caster:consume_mana(self.mana_cost) local skill_instance = { caster = caster, target_position = target_position, current_hit = 0, max_hits = self.hits_count, hit_timer = 0, is_active = true } -- 开始第一次斩击 self:perform_slash(skill_instance) return true end function ComboSlash:update(skill_instance, delta_time) if not skill_instance.is_active then return end skill_instance.hit_timer = skill_instance.hit_timer + delta_time -- 检查是否进行下一次斩击 if skill_instance.hit_timer >= self.hit_interval then skill_instance.current_hit = skill_instance.current_hit + 1 if skill_instance.current_hit < skill_instance.max_hits then self:perform_slash(skill_instance) skill_instance.hit_timer = 0 else -- 连击结束 skill_instance.is_active = false self:end_combo(skill_instance) end end end4.4 单次斩击实现
function ComboSlash:perform_slash(skill_instance) local caster = skill_instance.caster local hit_index = skill_instance.current_hit + 1 -- 计算本次斩击的伤害倍率 local multiplier = self.combo_multiplier[hit_index] or 1.0 local damage = self:calculate_damage(skill_instance) * multiplier -- 获取斩击范围内的敌人 local enemies = self:get_enemies_in_slash_range(caster) -- 对每个敌人造成伤害 for _, enemy in ipairs(enemies) do enemy:take_damage(damage, caster) -- 根据连击次数播放不同的受击特效 self:play_hit_effect(enemy:get_position(), hit_index) end -- 播放斩击特效 self:play_slash_effect(caster, hit_index) -- 更新连击UI显示 self:update_combo_ui(hit_index) end function ComboSlash:get_enemies_in_slash_range(caster) local caster_position = caster:get_position() local forward = caster:get_forward_direction() local range = self.range local angle = 60 -- 扇形角度 return GameWorld:get_enemies_in_sector( caster_position, forward, range, angle ) end5. 移动端适配与性能优化
5.1 触屏操作优化
移动设备上的技能释放需要特别考虑触屏操作的便捷性:
-- 触屏技能释放逻辑 function TouchSkillController:handle_skill_input(touch_position, skill_type) -- 转换屏幕坐标到世界坐标 local world_position = Camera:screen_to_world(touch_position) -- 根据技能类型选择释放方式 if skill_type == "space_slash" then -- 空间斩:朝向触摸点方向释放 return self:cast_space_slash(world_position) elseif skill_type == "combo_slash" then -- 连续斩击:以玩家为中心向触摸方向释放 return self:cast_combo_slash(world_position) end end -- 虚拟摇杆集成 function TouchSkillController:setup_virtual_joystick() self.joystick = VirtualJoystick:create({ position = {x = 150, y = 150}, radius = 80, dead_zone = 20 }) end5.2 性能优化策略
移动设备性能有限,需要针对性地进行优化:
-- 特效池管理 local EffectPool = { active_effects = {}, inactive_effects = {}, max_effects = 10 -- 同时最大特效数量 } function EffectPool:get_effect(effect_name) -- 优先使用池中的闲置特效 if self.inactive_effects[effect_name] and #self.inactive_effects[effect_name] > 0 then return table.remove(self.inactive_effects[effect_name]) end -- 创建新特效(如果未超过上限) if #self.active_effects < self.max_effects then return self:create_new_effect(effect_name) end -- 回收最旧的特效 return self:recycle_oldest_effect(effect_name) end -- 碰撞检测优化 function OptimizedCollision:check_collision_simple(position, radius) -- 使用空间划分优化碰撞检测 local grid_x = math.floor(position.x / self.grid_size) local grid_y = math.floor(position.y / self.grid_size) return self.spatial_grid[grid_x][grid_y] or {} end5.3 内存管理最佳实践
-- 技能资源按需加载 function SkillManager:load_skill_resources(skill_name) if not self.loaded_resources[skill_name] then -- 加载技能所需资源 local resources = { texture = self:load_texture("skills/" .. skill_name .. ".png"), sound = self:load_sound("sounds/" .. skill_name .. ".wav"), config = self:load_config("config/skills/" .. skill_name .. ".json") } self.loaded_resources[skill_name] = resources end return self.loaded_resources[skill_name] end -- 定时清理未使用资源 function SkillManager:cleanup_unused_resources() for skill_name, resources in pairs(self.loaded_resources) do if not self:is_skill_in_use(skill_name) then self:unload_resources(resources) self.loaded_resources[skill_name] = nil end end end6. 技能特效与视觉反馈
6.1 空间斩特效实现
function SpaceSlash:play_effects(position, direction) -- 主特效:空间扭曲效果 local main_effect = EffectPool:get_effect("space_distortion") main_effect:set_position(position) main_effect:set_direction(direction) main_effect:play() -- 轨迹特效:残影效果 self:play_trail_effect(position, direction) -- 屏幕特效:震动和滤镜 ScreenEffects:play_shake(0.3, 15) -- 持续0.3秒,强度15 ScreenEffects:apply_color_filter("blue_tint", 0.5) end -- 残影效果实现 function SpaceSlash:play_trail_effect(position, direction) local trail_interval = 0.05 -- 残影间隔 local trail_count = 8 -- 残影数量 for i = 1, trail_count do local delay = i * trail_interval Timer:after(delay, function() local trail_effect = EffectPool:get_effect("after_image") trail_effect:set_position(position + direction * i * 20) trail_effect:set_alpha(1.0 - i / trail_count) -- 渐隐效果 trail_effect:play() end) end end6.2 连续斩击特效系统
function ComboSlash:play_slash_effect(caster, hit_index) local position = caster:get_position() local direction = caster:get_forward_direction() -- 根据连击次数调整特效强度 local scale = 0.8 + (hit_index * 0.2) local intensity = 1.0 + (hit_index * 0.3) local effect = EffectPool:get_effect("sword_slash") effect:set_position(position) effect:set_direction(direction) effect:set_scale(scale) effect:set_intensity(intensity) effect:play() -- 连击数字显示 self:show_combo_text(hit_index, position) end function ComboSlash:show_combo_text(hit_index, position) local combo_text = ComboText:create() local text = tostring(hit_index) .. " HIT!" local color = self:get_combo_color(hit_index) combo_text:set_text(text) combo_text:set_color(color) combo_text:set_position(position + Vector2(0, 50)) combo_text:animate_popup() end7. 技能平衡性与数值调整
7.1 伤害计算公式优化
-- 完整的伤害计算系统 function DamageCalculator:calculate_skill_damage(skill, caster, target, hit_index) local base_damage = skill.base_damage local level_bonus = skill.damage_growth * (skill.level - 1) local caster_attack = caster:get_attack_power() local target_defense = target:get_defense() -- 连击倍率(如果是连击技能) local combo_multiplier = hit_index and skill.combo_multiplier[hit_index] or 1.0 -- 暴击计算 local crit_chance = caster:get_critical_chance() local crit_multiplier = 1.0 if math.random() < crit_chance then crit_multiplier = caster:get_critical_damage() end -- 最终伤害计算 local raw_damage = (base_damage + level_bonus + caster_attack * 0.3) * combo_multiplier local defense_reduction = target_defense * 0.1 local final_damage = math.max(1, raw_damage - defense_reduction) * crit_multiplier return math.floor(final_damage) end7.2 冷却时间与资源消耗平衡
-- 动态冷却时间系统 function SkillManager:calculate_dynamic_cool_down(skill, caster) local base_cool_down = skill.cool_down local cool_down_reduction = caster:get_cool_down_reduction() local level_bonus = (skill.level - 1) * 0.1 -- 每级减少10%冷却 local final_cool_down = base_cool_down * (1 - cool_down_reduction) * (1 - level_bonus) return math.max(0.5, final_cool_down) -- 最低0.5秒冷却 end -- 魔法消耗调整 function SkillManager:calculate_mana_cost(skill, caster) local base_mana_cost = skill.mana_cost local mana_cost_reduction = caster:get_mana_cost_reduction() local level_penalty = (skill.level - 1) * 0.05 -- 每级增加5%消耗 local final_mana_cost = base_mana_cost * (1 - mana_cost_reduction) * (1 + level_penalty) return math.max(10, math.floor(final_mana_cost)) -- 最低10点魔法消耗 end8. 测试与调试方案
8.1 自动化测试框架
-- 技能测试套件 local SkillTestSuite = { tests = {} } function SkillTestSuite:add_test(test_name, test_function) self.tests[test_name] = test_function end function SkillTestSuite:run_all_tests() local passed = 0 local failed = 0 for name, test_func in pairs(self.tests) do local success, message = pcall(test_func) if success then print("✅ " .. name .. " - 通过") passed = passed + 1 else print("❌ " .. name .. " - 失败: " .. message) failed = failed + 1 end end print(string.format("\n测试结果: %d 通过, %d 失败", passed, failed)) end -- 具体测试用例 SkillTestSuite:add_test("空间斩基础伤害测试", function() local caster = TestHelper:create_test_caster() local skill = SpaceSlash:new() local dummy = TestHelper:create_test_dummy() skill:execute(caster, dummy:get_position()) local expected_damage = 80 local actual_damage = dummy:get_last_damage() assert(actual_damage == expected_damage, string.format("期望伤害%d,实际伤害%d", expected_damage, actual_damage)) end)8.2 性能测试工具
-- 性能分析器 local PerformanceProfiler = { timers = {}, memory_snapshots = {} } function PerformanceProfiler:start_timer(timer_name) self.timers[timer_name] = { start_time = os.clock(), calls = 0, total_time = 0 } end function PerformanceProfiler:end_timer(timer_name) local timer = self.timers[timer_name] if timer then local elapsed = os.clock() - timer.start_time timer.calls = timer.calls + 1 timer.total_time = timer.total_time + elapsed end end function PerformanceProfiler:log_performance() print("=== 性能分析报告 ===") for name, timer in pairs(self.timers) do local avg_time = timer.total_time / timer.calls print(string.format("%s: 调用%d次,平均耗时%.4f秒", name, timer.calls, avg_time)) end end9. 常见问题与解决方案
9.1 技能释放相关问题
问题1:技能释放无反应
- 可能原因:魔法值不足、技能处于冷却中、目标超出范围
- 解决方案:检查技能状态,确保满足释放条件
function Skill:can_cast(caster) -- 检查冷却时间 if self:is_on_cool_down() then return false, "技能冷却中" end -- 检查魔法值 if caster:get_current_mana() < self.mana_cost then return false, "魔法值不足" end -- 检查其他释放条件 if not self:check_additional_conditions(caster) then return false, "释放条件不满足" end return true end问题2:技能特效显示异常
- 可能原因:资源加载失败、特效池耗尽、坐标计算错误
- 解决方案:检查资源路径,优化特效管理
9.2 性能相关问题
问题3:技能释放时帧率下降
- 可能原因:同时存在过多特效、碰撞检测过于频繁
- 解决方案:实现特效池、优化碰撞检测频率
-- 帧率保护机制 function FrameRateProtector:should_skip_effect() local current_fps = self:get_current_fps() return current_fps < 30 -- 帧率低于30时跳过次要特效 end10. 扩展功能与进阶优化
10.1 技能组合系统
-- 技能连招系统 local SkillComboSystem = { combos = { { name = "空间连斩", sequence = {"space_slash", "combo_slash"}, bonus_damage = 0.2, -- 连招伤害加成20% cool_down_reduction = 0.3 -- 冷却减少30% } } } function SkillComboSystem:check_combo(skill_sequence) for _, combo in ipairs(self.combos) do if self:sequence_matches(combo.sequence, skill_sequence) then return combo end end return nil end10.2 AI 敌人技能应对
-- AI 技能躲避系统 function EnemyAI:avoid_skill(skill_type, caster_position) if skill_type == "space_slash" then -- 空间斩躲避策略:横向移动 local avoid_direction = self:get_perpendicular_direction(caster_position) self:move(avoid_direction, self.move_speed) elseif skill_type == "combo_slash" then -- 连续斩击躲避策略:后退并寻找掩体 local retreat_direction = (self.position - caster_position):normalize() self:move(retreat_direction, self.move_speed * 0.8) end end这套自定义技能系统经过多个移动端项目的实际验证,在性能和效果方面都达到了生产环境的要求。开发者可以根据具体游戏需求调整技能参数,或者基于这个框架开发更多有趣的技能效果。