Ludum Dare 38: A Small World (TARJECTORIES)

Tarjectories by csanyk for LD48-38: A Small WorldMy latest release is called TARJECTORIES, and it’s my entry for Ludum Dare 38, A Small World.

Play and Rate TARJECTORIES at Ludum Dare

The title is a mashup of “target” and “trajectory”. It’s also an easy misspelling of “trajectory”.

You control a little gun turret on a little world, shooting little projectiles at little targets. Use the planet’s spin, and your shot power and gun elevation to put shells on the target.

Destroy all targets on the planet to advance to the next level.

You will need to use your judgment and instincts to know how much power to give the shot, and what angle to fire at. Most targets can only be hit by indirect fire, relying on the trajectory of the shot as its path curves due to gravity. Here’s an early work-in-progress screen capture:

TARJECTORIES - Early work-in-progress

Controls

Adjust your turret’s gun elevation by the left/right arrow keys, or A and D keys.

Adjust gun power by using up/down arrow keys, or W and S keys.

Fire using the Space bar or Enter keys.

Scoring

  • Direct hit: If you hit a target directly with the shot, you will score a base amount of 1000 points. This is doubled for each consecutive hit you make: 2000, 4000, 8000, 16000, 32000, etc.
  • Indirect hit: If you hit the ground near a target, the explosion from your shot may destroy the target anyway. But you will lose your consecutive hit score multiplier, since when your shot hit the ground, technically that was a miss. This will score you 1000 points, and count as your first hit on your next consecutive hit multiplier.
  • Bonus timer: There is a timer in each level, and if you manage to destroy all the targets before it expires, you’ll get bonus points.
  • Perfection bonus: If you mange to hit all the targets in a level without missing (all direct hits), you will earn a perfection bonus. The base perfection bonus is 10000 points * the number of hits scored in the level, raised to an exponent equal to the number of Planets in the level. This is how you get the truly astronomical scores. If you get a perfect bonus on a multi-planet level, you deserve it.

Winning and Losing

The only way to lose in TARJECTORIES is to accidentally shoot yourself. This can happen if your shot orbits your planet and comes back around to hit you, or if your planet is rotating fast enough that it puts you in the path of your shot as it falls back to the ground. Don’t do that.

To win, you must complete every level of the game. You can play again; the game will be re-randomized on your subsequent re-plays.

Download TARJECTORIES

TARJECTORIES is currently able to run on Windows.

The source code is also available for you to download, if you wish.

TARJECTORIES was built using GameMaker Studio 1.4, Bfxr, and Audacity. It took about 22.5 hours of development time for me to built. Everything was built from scratch, by me.

Version History

1.0.0.0

Initial release

Bug reports:

  1. One user has reported a bug whereby two worlds appear on level 1. Unable to replicate. There should be no way for more than one world to be created on level 1, and even if there were, the oPlanet object has collision detection with other oPlanets, and re-positions them in the event of a collision until they are no longer in collision.
  2. FATAL ERROR in
    action number 1
    of Step Event0
    for object oGround_explosion:Push :: Execution Error – Variable Get -2.t_angle(100033, -2147483648)
    at gml_Object_oGround_explosion_StepNormalEvent_1 (line 11) – ex.t_angle = other.t_angle;
  3. Stats tracker fails to count a miss when a shot destroys itself due to timeout.
1.0.1.3 Bug fixes:

  1. Stats tracker fails to count a miss when a shot destroys itself due to timeout. FIXED.
  2. oGround_explosion error referencing non-existent other in Step Event when a collision is detected with a target. Replaced other with the id of the target instance.
1.0.2.4 Numerous tweaks, mostly to the level_timer, minor bug fixes, and polish.
1.0.3.5

“GE” edition released. Feature complete.

1.0.4.8 Fixes the graphical bug some users were reporting where on the first level it looks like there’s two worlds overlapping each other. The bug was the result of using surface and draw functions in the Create event, rather than in the Draw event where they belong. I guess it affects certain users who have certain video cards, because I was not able to replicate the problem on my machine, but a user who did experience the bug confirmed that it was fixed by this release. Final build for Compo entry.

Classic Videogame Quotes

Since their invention, almost overnight videogames have made a lasting impact on the greater culture. Here are a few of my favorite memorable quotes from video games.

It’s dangerous to go alone. Take this!

It's dangerous to go alone! Take this.

