◀ 8.28.38.4 ▶

8.3 — Player Attack: Fight Back

You have a swing animation and an axe sprite, and neither of them has ever hurt anything. Today the swing stops being decoration: a click opens a short damage window, a shape in front of the player asks the physics engine what it is standing in, and the number you built in 8.2 goes down. "I hit it, it hurt" is a small loop — and the shape you ask that question with has a side effect most people get wrong.

In this lesson you will be able to answer“how do I make my swing actually hurt something?”Guided build
Break It Down~10 minGuided build

You have a swing animation, an axe sprite, and — since 8.2 — a boss whose health goes down when something subtracts from it. Nothing joins them up. "Make my swing hurt something" is really five smaller questions: where is the blade, who is allowed to be in the answer, when is the swing live, how do you ask that question, and what do you do with the collider you found? Four of those you have built before. One you have not.

First, before any code: cut that into parts small enough that you could look each one up on its own.

  1. Stand a shape where the blade isAn empty child of the Player called AttackHitbox carrying a BoxCollider2D with Is Trigger ticked, about one tile in front and parented to the root. A trigger reports overlaps instead of pushing things — the coin's collider from 2.1 doing a new job.Read: the trigger collider you added to the coin in 2.1 — the same component, on an object that exists for a fifth of a second instead of forever
  2. Decide who the question may noticeA LayerMask on the query rather than on the shape. The buckets are already in the project — Ground from 6.2, and Player, Enemy, Projectile and Decoration from 7.2 — so this part is a decision about which one the boss is really in, plus a mask that names it and nothing else.Read: the layers you made in 7.2, and the four-tool table in the activity above the walkthrough — the argument for narrowing the question instead of the world
  3. Open a window and let it close itselfTwo floats and two comparisons: one that stops a swing happening sixty times a second, and one that keeps the shape live for a moment longer than the single frame you clicked on. The turret's fire rate from 3.3 is the same mechanism under a different name.Read: the cooldown you built in 3.3 — cooldown, lastAttackTime, and the seeded first value that means "there has been no last shot"
  4. write this partRead: the Physics2D.OverlapBoxAll page in the Unity scripting reference — a point, a size, an angle and a LayerMask, handing back an array of colliders — and the third architectural specification in the build lab on this page
  5. Turn what you found into a number going downFrom each collider the query handed back to the script that owns the boss's health, then one call to its signed-amount method. A positive amount hurts and a negative one heals — the rule from 5.3, and the method 8.2 had you write.Read: the boss health method you wrote in 8.2, and the signed-amount rule that moved onto StatManager in 5.3

Four of those five parts are work you have already done or already read. Only one has no precedent in this course, and it is a small change with a large consequence: every query you have written so far — the ground check from 6.2, the turret's range check from 3.1, the pickups in 2.1 — asks about a point or a line, and the answer is always one thing. This query asks about an area, and the answer is a list. That is why the part after it is a loop and not an if, and why those two parts are separate problems rather than one.

The test: could you look this part up on your own, right now, in one search? "Make the swing hurt the boss" cannot be looked up. "Give me every collider inside a box, on one layer" can — and the page you land on hands you four arguments in a fixed order and a result type that is already an array.

Your turn

Your ground check from 6.2 and today's swing query both ask the physics engine about colliders. Say which of the two can come back with more than one answer, and what each one does with the answer it gets.

The window is what stops a swing being decided by a single frame. Name one other place in this project where state exists for a short time and then closes — and say what would break if it never did.

In the Editor ~15 min

