












ARPG之构建,初看似内容之患,然组合日积,其弊乃显。
此例乃自Zig ARPG游戏引擎出,其中技能、辅助、道具及运行之规,皆需相成。
初观之,每规似无大碍:
继而组合显现。
以广半径劈砍。以狭半径但增伤害劈砍。以带流血负荷劈砍。以双翼侧劈砍。一投射技能,具贯穿、连击、分叉之效。一物,言法术属性者,今施于近战攻击。一状态,暂易彼物与辅器已触之同属性。
诱人之途,乃渐积之特例也。
if skill == cleave and support == wide_sweep:
make cleave radius bigger
if skill == cleave and support == focused_edge:
make cleave smaller but stronger
if skill == cleave and support == twin_cleave and rule == guarded_arc:
quietly move to the woods
此法可应演示之需。然游戏一旦技能、支援、道具、状态、遭遇规则繁复,则事颇艰矣。
此处行之有效者,乃此框架也。
建工坊可视为一小型编译流程。
著述乃本源。辅件、物事、状貌、缀语及格律皆发其理。此理汇于派生之藏。战伐耗此藏焉。
此设计之中,技之解析,毋须自问:“此技是否为支持槽位三之Cleave with Wide Sweep?”及至技解之时,此问已成低阶运行之数据:
increased_damage_bp = 500
area_radius_bonus_subunits = 1000
area_sweep_profile = default
status_payload_count = 0
more_multipliers = [...]
支持之定义,非可执行之戏码代码。于此设计,乃词汇狭隘之数据:
const support_modifier_max = constants.support_modifiers_max;
const support_behavior_max = constants.support_behaviors_max;
const ModifierSlots = [support_modifier_max]SupportModifier;
const BehaviorSlots = [support_behavior_max]SupportBehavior;
pub const SupportDef = struct {
scope: SupportScope,
modifiers: ModifierSlots = undefined,
modifier_count: u8 = 0,
behaviors: BehaviorSlots = undefined,
behavior_count: u8 = 0,
};
辅佐之力可发状态之变,可易行止。此乃其器。非直探投射之库,非呼战法,亦非修补尘寰之随机域。
今举一辅佐之例:
const wide_sweep_index = @intFromEnum(SupportId.cleave_wide_sweep);
table[wide_sweep_index] = add_behaviors(
make_def(.skill, &.{
.{
.stat = .damage_increased_bp,
.op = .increased_bp,
.damage_type = .physical,
.value = 500,
},
}),
&.{
.{
.kind = .area_radius_subunits,
.value = constants.subunits_per_unit,
.tag_require = TagMask.init(&.{ .melee, .area }),
},
},
);
视之如文,非为行止之码:
辅技可嵌于Cleave之侧,然其发之行止,犹言内蕴之适用。曰"格挡兼广域",非"呼Cleave之实,更其半径也"。
未来之技,可协既有之规,无需手织每技于每助。亦存要义之辨,显于目:玩家所遇之标,与运行时适用之签,不必同体。
技位有变,旧之编译必去,新之输出乃发。
const mask = skill_data.active_support_mask(skill);
clear_support_effects(modifier_store, behavior_store, entity_index, skill_slot);
generate_support_stat_modifiers(modifier_store, entity_index, skill_slot, skill, mask);
generate_support_behavior_emissions(
behavior_store,
entity_index,
skill_slot,
skill,
mask,
);
此术变装备之技及其主动辅为列:
active skill gem + active support mask
-> stat modifier rows
-> behavior emission rows
active_support_mask之要,盖辅之嵌座非恒为动也。此码中,宝级制辅座之启。吾欲此细于编译一过,勿散于战码。
删除之步骤,与生成之步骤,其重等也。若支持者去而其旧行存,则构建之权,仍持其所未得。
此管线恒守其本源之真。
// Both IDs encode skill_slot | support_slot.
// Skill-scoped rows set the high bit so cleanup can target the right rows.
pub fn encode_entity_support_source_id(skill_slot: u8, support_slot: u8) u32 {
return (@as(u32, skill_slot) << 8) | @as(u32, support_slot);
}
pub fn encode_skill_support_source_id(skill_slot: u8, support_slot: u8) u32 {
return (1 << 15) | (@as(u32, skill_slot) << 8) | @as(u32, support_slot);
}
微物重负。
修饰行之用,更在于非惟言“增伤五百”,亦须明其数所自:乃技能位、辅助位、射程之故。后之系统,可凭此以去其不当之行,重其宜有之缓存,终能向试者或检者明其果也。
无源流可溯,数虽可合,然系统不能答其至要之问:
此数何以在此?
或有更易全体,或惟易所持之技。
此别异者,编入修饰行。
const modifier_scope: StatScope = switch (def.scope) {
.entity => .entity,
.skill => .skill_specific,
};
// Skill-scoped rows are keyed by skill identity.
const scope_param: u16 = if (modifier_scope == .skill_specific)
@intFromEnum(skill.gem.skill_id)
else
0;
沉闷,直至谬误。
若某破盾之助言"破盾之伤益甚",则疾冲不应因二技俱备而承此益,盖破盾之助所言之"破盾之伤益甚"乃狭义之规,其流变自有别途。
是故此流变恒显其域:
entity scope -> affects the actor
skill-specific -> affects a skill identity
rule emission -> changes how applicability is interpreted
此中编译之喻犹见其效:域界、名域及改写,皆以微形呈之。
统行之列,简矣:增伤,益血,施倍。
行止之变,纷纭。穿,链,分,多矢,域径,化,态负,宝级之差,皆变之异形。
是故别类其发,纳于技藏:
const more_multiplier_max = 4;
const status_payload_max = constants.skill_support_status_payloads_max;
const MoreMultipliers = [more_multiplier_max]i32;
const StatusPayloadSlots = [status_payload_max]SupportStatusPayload;
pub const SkillCacheEntry = extern struct {
increased_damage_bp: i32 = 0,
more_multipliers: MoreMultipliers = .{0} ** more_multiplier_max,
more_count: u8 = 0,
pierce_count: u8 = 0,
chain_count: u8 = 0,
fork_count: u8 = 0,
extra_projectile_count: u8 = 0,
convert_override: ConvertOverride = .{},
area_radius_bonus_subunits: i32 = 0,
status_payloads: StatusPayloadSlots = .{.{}} ** status_payload_max,
status_payload_count: u8 = 0,
effective_gem_level: u8 = 0,
gem_level_delta: i8 = 0,
area_sweep_profile: AreaSweepProfile = .default,
};
缓存之条目,乃技槽之运行概要也.
其不答"何助器嵌于孔中?"或"何物致此?"此信息存于上游之行列。缓存所应者,乃问题解析所关切之问:
damage modifiers
projectile behavior counts
conversion override
area radius delta
status payloads
effective gem level
此分野实要。战斗耗此概要。检视与清理犹可资源行列.
勿使模拟器每时每刻重筑所衍之实,虽能之,然变易者,污其域,则重筑之程,应其受损之体.
const domains = dirty_domains_for_entity(world, entity_index);
const rebuild_skill_cache = modifier_store.is_skill_cache_dirty(entity_index) or
behavior_store.is_dirty(entity_index) or
scope_rewire_store.is_dirty(entity_index);
if (!domains.any() and !rebuild_skill_cache) {
modifier_store.clear_dirty(entity_index);
behavior_store.clear_dirty(entity_index);
scope_rewire_store.clear_dirty(entity_index);
runtime_rule_store.clear_dirty(entity_index);
continue;
}
rebuild_entity(
modifier_store,
behavior_store.span(entity_index),
scope_rewire_store.span(entity_index),
runtime_rule_store.span(entity_index),
entities,
entity_index,
domains,
rebuild_skill_cache,
);
此乃微缩之渐增编译.
支持有变?污其技藏。防御有变?重筑其御。运行之规有变?重筑其规。无变?清其陈旗,继以往矣。
半由效能,半由所有。变异之说,明其所废之派态为何。重筑之程,于意战读之先,为所派之事于已知之序.
用数之系,不须问“人已更此否?”重筑之序,使此为真.
既行列有,行为重建之过,乃纳诸于每项技能之缓存中。
reset_skill_cache(cached);
for (skill_slots.skills, 0..) |skill, slot| {
if (skill.is_empty()) continue;
const skill_id = skill.gem.skill_id;
const skill_tags = catalog.skill.def(skill_id).tag_mask;
const entry = &cached.skill_cache[slot];
entry.effective_gem_level = skill.gem_level;
// Fold rows into the slot-local runtime summary.
fold_skill_damage_modifiers(...);
fold_entity_applicable_damage_modifiers(...);
fold_behavior_emissions(behaviors, slot, skill_tags, entry);
apply_runtime_skill_rules(skill_id, cached.rules, entry);
finalize_effective_gem_level(entry, skill.gem_level);
}
缓存乃自行列重建。非补缀一缓存之域,因旧之支持尝触之,重建之过乃涤其要旨,复纳今之实情于内。
适用者依标签而定:
pub fn tags_match(skill_tags: TagMask, require: TagMask, exclude: TagMask) bool {
const skill_bits = skill_tags.bits();
const require_bits = require.bits();
const exclude_bits = exclude.bits();
assert((require_bits & exclude_bits) == 0);
const has_required = (skill_bits & require_bits) == require_bits;
const has_excluded = (skill_bits & exclude_bits) != 0;
return has_required and !has_excluded;
}
凡所改易者,必具所求之签,而无所禁之签,方为有效。前文所述,即技之目录签是也。技之繁复者,此意可下移至交付或负载之签。
是使内容得言:
requires melee + area
requires attack + melee + physical
excludes cold
此可避巨技能支持身份之矩阵。亦予吾一目了然处,以察支持不适于事:所发之需谬,所标之签误,或适用之则非也。
诡谲之所在,在标签粒度。一宝可予技多端:近战击、投射物、爆炸、疾患之载。一标签于宝上,或不足以决诸修正之交互。所系者,乃附于所修正之事物之标签也。
犹有广厦,然至少其弊有微辞。
或有构建之效,非属统计。其变他事之解也。
例形如:"咒法之伤及近战。"非平直之伤数。其易咒法独修能否施于近战之技。
此行为重建,有小隙容之。
const direct_match = stat_applicability_matches(skill_tags, m.applicability);
const rewire_match = entity_rules.spell_damage_applies_to_melee and
skill_tags.melee and m.applicability == .spell_only;
if (!direct_match and !rewire_match) continue;
其本源不每战皆变,非复诸技之变。不摹咒语之偏于攻伐之偏。惟发一规。缓存之重解,循此规而释诸适之损偏。
今之码本,此规可自所撰物效、族树之效、或印刻之效来。其诡常止于一层数,不流于诸技之实。
技既解,投射之道始自缓存
const skill_cache_entry = cached.skill_cache[use_skill.action_slot];
const caster_ctx = build_caster_context(use_skill.caster, cached);
delivery_projectile.execute(
skill_id,
skill_preview.def,
caster_ctx,
spatial_ctx,
skill_preview.gem_level,
cached,
skill_cache_entry,
&world.projectiles,
);
投射之递,乃耗其所系之域:
const projectile_count = resolve_projectile_spawn_count(skill_cache_entry);
const increased_damage_bp = payload.saturating_add_i32(
cached_stats.damage_increased_bp,
skill_cache_entry.increased_damage_bp,
);
及其投射之配:
_ = projs.allocate(.{
.source = caster_ctx.id,
.damage = resolved_payload.base_damage,
.more_multipliers = skill_cache_entry.more_multipliers,
.more_count = skill_cache_entry.more_count,
.pierce_remaining = resolve_projectile_pierce(skill_cache_entry),
.chain_remaining = resolve_projectile_chain(skill_cache_entry),
.fork_remaining = resolve_projectile_fork(skill_cache_entry),
.convert_override = resolve_projectile_convert(skill_cache_entry),
}) catch continue;
察此层所缺者,乃支持ID.
投射物之递送,无须顾刺穿之由,乃自支持宝石、器物、状态,抑或未来圣坛乎?其得pierce_remaining.
溯源之归属犹存上游,以供清理、检视、调试及测试。热路径得编译之实.
契文中载有上限:
pub const support_slots_max: u8 = 9;
pub const support_modifiers_max: u8 = 3;
pub const support_behaviors_max: u8 = 1;
行为缓存亦具固定载荷槽,固定倍数存储,及固定投射行为计数。
其确之冠乃游戏所专。其善者,在限可见。助者或有趣,然此版不能发无涯之行。技可载状态之负,然唯引擎所识之储中可解。
若内容之形需超乎今制之限,则内容模型与引擎契约须共变。吾宁显此异,毋潜积隐效之堆。
此管道现貌如下:
authored support data
-> active slot mask
-> stat modifier rows
-> behavior emission rows
-> dirty domains
-> cached stats and skill cache
-> delivery resolution
-> combat/projectile/status queues
每阶段皆有职司。
著述数据本乎直言。存储固守本源之识。污损之兆示当重建者。标籤决其适用之宜。规则掌奇改之文。技艺缓存化精简之运行要略。战策览其要略.
此甚可期,盖增援者,乃增本源之实,非与诸战策各相诘难也。
悬而未决者,实而具体:标签粒度,行为-发射词汇,固定大写,及显式规则重写需如何随其增多而显明。吾悦此诸题,胜于杂陈之临时战支。
今之形制,予我以缝可试,置诡奇之所在。
express the build effect
-> emit rows
-> rebuild caches
-> consume facts
旨无巨技/助之方阵,无陈旧之助行,无战考古文,若非吾自勘编译之过则然.
惟一利剑之小编译耳。
此內容由慣性聚合(RSS閱讀器)自動聚合整理,僅供閱讀參考。 原文來自 — 版權歸原作者所有。