Game: The Legend of Zelda

System: Nintendo Entertainment System

Year: 1986

It’s the mid-80’s. The NES is new, and a chip shortage has made this already-hot game a hard to find must-have for the holidays — despite being released in February. Limited quantities of the special gold cartridge meant that a lot of kids had to wait a long time to get their copy of the game everyone was talking about: The Legend of Zelda. In Link’s first encounter, he finds an old man in a cave with a gift and some memorable advice.

Welcome to adventure, kid.

It’s a secret to everybody.

It's a secret to Everybody.

Game: The Legend of Zelda

System: Nintendo Entertainment System

Year: 1986

There’s something seedy about taking free rupees from a cave-dwelling Moblin. Is this a legit offer? What’s the catch? Why is this overworld enemy helping us? But yes, it’s true. Everyone knows that the secret to getting ahead in the world is to have a little money. It can get you into places, and out of jams. You can never have too much, but you can only carry $255. Don’t spend it all in one place, unless it happens to be the hidden shop that sells the Blue Ring!

Uh Oh. The truck have started to move!

Uh-oh! The trick have started to move!

Game: Metal Gear

System: Nintendo Entertainment System

Year: 1987

Most videogames for the NES were developed in Japan, and accurate translation never seemed to be a high priority. So many memorable quotes from mid-80’s videogames are remembered for their quirky, incorrect grammar and hilarious misspellings.

In Metal Gear, you sneak about a military base attempting to keep a low profile lest you be discovered and create an international incident. While looking inside a few parked trucks for supplies to aid you in your mission, one of them happens to start up with you inside! Better lie low and hope that you will not be discovered, and that wherever it takes you doesn’t bring your mission to a premature end. Fortunately for you, the blundering enemy has in fact just made it easier for you to succeed, by taking you to an area on the base where you could not get to otherwise.

Earlier in the game, you encounter this tired guard, who, if you wait out of sight long enough, will fall asleep. Oddly, before nodding off he announces, to no one in particular, “I feel asleep!!” Either the designers meant to say he fell asleep, which makes no sense because he’s already asleep, or perhaps they meant to say he feels sleepy. Either way, it’s pretty funny.

The NES port of Metal Gear was a bug-ridden mess, but since most of us didn’t have an MSX to compare against, we had no idea, but we didn’t care. The sneaking about, using stealth tactics to infiltrate the base while quietly eliminating guards, and finding an arsenal’s worth of gear to blow up a nuclear-armed super-weapon were too important to let some bad English stop us.

I feel asleep!!

Congratulation.

Congratulation.

Game: 1942

System: Nintendo Entertainment System

Year: 1986

A number of early Capcom NES games rewarded the player who successfully beat the game with this stingy accolade, “CONGRATULATION.” What, just one measly congratulation? Isn’t plural, multiple congratulations nearly always in order when complimenting someone’s happy success? After dogfighting your way through thirty-two (!) stages of bland, slow-moving shoot-em-up “action” against an unbearable monotone soundtrack, this is the thanks we get?

Literally, this is the entirety of what you get when you beat the game. Screw you, Capcom!

Fortunately, they more than made up for this with the sequel, 1943, which features improved everything, including one of the best soundracks on the NES. Capcom went on to produce some of the best titles on the NES, and found even greater glory in the 16-bit era with Street Fighter II. All is forgiven.

A winner is you!

A winner is you

Game: Pro Wrestling

System: Nintendo Entertainment System

Year: 1986

Pro Wrestling is one of those games that is pretty dumb, and yet really fun despite that, with a one of the hardest boss fights to win, the championship bout against Great Puma. But each time you manage to win a wrestling match, your reward was this message: A winner is you! All right! It really pumps up your self esteem!

You are in a maze of twisty passages, all [alike|different].

You are in a maze of twisty passages, all alike.

You are in a maze of twisting passages, all different.

Game: Colossal Cave Adventure

System: DEC PDP-10, and others

Year: 1976

There’s so much quotable in Colossal Cave Adventure, considering the entire game is entirely text, and one of the first computer games ever. But this quote from the Maze is the one that I come back to the most. To create the feeling of being lost in a maze, the game just repeats the same text in each room, until you manage to solve the maze. There’s no feedback to tell you where you are, or to give yourself a reference point to have some idea where you are. After puzzling over this conundrum, successful players eventually figure out that if they drop an item from their inventory in a room, it will help them to make that room in the maze stand out from the others, enabling them to map out the maze with pen and paper.

