Use when writing acceptance criteria for a task - express each as an observable Given/When/Then that QA can execute, including negative cases
中文简介正在准备中。当前显示原文说明。
Use when adding or changing tuning values, items, enemies, abilities or levels - put them in the engine's data assets (ScriptableObject, Resource, DataAsset/DataTable, JSON/RON) with validation and stable IDs, keep runtime state out of shared data, inject services instead of singletons, and test data invariants.
中文简介正在准备中。当前显示原文说明。
查看安装方法安装前可以查看交给 AI 代理的指令内容。
Designers balance games by changing numbers hundreds of times. If a number lives in code, each change is a programmer task, a rebuild and a review. If it lives in a data asset, it is an edit a designer can make and a test can check. The code defines what a sword is; the data says what this sword does.
Core principle: Behaviour in code, tuning in data. Data is typed, validated at load, referenced by a stable ID, and never mutated at runtime.
// Unity: ScriptableObject definition
[CreateAssetMenu(menuName = "Game/Weapon")]
public sealed class WeaponDefinition : ScriptableObject {
[SerializeField] string id;
[SerializeField, Min(0)] int damage = 10;
[SerializeField, Min(0.05f)] float cooldownSeconds = 0.5f;
[SerializeField] AnimationCurve falloff = AnimationCurve.Linear(0, 1, 1, 0);
public string Id => id; public int Damage => damage; public float CooldownSeconds => cooldownSeconds;
void OnValidate() { if (string.IsNullOrWhiteSpace(id)) Debug.LogError($"{name}: missing id", this); }
}
# Godot: a Resource saved as .tres
class_name WeaponDefinition extends Resource
@export var id: StringName
@export_range(0, 1000) var damage: int = 10
@export_range(0.05, 10.0, 0.05) var cooldown_seconds: float = 0.5
// Unreal: a primary data asset (or an FTableRowBase row in a DataTable imported from CSV)
UCLASS(BlueprintType)
class UWeaponDefinition : public UPrimaryDataAsset {
GENERATED_BODY()
public:
UPROPERTY(EditDefaultsOnly, meta = (ClampMin = "0")) int32 Damage = 10;
UPROPERTY(EditDefaultsOnly, meta = (ClampMin = "0.05")) float CooldownSeconds = 0.5f;
virtual FPrimaryAssetId GetPrimaryAssetId() const override { return FPrimaryAssetId("Weapon", GetFName()); }
};
// Web: JSON validated once at load (zod)
export const Weapon = z.object({
id: z.string().min(1),
damage: z.number().int().nonnegative(),
cooldownSeconds: z.number().min(0.05),
});
export const weapons = z.array(Weapon).parse(await (await fetch("data/weapons.json")).json());
// Bevy: a serde struct loaded from RON/JSON via an asset loader (or bevy_common_assets)
#[derive(Asset, TypePath, Deserialize)]
pub struct WeaponDefinition { pub id: String, pub damage: u32, pub cooldown_seconds: f32 }
damage = 25 in a script is a data bug. Physics constants and engine settings belong in project settings or a config asset too.OnValidate / IsDataValid(FDataValidationContext&) / @export_range / a schema parse."sword_iron", never "the third weapon" or "Iron Sword"..tres mutated by one enemy changes every enemy. Copy into a runtime state object, or duplicate() the Resource (or set resource_local_to_scene).AnimationCurve, Godot Curve, UCurveFloat/UCurveTable, or a table in JSON. A level-1-to-50 XP formula hard-coded in a switch is not tunable.// ❌ hidden global state; tests share it; order of initialisation decides behaviour
GameManager.Instance.Gold += reward;
// ✅ the dependency is explicit and replaceable in a test
public sealed class LootSystem {
readonly IWallet _wallet; readonly IRandom _rng;
public LootSystem(IWallet wallet, IRandom rng) { _wallet = wallet; _rng = rng; }
}
Each engine has a sanctioned place for services: Unity — a composition root or the repository's DI container (VContainer, Zenject); Godot — an autoload for a service (save, audio, scene loading), never for a level's state; Unreal — subsystems (UGameInstanceSubsystem, UWorldSubsystem); Bevy — Resources inserted by a plugin. Use the one the repository already uses.
Two kinds of tests:
ScriptableObject.CreateInstance<WeaponDefinition>(), WeaponDefinition.new(), a literal object) so the test does not change when a designer rebalances.describe("shipped data", () => {
it("every drop table has positive total weight and only known item ids", () => {
for (const t of dropTables) {
expect(t.entries.reduce((s, e) => s + e.weight, 0)).toBeGreaterThan(0);
for (const e of t.entries) expect(itemIds.has(e.itemId)).toBe(true);
}
});
});
In Unity this is an EditMode test using AssetDatabase.FindAssets("t:WeaponDefinition"); in Godot a test iterating DirAccess over the data folder; in Unreal an Automation test using the Asset Registry or the editor's Data Validation.
Assert.AreEqual(25, sword.Damage)) — that is a change detector that breaks on every rebalance.Update "just for now"..tres at runtime.Instance holding gameplay state across scenes and tests..cs, .gd, .cpp, .ts or .rs file.还没有评价。欢迎分享你的使用感受。
无需登录即可发表。欢迎分享使用感受、提问,或说明在哪些环境下无法运行。
还没有评论。来写第一条吧。
Use when writing acceptance criteria for a task - express each as an observable Given/When/Then that QA can execute, including negative cases
中文简介正在准备中。当前显示原文说明。
Use when the diff adds or changes an endpoint, resolver, RPC, job or query that takes an object id, a role check, a request binding or a tenant filter - BOLA/IDOR, function-level authorization, mass assignment and tenant scoping
中文简介正在准备中。当前显示原文说明。
Use on every UI change - semantic HTML, labels for controls, keyboard-navigable dialogs/menus, visible focus, and never color as the only signal
中文简介正在准备中。当前显示原文说明。
Use when a task changes any screen, form, dialog, menu or control - Lighthouse/axe scan of the changed screens, a keyboard walk, and the thresholds that fail a task
中文简介正在准备中。当前显示原文说明。
How to work a task returned with review, QA or UAT findings. Use when a task is in need_revision or PR review comments are in your context.
中文简介正在准备中。当前显示原文说明。
Use when the repository keeps its alerting in code, or a person asks for alerts outside TaskTrooper - PrometheusRule YAML, a Cloud Monitoring alert policy in Terraform, a Datadog monitor, each routed to TaskTrooper's alert ingest URL so it opens an incident here
中文简介正在准备中。当前显示原文说明。