September 27, 2009

Bamf: DevLog 2 — Platforming & Line of Sight

Alright, so now I know it's going to be a platformer, it needs a platformer engine! These are fairly simple affairs.

This is partially for my family and might be of interest to those unfamiliar with programming so I'll start from the ground up.

Basically you want stuff to happen on the screen, which is a 2D grid. Each space is a pixel, and the area has a finite size. So you have objects, and each object has an X (horizontal) and Y (vertical) coordinate. Then each object just gets drawn to the screen at whatever its coordinates are. In most cases, the upper left corner is 0,0 and it counts up from there as you go right or down.


Then you can do some more complex things, like change the coordinates of an object by a small amount and repeating every split second, to get the illusion of movement (commonly known as 'animation'). You can tie changes to X to whether a button on the keyboard is pressed, and just have a constant addition to Y for gravity.

Now what if you want the character to stop if he hits something? You need to be able to see if two objects are 'colliding'; occupying the same area of the screen. Typically your objects aren't a single point on the grid, they're an area of the grid, so you can't just check their coordinates and see if they match. You need to check every coordinate within the first object's area, and compare them with all the points within the other object's area. It's easiest to do this if you just assume everything is a rectangle, and that's usually sufficient. If two objects have overlapping areas, they're colliding.

So now we can tell whether two objects are colliding. To make floors and walls, we have more objects, which we'll just call 'solids'. Whenever the player collides with one of these solids, it stop moving. But, if you just did that, the character would get stuck and never move again the first time it collided with a solid. So what you do is check for collisions at where the character would be if it moved, and stop movement beforehand.

Anyway, the collision checking and movement I'm doing is a more complex than this, but I'll talk about that later when I go over how I'm tweaking it. It still works essentially how I've described. At this point I've got a playable platformer engine. I can scatter some solids around a room, and run and jump around on them. It took me about half an hour to get to this point-- Hooray Game Maker.

Now I moved on to Line of Sight, because I really had no idea how to do it. I want any area of the screen that's blocked from the character's vision to covered up with black. This way, you won't be able to see through walls or floors as you typically can in a platformer.

My first idea is to cover the whole screen with black, then erase the black from the areas you can see by tracing lines starting at the player and going outward in incremental angles, continuing each line until it hits a solid. To get perfect shapes, I'd have to send a trace out to every pixel on the edge of the screen, covering any possible gap between lines. But, this is far too many lines to draw every 30th of a second. It slows the game down considerably because for every line, it has to check for collision at each of about a hundred or so positions on the grid to find out where it collides. There's a lot of math involved in comparing all the coordinates of every object with the given coordinates to see if they match up, and it costs time.

So I try drawing really wide lines, and only send out about 60 (or so, I don't remember the exact amount). The lines are wide enough that they cover up the gaps between them. But it's very imperfect now. It basically works; it gets across the idea that you're only seeing what the character can see, but it just looks awful, especially in motion, because it fudges so much.


So clearly this needs a different approach entirely. What if, instead of erasing the visible area, we only draw over the invisible area in the first place? For every solid object, trace out basically a shadow based on where the player is, and draw that shape.

You trace a line to each corner of the solid to find the angle (red lines), then draw shapes from the corners, to other coordinates that are far away but along that same angle (blue lines). By doing each pair of opposite corners, you don't even have to worry about figuring out which corners to draw from, as long as all the solids are rectangular. (If you want to be able to build objects out of non-rectangular shapes, that's more complex. So I'm just going into this knowing my levels will be mostly straight lines.)

Now instead of drawing a line for every pixel on the screen, we're drawing a shape (a polygon actually) for each solid object. We could probably do some checks to make sure we're not drawing shadows for the ones that are going to be covered up by other shadows anyway, but's faster just to draw them all and not worry about drawing over the same areas of the screen a bunch of times.

Plus, now it's pixel-perfect instead of having big, noticeable, weird shapes.

And as a bonus, holy crap, now we have all the math and everything already in place to calculate shadows. Time for lighting.

No comments:

Post a Comment