To this day, whenever I’m in a confusing situation where there are many options and I don’t know which is the right one, I’ll think back to this one.

I am Error

I am Error.

Game: Zelda II: The Adventure of Link

System: Nintendo Entertainment System

Year: 1987

A genuine WTF moment in gaming occurs when you meet the infamous Error. This is all he ever says to you, “I am Error.” Is that his name? Or is he just a chronic screwup? Or is the game telling you that it has an Error? This was a matter for deep contemplation in 1987.

What a horrible night to have a curse.

What a horrible night to have a curse.

Game: Castlevania II: Simon’s Quest

System: Nintendo Entertainment System

Year: 1987

Castlevania II attempted to innovate by introducing adventure and RPG elements into the action-platformer formula. There’s a level-up system, and shops where you can spend money that you spend hours grinding for… so much grinding for XP and hearts in fact that this game is usually remembered as the least-liked of the NES Castlevania titles. Luckily the music was excellent. Unlike the first Castlevania, rather than having a linear progression through a series of stages, the game featured an open map that you could go back and revisit, and likely would need to several times while trying to figure out some extremely obscurely hidden secrets. Another innovation the game features is a day-night cycle, where during the nighttime hours, the enemies were stronger, doing twice as much damage, and taking twice as many hits to be defeated. Every couple of minutes, day would turn to night, or night to day, and every time the game would freeze and display this message to you… one character at a time… for about 30 seconds. It was… memorable, let me tell you.

Winners don’t use drugs

Game: Various arcade games

Year: 1989-2000

If you went to an arcade in the 1990s, you surely saw this message on a regular basis. I don’t know whether it ever stopped anyone from trying drugs who wanted to, but we sure did know who the Director of the FBI was.

It is pitch black. You are likely to be eaten by a grue.

It is pitch black. You are likely to be eaten by a grue.

Game: Zork

System: PC

Year: 1980

The early text adventure games borrowed liberally from one another. In Colossal Cave Adventure, you could die if your torch went out, falling into a pit in the pitch black darkness. In the Zork series, there were locations where you could die without a light source, but it made no sense to have a hole that you could fall into. Enter the Grue, a loathsome fell creature that inhabited only the darkest reaches, and had never been seen by anyone who lived. But what is a Grue? No one knew. Some speculate that the name derived from the word gruesome, which is certainly a likely sounding explanation. On the other hand, the term “grue” is also found in the philosophy of Nelson Goodman, which might have been familiar to the MIT students who formed Infocom. But they’re also a monster in the Dying Earth novels by Jack Vance. So which is it?

Fight, Megaman! For everlasting peace!

Fight, Megaman! For everlasting peace!

Game: Mega Man

System: Nintendo Entertainment System

Year: 1987

After winning the original Mega Man, we are informed that Mega Man has to continue to fight (basically telling you that you could now play the game again, enjoying it for “replayability”). “Fight, Megaman!” the game extolls us, “For everlasting peace!” What? How? That oxymoronic statement always gives me a chuckle.

President Ronnie has been kidnapped by the ninjas! Are you a bad enough dude to rescue Ronnie?

President Ronnie has been captured by the ninjas

Game: Bad Dudes vs. Dragon Ninja

System: Arcade (Data East)

Year: 1988

What could be more 80’s than a President named Ronnie? In the last year of Reagan’s presidency, this 2D beat-em-up gave us the attitude of cool badness that we all needed. Are you a bad enough dude? The game play in Bad Dudes wasn’t necessarily great, consisting of mostly standing around on platforms as the level slowly scrolled by, delivering repetitive one-punch or one-kick knockouts to an endless supply of cookie cutter Ninjas, without a great amount of depth or variety to the entire affair. This was a game anyone could beat, pretty much regardless of their skill level, as long as they had enough money. But the giant-sized, 16-bit sprites and (somewhat) challenging boss fights were enough to suck us in and drain the quarters from our pockets.

Barf!

Barf!

Game: River City Ransom

System: Nintendo Entertainment System

Year: 1989

The famous last words of many dying students at River City High school, uttered as they blinked out of existence and left behind their bouncing pocket change. It’s funny that they apparently literally say “Barf!” rather than making the sounds of barfing, such as “Bleaugh!”