Every physics question in Unity is asked about a shape, and this one is placed by hand — it is the only object in this lesson that the code does not make for itself.

  1. Hierarchy → the boss you assembled in 8.2, then Inspector → its Layer field. Read which bucket it is really in instead of assuming Enemy: a mask can only find what is genuinely in the layer it names, and the buckets themselves are already built — Ground from 6.2, and Player, Enemy, Projectile and Decoration from 7.2.
  2. Project window → double-click Assets/Prefabs/Player to open the prefab itself, then Hierarchy → right-click the Player root ▸ Create Empty — as a child of the root, not of the child called Sprite — and Inspector → rename it AttackHitbox. It is a Transform and nothing else, no renderer and no script, exactly like the GroundCheck empty you made in 6.2. Where you parent it decides what its position means: Sprite is scaled to 2, so a box sized one tile inside it would arrive as two.
  3. Inspector → Add Component ▸ Box Collider 2D, set Size to about (1.2, 1), and tick Is Trigger. A trigger reports overlaps instead of pushing things — the coin's collider from 2.1 wearing a new job.
    Inspector showing AttackHitboxInspectorAttackHitboxLayerDefaultTransformPositionX 0 Y 0 Z 03Box Collider 2DIs TriggerUsed By EffectorComposite OperationNoneOffsetX 0 Y 0SizeX 1.2 Y 1Add Component
    Box Collider 2D on the empty you just made. Size is the box in local units — X across, Y up — and Is Trigger is the tick that stops it pushing things and starts it reporting what enters it. Offset is where the box's centre sits inside the object; leave it at zero and move the whole empty instead, which is what the next step does.
  4. Inspector → Transform ▸ Position to about (0.6, 0, 0): one tile in front, on +x. The box runs from the player's centre outward on purpose — a blade that only reaches where the player is not is a blade that misses — and that also means it overlaps the Player's own CircleCollider2D. This project ships Queries Start In Colliders ticked, so a query that starts inside your own body can report your own body — one of the reasons the mask in the code half is doing real work.
  5. Inspector → the Layer dropdown on AttackHitbox: leave it on Default. The layer decision belongs on the query, not on the shape.
  6. Inspector → the PlayerController on the Player prefab, after the code half: six slots to fill from the fields you are about to declare. Attack Hitbox = the AttackHitbox child, Hitbox Size = (1.2, 1), Attack Damage = 1, Boss Layer = the layer you just read, Attack Cooldown = 0.5, Damage Window = 0.2. Wire them before you press Play: an empty slot throws on the first line that reads it.
The Problem: The Physics Engine Answers Question You Did Not Ask ~10 min

An attack hitbox is a shape that exists for about a fifth of a second and then disappears — and it is not a private object. A trigger is a full participant in every physics question anyone asks.

Here is the fact that decides this whole lesson, and it is worth reading twice: Physics2D.queriesHitTriggers defaults to true. It is a global setting, and while it is true, every physics query in your game — Physics2D.Raycast, Physics2D.OverlapCircle, Physics2D.OverlapBox — returns trigger colliders, unless that particular query's layer mask excludes them.

A trigger absolutely can confuse a raycast. Do not build on the opposite belief. You may have heard that ground checks are safe from triggers because "triggers are not what raycasts look for". That is the wrong model. Your ground check asks what is directly under my feet?, and a raycast returns the first collider it meets — so a hitbox near the player's feet is a candidate answer. The ray hits the hitbox, your CompareTag("Ground") check fails, and for that frame your player believes they are standing in mid-air. The symptom is a jump that works most of the time, usually failing exactly when you are swinging or standing still. You spent Unit 6 chasing that class of bug; this is where it comes back.

So the question is not whether the engine will hand your hitbox to a query — it will. The question is which tool you use to make it stop. There are four, and they are not equivalent:

