In my game, I would like there to be an overworld combat system.
I’ve gotten most functionality figured out up to this point, with the exception of the functionality of throwing attacks that actually interact with the enemy; I’m not sure how to do that.
Does anyone know how I can have the player land a punch on an enemy in the overworld, that can then have an effect on the enemy?
TLDR, in the overworld, I want the player to be able to press an “Attack” key (in this case, the Z key), which will perform an “attack” action that, upon touching an enemy, will allow for several actions to happen depending on who the enemy is and what attack was thrown by the player (are they instantly KOed, are they knocked back, do they just take damage, etc)
Z is already mapped to OK in MZ, so a parallel common event can watch Conditional Branch → Button → OK is Triggered and you don’t need anything extra to read the key.
For the swing itself I’d skip trying to detect a hitbox and just open a window instead. Pressing Z turns a switch called something like Attacking ON, plays the punch animation on the player, waits however long the swing should last, then turns it back OFF. Set a second variable right before that for which attack was used.
The enemy event is where the reacting happens. Give it a page set to Event Touch, which fires whether the player walked into it or it walked into the player, and branch on that switch at the top. Attacking ON means it got hit, so its damage, knockback or instant KO goes in that branch along with a self switch when it dies. Attacking OFF means a plain collision, so that’s where the player takes damage instead. The attack type variable gets read in that same hit branch, so one enemy can shrug off a punch and drop to a kick while another does the opposite.
Does the punch need to reach a tile in front of the player, or is walking into them mid swing close enough? Everything above is the contact version. Reaching ahead means pulling the player’s X, Y and direction into variables and comparing them against each enemy’s X and Y, which is doable but a different build, so worth knowing which one you’re after before you wire it up.
Yeah the button stuff is all handled and buttons function, I’m the same person you were talking to about adding more buttons from the other day lol. Just starting a new post for a related issue
This is a very clever idea! The problem is that the enemy event can ONLY have one page, because it automatically sets its own sprite/stats/attackset based on certain variables set before the battle, so making a new page would remove all the coding and make it blank and nonfunctional again. It’s a bit more nuanced than that and I can explain further if it helps, but basically, I think I can only sustainably work with one page here.
The enemy event is a Parallel Process, and the end of its code Loops infinitely once all of it’s internal calculations are done. It checks what attacks it is allowed to use, and then checks to see if its HP is at 0 to break the loop. Can I put the attack reaction somewhere in this Loop?
The attack does need to reach the tile in front of the player, yes. I had a concept for that already, but i’m not sure how good it is yet. I have another Parallel Process offscreen, which constantly sets a different invisible event to always be on top of the player. This invisible event is the attack hitbox itself, and depending on where the player is facing and what they press, it is quickly moved one tile in front of the player, and then retracted.
Yes, and it doesn’t need a second page. I’d put it ahead of the HP zero check you’ve already got, so a killing blow breaks the loop on the same pass instead of waiting for the next one.
The thing I’d add is a guard, since a parallel loop will re register the same hit every frame the hitbox is parked on that tile. A self switch handles it without touching your page setup:
◆If: Attacking is ON
◆If: Self Switch B is OFF
◆Control Variables: #0031 Hit X = Character [Hitbox] Map X
◆Control Variables: #0032 Hit Y = Character [Hitbox] Map Y
◆Control Variables: #0033 My X = Character [This Event] Map X
◆Control Variables: #0034 My Y = Character [This Event] Map Y
◆If: Hit X = Variable [My X]
◆If: Hit Y = Variable [My Y]
(damage, read the attack type variable, knockback)
◆Control Self Switch: B = ON
◆End
◆End
◆End
◆Else
◆Control Self Switch: B = OFF
◆End
Pick whatever letter you aren’t already using. It only means “already counted this swing” and the Else clears it when the swing ends.
If you keep the hitbox event, Set Event Location will place it rather than walk it, so it can’t get blocked or arrive a few frames late the way a Movement Route can.
The other direction is to drop the hitbox event and let the enemy work the tile out itself. Pull Player Map X, Player Map Y and Player Direction, offset by the direction (2 is y+1, 4 is x-1, 6 is x+1, 8 is y-1), and compare that against its own X and Y. Same comparison you’d be doing anyway, with nothing to keep repositioned every frame.
Structure looks right, self variables for the coordinates is a good call with multiple copies on the map.
One thing to change is the Else branch. It’s setting B to ON, and it needs to be OFF. That branch runs while ATTACKING is off, so it’s the one that clears the flag and arms the enemy for the next swing. As written, the first swing sets B ON and nothing ever turns it back off, so that enemy only ever registers one hit for the rest of the battle.
: Else
◆Control Self Switch: B = OFF
The comment block is fine where it is, just note that those three branches all live inside the Self Switch B is OFF check, so whatever you build there runs once per swing rather than every frame. That matters most for the knockback one, since “until can no longer move” wants to be a Move Route or a loop that finishes on its own rather than something re triggered each pass.
There’s no “until it hits a wall” terminator on move routes, so the nearest thing is a set number of Move Backward steps with Skip If Cannot Move ticked on. Against a wall the leftover steps get skipped and the route ends on its own.
The part that would bite in the context it’s in now is Wait for Completion inside a Parallel Process. The event’s own loop sits on that command until the route reports finished, and with Skip If Cannot Move off, a blocked step doesn’t advance the route at all, it just retries. So an enemy shoved against a wall would stop checking anything else, including its own HP.
Is the enemy facing the player when it gets hit? Move Backward goes opposite its current facing rather than away from whatever hit it, so one that’s mid patrol could get pushed sideways depending on where its route left it pointing.
Yeah that’s what I mean-“skip if cannot move” and then the move route runs until a wall is hit.
So you’re saying the problem with a Wait for Move’s Completion command inside a Parallel Process is that the WFMC command will overtake the priority for all other events within the Parallel Process until the WFMC command is over?
I would be okay if the enemy only became defeated after hitting the wall, it could look cool.
Or are you saying that if another enemy blocks their path, it will hang there until the enemy moves? Because that’s a fair point-perhaps changing the Priority at the start of the knockback event could help.
The enemies have to face the player in order to attack, but it’s a fair point if the player hits them while they’re facing away. I’m not sure how to make the enemies be knocked back away from the player relative to the position they’re facing.
Just this event. Every parallel event runs its own interpreter, so Wait for Move’s Completion only parks the one it’s written in. Everything else on the map keeps going.
With Skip If Cannot Move on, a blocked step gets skipped instead of retried, so another enemy in the way won’t hang it either. The route burns through whatever steps are left and reports finished. The hang case is only with skip off. On passing through each other, there isn’t a clean way. Priority only affects the player, events block each other regardless, and the Through toggle in a move route ignores walls too, so it would break the wall stop. Stopping short of the blocker with skip on is the harmless outcome, so I’d leave it.
For the direction, there’s a Turn away from Player command in the move route list. Put that at the front and Move Forward after it, and the push goes away from the player regardless of which way the enemy happened to be facing when the hit landed. It leaves them facing away at the end, so add a Turn toward Player as the last step if their facing feeds anything else.
Attack Collision is the event that is always sent to the player, and what tells the enemies they’ve been hit if they’re in the same place as it during ATTACKING=ON.
BPlayer X and BPlayer Y are coordinates that are set to be identical to the player’s at all times.
Okay never mind, this was a fairly obvious one. I had ATTACKING=ON in the wrong place and it never turned off and so Attack Collision was always stuck to the Player.
A new issue came up immediately after, though; sometimes, when hammering Z over and over, the Attack Collision event can just get left behind and stop following the player altogether. It snaps back to them when they press Z again, but leaving their attack hitbox behind is a…problem. Any ideas?
Hmm, couple questions here before we move on. Does the Attack collision event have Through checked in its options, and do the two Set Movement Routes have Skip If Cannot Move on?
Here’s why I’m asking. Move routes respect collision, so 1 Step Forward fails if the tile ahead is a wall or has another event on it. With Skip If Cannot Move off, a failed step retries every frame instead of skipping, and the common event sits on that Wait with ATTACKING still ON. ATTACKING ON is exactly the condition that stops the collision event from following, so it’d stay parked wherever it was until whatever’s blocking it moves. Hammering Z while walking around means a swing eventually lands facing a wall or an enemy, which could be the sometimes. One way to check is whether Z does anything at all while it’s stuck. If it’s this, it won’t until you step away from what’s ahead of you.
Either way I’d put Through on the Attack collision. It has to be able to move onto a tile an enemy is standing on to register a hit, and events block each other regardless of priority, so without Through a swing at an enemy is the one that gets blocked.
The other option is to drop the stepping and place it directly. You already have the four facing branches in the collision event, so Set Event Location to BPlayer X and Y offset by one in the facing direction, hold it for however many frames the swing lasts, then let the normal follow logic pull it back. No move routes, nothing to wait on, nothing that can be blocked.
This is fixed! The attack collision sticks to the player at all times.
As for the next issue (Sorry, there’s gonna be quite a few due to the complexity of the system so feel free to tap out at any time lol)
There’s a small knockback command on the regular attacks, aside from the major knockback attacks. However, it seems that the enemies have to complete the step they are in the middle of in order for them to actually be knocked back. This results in situations such as an enemy beginning to walk onto a new tile, getting hit as soon as they start that movement, and not being knocked back until they fully step onto the tile, up to a whole half-second later.
Basically, enemies are not being knocked back instantly.
The only thing I can think of to fix this is enemies somehow forcing themselves into the middle of the tile they’re currently “standing on” according to the game, but I don’t know if this is the right idea either. Any suggestions, if I’ve made sense?
You’re right, a forced move route only starts processing once the character finishes the step it’s already on, so a knockback set mid step waits out the rest of that walk first. At the default move speed a full step is a bit over half a second, which lines up with what you’re seeing.
Your fix idea is the right one too, and you already have the numbers for it. The engine moves an event’s map X and Y to the destination tile the moment a step begins, so the SV: My X and My Y you pull for the hit check are the tile it’s headed to. Set Event Location: This Event to those two variables right before the knockback route and it snaps to that tile instantly, which ends the step, so the route runs on the very next frame.
The cost is a visual pop of up to half a tile when the hit lands mid step, since the walk gets finished in one frame.
It’s definitely helped the issue substantially. However, in some cases, it doesn’t seem to trigger knockback at all, and they might even be popping forward by a tile upon being hit? I have an inkling that the code that just went in is causing this issue, but you?
The pop forward is the snap itself when the hit lands right as a step begins. The event’s map X and Y are already the destination tile at that instant, so it gets pulled a full tile ahead, not the half I said earlier. The missing knockback is the same thing seen from the other side. Snap one tile toward the player, knock one tile away, and it ends up where it visually started, so nothing appears to happen.
Snapping to the tile the sprite is visually closest to instead of its logical tile fixes both. Real position isn’t exposed as an event command, but it’s one line each in Control Variables with the operand set to Script:
◆Control Variables: Snap X = Math.round(this.character(0)._realX)
◆Control Variables: Snap Y = Math.round(this.character(0)._realY)
◆Set Event Location: This Event, ({Snap X},{Snap Y})
Put that right before the knockback route, after the hit check has already passed. The pop drops to half a tile at most in whichever direction is shorter, and the knockback is always a full tile of travel from where the enemy looked like it was.
If you’d rather stay off script, the tile it’s coming from is its logical tile minus one step in its facing direction, so the same thing works with This Event’s Direction and four branches.
One check on the cases where nothing happens, does the damage still apply? If it doesn’t, that’s a different thing, the enemy stepping out of the hitbox tile so its logical position never matched in the first place.
Second script line has _realX where it needs _realY, so My Y is getting the X coordinate and Set Event Location sends them to (x, x). That’s the spot in the void.
◆Control Variables: #0195 SV: My Y = Math.round(this.character(0)._realY)