Baike.dev
All toolsOnline toolsAI codingTrendingOpen sourceNewsSubmit
Log in
< Back to tools
M

MoBaDemo

> 编程语言
Open source

用Unity做的一个类Moba游戏Demo

785 stars0 likes1 views
WebsiteGitHub

About

用Unity做的一个类Moba游戏Demo

  • 1. 游戏整体逻辑
    • 1.1. 出兵逻辑
      • 1.1.1. 需要注意的点
      • 1.1.2. 规定路径的寻路
      • 1.1.3. 如何实现路径点寻路
  • 2. 资源管理
    • 2.1. 概述
  • 3. 输入系统架构与描述
    • 3.1. 在外部如何组织数据
    • 3.2. 如何将外部数据输入进Unity
      • 3.2.1. 如何初始化
    • 3.3. JSON数据组织
      • 3.3.1. 输出系统架构与描述
      • 3.3.2. 技能
      • 3.3.3. 所有JSON数据的数据格式
  • 4. GamePlay数据类
    • 4.1. GamePlay数据类中的Model类和Mono类
      • 4.1.1. 为什么要引入看起来变扭的Model类和Mono类
      • 4.1.2. Model类和Mono类应该遵循怎样的设计原则
    • 4.2. 技能设置描述
    • 4.3. 技能
      • 4.3.1. 基类 BaseSkill
      • [4.3.2. ***主动技能类 ActiveSkill 这种数组来保存这些技能对象,最后通过skillList[ i ]来通过下标访问任意一个技能。

但是!!在本游戏中,每个单位都是有技能的,这些单位的技能毫无疑问应该是之前已经编辑好的技能,这会遇到以下几个问题:

  1. 所有单位的技能都必须是技能表中的技能的深拷贝吗?(因为每个单位它的技能都会有自己的等级,正在缓冲的CD时间等,这个是每个单位都不一样的)
  2. 其次,本游戏中物品、状态的设计同样是通过技能来实现的,物品、状态中的技能,也得是深拷贝技能列表里的技能的吗?

根据上面两点,可以知道如果单位、物品、状态技能都是深拷贝自技能列表中的技能,那么可能游戏的内存消耗会相当巨大了,因为很多时候技能都是有共性的。

4.4.7.2. 简单的深拷贝对象将带来巨大的内存消耗

举个例子,比如多个技能之间释放的技能特效可能是同一个特效对象(Prefab)。这个比较好解决,只要在深拷贝的时候,不深拷贝特效对象就行了。

但是,同种技能之间的共性更多在他们的MP消耗,技能范围,技能图标,技能描述等属性上面。

举个例子,在游戏中有一招“地狱火”技能,随着等级的提升,它的MP消耗,技能范围可能会扩大,但是他的技能描述、技能名、技能图标这些是不会变化的。

而如果有一个英雄(比如“恐惧魔王”),他会这招“地狱火”技能,那么按照上面说的方法,他就深拷贝了一份技能列表中的地狱火。此时内存里有一份地狱火的技能描述、技能名、技能图标等属性。

这时,又有一个物品(比如“恶魔岩石”),使用这个物品的效果同样是“地狱火”技能的效果,那么按照上面说的方法,这里又得深拷贝一份技能列表中的地狱火。此时内存已经有两份同样的地狱火技能描述、技能名、技能图标等属性了。这就会带来多余的内存占用率。

同样的思路,如果此时又有一个状态,比如死亡后会释放地狱火技能,又得深拷贝一份。

4.4.7.3. 技能数据中变化与不变的部分

这里其实可以敏锐的察觉到,其实,技能对象中,大多数属性是不用变化,不用深拷贝的,这些属性,不论这些技能是在什么对象上,无论这些技能是由英雄释放的,还是物品释放的,都是不变的。

而技能对象的另外一些属性,才是真正会发生变化的,比如它的等级(由英雄加点发生变化),缓存的CD时间(经过多长时间可以继续释放它,由上一次释放它的时间决定)等属性,是每个具体的技能对象不同的,而其余的基本都相同。

下面以主动技能为例,列举一下其中变化和不变的属性。

不变的属性有以下几种:

  1. skillName 技能名
  2. icon 技能图标
  3. longDescription 技能长描述
  4. shortDescription 技能短描述
  5. backgroundDescription 技能背景描述
  6. mp 消耗MP,数组类型,mp[ i ]表示该技能第i级时消耗的mp
  7. spellDistance 施法距离,数组类型,spellDistance[ i ]表示该技能第i级时的施法距离
  8. skillInfluenceRadius 技能影响范围,数组类型
  9. cooldown 技能CD时间 数组类型

......等等

变化的属性有以下几种:

  1. skillLevel 技能等级,由英雄加点时变化
  2. finalSpellTime 上一次释放这个技能的时间,由上一次释放该技能的时机决定,用来判断该技能是否到达CD时间

可以看到,每个技能不变的属性有相当多,而每个技能对象不同的属性却只有两个(暂时只有两个,或许后续会增加)。

这种时候,就应该使用享元模式来管理技能对象的数据。

4.4.7.4. 如何使用享元模式管理技能数据

使用享元模式管理,那么最后在每个单位上的技能不变的那部分属性都只需要引用技能列表中的技能就够了,而不变的属性则由它所依附的那个对象来使用。

那么如何使用享元模式来管理呢?

我的做法是,新增一个SkillModel类,每个Skill对象都会引用一个SkillModel对象,这个Model类中的属性即为这个Skill不变的属性,即技能名、技能描述等属性。

而该技能会变化的属性,即Level等属性,则作为Skill类的属性,在游戏中会发生变化。

最后,我们在外部编辑器编辑的SkillModel的属性,读取进来也是SkillModel对象,全局存储的也是类似List这类属性,而具体的技能则是必须通过SkillModel来进行创建。

这样,每次都需要深拷贝的一部分技能类的属性,就变成了公共的属性,只需要Skill类中存储一个指向这些Model类的指针的就行了。

4.4.7.5. 要为每一个技能子类(操作)都编写一个Model类对应吗?

前面提到,在本游戏中,由技能组设定(SkillGroup)类增加的技能的易扩展性,也就是多个操作来组成一个技能,技能的种类由操作的排列组合形成。

实际上,我们在外部编辑器中,输入的就是这些操作的参数。每个原子技能类都各自继承自ActiveSkill和PassiveSkill类,他们也会各自派生出其他属性。

举个例子,比如派生于ActiveSkill的AdditionStateSkill(为某个目标增加一个状态的技能),就会有一个独立属性StateID,即要附加的状态的ID,这是ActiveSkill所没有的。这个时候,按照上面的做法,还得再派生自基类ActiveSkillModel得到一个AdditionStateSkillModel类,若是有多个原子技能,就会有多个这样的Model类,如果要一个个都全部编写,那效率会比较低下。

这里我采取一个方案,在BaseSkillModel类中使用一个类型的字典,存储这些所有的子类中的额外属性(相比于父类)。

而当要使用这些属性时,就直接从字典中取出该属性并进行强转。

4.4.7.6. 技能基类与技能等级提升带来的技能属性变化

前面提到享元模式中基类的属性就是在整场游戏中基本不会变化的属性,同时,这里的Model类还担任一项职责,那就是保存技能数据编辑器中的技能基本数据。也就是说,这个技能需要的mp,伤害等,一开始也是由编辑器输入的。

但是!对于一个技能来说,当它的等级变化的时候,它的一些属性理应也发生变化,如它的伤害,需要的mp,冷却CD等等。魔兽地图编辑器的做法是将这些属性按照等级变成N个序列值,某等级对应序列中一个值,但这样的话如果技能有100级,那设置技能的人岂不是要累死。。。

这里我采用的做法是在技能编辑器中设置基础属性,其变化属性根据公式及基础属性来进行计算。这样,在Model类我们设置的就是基础属性,即技能等级为1级时的默认属性,而当技能等级上升后,对应技能的属性会根据公式进行计算。

4.4.7.7. 基于享元模式编写的技能对象样例(Sample)

使用享元模式蛇者技能系统最重要的一点就是,可以很轻松的将技能(Skill)拷贝给某一对象(单位,技能组,物品,状态等),而无需使用深拷贝.

下面描述使用样例:

  1. 首先从AssetBundle中读取已设置好的所有技能对象

  2. 将得到的技能对象(Skill : BaseSkill)用数组的形式保存在内存中,之后要使用的时候,就直接访问这个全局的技能数组对象(假设为skills)

  3. 假设此时有一个物品它需要3号技能,那么,将物品的skill设置为 skills[3].copy();

  4. copy方法的伪代码实现如下:

    public override BaseSkill copy(){ BaseSkill skill = new BaseSkill(this.skillModel); return skill; }

上述代码的意思是,直接新建了一个新的技能对象,然后简单的让他的SkillModel指针指向这个需要被拷贝的技能对象的Model对象.
这是因为Model对象中保存了这个技能对象基本不变的属性(已预先在编辑器中设置好的),所以Model对象就是享元模式中不变的那个基类对象,只需简单用个指针引用即可.

4.5. 伤害系统的设计

  1. 所有单位受到的伤害都必须由Damage类提供,在代码中不允许出现 HP -= xxxx 这样的字眼.
  2. Damage类是值类型
  3. 某些被动技能拥有提升伤害的特性,对于这些被动技能,在计算完攻击 or 法术伤害后,再依次计算各个被动技能对伤害的提升。
  4. Damage类本身用于计算伤害的属性皆为Float属性,但是,当要具体拿出来计算时,全部都使用Floor强转为int型。

4.6. 物品系统设计

4.6.1. 功能描述

在MOBA游戏中,一个物品有多种功能。下面简单举例:

  1. 作为消耗品,为英雄进行补血等操作
  2. 作为装备,为英雄提供属性或特技
  3. 作为某些永久性使用物品,给英雄进行使用

可以看出物品其实可以看成是拥有一堆主动、被动技能的物体,只要携带在英雄身上(物体未消耗完毕),英雄就可以使用这些主动、被动技能。

4.6.2. 物品类(Item)属性

  1. 单个物品可持有最大数量 MaxCount : int : : 表示该装备需要什么装备来进行合成,列表的值是需要的装备的ID值
  2. SuffixEquipments后缀装备 : List : 表示该装备可以合成出什么装备来,列表的值是可以合成的装备的ID值,需要注意的是后缀装备的值是输入系统自动生成的.

对于游戏策划来说,他只需要设计每个物品的前缀装备就行了,也就是,每个高级物品,他需要的低级物品列表,对于后缀装备列表,我编写一套输入系统根据前缀装备进行自动输入.

4.8.1. 为什么要有后缀装备这个属性

一个最直接的原因是,实现装备合成的方法,我放在了人物的GetItem方法中,也就是说,当玩家获得一件装备时,系统会首先观察这个新获得的装备是否可以和目前装备栏的现有装备进行合成出高级装备.

这个判断的过程也就是查找的过程,如果没有后缀装备这个属性,要遍历整个物品表来查找是否可以合成.

当有后缀装备属性时,只需遍历后缀装备列表,查看是否物品栏内有装备可以一起合成这个高级装备.

4.9. 状态系统设计

4.9.1. 功能描述

状态是一种持续影响某一物体的表现,最典型的状态比如:中毒、伤害加深。
当单位受到中毒状态时,他会每隔一段时间受到一定比例的伤害。
而当单位受到伤害加深状态时,他会在遭受攻击时,受到的伤害加深。
更为特殊的状态,当单位拥有某一种特殊状态时,它的攻击会带有某种特效之类的。

从上面的描述中,可以看出状态有以下特点:

  1. 持续性的影响某一单位,当时间超过某一限度,此状态将会消失
  2. 除了持续性的影响外,还可能带有某些特殊效果,这些特殊效果可能会在状态持有者 攻击时、遭受伤害时等情况下触发

4.9.2. 实现思路

根据功能描述,可以把状态看成是一个在Update状态不停作用单位的一种Mono类。

但是,对于状态拥有的某些特殊功能,仅仅是上面这些还不够。对于状态的特殊效果,可以看出它的描述特别像被动技能。

在这里,可以将状态定义为:

  1. 在Update状态下不停作用物体
  2. 拥有一系列被动技能作用物体

4.9.3. 状态(BattleState)属性

  1. 状态名 stateName : string : :
    1. 该单位拥有的所有被动技能 passiveSkills : List
    2. 攻击力 attack : int : <==
    3. 攻击类型 attackType : AttackType : <==
    4. 攻击间隔(以秒为单位,每经过一段攻击间隔,便进行攻击一次) attackSpeed : float : <==
    5. 防御力 defense : int : <==
    6. 防御类型 defenseType : DefenseType : <==
    7. 移动速度 movingSpeed : int : <==
    8. 转身速度 turningSpeed : int : <==
    9. 投射物 projectile : Projectile : <==
    10. 等级 level : int : <==
    11. 回血速度 restoreHpSpeed : float : <==
    12. 回魔速度 restoreMpSpeed : float : <==
    13. 是否可被攻击(无敌) canBeAttacked : boolean : <==
    14. 单位类型 unitType : UnitType : <==
    15. 单位阵营 unityFaction : UnitFaction : <==
    16. 单位被杀死后将提供给英雄单位多少经验 supportExp : int : <==
    17. 单位被杀死后将提供给玩家单位多少金钱 supportMoney : int : <==

4.10.2. 英雄单位 Hero < Character

  1. 属性
    1. 力量 forcePower : float : <==
    2. 力量成长 forcePowerGrowthPoint : float : <==
    3. 敏捷 agilePower : float : <==
    4. 敏捷成长 agilePowerGrowthPoint : float : <==
    5. 智力 intelligencePower : int : <==
    6. 智力成长 intelligenceGrowthPoint : float : <==
    7. 技能点 skillPoint : int : <==
    8. 技能点成长 skillPointGrowthPoint : int : <==
    9. 经验值 exp : int
    10. 经验值因子(每次升级所需经验值关联系数) expfactor : float : <==
    11. 升级所需经验值(指第0级升到第一级所需经验) needExp : int : <==

4.10.3. 英雄单位相比普通单位有什么区别

  1. 更多的属性
  2. 杀死怪物后获得经验
  3. 当经验到达阈值,升级
  4. 升级后,获得技能点,同时智力、敏捷、力量等进行成长
  5. 拥有物品栏
  6. 使用技能点可以学习技能

4.10.4. 英雄单位的特殊属性对基本属性的作用

英雄单位相对于普通单位,有以下三点特殊属性:

  1. 力量
  2. 敏捷
  3. 智力

这三点属性的增加会导致单位的基本属性增加(攻击力、防御力、闪避率、hp、mp等等),下面给出详细叙述。

力量:
  每点增加x点HP
  每点增加x点hp回复速度
  每点增加x点攻击力(主属性加成)

敏捷:
  每点增加x点防御力
  每点增加x点攻击速度
  每点增加x点攻击力(主属性加成)

智力:
  每点增加x点MP    每点增加x点MP回复速度
  每点增加x点攻击力(主属性加成)


4.10.5. 投射物Projectile

投射物是一个比较特殊的单位,在一些技能和单位的远程攻击里面出现,投射物一般有一个目标位置。

4.10.5.1. 投射物的伤害计算

要明确的一点逻辑是,投射物本身是不具有伤害的,具有伤害的是发出投射物的单位,所有投射物都是由单位(继承自CharacterMono的所有子类)产生的,由该CharacterMono类指定伤害。

总结一下,也就是,由CharaacterMono类监听投射物的事件,当投射物到达目标地点后,触发伤害事件,调用CharacterMono的GetDamage的方法对敌人造成伤害。

对于一些攻击,如普通攻击等,需要计算敌我的防御值、攻击值,才能产生伤害,这些都是由Character类做的事情,投射物本身不进行任何伤害计算,他只是一个传递者。

4.10.5.2. 投射物属性

  1. 目标位置 targetPosition : vector3
  2. 移动速度 speed : float
  3. 此次运动是否是弧线轨迹 isArcMotion : bool
  4. 弧线运动中投射物上升的高度 riseHeight : float
  5. 弧线运动中投射物初始角度(基于竖直平面) angle : float
  6. 弧线运动中投射物结束角度(基于竖直平面) endAngle : float
  7. 投射物到达指定地点时产生的特效 targetPositionEffect : GameObject
  8. 投射物击中敌人时,在敌人身上产生的特效 targetEnemryEffect : GameObject

4.10.5.3. 投射物的飞行轨迹

对于一个投射物来说,他有两种飞行轨迹:
      一种是直线的,也就是直接从某处平移到某处,这种飞行轨迹容易实现,只需让投射物对着目标地点进行均匀的平移就可以了
      一种是有弧线的,这种飞行轨迹一般用于弓箭、炮弹、飞行生物降落的攻击等投射物上,这种飞行轨迹较难实现,因为对于弓箭这种投射物,它的旋转角度也会发生改变,对于从高处降下,基于DoTween插件可以较为容易的完成这项功能。

4.10.5.4. 投射物飞行轨迹实现

为了统一两种飞行轨迹,可以直接都使用DoTween插件中的DOLocalPath方法进行计算,对于弧线轨迹中的旋转基于DoRotate来进行旋转(计算好duration)。

4.11. 玩家

4.11.1. 描述

4.12. 音频管理与特效管理

4.12.1. 音频管理

在游戏中有很多地方需要播放音乐,我将播放音乐的单位(或者情况)分为场景音频、单位音频和UI音频。所有音频的接受者都是摄像机。场景音频、UI音频为2D音频,单位音频为3D音频。场景音频、UI音频的AudioSource在摄像机上,而单位音频的AudioSource在单位身上。

4.12.1.1. 场景音频

场景音频主要是整个游戏的BGM播放,由摄像机作为音频的播放者。需要注意的场景BGM是轮播的,也就是说,需要有一个脚本来管理轮播的BGM,以及决定是否轮播以及什么时候播放下一首BGM。

4.12.1.2. 单位音频

单位是游戏中主要发声对象,而单位在游戏中我分为两种,一种是生命实体(泛指一切拥有CHaracterMono组件的GameObject对象),一种是特效,特效粒子在后面进行解释,现在先看生命实体的发声机制。
他有可能在以下情况播放音频:

  1. 攻击时,武器发出的撞击声
  2. 死亡时,倒地音效
  3. 施法时特殊的音效
  4. 单位被玩家进行命令时,发出的语音 .etc

两种方法设置单位音频,一是为单位设置声音模块,基于观察者模式在单位特定事件触发(攻击、死亡、施法)时播放音频,二是基于反射对各个方法进行拦截,使用AOP的方法在单位执行特定方法(Attack,Spell)时执行。

因为基于反射的方法性能低下,同时基于AOP的方法只能在方法执行前后对音频进行播放,而不能在方法执行时进行播放(这样的话,可能会造成单位闪避了,但还是播放了单位受伤的音频),所以放弃方案2,启用方案1。

下面简单介绍一下方案一:

单位基于一个声音模块CharacterAudio来对单位播放的音频进行管理,CharacterAudio在初始化时,根据CharacterModel设置的基本属性(攻击时使用什么音效,死亡时使用什么音效,此处使用的是这些音效的地址),对Resource(也可以使用AssetsBundle)进行读取,根据这些音效的地址,读取一个个实质的AudioClip对象进内存,并以字典的形式保存到单位身上(CharacterMono)。
同时,声音模块拥有一个绑定方法Bind,在初始化时,声音模块订阅单位身上的事件,当特定事件触发(攻击、死亡),播放特定的声音。

4.12.1.3. UI音频

在玩家对对应的UI进行点击的时候,会有对应的音频进行播放,大致有以下事件,下面简单列举一下:

  1. 点击学习技能的按钮时,播放音频
  2. 弹出提示窗口时,播放对应音频
  3. 购买商店物品时,播放音频
  4. 弹出警告窗口(警告玩家不够金钱或不够mp放技能等等),播放音频

4.12.2. 特效管理

在游戏中,特效基本为GameObject对象,因为一个GameObject对象可能同时包含多个粒子特效系统,所以在创建一个特效时,一般我都以GameObject对象为初始化的模板。
在游戏中,出现特效的时机一般为以下几点:

  1. 单位攻击时,自身产生的特效以及敌方产生的特效
  2. 当一个技能被释放时,施法者和目标(单位或地点)产生的特效

一个单位出现特效的地点有以下几点(根据单位身上的collider来判断位置):

  1. 头部
  2. 身体部位
  3. 脚部

对于特效出现的时机,我目前使用的方法是将特效出现的代码 耦合 到技能类BaseSkill类中,在技能类的Execute方法中,负责在双方身上创建特效对象。对于特效对象的创建,使用临时对象工厂。

一个特效对象,是有其存在时间(生命周期)的,一般来说分为以下几种:

  1. 状态型持久特效(如:由被动技能、光环技能产生的特效对象,这种特效对象将持久的依附在单位身上,当单位失去状态时,状态消失)
  2. 持续一段时间(典型例子:中毒、流血状态,当处于流血状态,给予单位一个流血特效,流血特效将会持续到单位死亡或是状态消失)
  3. 播放完粒子特效就自动消失(典型例子:暴击技能,对单位造成一个暴击,播放一个特效,播放完毕,粒子特效自动消失)

4.12.2.1. 如何管理特效对象的生命周期

一开始的思路是通过一个临时对象工厂(TransientObjectFactory)来负责创建临时对象并管理临时对象的生命周期,但是,因为临时对象最常用的一个方法--在N秒后自动消失,播放完自动消失等特性,工厂如果不开辟协程,将很难实现这种效果。但是没开辟一个协程,都会造成内存的浪费,故这里放弃这个方案。

我采用的方案是,创建一个用于管理特效对象生命周期的MonoBehavior组件,还是用工厂模式来创建粒子特效对象,但是,这里的工厂模式不负责管理对象的生命周期,只负责创建,在创建时,工厂对象自动为这个GameObject对象AddComponent一个EffectsLifeCycle组件类。

EffectsLifeCycle组件在Update(或FixedUpdate)方法中检查对象消失条件,一旦满足对象消失条件(如:经历一段时间、播放完粒子特效),就Destory目标对象。
对于状态型的持久型特效对象,由状态对这些特效对象进行管理。也就是说,当进入某个状态时,自动产生某些例子了特效对象。当离开状态时,自动Destroy这些粒子特效对象。

下面列举一下各种特效对象生命周期的管理:

  1. 播放完粒子特效就自动消失的: 使用EffectsLifeCycle组件进行管理
  2. 持续一段时间的: 使用EffectsLifeCycle组件进行管理
  3. 状态型特效: 使用EffectsLifeCycle组件进行管理,与前两个不同的是,在明确此次创建的是状态型特效时,EffectsLifeCycle将在粒子生命周期的开始,订阅状态消失事件,当检测到某次状态消失事件消失的状态是本状态型特效依附的状态时,删除粒子对象,并取消订阅。

4.13. 战斗系统规则

4.13.1. 判定伤害

  1. 对于普通攻击来说,只有当一个人物动作完整的播放完攻击动画时,才对对面给予伤害,最终伤害判定根据被攻击者和攻击者的距离来判定(这里针对近战攻击),距离每超过攻击者的攻击范围的10%,被攻击者的闪避率增加10%。
  2. 所有伤害判定均由"伤害类"来进行判定,所有普通攻击,伤害技能....等等,最终都会产生伤害类,由伤害类来判断最终给予目标的伤害。
  3. 对于拥有投射物的单位,其攻击逻辑与直接造成伤害的不一样,当攻击动画播放完毕后,会自动在发射点创建一个投射物向敌人进行移动,当投射物碰到敌人时,造成伤害。

4.13.2. 技能

  1. 所有技能都有一个基类,基类Skill包含了技能的基本特性,如:造成的伤害,出现的状态等等,拥有一个通用的用于计算技能伤害的方法,该方法将会产生一个伤害类,并执行此伤害类。
  2. 技能细分下来,分为主动技能和被动技能,主动技能中分为指向性技能、原地释放技能等等,这些分为的种类,都各自写一个类。
  3. 对于技能的编辑,到时可以撸一个类似Rm的技能编辑窗口,这个窗口最终将编辑好的技能保存为Json、CSV、sqlite等数据集合

5. 编辑器设计

编辑器用来对游戏中常见的数据进行编辑,如单位属性、技能属性等等。编辑器可以让非程序员完全不用与数据存储格式打交道(也就是说,使用编辑器的人不用关心到底是用哪种格式来存数据,如txt/json/xml/sqlite)。

从而可以让策划快速验证一些东西,比如某些数值公式、自定义编辑的技能的效果等待。

好的编辑器,要做的事情只有两点:

  1. 编辑数据
  2. 展示数据

第一点,必须要完全没有学习成本,并且可以大量减少策划和程序工作量。也就是说,对于数据和公式的编辑,是不复杂的(就算是复杂的,最后也要简化成基本类型的数据填写)

第二点,必须要在策划编辑完数据后(或者编写的同时)展示数据编辑后的效果。举个例子,对于技能编辑器来说,策划必须要知道刚才编辑的技能特效在游戏中具体是什么样的,最后编辑好的技能在游戏中释放后具体是怎么样的。

5.1. 编辑器技术选

Issues· 2 open

View all issuesOpen on GitHub

No open issues yet, or sync has not completed.

> Tags

C#mobaunity3d-games

No comments yet. Be the first to share.

> Details

PublishedAug 1, 2026
UpdatedSep 17, 2026
Category编程语言
PricingOpen source

> Related tools

T
TypeScript
JavaScript 的超集,为前端与全栈提供静态类型
P
Python
通用编程语言,广泛用于 Web、数据与 AI
G
Go
Google 推出的简洁高效系统语言