ToolWhat it actually doesWhat it costs
(a) A layer mask on the query Physics2D.Raycast(origin, direction, distance, groundLayer) names the layers the query is allowed to see. Anything outside the mask does not exist as far as that call is concerned. The layers are yours to name, and yours to keep honest. The buckets already exist in this project — Ground from 6.2, and Player, Enemy, Projectile and Decoration from 7.2 — but a mask only works if the object you are looking for is actually in the bucket it names, and every query you wrote earlier that needs to see a newly layered object needs its mask widened too.
(b) The built-in Ignore Raycast layer An unmasked raycast uses Physics2D.DefaultRaycastLayers, which already excludes the built-in Ignore Raycast layer. Put the hitbox on it and every unmasked query in the game skips it — for free, with no code. It is a blunt weapon: if something you genuinely want to find also lives on that layer, unmasked queries will miss it too. It is also a poor name for "the layer your attack lives on".
(c) Physics2D.queriesHitTriggers = false; One global switch that removes triggers from every query in the game at once. Also global: any query that legitimately needs to find a trigger — an OverlapCircleAll that gathers pickups, say — silently stops working, with no error. It is also a runtime setting, so it must be re-applied from code on every play session, not ticked once in the Inspector.
(d) Query everything, filter by tag OverlapCircleAll with no mask, then skip every result whose tag is not the one you wanted. Works with zero setup, and it is what most first attempts look like. It allocates an array on every call, and it is fragile: the day something new is tagged Ground, your floor has a new member.
Which tool is "correct"? There is no universal answer, and you should be able to argue for the one you pick — the four cost columns above are the whole argument. The professional default is (a): narrow the question, not the world.

Socratic: Sensors and the Questions They Answer

  1. A raycast returns the first collider it meets along its path. Why does that single fact turn a stray trigger into a ground-check bug rather than a harmless extra result?
  2. Your ground check and your attack query are both asking the physics engine a question — "what is under my feet" and "what is in front of me". Which of the two is the hitbox a legitimate answer to?
  3. Option (c) fixes the hitbox and breaks every other query in the game that wanted to find a trigger. Which query in your own project, written by you, would break?
In the CodeGuided build

The question this answers“how do I make my swing actually hurt something?”

You are editing Assets/Scripts/PlayerController.cs. Six Inspector fields, a branch you already wrote in 1.6 with three lines added to it, and one new method. The only line here with no precedent in the course is the query itself: every question you have asked the physics engine until now has come back with a single answer, and this one comes back with a list. The shape it is asked about is the child you placed by hand in the editor half above — the code cannot make that object for you.

This build leaves 1 line for you to write. Everything around it is given, so you always know the shape of what you are producing.

Step 1Declare the shape, the reach, the damage, the filter and two clocks

Eight lines in the class body, beside moveSpeed and the ground-check fields from 6.2. Six are [SerializeField] knobs — the pattern from 1.3 — and two are the state that makes a swing a moment rather than a frame.

    [SerializeField] private Transform attackHitbox;
    [SerializeField] private Vector2 hitboxSize = new Vector2(1.2f, 1f);
    [SerializeField] private int attackDamage = 1;
    [SerializeField] private LayerMask bossLayer;

    [SerializeField] private float attackCooldown = 0.5f;
    [SerializeField] private float damageWindow = 0.2f;
    private float nextAttackTime;
    private float windowClosesAt;
  1. 1The slot that holds the AttackHitbox child you made in the editor half. The same kind of slot as groundCheck in 6.2: a reference to another object, wired from the Inspector instead of looked up in code.Wire it before you press Play, not after. An empty slot says nothing when the scene loads and throws a NullReferenceException on the first line that reads it — and the click branch below reads this one every time you click.
  2. 2The size of the box the query will ask about — the same two numbers you typed into the collider in the editor half. A Vector2 is a pair of floats: you have been building them since 1.4.The query takes a size, not a collider, so these numbers now exist twice in your project: once on the shape you can see, once on the question you ask. Keep them equal — and know which one the physics engine actually reads. It is this one, not the collider's.
  3. 3How much one landed swing takes off the boss. An int, because the damage method below takes an int.And a positive number, because that amount is signed. 5.3 made the rule explicit when the method moved onto StatManager: a positive amount hurts and a negative one heals. Pass -1 here and your attack becomes a healing spell the boss will thank you for.
  4. 4What the query is allowed to notice. The same type as groundLayer in 6.2, drawn in the Inspector as a dropdown of layer names rather than a number.Leave it empty and the query reports nothing, with no error at all. That is a mask's failure mode, and it is why 6.2 had you test the ground check with a deliberately empty one. Point it at the layer the boss is on — and at nothing else, unless you have decided a swing should also hit scenery.
  5. 6The shortest gap between two swings — your turret's fire rate from 3.3, moved onto a melee attack. The value is a starting guess you tune against the animation.
  6. 7How long the shape stays live after a click. A fifth of a second, which is also a starting guess rather than a measurement.
  7. 8The earliest Time.time at which another swing may start. Time.time is the seconds since the game started — the clock from 3.3.It needs no seed, and that is worth noticing. 3.3 wrote its first value as one cooldown before the clock started, because a turret could otherwise fire on frame one. A float field already starts at 0, and Time.time is past zero on the first frame you could possibly click — so 0 is a time in the past, which is exactly what "there has been no last swing" means.
  8. 9The Time.time value at which the damage window shuts. While the clock is below it, the swing is live.A deadline, not a bool. isAttacking = true has to be set back to false by something, and that something is always a second place that has to remember to do it. A deadline needs nothing to switch it off: it arrives. The only place you write this field is the click that opens it and the hit that closes it early.

