Today I’ve released a new asset on the YoYoGames Marketplace, called iMprOVE_WRAP.
Many video games have the feature that exiting one side of the screen will “wrap” you around to the opposite side — notable examples of this include the smash hit classics Asteroids and Pac Man. GameMaker: Studio has a GML function, move_wrap(), which is intended to provide this feature easily, but it has a few limitations. iMprOVE_WRAP addresses these limitations, resulting in a more powerful, flexible implementation.
iMprOVE_WRAP improvements over move_wrap()
Wrap behavior is no longer based on the built-in GML speed variables.
One of the most troublesome limitations of move_wrap()
is that it only works for instances that have a speed
. In standard GML, the variables speed
, hspeed
, and vspeed
are used to move instances. But you can also “move” an instance by changing it’s x
and y
position in the room, without setting a speed. Many GM:S users will implement their own movement systems that eschew these variables, in order to give themselves complete control over the movement engine. When they do so, they are often confused when they discover that move_wrap()
doesn’t do what they expect.
iMprOVE_WRAP eliminates this dependency, so that an instance no longer need to use the GML speed variables in order to wrap; wrap behavior in iMprOVE_WRAP is based entirely on an instance’s x,y position.
Wrap region no longer limited to room borders
Another problem with move_wrap() is that it is intended to work with the Outside Room or Intersect Boundary GML Events. This means that move_wrap() is only useful when an instance moves outside the room, or encounters the edge of a room. But in many games, the “play field” may not be synonymous with the entire room — the room might have a border, or “dashboard” or “heads up display” which ideally should be considered “out of bounds” with regard to the play field.
iMprOVE_WRAP allows you to define a “wrap region” — a rectangular area inside the room, which instances wrap around the borders of, rather than the borders of the room.
Independent horizontal, vertical margins
With move_wrap()
the margin argument which determines the margin by which the instance must be outside of the room is the same for both horizontal and vertical travel; with iMprOVE_WRAP the horizontal and vertical margins may be set independently of each other.
Wrapping instances can (optionally) draw themselves on both sides of the border
With move_wrap()
, an instance still draws its sprite in the default draw in only one location: at (x,y
). If the instance is off the edge of the wrap boundary, but hasn’t yet crossed over, the instance draws on the “pre-wrap” side of the room; after the instance progresses by margin
pixels over the border, then the instance’s position is moved over to the “post-wrap” side of the room, and the instance is drawn there. This is not a big deal if the instance crosses the wrap boundary quickly, and has a relatively small sprite; but for slower-moving instances, or instances with larger sprites, it creates a jarring “jump” effect, where suddenly the instance appears on the “post-wrap” side of the boundary, with no real warning, rather than gradually entering the room.
iMprOVE_WRAP solves this by providing a new function, iw_draw_self_wrap()
. This new function augments the default draw by drawing the calling instance four additional times, at positions left, right, up, and down from the actual instance, the width or height of the wrap region away from the actual instance. Thus, when your wrapping instance is moving off the edge of the wrap region, one of these extra drawings is poking out on the opposite side, creating an illusion of continuity as the instance leaves one side and emerges from the other.
For a wrap region that is smaller than the room itself, it’s best to do your drawing on a surface that is sized to the area of the wrap region; otherwise the parts of the drawing that should be outside of the region will be visible outside of the wrap region. Alternately, if drawing to a surface is not something you want to do, you can “mask off” the portions of the room outside of the wrap region by layering objects at a higher depth around the border, like a picture frame or dashboard.
Collision detection on both edges of the border
To compliment the iw_draw_self_wrap()
function, I’ve added a new collision function, iw_collision_wrap()
. This function checks for collisions at the four places occupied by the four drawings drawn by iw_draw_self_wrap()
. There are actually two iw_collision_wrap()
functions.
The more basic, iw_collision_wrap()
sets five instance variables in the calling instance to store the id of any instance in collision: other_center, other_up, other_down, other_left,
and other_right
.
The more advanced, iw_collision_wrap_map()
, returns the id of a ds_map, which holds those same five instance variables as keys, which you can access using ds_map_find_value()
.
Which to use is up to you, and the style of programming you prefer. iw_collision_wrap()
is easier to use, and if you don’t mind the instance variable names, is probably slightly faster at runtime. iw_collision_wrap_map()
is for programmers who get pedantic about “correctness” and want their functions to return something, not cause side effects in the application state. Since it’s not possible in GML to have a function return 5 separate values, we return a data structure that stores the five values. The downside of this is that you have some overhead, namely a need to clean up the ds_map when it is no longer needed. Fortunately, it’s not hard. The example project will demonstrate how to do this properly, so don’t worry.
iMprOVE_WRAP is available at the YoYoGames Marketplace for $2.99; however I am making it free for the first 10 downloads. Please rate it and review it if you give it a try!