When 10 days go by without a post, you know something is cooking! Hold on tight — I’m about to drop a ton of new features:
Polishing
Okay, I guess this isn’t really a feature. But the last 10 days have seen quite a bit of improvements and polishing. The main reason for this is we’ve had quite a few people play test the game in the past week, and we wanted the experience to be as good as possible. There are many, many more improvements not listed here — an exhaustive list would be many pages long, and probably quite boring. Here are a list of notable, exciting (from my perspective) improvements:
Fatal Error Fixes
There have been several game breaking errors that have been occurring. Most of them I originally thought were because of objects going inside / outside of the loading zones. However, this wasn’t the case. They were mostly due to a pointer to instances being destroyed not getting nulled
(or in Game Maker, set to noone
). We have one nagging fatal bug left that I cannot for the life of me figure out where it’s coming from. We’ll eventually squash it though. 🙂
Masking on Weapons
Many objects use invisible masks to represent collisions. One of these objects were weapons. Game Maker has a limit in that an animation cannot turn on and off masks for a given sprite animation. We can specify per frame collisions based on the sprite image itself. But if pixels exists for a certain frame, the collision will be on those pixels. Why this matters is, for example, the sword animation should not have collisions on the first two images of the animation, because they are “wind up” frames.
Behind the scenes, we wrote a script that every frame of the animation would check which frame the animation was on and swap the mask for a certain specific separate masking image for each frame. The code became quite a case statement mess. Though Game Maker’s turning on / off masks limitation still exists, I found that swapping for a specific separate masking image was redundant as we could simply swap for the actual image in the animation at a given frame. Though there are still a few case statements, we were able to condense them by saying:
switch (image_index) { case 0: case 1: mask_index = spr_MaskNone; break; case 2: case 3: case 4: mask_index = -1; //which means the actual sprite image break; default: mask_index = spr_MaskNone; break; }
vs.
switch (image_index) { case 0: case 1: mask_index = spr_MaskNone; break; case 2: mask_index = spr_SwordMask1; break; case 3: mask_index = spr_SwordMask2; break; case 4: mask_index = spr_SwordMask3; break; default: mask_index = spr_MaskNone; break; }
As we can see, we no longer have to maintain specific images for specific frames. This will scale much better as well.
Shooting Fixes
When shooting arrows, fire or ice, sometimes where the arrow spawned was a little inaccurate. This was most noticeable with enemies. This issue has now been fixed.
The other shooting fix was objects like bushes or rocks were preventing these objects from continuing flight. This is no longer the case, and seems more natural as these objects are drawn to be “smaller”.
Bottom Buttons Multiple Buttons
One of the new features added is the ability to shield bash (continue reading for more information). To do a shield bash, the user must press L Trigger
plus Xbox B
on a classic layout. Before now, the bottom button tutorial only supported one button. Now we can supply infinite amount of button combinations (not practical, of course) for the player to learn how to do certain moves / tasks.
Enemies Attacking Outside View
I am not entirely sure when this bug came into the system, but the code to determine whether an enemy can “see” the player was broken. This code is made up of three overarching checks:
- Is there a
solid
wall between the enemy and the hero? - Is the hero in the enemy’s field of vision?
- Is the enemy in the viewport?
The last check was broken. Somehow the location we were checking for the viewport was always the hero’s location instead of the enemy’s location. Thus, the hero is always in the viewport and this check would return true.
The side effects of this were that enemies would attack the hero even when the player couldn’t see the enemy. This was “unfair”. The other was that hoards of enemies would come attacking the hero — to the point of overwhelming. An enemy’s vision might be able to see the player, but the player couldn’t “see” the enemy due to the viewport not showing the enemy. Again, this was “unfair”. However, this is now resolved! 😀
Alright, onto new features!
Shield Bash
One of my friends since conception of the game has wanted the ability to attack with the shield. I’ve liked the idea, but wasn’t sure the best way to go about it. Finally, a year or so later, the ability to shield bash has been implemented.
Shield bashing is meant to be more of a defense of tactic. If the player can time the attack right, the hero will parry the attack and cause the enemy to stop their attack and be stunned. However, the player can deal damage with their shield if the shield collides with the enemy. However, attacking with the shield is slower and will degrade the shield very quickly.
Coming up with this tactic was a very interesting problem to solve for. Originally we were checking to see if the shield bash object was colliding with the weapon object. However, as we were play testing this, it didn’t feel right. I whipped out The Legend of Zelda: Breath of the Wild to see how they implemented countering. What I ended up discovering was that to perform a successful parry, the player had to anticipate the attack coming — almost performing the inputs of the parry before the enemy’s attack had happened.
To implement such a tactic, we could no longer perform collision events on the attacking weapon and the shield bash object, since the attack and input was not in “anticipation”, but rather as it was happening, or had happened. Thus, we had to check if the attack was going to happen to see if a parry was happening.
How do we predict the future? Magic.
No, just kidding. In the polishing section of this post where we summarized the masking updates (you didn’t skip that, right? 😉 ), there are a couple frames were collisions are purposely turned off. What we check for is:
- An enemy is within the vicinity and is currently holding a weapon.
- If the above is true, we check to see if the above weapon is in the initial part of the animation.
- If the above is true, and we are shield bashing, a parry can be performed.
There’s enough frames before the attack happens, that this feels “right”. There currently is a “/” above the enemy’s head, so the player knows they are about to attack. With this knowledge, a player can shield bash at the right time to perform a parry.
Updates to the Soldier AI
Since the hero has the ability to perform a parry, it was decided that the enemies who carry shields should be able to do the same. This was implemented much like how the dodge counter works in the Updated Enemies and AI section. If an enemy carries a shield, the more often the player uses their weapon, the more chances the enemy will perform a shield bash themselves.
The other update to the Soldier AI was after a Soldier
gets stunned, they have the ability to either perform a dodge or a shield bash. There is a science to this, but we don’t want to spill all the beans on how it works. You’ll have to figure it out yourself!
Resolution and Camera
In the post “Audio Controller and More“, it was mentioned that there was an ability to pan the camera. The more I played with the feature in the game, the more I disliked it. This was mainly due to trying to play the game with a camera that snapped back — it just didn’t feel right. However, I thought it was still important to be stealthy in this regard.
The other big problem we were trying to solve for in the last couple weeks was defining the screen resolution of the game. This is an important thing to nail down as it decides game play, as well as device support.
Pixel art does not scale very well when using multiples other than integers. For example, we can scale pixel art up by 2x, 3x, etc., but distortion will happen when using non integer values (1.5 for example). When coming up with the original game play, we were using 1440 x 810 as the resolution. We knew this was not going to be the final resolution, but kept with this as it was easy to see code with the the game window up (made for easier debugging).
When figuring out resolutions, we started with 960 x 540. This felt like a good resolution because it could be scaled to 1920 x 1080 since multiplying by 2 is a whole number. However, since the viewport was shrunk down, the combat felt “claustrophobic” to me. However, when people were play testing, it didn’t seem to bother them. I am sure the bug fix with the enemies and the view certainly did help, but something was still missing.
Instead of panning the camera, I thought “what if we could zoom out the camera instead”? Originally I tied moving the R-Stick
up and down to zooming in and out. However, this seem to conflict when trying to target enemies in lock on mode. We decided to make pressing in the R-Stick
to be a zoom toggle:
As we can see, zooming the camera out “scratches that itch” to be able to see more around the player, giving the stealthy player more area to view. You may have noticed that we’re using 1280 x 720 instead of 960 x 540. I felt 1280 x 720 gave the player more view area by default, which made combat feel less daunting. However, 1280 x 720 is possibly the worse number to chose for our resolution, since a graphics card has to decide what a 1.5 pixel is supposed to be.
One thing we noticed though when scaling our art is that the art Hannah has drawn scales pretty well, even at non integer numbers. I am not sure what magic is happening there, but I will be doing some pixel art research to figure out why that is. The hero and enemies do distort, but it is hardly noticeable, especially when viewing from longer distances, such as playing on a TV. Because of this, I think the game play outweighs the slight distortion one would see when zooming in / out. Plus, we are using 64 x 64 graphic tileset in most cases, which I believe gives us more leeway in how art scales.
The only other issue we were having was since 1080 is the maximum “zoomed out” resolution, all of our surfaces must be drawn to that scale, since we never know when the player will “zoom out”. Because of this, I noticed some slow down in the drawing events. This is something I will keep my eye on, but the issue seems to be better with the 2.2.2.308 Beta Runtime version of Game Maker Studio.
Map Screen
The next WIP feature that we added was a map screen. There is still a lot to do with this feature as the map that has been present is just the “playground” map. Pressing Xbox Back
or Select
will pop up the map screen:
For one, it does not fill the entire screen. We figured since the real map will be much larger, this will not be a problem. However, the map WILL BE MUCH LARGER, which means the texture will probably need to be split into several surfaces to be drawn on screen. This will be an interesting problem to solve for. The other is we’ll most likely want to be able to zoom in and out of the map, pan, and set “interesting stamps” or waypoints the player can follow. Right now they yellow dots simply represent where the violets or at, but they can really be anything. Really, the only thing the map feature does right now is display, and lets the player be able to go to the main “pause” screen and back again without any issues. Hence, WIP. 🙂
Water Rod
The water rod is the newest item to be added to the game. It’s the missing link to our “rock, paper, scissors” concept, where fire beats ice, ice beats water, and water beats fire. The water rod will blast a gush of water into enemies and pushes them back.
The water that comes out of the water rod automatically stuns enemies, as well as pierces shields. It’s damage dealt is generally not much, but works great as a defensive powerhouse. It will also put out fires and grow / restore plants that were being burnt from fire. Finally, in the future, we may even add the ability for the water rod to push blocks or other objects (to potentially solve puzzles).
Bridges / Layers
The final feature that we want to share is the concept of bridges. In a 2D perspective, we are only given x
and y
variables for positioning. Thus, anything above or below the character is faked. For example, if the hero’s position occupies a position on a bridge, is the character on the bridge, or under the bridge? It’s a tricky problem to solve for sure!
I spent quite a bit of time thinking and looking at games like The Legend of Zelda, A Link to the Past (LoZ aLttP) to figure out how they “faked it”. I kept looking at this image to figure out how this was solved. In the image, there are several spots where there are overpasses Link can travel on. When examining the image, I noticed the only way to get to the above section of the overpasses was by climbing stairs. My guess then was in LoZ aLttP, the stairs was used as a flag internally because when moving up or down the stairs, the player loses control as Link goes up or down these stairs. This flag, we’ll call layerDepth
would be set to 1
when going up the stairs, and 0
when going down the stairs. If Link’s layerDepth
is equal to 1
, he would be considered on the overpass, thus drawn on top of it, and collisions on the overpass would occur (probably checking if collision and layerDepth == 1
). If Link’s layerDepth
is 0
, collisions on the overpass would be ignored so that the player could “pass under” the overpass, and thus be drawn underneath.
The layerDepth
concept was implemented in a similar way, except we didn’t want to be bound to stairs to set the flag. Thus, we added a Ramp
object — an invisible object before an overpass, or bridge, that increments the hero’s layerDepth
. The way the ramp works is if the player is traveling in a certain direction range towards the bridge (for example, down, we check the direction between 225 – 315), we would increment the layerDepth
variable. If the player is traveling in a certain direction range away from the bridge, we would decrement the layerDepth
variable. Thus, what we get is the hero standing on top of a bridge, because we incremented the layerDepth
to be 1
.
If we were to continue moving down past the bridge, our layerDepth
would be set to 0
. Thus, if we were to jump into the water, the collisions of the bridge would be ignored (since they are checking for collision and layerDepth == 1
), and thus, our hero would pass under the bridge.
There are still a few kinks to work out as well as needing to apply this to all other objects because the hero is the only object that works right now. However, this concept seems to be working well, and theoretically scales beyond layerDepth
‘s of 0
and 1
(though not sure how practical going more than two depths actually is).
Conclusion
If you made it through all the way, give yourself a gold star, because that was a lot of information to take in. I think we are about a week out from beginning to build an actual game. Stay tuned!