Step 2Open the window inside the click branch you already wrote

CheckAttack() in your file already reads mouse button 0 and fires the Attack trigger — that is 1.6. Today it gets a gate in front of it and three lines inside it.

        if (Input.GetMouseButtonDown(0) && Time.time >= nextAttackTime)
        {
            animator.SetTrigger("Attack");
            nextAttackTime = Time.time + attackCooldown;
            windowClosesAt = Time.time + damageWindow;
            attackHitbox.localPosition = spriteRenderer.flipX ? new Vector3(-0.6f, 0f, 0f) : new Vector3(0.6f, 0f, 0f);
        }
  1. 1The click from 1.6 with one term added: the clock must have passed the earliest time the next swing is allowed. Two questions in one if — the same folded && guard you built into the lever in 5.2 and the jump in 6.2.GetMouseButtonDown is true on exactly one frame, so this gate is not protecting against the click — it is protecting against the player clicking again. Hold the button down and Down fires once; tap it as fast as you like and the clock is what refuses. 3.3 gave you both halves of that: the one-frame read, and the comparison against a stored time.
  2. 4Write the next allowed time into the clock: now, plus the cooldown. Exactly the line you wrote for the turret in 3.3."Now plus the cooldown" — both halves matter. Store Time.time on its own and the gate never closes. Store the cooldown on its own and you are comparing a duration against a timestamp, which is a comparison that means nothing and gives the same answer forever.
  3. 5Open the damage window: the moment of the swing, plus how long it stays live. This one line is the whole of "the swing is a moment rather than a frame", and without it the swing would be a single instant.This is what stops a hit depending on one frame. The click lands on one frame, and the animation, the physics step and your frame rate do not have to agree about which one. A window gives the shape time to still be there when the query looks, which is the difference a player feels between "my swing missed" and "my swing connected" — and it is a difference they will never be able to explain to you.
  4. 6Move the shape to the side the player faces. spriteRenderer.flipX is the flag you set from the input in 1.6 and cached in Start() back then — and true means the sprite is mirrored, which means facing left. The ? : is the conditional expression from 6.2's gizmo: a yes/no branch written as a value.flipX mirrors the picture and moves nothing else. It is a rendering instruction, so no collider, no child object and no transform follows it. That is precisely why the hitbox has to be placed by hand, and why a swing that damages things behind you is the first bug a playtester will report — usually while insisting the animation looks fine, because it does.

Step 3Ask what is inside the shape

