# 6. Worked Example: A Three-Hit Melee String Part of [[Combat Feel and Hit Feedback]]. Prev: [[5. I-Frames Dodge and Guard]]. Next: [[7. Combat Feel Pitfalls]]. Build this in an empty 3D URP scene. Placeholder capsules are enough. The goal is a string you can *feel*, not a hero mesh. ## 6.1 Scene and layers 1. Project Settings > Tags and Layers: add `PlayerHurtbox`, `PlayerHitbox`, `EnemyHurtbox`, `EnemyHitbox`. 2. Edit > Project Settings > Physics > Layer Collision Matrix: check only the pairs in [[3. Hitboxes and Hurtboxes]]. Uncheck PlayerHitbox vs PlayerHurtbox. 3. Floor plane, a directional light, a `CinemachineCamera` following the player, **Cinemachine Brain** on Main Camera, **CinemachineImpulseListener** on the virtual camera ([[4.Impulse Noise and Extensions]]). 4. Input Actions asset with `Attack` (button) and `Dodge` (button). Pair them as in [[Input System]] / [[5. Worked Example Remappable Player]] if you already have that asset. ## 6.2 Player prefab ``` Player (layer Player) CharacterController CombatVictim (team Player; wire hurtbox, knockback, hitStop, impulse, feedback) KnockbackReceiver MeleeComboController DodgeController AttackHitboxEvents HitImpulse + CinemachineImpulseSource HitVfxAudio + child VisualEffect (or a ParticleSystem if you have not opened VFX Graph yet) Animator (controller described in part 2) Hurtbox child: capsule trigger, layer PlayerHurtbox, Hurtbox component Weapon child: box trigger, layer PlayerHitbox, Hitbox component (hurtboxMask = EnemyHurtbox) ``` Create three `AttackDefinition` assets: | Asset | attackId | damage | hitStopDuration | hitStopScale | knockbackSpeed | impulseForce | |---|---|---|---|---|---|---| | Light1 | 0 | 6 | 0.04 | 0.12 | 3 | 0.25 | | Light2 | 1 | 8 | 0.05 | 0.10 | 3.5 | 0.3 | | Finisher | 2 | 16 | 0.08 | 0.06 | 6 | 0.7 | Assign them in order on `MeleeComboController.stringAttacks`. Plant events on each attack clip: `Anim_HitboxOn`, `Anim_HitboxOff`, `Anim_OpenCancel` / `Anim_CloseCancel` (or use the normalized-time fallback), and `Anim_CombatExit` on the finisher's last frame (and on Light1 / Light2 if the string is allowed to end there). `Anim_OpenCancel` can be a one-liner on the combo controller or the facade from [[5.Sub-State Machines Combat Locomotion]]: ```csharp public void Anim_OpenCancel() => animator.SetBool(CanCancelHash, true); public void Anim_CloseCancel() => animator.SetBool(CanCancelHash, false); ``` Add a `HitStopService` to a bootstrap object in the scene and reference it on both the player and the dummy. ## 6.3 Dummy ``` Dummy (layer Enemy) CombatVictim (team Enemy, maxHealth 40) KnockbackReceiver Hurtbox child, layer EnemyHurtbox Animator with Idle / Hit / Die (Hit may be a flinch clip, or a color flash if you have no clip) ``` No hitbox required. The dummy exists to be hit. A minimal `CombatVictim` already plays Impulse, VFX, hit-stop and knockback. If you want the dummy to flash, hook a material color in a tiny observer: ```csharp using UnityEngine; public sealed class HurtFlash : MonoBehaviour { [SerializeField] Renderer body; [SerializeField] Color flash = Color.white; [SerializeField] float seconds = 0.08f; Color _base; float _until; void Awake() { if (body != null) _base = body.material.color; // instanced material; see pitfalls } public void Flash() { if (body == null) return; body.material.color = flash; _until = Time.unscaledTime + seconds; } void Update() { if (body == null || _until <= 0f) return; if (Time.unscaledTime >= _until) { body.material.color = _base; _until = 0f; } } } ``` Call `Flash()` from `ApplyDamage`. `Renderer.material` instantiates a material at runtime; acceptable on one dummy, not on a crowd ([[URP Shaders and Shader Graph]] / a MaterialPropertyBlock is the better path when you batch). ## 6.4 Playtest checklist 1. One tap: Attack1 plays, dummy takes 6, short freeze, spark, camera kick, dummy flinches. Exactly one damage tick. 2. Tap-tap inside the cancel window: Attack1 -> Attack2, two hits, two freezes. 3. Tap-tap-tap: the finisher's longer freeze and heavier Impulse are obvious. Dummy dies on or before the third hit at 40 HP (6+8+16=30, so raise dummy HP or add a second dummy). 4. Mash during windup: Attack2 does **not** start until the cancel event. 5. Dodge through a *second* dummy that has a slow swing (or temporarily give the dummy a Hitbox and an Attack clip): you pass through on i-frames; you are hit if you dodge too early. 6. Stand inside the dummy's hurtbox and start a swing: you still get one hit (overlap-on-enable). 7. Pause the Editor mid-freeze and confirm `Time.timeScale` is not left small after you stop Play Mode. (Play Mode exit should restore it; if you ever set timeScale from an Editor script, you can stall the Editor - part 7.) ## 6.5 Optional: mood and UI - [[Music Mood Mixer]] can listen to a "in combat" bool. - [[3. Worked Example - Grade Damage and a Cave]] can pulse a hurt Volume when the *player* is the victim. - [[Quest Systems]] can listen to a `Killed` event for a "defeat the dummy" objective. Do not poll `FindObjectsOfType`. ## Exercise Swap the finisher for a charged hold (attack action `performed` starts, `canceled` releases). If the charge never reaches a threshold, play Light1; if it does, skip to a heavy that is not part of the string. This is still one brain and one hit pipeline - only the *request* changes.