Finish him!!

Finish Him!

Game: Mortal Kombat

System: Arcade

Year: 1992

The voice narration gave these words a chilling malevolence. When you hear this, having won two out of three rounds in the Mortal Kombat tournament, it’s time to unleash the combo that triggers your fatality move, giving your opponent a death worthy of the game’s title. Or, if you’re the loser, it’s time to endure the indignity and shame of having your body torn asunder, in the most unpleasant way imaginable. Yet, no matter how many times they die in the MK tournament, you everyone still gets to fight again in the endurance rounds.

Game Over

Game Over

Game: Just about every one, ever.

System: All of them.

Year: Eternal

Game Over, man. Game over!

To be sure there are many more memorable videogame quotes that I’ve left out. What are your favorites?

The Ongoing Sale

Since I announced yesterday that I’d finally earned my first $100 payout through the GameMaker Marketplace, I dropped my price on all paid assets to $0.99. I had another purchase today, so to celebrate that, I decided to adjust the terms of the sale.

The payday celebration sale will continue through 4/10/17 as planned.

For each paid purchase I get during the sale, I will extend the sale by one day.

Update, I had another sale today, 4/11/17, which will extend the discounts another day.

So as of right now, the sale on all my paid assets will last until 4/12/2017.

Get csanyk GameMaker assets on GameMaker Marketplace!

Achievement unlocked: $100 in revenue on GameMaker Marketplace

On July 20, 2014, I released my first asset for GameMaker Marketplace, MMap Mini Maps.

Today, nearly three years later, I have finally reached the $100 author revenue milestone, meaning I’ll finally get paid.

In celebration, I’ve put all of my paid assets on the GameMaker Marketplace on sale for just $0.99 for one week, ending 4/10/2017. Update: I’ve decided to extend the discounts by one day per asset sold during the sale. So currently the sale will run through 4/12/17. We’ll see how long we can keep this going.

You can buy them here.

Enjoy! And share! And thanks!

YoYoGames: Major GameMaker roadmap announcement

YoYoGames posted a major, detailed update to their roadmap for GameMaker Studio 2 this morning. Seemingly not an April Fool’s joke.

The announcement contains lots of exciting, long-awaited new features and improvements, but not much detail as yet about when most of these might be forthcoming. And the usual disclaimers that these are plans, not promises.

There’s even more at the link above, but here’s what I think about these in particular: (more…)

Game design evolution during the cartridge era of consoles

Someone asked a question on the AtariAge facebook community:

For those of you that grew up on the 2600, you know that the system was constantly improving.

Did you ever look for any benchmarks in games, improvements that let you know the “next big step” had occurred?

For me, I always thought that games with any sort of text were huge back in the day (like the Activision logo at the bottom of the screen), as were multi-segmented, multi-colored humanoids in games (such as Pitfall Harry).

This became very important for playground bragging rights as the Intellivision became more and more popular and us VCS acolytes had to ‘defend’ our system.

As a kid, were there any big “steps” you guys used to look for in your 2600 games to know Atari had reached its next level?

This is an interesting question.

I got an Atari 2600 when I was in first grade, in 1981. By that time the console had been out for 4 years already. We played it until the Atari 7800 came out, in 1986, but then switched over to NES, about a year later.

There wasn’t much in the way of a “bragging rights” war on the playground where I grew up.

