How to Throw Objects in 2D Games

In the last post, one of the major clean up items we briefly mentioned was that we updated and fixed throwing of objects.  In this post, we’re going into detail on how that was accomplished.  Heads up — this is more of a technical post, but we will try to simplify the technical parts.

In Violet, we had an issue where if we were standing next to a wall and throwing an object, the object would “get stuck” in the wall.  This is demonstrated by this amazing illustration below.

Problem
Problem

The solution to the problem was not very hard to solve for.  However, we originally overengineered a solution by examining what direction a thrown object was going to determine if it should collide with a wall or not.  Each time we fixed the current issue, we’d find another scenario where the solution failed.  This prompted me to do a quick search on the internet, which yielded little results.  I’d been playing through The Legend of Zelda: Oracle of Seasons recently, so I decided to throw hundreds of bushes to figure out if a simpler solution could be accomplished.  Eventually a light bulb went off and I realized how simple of a solution this really was.  So, how do we throw objects in 2D top down videogames, much like the Zelda games of yesteryear?

How the 2D Zelda Games Probably Handled Throwing Objects

Since we’ve been developing Violet in Game Maker Studio 2, the code and examples will come from this engine.  However, the concepts should be transferable to the engine of your choice.

We have two variables we keep track of for picking / lifting / holding / throwing.  We are calling these variables holdingState and holdingholdingState can have these values:

  • false – we aren’t doing anything with holding objects
  • "picking" – we are currently in the picking up animation of an object
  • "holding" – we are currently holding the object over our head
  • "throwing" – we are currently in the throwing animation of an object

holding represents a pointer to the object we are holding.  For example, if we are interacting with a rock, holding would contain a reference to be equal to that said rock (hero.holding.reference gets us said rock).  We’ll talk about holding soon.

With these core variables in mind, we need the hero to start interacting and picking up the said objects.  This could look like a million different things, but our check is simply check a spot in front of the hero they are facing and see if the spot contains an pickable object.  If it does, and the player is pressing input for picking up objects, then we begin our pickup script.  We’ll talk about what picking up a pickable object looks like soon.  But let’s start to define what a holding object looks like.

The holding object is an invisible object that acts as a placeholder for the actual object being thrown.  This is the solution to the aforementioned problem in the original illustration.  See, by having an object act in the place of the object being thrown, we can fake a lot.  The holding object is always located at the hero’s x and y location, and we render the holding object above the hero’s head (or wherever).

Solution
Solution

We therefore have an illusion that the object is above the hero’s head, but in reality, it is located in the hero.  When we throw the object, it simply acts like a normal projectile being shot from the hero, and therefore, won’t immediately collide with the wall when thrown.

Holding Example
Holding Example

The holding object has a number of properties, and we’ll highlight a few now:

  • reference – a pointer to the object being thrown
  • holder – a pointer to who is holding the object.  If we set it to noone (null in other languages), that means the object is being thrown.
  • renderX and renderY – the location of where it is being drawn — the illusion
  • old_reference_mask_index – we use the hero’s mask for the holdable‘s collision so we ensure there is no way the object being thrown will hit the wall.  This keeps track of the original mask_index of the object to be thrown.

Now that we understand the basics of the holding object, let’s get back to picking up a pickable object:

/// @description pickup_pickable(conditionIn, inst) picks up a pickable item
/// @param conditionIn
/// @param inst
//assumes holding variable

var conditionIn = argument0;
var inst = argument1;

var success = false;

if (conditionIn)
{
  holdingState = "picking";
  holding = instance_create(inst.x, inst.y, obj_Holdable);

  //update some pointers
  holding.reference = inst;
  holding.holder = self;

  //we use the character mask here because the x and y location of holding is exactly the character
  //since we don't want the rock to collide with the wall when thrown, we want to make sure it uses the same mask
  holding.mask_index = spr_link_mask;

  //copy old mask index
  holding.old_reference_mask_index = inst.mask_index;

  //make the reference not have collisions
  inst.mask_index = spr_mask_none;

  //make the reference invisible
  inst.visible = false;

  success = true;
}

return success;

The biggest things to take away from this script are:

  1. We update holdingState to "picking" and create the holding pointer
  2. We set the holding‘s reference pointer to the pickable object, and the holder object to the hero (self, or this in other languages)
  3. We set the holding‘s mask_index to the hero so we ensure no collisions with the wall when thrown
  4. We keep a copy of the pickable‘s original mask, and give it no mask so while it is moving around inside the hero, it won’t receive collisions
  5. We hide the original pickable object, so we don’t see it inside the hero
Picking Up Example
Picking Up Example

Holdable

We can now talk in detail about the holding object.  If the hero is currently holding the holdable object, we want to set the render variables to a location where the hero is holding the object.  If we are currently in a picking up animation, we do some math to tween the object from its starting place on the ground into the final position above the hero.  We will always set its actual location (x and y values) to the hero’s (holder‘s) location.  Again, the rock is invisible, so we won’t actually see it in the hero.

We finally need to throw the object.  Throwing is actually pretty simple, other than the “maths” to calculate where it should go.  This again can be whatever feels right for your game, but we’re simply using some quadratic functions to tween the positions from the start of the throw to the where it should land.

When the player inputs the throw button, we should invoke the throw event of the holdable object, and set the hero’s holdingState to "throwing" and set the holding pointer to noone.

The throw event for the holdable object currently looks like this:

speed = 6;
direction = holder.animation_direction;

//the actual object (not the render, moves constantly)
var dist = predict_distance(throwTime, speed, speed, ease_linear);

//set final locations for actual and render
throwYEnd = y + lengthdir_y(dist, direction);

//set the starting location for the throw
throwYStart = renderY;

holder = noone; //the holder is deference since we are no longer holding

High level, this script sets the speed and the direction of the holdable object to be thrown (animation_direction is what direction the hero is facing, limited to 0, 90, 180, 270).  Since we’re dealing with illusion values, we need to calculate where the ending location will be.  We take the real location values and simply calculate linearly where the object will be in so many frames of game play (throwTime).

Then, in our step event, our renderX value will always be the real x position of the holdable object, but we can interpolate the y value to give it the illusion that it is being thrown.  Here’s an example of that:

if (direction_between(direction, 10, 170))
{
  renderY = ease_out_quad(thrownFramesCount, throwYStart, throwYEnd, throwTime);
}
else
{
  renderY = ease_in_quad(thrownFramesCount, throwYStart, throwYEnd, throwTime);
}

We should also point out that since the reference‘s visibility is set to false, we can’t see it.  In our drawing event of the holdable object, we draw two things — the shadow of the reference as well as the reference.

if (holder == noone)
{
  draw_shadow(x, renderY + shadow_yoffset + LINK_SHADOW_OFFSET);
}

draw_sprite_ext(reference.sprite_index, reference.image_index, renderX, renderY, reference.image_xscale, reference.image_yscale, reference.image_angle, reference.image_blend, reference.image_alpha);
Throwing Example
Throwing Example

When throwing is complete — whether the timer expired, or the holdable object hits a wall (legitimately), we need to “turn the reference back on”.  Each pickable object has a break event that we invoke at that time.  Currently with our rock, we simply call a shatter animation, but this could be whatever.  We also want to turn visible back on for the reference object as well as reset the original mask from the holdable‘s old_reference_mask_index.

Conclusion

Though this post covered a lot, there were still a lot of of details not covered in this blog post.  Therefore, we decided to host a simple example on Github.  This example is a very simple, stripped down version of what Violet is currently using.  You’re welcome to modify freely and use for your own projects.

Leave a Reply