One method, one question, one call. Everything above this step exists to give it something to ask with.

    void TryHitBoss()
    {
        Collider2D[] hits = ____;
        foreach (Collider2D hit in hits)
        {
            BossHealth boss = hit.GetComponentInParent<BossHealth>();
            if (boss == null) { continue; }
            boss.TakeDamage(attackDamage);
            windowClosesAt = Time.time;
            break;
        }
    }
  1. 1A method whose whole job is one question and one call. Same shape as CheckGround() in 6.2: empty parameter list, void, and everything it needs is already a field.
  2. Your turn3Ask the engine what is inside the shape, and keep everything it hands back. The question is about a box that sits where the attackHitbox child is, is as big as hitboxSize, is not rotated at all, and is allowed to notice only the layers in bossLayer — and the answer is every collider in that box, not the first one it meets. Four arguments, and they are the same four inputs this page has been pricing since the table at the top: where, how big, at what angle, and what may it see. Store the result in hits; the loop below is what reads it.You want the Physics2D query whose name ends in All — the one that hands back an array of every collider inside a box, where the raycast you wrote in 6.2 handed back only the first thing along a line. Pass the four arguments in the order the third architectural specification in the build lab lists them: the centre point (the hitbox child's position — a Vector3 is accepted where a Vector2 is asked for, exactly as it was for the raycast), then the size field, then the angle (0f, an unrotated box), then the mask. Read the Unity page for it before you write it; the argument order on that page is the order the method wants.Two things are new in this line. It asks about an area rather than a point or a line, which is the only reason a swing can hit something the player is not standing exactly on top of. And an area can contain more than one thing, so the answer is an array rather than a single collider — which is why the next line is a foreach, and why this cannot be assigned into a bool the way 6.2's raycast was. Notice also what the line still does not do: it finds colliders and touches nothing. The damage is the next statement, and it is a separate part because "what is there" and "what to do about it" are two different problems.
  3. 4A foreach over the array — the loop you met in Unit 3. It runs once for every collider the query found, in whatever order the physics engine felt like handing them over.A loop and not an if, and that is the difference this whole lesson exists to show you. Physics2D.Raycast reports the first collider along a line, so 6.2 could assign its answer straight into a bool. An area query reports everything inside the area, so the answer is a list — and the only way to look at a list is one item at a time.
  4. 6From the collider to the script that owns the boss's number. GetComponentInParent walks up from the object it found, which matters because a collider and a script do not have to sit on the same object — 2.1 taught you that about the coin's renderer. The type name here is whatever you called your boss health script in 8.2.This can come back as nothing, and that is a normal case rather than an error. The query found a collider of the kind you asked for, and it tells you nothing about what is attached to it. The next line skips those, and silence is also the honest response to a swing that lands on a wall.
  5. 7continue skips to the next collider in the loop without running the rest of the body — the same early-guard shape as the null check in front of 6.2's ground-check gizmo.
  6. 8The call that makes the swing hurt: a positive attackDamage, taken off the boss's health. The sign rule is 5.3's, and the method itself is the one 8.2 had you write.
  7. 9Close the window, by writing a deadline that has already passed so the next frame reads it as shut.Without this line one swing deals damage on every frame the window is open — a dozen hits from a single click, and the fight is over before the swing animation finishes. This is the "one swing, one hit" rule, and a deadline is how you say it. It sits inside the loop but the break below ends the loop, so it is written exactly once per swing.
  8. 10Stop looking. One swing hits one boss.

Step 4Run the question while the window is open

Four lines in Update(), beside the ground check from 6.2. This is what turns the method above from a script into an attack.

        if (Time.time < windowClosesAt)
        {
            TryHitBoss();
        }
  1. 1The window, read once per frame: is the deadline still ahead of me? Time.time is the clock from 3.3. Nothing sets a flag and nothing clears one — the comparison answers itself, and it goes back to false on its own the moment the clock passes the deadline.Notice how different this is from the cooldown gate in the click branch above. That gate is checked on a click, because a click is when a swing is asked for. This one is checked on every frame, because "is the swing live right now?" is a fact about right now — the same distinction 6.2 drew between an event and a standing fact. It also means a click that opens the window is read on the same frame that opens it, so the earliest a hit can land is the frame you clicked.
  2. 3The call — one question asked per frame for as long as the window is open, and each one asks the physics engine the same thing again.A method you write yourself does nothing until something calls it, which is the rule 1.6 taught the hard way. Put this call after the input is read in Update(): CheckAttack() opens the window and this reads it, in that order, on the same frame. Swap them and the first frame of every window is spent before it is open.
Check your work: the finished filetry every gap first — then open this to compare

Compare against your own file rather than copying it. Everything around the query is yours: where you placed the hitbox, which of the four tools you used for the layer problem, whether damage lands on the click or partway through the swing, and every number. What has to match is the shape, not the text: one shape in front of the player, one mask naming what it may notice, one window that closes itself, and one call to the boss's signed-amount method with a positive number in it.

// ------------------------------------------------------------------
//  REFERENCE FILE — Assets/Scripts/PlayerController.cs
//  Written to be read, not pasted. Build it with the walkthrough
//  above, one step at a time, then open this and compare.
//  If it is sitting in your project unchanged, that is the thing
//  to go and do now — the walkthrough is still above you.
// ------------------------------------------------------------------
using UnityEngine;

// PlayerController.cs at the end of 8.3 — everything from 1.4 through 8.2,
// plus the first question this project has ever asked about an AREA. Every
// query before it came back with a single answer: the ground check in 6.2
// assigns its result straight into a bool. This one comes back with a list,
// which is the entire reason the loop below exists.
public class PlayerController : MonoBehaviour
{
    [SerializeField] private float moveSpeed = 5f;
    [SerializeField] private float jumpForce = 7f;              // yours, from 6.1

    // From 6.2 — the ground check.
    [SerializeField] private Transform groundCheck;
    [SerializeField] private float groundCheckDistance = 0.1f;
    [SerializeField] private LayerMask groundLayer;

    // Today's knobs, all visible in the Inspector (1.3).
    [SerializeField] private Transform attackHitbox;              // the empty child on the Player ROOT
    [SerializeField] private Vector2 hitboxSize = new Vector2(1.2f, 1f);
    [SerializeField] private int attackDamage = 1;                // POSITIVE hurts — 5.3
    [SerializeField] private LayerMask bossLayer;                 // what the query may notice
    [SerializeField] private float attackCooldown = 0.5f;         // the fire rate from 3.3
    [SerializeField] private float damageWindow = 0.2f;           // how long a swing stays live

    private Rigidbody2D body;
    private Animator animator;
    private SpriteRenderer spriteRenderer;

    private bool isGrounded;

    // Two clocks, both compared against Time.time. Neither needs a flag,
    // because a deadline switches itself off by arriving.
    private float nextAttackTime;      // 0 is already a time in the past
    private float windowClosesAt;

    void Start()
    {
        body = GetComponent<Rigidbody2D>();
        animator = GetComponentInChildren<Animator>();
        spriteRenderer = GetComponentInChildren<SpriteRenderer>();
    }

    void Update()
    {
        // ... your input, movement, gravity, animation and ground-check
        // lines from 1.4, 1.6, 2.2, 6.1 and 6.2, unchanged and left out ...

        CheckAttack();   // a click may open the window
        CheckGround();

        // Is the swing live right now? This is a fact about now, so it is
        // re-read every frame rather than remembered from the click.
        if (Time.time < windowClosesAt)
        {
            TryHitBoss();
        }
    }

    void CheckGround()
    {
        isGrounded = Physics2D.Raycast(groundCheck.position, Vector2.down, groundCheckDistance, groundLayer);
    }

    // The click from 1.6, with a gate in front of it and three lines inside.
    void CheckAttack()
    {
        if (Input.GetMouseButtonDown(0) && Time.time >= nextAttackTime)
        {
            animator.SetTrigger("Attack");
            nextAttackTime = Time.time + attackCooldown;

            // Open the window. The click lasts one frame; the hit does not
            // have to be decided by which one.
            windowClosesAt = Time.time + damageWindow;

            // flipX is a render mirror and moves no collider, so the shape is
            // placed by hand. true means facing LEFT.
            attackHitbox.localPosition = spriteRenderer.flipX
                ? new Vector3(-0.6f, 0f, 0f)
                : new Vector3(0.6f, 0f, 0f);
        }
    }

    // The only question in this file about an area rather than a point, and
    // the only one whose answer is a list. OverlapBoxAll reports EVERY
    // collider inside the box on the layers the mask names — which is why the
    // answer is an array, and why what follows is a loop rather than an if.
    void TryHitBoss()
    {
        Collider2D[] hits = Physics2D.OverlapBoxAll(attackHitbox.position, hitboxSize, 0f, bossLayer);

        foreach (Collider2D hit in hits)
        {
            // InParent, because the collider and the health do not have to be
            // on the same object. <- whatever you named the boss's health
            // script in 8.2
            BossHealth boss = hit.GetComponentInParent<BossHealth>();
            if (boss == null) { continue; }

            boss.TakeDamage(attackDamage);   // positive hurts
            windowClosesAt = Time.time;      // one swing, one hit
            break;
        }
    }

    // ... your jump, your pickups and your animation, unchanged ...
}
Build Lab: The Decisions the Walkthrough Made for You ~30 min

The walkthrough above gives you one working version of every part — and it makes a dozen decisions on your behalf along the way. A decision you did not make yourself is a decision you cannot defend, and defending it is the exam task. Work through the three specifications below, answer every question in writing, and change the build wherever your answer disagrees with the one above. The specification is the work; the code is an opinion about the specification.

Begin with the shape you actually built. Re-read the four-tool table above with a hitbox now sitting in front of you, and write down which of the four you used and what it cost you. Then check the two readings the walkthrough had to assume: AttackHitbox parented to the Player root rather than to the scaled Sprite child, and the boss's collider genuinely on the layer your mask names. Both are Inspector readings, not opinions — if either one is wrong, the mask is filtering for something that is not there.

Architectural specification: when does damage happen?
  • Does damage land the instant of the click, or partway through the swing? Argue from the player's point of view: which one matches what they see?
  • If you use a fixed delay after the click, what is the delay measured against — and what happens to it if the animation is ever retimed?
  • Could the window be driven by the animation itself rather than a timer — an event placed on the clip, or a check against the clip's normalised time? What does that buy you, and what does it cost you in complexity?
  • Can the player swing again before the animation finishes? Decide, and name the variable that enforces your decision.
Architectural specification: where does "the direction I am facing" come from?
  • Your animator already carries a WalkDir integer (0 = Up, 1 = Horiz, 2 = Down). Does it record which horizontal direction — and if not, what does it fail to tell you?
  • If you add a facing field of your own, every branch that reads movement input now has to keep it up to date. What is the bug when one branch forgets?
  • Could you simply read the movement input again at the moment of the swing, instead of storing it? What happens to that idea when the player swings while standing completely still?
  • Or does your swing hit all directions — a spin? Name what that costs you in the fight your 8.1 spec describes.
  • Where does the hitbox go when no attack is happening — destroyed, deactivated, or simply moved out of the way? Pick one and say why the other two are worse.
Architectural specification: how does the hitbox find the boss?
  • Physics2D.OverlapBoxAll at an offset, with a layer mask — what are its arguments, and which of them is the layer mask?
  • Does the query hand you back one collider or a list — and when the list holds something with no boss health component on it, what does your code do? Why is that a normal case rather than an error?
  • OnTriggerEnter2D(Collider2D other) on the hitbox itself, with a small script of its own — what does that cost you in objects and scripts?
  • OnTriggerStay2D(Collider2D other) plus a record of what this swing has already hit — how do you stop one swing dealing twenty frames of damage, and what data structure holds "already hit"?
  • Whichever you choose, how do you get from the thing you found to the boss's health component? What method do you call on it, and what sign does its argument carry?

Checkpoint — before moving on, you must be able to:

Reference Docs

Need a deeper explanation? These pages cover the same concepts:

Next: 8.4 — Boss Attacks: Patterns & Phases, where the thing you are hitting starts hitting back.

0/5 answered

Answers save on this device as you type. At the end of class, click Download and upload the file to the Canvas assignment.

Saved on this device only — the Dashboard tracks your progress.