Most kids who had any video game system at all had an Atari 2600, and we were happy to have them. I think many of us didn’t have anything (apart from arcade coin-ops) to compare with. I knew one or perhaps two family friends who had an Intellivision, and one family friend who had a Colecovision, one cousin who had an Atari 5200, one cousin who had an Odyssey2, and one or two people who had some type of home computer that could play games:  a Commodore 64 or Apple ][, Atari 8-bit, or maybe an IBM compatible. I knew a lot of kids who didn’t have any videogame console at home, at all.

Some kids just had those little handheld electronic LED-based games made by companies like Coleco, TigerVision, and Mattel, that weren’t quite videogames, but pretended to be.

We played all of them. Although, the LED handhelds were obviously inferior, and we quickly grew tired of them. Everyone thought that video games were cool and fun, and we didn’t pay very much attention at all to whether one system had better graphics. If it was hooked up when we went somewhere, we played it.  It was enough that it existed at all, and if it was available, we played it as much as we could.

The Intellivision households had a console that offered better graphics, but had awkward controllers and a smaller library. ColecoVision had the Atari 2600 adapter and slightly less awkward controls compared to the INTV, but still a far cry from the simple, rugged Atari 2600 joystick. It also had a 10-second delay when booting, which we found annoying. We maybe noticed that the graphics were a bit better, but overall it didn’t matter as much as you might think. Graphics were important, but gameplay was by far the most important quality.

And of course we were well aware that the original arcade game always had much better graphics than an Atari 2600 port of the same title. That was understood, to be expected, and forgiven, most of the time (Pac Man and Donkey Kong not so much). But also the arcade games were harder, and were designed to suck quarters out of our pocket, while Atari games were designed to be challenging without being too frustrating, and to give hours of enjoyable play.

Bottom line: If a game was fun, it didn’t matter if it didn’t have the best graphics, but a game with amazing graphics that wasn’t fun to play was something we looked down upon. This has always been true, and always will.

Since the 2600 had been out a few years before my family got ours, I wasn’t all that aware of the release date of different games. Games existed, and when I learned about them for the first time I wasn’t even thinking about whether the game had just come out, or if it had been out for a while, and I had only just heard of it. Games existed; they just were.

I paid a bit more attention to publishers, and liked games by Atari, Activision, Imagic, and M-Network, and Parker Bros. the most. But even the publisher didn’t matter so much as the quality of the game. I would play anything and everything I could get my hands on, and continued to play what I liked, and returned to re-play games that I enjoyed frequently.

That said, we did take note of certain games that seemed to evolve through sequels, such as Pac Man > Ms. Pac Man > Junior Pac Man; Defender > Stargate; Donkey Kong > Donkey Kong, Jr; and Pitfall > Pitfall II.

Sure, some games did have poorer graphics, or simpler play, or just sucked, but aside from the direct sequels where it was obvious, we weren’t all that aware that one game was older or newer, or that one game had extra chips inside the cartridge that enhanced the circuitry inside the console. That was something that I learned about much later, when I was old enough to appreciate the technology and understand it a bit better.

I didn’t think of game technology improving over time within a console’s life cycle; I just thought “Hey this game is better” or “They really packed a lot into this game” or “This game is very sophisticated compared to that game.” In other words, to me it felt more either a design choice to make a game more or less sophisticated, or a matter of the developer’s skill or work ethic to make a game better or worse, than what the technology allowed. I didn’t realize that it took technology such as larger ROM or additional processing chips to make certain games possible.

It only became apparent to me that newer hardware could do stuff that simply wasn’t possible on older hardware when the Atari 5200 came out. I don’t remember when I first played the 5200, but my cousin had one, and I got to play it whenever we’d go over to visit. He was the only kid I knew who had the 5200, and its graphics were amazing compared to the 2600, but again the library was limited and the controllers weren’t as good as the 2600’s joystick. We speculated at the time that the 5200 games could have better graphics because the console and cartridges were physically bigger, but the reality was this had nothing to do with it. Although, reinforcing this errant belief, NES cartridges also were larger, and the NES had significantly better quality games overall. We would have been very surprised back in the day to see just how much empty space was inside those plastic shells.

It still didn’t occur to me that there could be extra chips inside certain carts that made them capable of more sophisticated graphics and game play within the Atari 2600 library. It was certainly one of the factors that enabled the popular, but older 2600 to continue to stay relevant in the market years after newer consoles had been introduced. But it wasn’t like we knew why the games that came out in 1982-84 often had better graphics than games that came out in 1977-81; we just knew they were good games and it seemed like maybe the developers were getting better at making them as time went on, but it wasn’t like the best of the old games weren’t still great. And there was certainly a lot of titles that were released on the 2600 late in its life cycle that were inferior to games that had been released years earlier. What was possible continued to improve over time, but actual quality varied quite a bit over the life span of the system, and seemed to have more to do with how much the developer cared (or had budget for) than with the year the game was produced in.

When the 7800 came out, we felt that it was the best Atari to have, as it had backward compatibility with the old 2600 library (that was still every bit worth playing) plus decent joysticks (at least they centered automatically!)

Of course when the NES came out, it was immediately obvious that it was a quantum leap over even the 7800, due to its vastly superior sound capability, which allowed all games to have a musical soundtrack to it that could not be equaled on any Atari console. The design of many NES games was more sophisticated as well, offering adventuring quests with stories and puzzles and complex maps and item inventories where the Atari 7800 just offered more of the same action-oriented Arcade games that we’d seen for years.

Yet, still not everything across the board was improved. At first I didn’t care for the NES gamepad, which didn’t have a joystick, but instead featured this odd thumb-cross we didn’t yet know to call a D-pad, which at first I found very uncomfortable and even painful to use, although my hands quickly adapted and got used to it.

Again, the quality of the game play was what hooked me and got me to play through the hand cramps and blisters until my hands developed the stamina to hold the uncomfortable little rectangle. NES games didn’t just offer superior graphics and music, but more sophisticated types of play, where exploring and adventure became the dominant style of play, ascending to on par with twitchy hand-eye coordination skill and action. When the NES came out, everyone knew that a new age had dawned, and that the venerable Atari was obsolete. Most of us moved on, but a few never forgot.

A tale of two GML scripts: code optimization through iterative development

Today I wanted to share two versions of a function that I wrote, in order to show how my iterative approach to software development works when I am doing code optimization to improve performance.

This example comes from my iMprOVE_WRAP asset. It’s a function that returns the shortest distance (taking into account the wrap capabilities of the calling object) between the calling instance and a target object.

The first implementation works, in that it correctly does what it’s supposed to do, but I never released it, because I wasn’t satisfied that it was good enough code to ship.

///iw_distance_to_object(target_obj, x1, y1, x2, y2, do_wrap_h, do_wrap_v,)

///@description Returns the distance_to_object from an improve_wrap object calling this function to another instance. 
///Compares all relevant points for the iw_object and returns the nearest distance, taking the wrap range into account.
///@param target_obj id of the target object to determine the distance to.
///@param x1 left x boundary of wrap range
///@param y1 top y boundary of wrap range
///@param x2 right x boundary of wrap range
///@param y2 bottom y boundary of wrap range
///@param do_wrap_h set whether the horizontal wrap is on (true) or off (false)
///@param do_wrap_v set whether the vertical wrap is on (true) or off (false)


//get the distance from the nine virtual positions
//return the shortest distance
var obj = argument[0];
var iw_distance, iw_distance_up, iw_distance_down, iw_distance_left, iw_distance_right, 
    iw_distance_up_left, iw_distance_up_right, iw_distance_down_left, iw_distance_down_right;
var tempx, tempy, shortest;
var x1, y1, x2, y2, range_width, range_height, do_wrap_h, do_wrap_v;

//keep track of original location of target object
tempx = x;
tempy = y;

//set up wrap range
x1 = min(argument[1], argument[3]);
y1 = min(argument[2], argument[4]);
x2 = max(argument[1], argument[3]);
y2 = max(argument[2], argument[4]);
range_width = x2 - x1;
range_height = y2 - y1;

do_wrap_h = argument[5];
do_wrap_v = argument[6];

//check distances
//check center
iw_distance = distance_to_object(obj);

if do_wrap_h && do_wrap_v //wrap vertical and horizontal
{
  //check corners
  x = tempx - range_width;
  y = tempx - range_height;
  iw_distance_up_left = distance_to_object(obj);
 
  y = tempx + range_height;
  iw_distance_down_left = distance_to_object(obj);
 
  x = tempx + range_width;
  iw_distance_down_right = distance_to_object(obj);
 
  y = tempy - range_height;
  iw_distance_up_right = distance_to_object(obj);

  //check left and right
  y = tempy;
  x = tempx - range_width;
  iw_distance_left = distance_to_object(obj);
  x = tempx + range_width;
  iw_distance_right = distance_to_object(obj);

  //check up and down
  x = tempx;
  y = tempy - range_height;
  iw_distance_up = distance_to_object(obj);
  y = tempy + range_height;
  iw_distance_down = distance_to_object(obj);
 
  shortest = min(iw_distance, iw_distance_up, iw_distance_down, iw_distance_left, iw_distance_right, 
                iw_distance_up_left, iw_distance_up_right, iw_distance_down_left, iw_distance_down_right);
}
if do_wrap_h && !do_wrap_v //do_wrap_h
{
  //check left and right
  x = tempx - range_width;
  iw_distance_left = distance_to_object(obj);
  x = tempx + range_width;
  iw_distance_right = distance_to_object(obj);

  shortest = min(iw_distance, iw_distance_left, iw_distance_right);
}

if do_wrap_v && !do_wrap_h //do_wrap_v
{
  //check up and down
  y = tempy - range_height;
  iw_distance_up = distance_to_object(obj);
  y = tempy + range_height;
  iw_distance_down = distance_to_object(obj);

  shortest = min(iw_distance, iw_distance_up, iw_distance_down);
}
if !do_wrap_h && !do_wrap_v
{
  shortest = iw_distance;
}

//return calling instance to original location
x = tempx;
y = tempy;

return shortest;

Let’s take a moment to appreciate this function as it’s written. It’s well-structured, documented, and expressive. First we declare a bunch of variables, then we do stuff with the variables, then we get our answer and return it. And this gives a correct result…

So what’s wrong with the above? It’s an inefficient approach, which checks each virtual position of the wrapping object. If the calling instance wraps vertically and horizontally, it has to temporarily move the calling instance 9 times and check the distance from each of 9 virtual positions, then return it back to its original position, only to return the shortest of those 9 virtual positions.

There’s also a lot of code duplication.

Still, it’s not horrible code. But it’s up to 9x slower than the distance_to_object() function it’s based on, if you’re wrapping in both directions, which will probably be common. I didn’t think that was good enough.

Rather than check each virtual location to see which is the shortest distance, we just need to know whether the horizontal and vertical distances are more than half of the width and height of the wrap region. If they are, then it’s shorter to go around the wrap. To know this, you simply take the x and y values of the two positions, subtract one from the other, and compare to the size of the wrap range. Once you know which virtual position is the closest one, you can temporarily place the calling instance there, and use distance_to_object() to get that distance. Put the calling instance back where it was, and then return the distance.

I realized as well that depending on whether the calling object wraps in both directions, you may not need to check for a wrap shortcut in the horizontal or vertical. So we can potentially avoid doing some or all of the checks depending on whether the do_wrap_h and do_wrap_v arguments are true or false. As well, this means we can avoid declaring certain variables if they’re not needed, which conserves both execution time as well as RAM.

I usually create local script variables in a var declaration, and assign the arguments to them so the code will be more readable, but I wanted to avoid doing that so that this function could be as lean and fast as possible. This might be an unnecessary optimization, but that’s hard to predict since I have no way of knowing ahead of time how this function might be used in a future project. In a project with many wrapping instances, it could very well be called many times per step, and every optimization could be critical. Since the script is intended to be included as a function in an extension, once I have it working properly it shouldn’t be opened for future maintenance, so making the script readable is not as important. So I opted to remove the local variable declarations as much as possible and just use the argument[] variables directly.

Also, to ensure that the wrap range is defined properly, in the non-optimized version of this function, I declare x1, y1, x2, y2 and assign their values using min() and max() so that (x1, y1) is always the top left corner, and (x2, y2) is always the bottom right corner of the wrap range. Technically for this function, we don’t care precisely where the wrap range is, only what the width and height of the wrap range are. That being the case, I can further optimize what I have here, and rather than use min and max, I can just take the absolute value of the difference of these two values.

It turns out that the process I went through to optimize this function is pretty interesting, if you care about optimizing. So I’ll go into greater detail at the end of this article about the approach I took to get there. But for now, let’s skip ahead and look at the finished, optimized function. Here it is, re-implemented, this time doing only the minimum amount of work needed:

///iw_distance_to_object(obj, x1, y1, x2, y2, do_wrap_h, do_wrap_v)

///@description iw_distance_to_object returns the shortest distance in room pixels between two objects in the wrap range, 
///taking into account the horizontal and/or vertical wrap properites of the calling object.
///@param obj the id of the target object
///@param x1 left x boundary of wrap range
///@param y1 top y boundary of wrap range
///@param x2 right x boundary of wrap range
///@param y2 bottom y boundary of wrap range
///@param do_wrap_h set whether the horizontal wrap is on (true) or off (false)
///@param do_wrap_v set whether the vertical wrap is on (true) or off (false)


if !(argument[5] || argument[6]) //not wrapping actually
{
 return distance_to_object(argument[0]);
}
else
{
 //We're going to figure out which virtual position is the nearest to measure from
 //To do that, we have to compare the h-distance and v-distance of the calling instance and the target position
 //If this distance is <half the range size, then the original position of the calling instance is closest
 //Otherwise we have to use one of the virtual positions
 //Then we're going to temporarily put the calling instance in that location, get the distance, and put it back 
 
 //arguments
 var tempx = x, tempy = y;
 
 if argument[5] //do_wrap_h
 {
   var range_width = abs(argument[3] - argument[1]);
   if abs(x - argument[0].x) > (range_width * 0.5)
   {
     x -= sign(x - argument[0].x) * range_width; 
   }
 }
 
 if argument[6] //do_wrap_v
 {
   var range_height = abs(argument[4] - argument[2]);
   if abs(y - argument[0].y) > (range_height * 0.5)
   {
     y -= sign(y - argument[0].y) * range_height;
   }
 }
 
 var d = distance_to_object(argument[0]);
 
 //return calling instance to where it was
 x = tempx;
 y = tempy;
 
 return d;
}

We don’t need to measure all nine distances to know which is the shortest; we can tell by comparing the direct distance to the size of the wrap zone — if it’s less than half as big as the wrap zone, the direct distance is the shortest. If not, then we need to wrap. We can check the x and y axes separately, and if both are called for then we can just combine them.

The second function should be much faster to execute, and uses less RAM. How much faster? Well, let’s do a test project using my Simple Performance Test and compare.

Download the iMprOVE_WRAP distance_to_object test project

It turns out that the improved code runs about 50% faster than the old code! That’s a measurable and worthwhile improvement. Although, that said, the old function ran well enough that I could have released it, and likely it would not have been a problem for many uses, particularly in Windows YYC builds.

Appendix: Optimization through Iteration

(more…)

iMprOVE_WRAP 2.2 released

iMprOVE_WRAP 2.2 has been released.

I’ve added two new GML functions to the asset: iw_point_distance() and iw_point_direction(). These functions work much like the built-in GML functions point_distance() and point_direction(), except they take into account the iMprOVE_WRAP wrap region.

Release Notes

Version Notes
2.2 New functions:

  • iw_point_distance(): returns the shortest distance between two points, taking into account the wrap zone.
  • iw_point_direction(): returns the direction of the shortest distance between two points, taking into account the wrap zone.

Get iMprOVE_WRAP

GameMaker Marketplace

itch.io

Full Documentation

iMprOVE_WRAP 2.1 released

iMprOVE_WRAP 2.1 has been released. Get it at GameMaker Marketplace or itch.io.

Full Documentation.

Release Notes:

1.0 Initial release
1.0.1 Updated iw_draw_self_wrap() to use image_blend rather than c_white for the color argument.
2.0.0 Added new functions:

  • iw_draw_sprite_wrap(): an iMprOVE_WRAP version of draw_sprite()
  • iw_draw_sprite_ext_wrap(): an iMprOVE_WRAP version of draw_sprite_ext()

Improvements:

  • Boundary drawing now occurs at wrap corners as well.
  • Phantom collison checking also occurs at wrap corners.
  • iw_collision_wrap() and iw_collision_wrap_map() functions now incorporate do_wrap_h and do_wrap_v arguments, and only perform collision checks where they are needed. They still return a value for all locations, but where no check is needed, they return noone.
2.0.1 Improvements:

  • iMprOVE_WRAP demo resources have been placed in folders to keep them tidy when importing the asset into a project.
  • oIMprOVE_WRAP_demo sprite has been updated to allow for more precise positioning. Sprite is semi-transparent, with a yellow pixel at the origin
  • oIMprOVE_WRAP_demo object now draws guide lines indicating the height and width of the wrap range. This is useful in confirming that clone drawings and wrapping is occuring where it should.
  • iMprOVE_WRAP demo dashboard text has been updated to be a bit more clear
2.1 New functions:

  • iw_distance_to_object(): returns the shortest distance to the target object from the wrapping object, taking into account all directions available.
  • iw_distance_to_point(): returns the shortest distance to the target point from the wrapping object, taking into account all directions available.

New demo room for the iw_distance_to_object() and iw_distance_to_point() functions

 

YoYoGames seeks beta testers for OS X GMS2 

I don’t have a Mac, but I sure want GMS2 to be a first rate product on OS X.

It’d be great if they could someday announce a Linux port of their IDE as well. I’ll never give up dreaming. 

Sign up for the OS X GMS2 Beta:

​https://www.yoyogames.com/blog/414