主题
Aria 脚本示例
1. 属性定义脚本
属性脚本采用 一属性一文件 + 注解 的写法。详细注解参考见 02-attribute-config.md。
从零开始定义一套属性
目录结构:
scripts/attributes/my_rpg/
├── str.aria
├── dex.aria
├── int.aria
├── vit.aria
├── base_atk.aria
├── base_hp.aria
├── atk.aria
└── max_hp.ariastr.aria:
aria
@attribute('str')
@displayName('力量')
@description('影响物理攻击力和负重')
@category('base_stat')
@default(10.0)
@format('integer')
@priority(1)
class Str {}dex.aria / int.aria / vit.aria 同构,仅 id / displayName / description / priority 变化。
中间属性 base_atk.aria:
aria
@attribute('base_atk')
@displayName('基础攻击')
@category('hidden')
@default(5.0)
class BaseAtk {}派生属性 atk.aria:
aria
@attribute('atk')
@displayName('攻击力')
@category('derived')
@format('integer')
@readonly
@vanillaBinding('generic.attack_damage')
class Atk {
@derive
calc = -> {
val.h = args[0]
val.str = symphony.attribute.getRaw(h, 'str')
val.baseAtk = symphony.attribute.getRaw(h, 'base_atk')
return baseAtk + str * 2.5
}
}派生属性 max_hp.aria:
aria
@attribute('max_hp')
@displayName('生命上限')
@category('derived')
@format('integer')
@readonly
@vanillaBinding('generic.max_health')
class MaxHp {
@derive
calc = -> {
val.h = args[0]
val.vit = symphony.attribute.getRaw(h, 'vit')
val.baseHp = symphony.attribute.getRaw(h, 'base_hp')
return baseHp + vit * 8
}
}添加自定义元素
直接复制 elements/ 目录下任一文件,改 id/displayName 即可。例如新增风元素伤害 scripts/attributes/elements/wind_damage.aria:
aria
@attribute('wind_damage')
@displayName('风伤害')
@description('附加风元素伤害')
@category('element')
@default(0.0) @min(0.0)
@format('number')
@priority(100)
class WindDamage {}wind_resistance.aria 同理。要批量生成多个元素,可以写一个小脚本/模板生成器一次性铺好文件,比循环更直观。
带联动回调的属性
@onChange 标注值变更回调(接入完成后生效):
aria
// scripts/attributes/combat/max_health.aria 的扩展示例
@attribute('max_health')
@displayName('最大生命值')
@default(20.0) @min(1.0)
@vanillaBinding('generic.max_health')
class MaxHealth {
@onChange
sync = -> {
val.entity = args[0]
val.oldMax = args[1]
val.newMax = args[2]
if (oldMax > 0) {
val.ratio = symphony.entity.getHealth(entity) / oldMax
symphony.entity.setHealth(entity, ratio * newMax)
}
}
}2. 公式脚本
伤害计算
aria
// formulas/damage.aria
// args[0] = 攻击力, args[1] = 防御力, args[2] = 穿透率
val.atk = args[0]
val.def = args[1]
val.pen = args[2]
val.effectiveDef = def * (1 - pen)
val.damage = math.max(1, atk - effectiveDef)
return damage经验曲线
aria
// formulas/exp.aria
// args[0] = 当前等级
val.level = args[0]
// 二次曲线 + 线性增长
val.base = 100 * math.pow(level, 1.5)
val.linear = level * 50
// 每 10 级一个台阶
val.milestone = math.floor(level / 10) * 200
return math.floor(base + linear + milestone)暴击伤害
aria
// formulas/critical.aria
// args[0] = 基础伤害, args[1] = 暴击倍率, args[2] = 幸运值
val.baseDmg = args[0]
val.critMult = args[1]
val.luck = args[2]
// 幸运值提供微量暴击伤害加成
val.luckBonus = luck * 0.001
val.finalMult = critMult + luckBonus
return baseDmg * finalMult2. 技能脚本
连锁闪电
aria
// skills/chain_lightning.aria
val.caster = server.caster
val.target = server.target
val.level = server.skill_level
val.damage = 20 + level * 15
val.chainCount = 2 + level
val.chainRange = 5.0
// 第一次伤害
symphony.entity.damage(target, damage, 'lightning')
symphony.effect.particle(target, 'ELECTRIC_SPARK', 15)
symphony.effect.sound(target, 'entity.lightning_bolt.impact', 0.5, 1.5)
// 连锁
var.lastTarget = target
var.hitTargets = [target]
for (i in Range(0, chainCount)) {
val.nearby = symphony.entity.getNearbyHostile(lastTarget, chainRange)
val.nextTarget = nearby.find(-> {
return !hitTargets.contains(args[0])
})
if (nextTarget == none) { break }
// 每次连锁伤害衰减 30%
val.chainDamage = damage * math.pow(0.7, i + 1)
symphony.entity.damage(nextTarget, chainDamage, 'lightning')
// 画闪电线
symphony.effect.line(lastTarget, nextTarget, 'ELECTRIC_SPARK', 10)
symphony.effect.sound(nextTarget, 'entity.lightning_bolt.thunder', 0.3, 2.0)
hitTargets.add(nextTarget)
lastTarget = nextTarget
}治疗波
aria
// skills/healing_wave.aria
val.caster = server.caster
val.level = server.skill_level
val.healAmount = 15 + level * 10
val.radius = 5 + level
val.maxTargets = 3 + level
// 获取附近友方
val.allies = symphony.entity.getNearbyPlayers(caster, radius)
// 按生命值百分比排序(优先治疗血量低的)
var.healCount = 0
allies.forEach(-> {
if (healCount >= maxTargets) { return }
val.target = args[0]
val.hp = symphony.entity.getHealth(target)
val.maxHp = symphony.entity.getMaxHealth(target)
// 跳过满血的
if (hp >= maxHp) { return }
// 治疗量随距离衰减
val.dist = symphony.entity.distance(target, caster)
val.distMultiplier = 1 - (dist / radius) * 0.3
val.finalHeal = healAmount * distMultiplier
symphony.entity.heal(target, finalHeal)
symphony.effect.particle(target, 'HEART', 5)
symphony.effect.line(caster, target, 'VILLAGER_HAPPY', 8)
healCount = healCount + 1
})
// 施法特效
symphony.effect.circle(caster, radius, 'SPELL_WITCH', 40)
symphony.effect.sound(caster, 'block.beacon.activate', 1.0, 1.5)冰霜护盾
aria
// skills/frost_shield.aria
val.caster = server.caster
val.level = server.skill_level
val.duration = 5000 + level * 2000
val.damageReduction = 0.1 + level * 0.05
val.thornsPercent = 0.05 + level * 0.03
// 添加防御 Buff
symphony.attribute.buff(caster, 'damage_reduction', 'FLAT', damageReduction, duration)
symphony.attribute.buff(caster, 'ice_resistance', 'FLAT', 0.3, duration)
// 特效
symphony.effect.sphere(caster, 2.0, 'SNOWFLAKE', 30)
symphony.effect.sound(caster, 'block.glass.place', 1.0, 0.5)
symphony.effect.actionbar(caster, "&b&l冰霜护盾已激活! ({duration / 1000}秒)")3. 条件脚本
Boss 战判定
aria
// conditions/is_boss_fight.aria
// 检查目标是否为 Boss 级怪物
val.target = server.trigger_victim
if (target == none) { return false }
// 检查是否有 Boss 标记(通过 PDC 或名称)
val.name = symphony.entity.getName(target)
val.maxHp = symphony.entity.getMaxHealth(target)
// Boss 判定:生命值超过 500 或名称包含特定标记
return maxHp > 500连击判定
aria
// conditions/combo_check.aria
// 检查是否达到连击阈值
val.player = server.trigger_entity
val.comboKey = 'combo_count_' + symphony.entity.getName(player)
// 读取连击计数
var.combo ~= 0
combo = combo + 1
// 超过 3 秒重置
val.lastHitKey = 'last_hit_' + symphony.entity.getName(player)
val.now = symphony.util.currentTime()
return combo >= 34. 公共模块
工具函数
aria
// modules/utils.aria
export var.clamp = -> {
val.value = args[0]
val.min = args[1]
val.max = args[2]
return math.max(min, math.min(max, value))
}
export var.lerp = -> {
val.a = args[0]
val.b = args[1]
val.t = args[2]
return a + (b - a) * t
}
export var.randomRange = -> {
val.min = args[0]
val.max = args[1]
return min + math.random() * (max - min)
}
export var.chance = -> {
val.percent = args[0]
return math.random() * 100 < percent
}
export var.formatNumber = -> {
val.num = args[0]
if (num >= 1000000) {
return (num / 1000000).toFixed(1) + 'M'
} elif (num >= 1000) {
return (num / 1000).toFixed(1) + 'K'
}
return num.toFixed(0)
}常量定义
aria
// modules/constants.aria
export val.DAMAGE_TYPES = ['physical', 'magic', 'fire', 'ice', 'lightning', 'poison', 'holy', 'dark']
export val.MAX_LEVEL = 100
export val.TICK_RATE = 20
export val.COMBAT_TIMEOUT = 10000在其他脚本中使用
aria
import modules.utils as u
import { DAMAGE_TYPES, MAX_LEVEL } from 'modules.constants'
val.clamped = u.clamp(damage, 0, 9999)
val.formatted = u.formatNumber(totalDamage)
if (u.chance(30)) {
// 30% 概率执行
}6. 最佳实践
- 属性脚本保持独立 —
scripts/attributes/下的脚本不应import其他脚本,它们是最底层的定义 - 公式脚本保持简短 — 公式(包括属性的
formula/derive)会被高频调用,避免复杂逻辑。所有公式和技能脚本会被 Aria JIT 编译缓存(Aria.compile()),相同代码只编译一次,后续执行直接使用编译产物 - 善用批量注册 — 有规律的属性(如元素系列)用循环注册,减少重复代码
- 派生属性用
readonly: true— 防止外部修改器干扰计算结果 - 使用
val声明不变的值 — 语义更清晰 - 善用模块系统 — 公共函数放在
modules/目录,通过import复用 - 注意沙箱限制 — 脚本中不能直接访问 Java 类,只能通过
symphony.*命名空间 - 避免无限循环 — 沙箱有执行时间限制,超时会被终止
- 使用
server.*访问上下文 — 触发器和技能的上下文变量通过server.*前缀访问 - 属性
on_change回调中不要触发属性重算 — 避免循环依赖