I wrote the first version of this game about eight or nine years ago using
Visual Basic 6.1. I didn't have the internet and my VB experience was quite limited but despite the drawbacks of coding without my pal the ever versatile 'pointer' which C# proudly boasts I did manage to put together a project that wasn't half bad. Mind you when I started out I wanted to write a simple space game without too much fan-fare. Maybe the earth and the moon and a few klingons, a phaser cannon and some colonists stranded somewhere. But as I was writing from week to week and burning little green bags of inspiration I tangled my spider code into a jumbled mess of convoluted patches of ideas that wound up being a really cool game, despite the ugly mess of spaghetti-code behind it. Instead of controlling a single space-ship, as I had intended, you were controlling hundreds of astronauts with proficiencies like "engineering", "chemistry", "geology", "piloting" and "medicine". There were many ships which your engineers built from materials you mined with the aid of your geologists and their EVA suits. Chemists compressed atmospheric gases to produce fuel, they used their chemistry-labs to provide your engineers with raw materials and your pilots flew the ship around. After about 8 or 9 months of debugging testing and rethinking, twisting the whole mess around and playing with it, I never managed to write the 'save game' and 'Load game' functions which were a major drawback since getting to Mars from your Terran-One base on the planet earth could take up to 12 hours! Yikes! There wasn't any real plot, no aliens, no fighting or firing until the last few weeks when I hid a bomb somewhere inside Terran-One and your hero had to rescue his girl Miranda off some asteroid, bring her home and save the planet by defusing the bomb. Granted, the plot was thin, but the whole concept was "Explore the Solar System" so the planets and moons revolved according to the latest info at NASA's web-site and the gravitational pull of each body was realistic enough to be a lot of fun. There were caves and resources, crashes and explosions. And it was a major project that was both interesting and fulfilling.
This time around I've got the whole thing plotted out in my head before hand. It will be very similar to the original but this time its War! Evil clones, or spider aliens from Mars, or David Bowie in a Tin-Can shooting asteroids with his gory minions, I'm not sure yet because so far I've been working on this for about a month and a half and I've got the landscape collision detection working pretty good. You'll notice the (1) in the title "Mars Mission (1) : Surface Landing" this is an on-going project and since there's a lot to talk about in the collision detection that I've got working so far I figured I'd break the project down into a few different articles and we'd start with this one. There was a smaller project published here on the CodeProject about a year ago called A Simple Lunar Lander Clone which let you fly around a single frame landscape and try to land your ship.
The source code available in this article is an executable which I put together to help me test-run and debug the landing phase of the game. It has a bunch of features you can see using the F1 key and summoning the
groupbox shown in the image below that won't appear in the final release, so to speak.
The Horizontal-scroll bars and all the other controls here allow you to generate the landscape that you want.
- Average Altitude : essentially, is 'sea-level', though there is no actual 'sea'. Its the average ground-level measured from the top of the atmosphere (0) to the ground below(>0).
- Altitude-Deviance Factor : for the
Rolling type of terrains(set with the help of the combo-box near the bottom right) the alt-dev factor specifies the height of the hills and rocks that make up the landscape
- Width : the number of
landscape-cells that make up your landscape
- Gravity : the force of gravity can be changed anytime you're flying the ship around
- Num.Caves : number of caves that will be generated on your landscape
- Cave Ave Depth : the average number of
cave-cells which each cave will have once generated.
The checkboxes were extras that I needed to help find and resolve the various bugs that caused me to trip along the way :
- Show crease : the landscape winds around and around so the last cell is connected to the first cell. This options draws a vertical line between these two cells
- Show Ship Radius : this draws a circle around the ship which defines the border of the collision detection object. The collision detection functions do NOT take the ship's image into consideration when determining if the ship has collided with the landscape but rather approximates the ship's size and location with a single point and radius which define the circular area that the ship takes up. Simply put, its like a ball bouncing around out there.
- Draw Points : will draw the points that are used to connect the landscape cells making it easier to see where you are and what you're hitting.
- show cave normal : the normal angles of the cave walls are the angles at which each cave wall pushes the moving objects away from them. These are used to determine the deflection angle which a collision makes with the wall and subsequently calculate what angle the ship will take once it bounces off something. Checking this will draw a bunch of hair-like lines sticking out of the walls of the cave.
- Show Cave Cell : the biggest distinction between the surface and the caves is that on the surface every single
(x, y) cartesian point is directly above a single landscape cell and there is no confusion. However, because the caves wind around and overlap each other any point on the cartesian plane can logically correspond to any number of cave-cells inside any number of caves. And so it is crucial to remember where your ship is at the end of every
move() or it'll teleport for no reason or, worse!, fail to detect a collision and you wind up in the nether regions of nowhere looking for a way back onto the game-map! and that can't happen.
- Draw Debug : handy! this was a very powerful tool. The collision detection scheme I used involves rotating the game-world around the moving object and making a rectangular path that is square to the screen before testing if the object collides with anything. What this feature does, when in debug-mode, is show you a second form with the rotated collision detection system so that you can see where things are going wrong and what needs to be fixed. Before I put this together I was using plastic celluloid sheets, sticking these to my screen, drawing the landscape, spinning the whole thing around on my desk and writing down 6-digit numbers of points and line locations trying to figure out what was going on. And I got the landscape working that way but when I got to the caves I decided to go the graphic route and it really paid off!
Aside from those
CheckBoxes you also have one
comboBox to let you pick between three different types of landscape :
Cratered. You have a button to generate a new landscape and there are two
radioButtons to let you "fly" or "debug". The
classControls has a label-button which will change color when a mouse moves over it. It has a
boolean Flag which can toggle when clicked and be used by your program as if it were a regular boolean variable. You can see examples of these buttons in below the checkbox "Color Background" which lets you specify the color of your landscape, sky and caves.
Below you'll see screen captures of the three different types of terrains :
here's a look at a typical cave
Flying Your Spaceshuttle
All your flying is done using the mouse except for your thruster selection (jet-speak for "gears") which are selected using the numbers 1-5. F2 will pop-up your ship's info shown below.
The values you see here and presented for the gamer are a bit misleading when trying to debug the game. For example, if you're on the surface (or flying around above it) you can read your Longitude, that's the distance east or west of the meridian, but internally 0east(zero degrees east) is the cell midway down the array of landscape cells and a bit further east is a bit further down the array until you reach the end of the array and have looped around again to find yourself at
cell, or 180West. Altitude, though measured as zero at the top of the atmosphere internally, is the distance above sea-level, so the gamer sees an altitude of e.g. 1000meters but internally the Y location of the ship is
Y = <code>intAverageAltitude - 1000. The Velocity is indicated here as a magnitude and a red arrow in the
circleControl, but internally velocity is stored in a double-point called
ptVel. The circleControl on the left has two arrows, the velocity which I just mentioned, and the ship's angle. The red arrow points away from the direction of motion and the black arrow tells you which direction the ship is facing. And finally, Engines refers to the amount of force exerted by your thrusters when you've pressed the throttle.
When you're flying around you'll want to set the Engines to a value strong enough to give you lift but not so strong that you'll fly off further and faster than you wanted. For the time being your ship will only bounce around and never actually "crash" in the sense of the balls of fire which a real collision would produce so there's no real danger if you crash but you might have more fun trying not to. In this first phase of the game the ship rotates on a dime as fast as you can move the mouse, later I'll add a dampening feature that will make some ships spin faster than others making it more difficult to pilot some heavier vessels and keeping the stealthier fighter ships more maneuverable. But for now your ship will face towards the mouse on the screen unless you've already landed safely. Then the left-click of the mouse will fire the engines and you're on your way. Remember you can adjust gravity to whatever you want and there's that last button "Halt ship" that I didn't tell you about which will instantly halt the ship's motion.
you can zoom in as close as you like using the mouse wheel
or you can zoom out (the limits aren't yet defined so if you go too far it'll just look weird)
Aside from that you don't need to know much else to get going so just take her out for a ride, bring her back to the dealership and we'll have the insurance papers ready for you to sign.
But this wouldn't be much of an article if I didn't explain anything about what I did or how I got it working so, now that you've taken your gravol pills and suited up, we'll talk about the code for a bit.
The surface landscape is made up of elements called
classLandscapeCells joined together in an array in a parent class called
classLandscape. Each cell element that makes up the landscape has a single point of type
classDoublePoint which is nothing more than a fancy
PointF that uses double variables instead of floating point variables. The X components of each cell are uniformly distributed on the surface and only the change in altitude from one cell to the next varies from cell to cell making it a trivial matter to determine which cell any object is at at any time. Their values are measured in a positive Y axis growing downwards and a positive X axis growing to the right and so, all my calculations in this project (and most other projects that involve graphics) are done using a variation of everything we all learned in high-school trigonometry. By simply rotating the unit-circle clockwise instead of counter-clockwise for all my calculations everything I plan on paper needs no translation to put directly onto the screen. Positive Y is down and we have no fuss.
In the diagram below you see an example of a few landscape cells, note that the class holds a pointer to two points although the cell only actually has one point to call its own, the point to the left of it. It also has a pointer to the right neaghbour's left point. This way when I decide to create earth-quakes and volcanoe eruptions the points of each cell can vary and the landscape's cohesion remains intact. The glorified
classDoublePoint that I've been using has a locking mechanism which actually came in handy. Using a static boolean variable to lock/unlock the entire class in conjunction with public boolean variable in each instance I can lock the values of any
classDoublePoint so that if my code at a later time tries to change one of these values it will warn me and I'll realize that I've bungled something along the way(and this proved to be a very effective debugging tool!).
classLandscapeCell also has a
type so that the program knows if its a simple landscape cell or the opening of a deep dark forboding scary cave. In the case of a cave type landscape cell the
classCave pointer in the
classLandscapeCell is not
null but actually points to a cell which was generated when the entire landscape was built.
In the original Mars-Mission game the caves were similar to the landscape cells in that you could tell which cave-cell you were in simply by looking at the Y component of your ship's location since each cell was measured straight down and then the sides shifted to the left by so much and to the right by some other value. That had its drawbacks because it couldn't loop around but it was a lot easier to manage than the scheme I've got working here.
classCave also has an array of type
classCaveCell but the cave cells do not drop straight down. Instead each new cell drops down in a direction normal to the angle between the previous cell's two sides a constant distance equal to
K in the
classLandscape. Then, when the center point of the next cell is located, the new cell is angled slightly to one way or the other and the side points are located an equidistant to either side of the center point. And the next center point is then located based on the last cell's center point and angle. See the diagram below, its not as complicated as this last paragraph makes it sound.
In the diagram above you can see that the 0th cell opens onto the landscape and is exactly
K game units wide and also drops vertically downwards because every cave-mouth has both its
ptLeft points set to the same altitude, or Y values, with their X values separated by
K units. I'm using these variables here for the sake of explanation but
K is really called
static</span /> public</span /> int</span /> intLandscapeCellWidth = 1000</span />;
(which I will set equal to 1024 before moving any further in this project since the nice round decimal number doesn't jive with my computer who'd rather do binary bit-shifts than long division) and instead of having
ptLeft the cells have three cell walls of type
classCaveCellWall, one right wall, one left wall, and one bottom wall which is only actually instantiated in the last cell and is otherwise left to point to
null. Each cell wall either has a
ptMine & a
ptNext or an actual
ptRight in the case of the bottom wall. Also, each cell wall has a
double variable which indicates the angle normal to that wall. Similar to the Landscape cells that had their
ptRight pointers pointing to their right neaghbour's
ptLeft point, as explained above, the cave cell walls
ptNext really points to the next cave cell's
ptMine so nothing is doubled up and we can all rest easy knowing that we can "reuse, reduce and recycle!" on the cheap.
But none of this means anything if the program can't figure out whether or not your ship should bounce or fly. But any space-cadet worth his vacuum of space will tell you you don't get to be an astronaut if you don't eat a good breakfast, work real hard at school and dream about Tang. and besides all that you might be surprised to discover that all you really need to understand the mathematics behind the collision detection scheme is the same basic geometry, trigonometry and algebra you did in high-school. Though it took me weeks to get this working properly the math is not that bad. Let me just wind you through it a bit and you'll see what I mean. There was a lot of tinkering involved and I had to devise a few schemes to see what was actually happening because the numbers I was dealing with were big and ugly so getting down and dirty with the digits was slow and tedious but here it is nice and neat for you.
The most important thing about the collision detection in this project is the fact that any thing that moves and can potentially collide with anything else has all of its collision-moving-crashing-and-flying information held together in a single class which can be added to a space ship, an astronaut, a missile or Bob's lunch box out in the mine. What I'm talking about here is the versatile, portable and happy to be thrown, fired, launched or dropped class apply known as the
public</span /> class</span /> classCollisionDetectionObject
This is where we keep the
classDoublePoint variables like velocity, current location, intended location as well as some other stuff like mass and radius, and an enumerated type that keeps track of "where" the object is such as "cave", "surface", "base" or "interplanetary space". Mass doesn't come into the picture just yet because we'll just assume that whatever is hitting a mountain side is so much smaller than the mountain side that the mass difference between them is infinite and the mountain just ain't gonna budge. But later on, when Bob's lunch-box falls off the precipice and plumets down towards Jenny below, we'll have to see if Bob's aunt's pound cake is enough to make a dent in Jenny's favourite ride or whether her moon rover brushes it off and she gets a free lunch. For now, however, we're just going to see whether or not our space shuttle should fly or bounce(we're not keeping track of damage just yet we're just gonna bounce around for a while and come back to that in later chapters of this on-going project).
And so, here we have ourselves a mighty class, the great and unsurpassed
classCollisionDetectionObject which is passed, by reference, to the functions
public</span /> classCollisionResult moveLandscape(ref</span /> classCollisionDetectionObject cCDO)
in the class
public</span /> classCollisionResult moveCaveScape(ref</span /> classCollisionDetectionObject cCDO)
in the class
For now, in this testing ground, the timer in the executable calls
move() function which in turn determines whether control really falls to the
move() function. Sometimes this happens right away such as when the object in question is already inside a cave as indicated by its enumerated type defined just for that purpose. At other times the collision detection scheme goes through the motions in the
move() function and there calculates that the object is moving from the surface into the cave at which point the
move() function is called and a resultant collision (or not) is taken from that function, kept on hand to see if there may be something else obstructing the object from reaching the mouth of the cave and when all has been tested whatever collision occurs first is where the object is stopped. I say "whatever collision occurs first" because in cases where the object does not transition from one environment to the next and is just flying around without hitting anything the default "collision" of no-collision at infinity places the object right there where it intended to go. But when the object starts in one location like the surface and then transitions into another such as the cave then the cave has to return a "collision" report telling the program that the object is no longer on the surface but inside the cave whether or not it actually collides with the cave wall.
The transition is pretty smooth when you fly into a cave because on the surface you see the surface and a couple of cells into the caves over which you fly until you enter a cave and then you see as far into the cave as the narrow passage allows as well as the surface before diving right in and passing the first few cells where you no longer see the surface at all but are rather immersed into the cavernous darkness below.
Let's get down to business, shall we. The collision detection uses the object's current position on a cartesian plane, as well as that object's intended position or "end-point" which is also on the same cartesian plane. However, inside a cave, the cave-scape's
move() function also needs to know what cell the object is flying in at the start and so this is included in the
classCollisionDetectionObject when the object in question is already inside some cave. In order to calculate whether or not the object's intended trajectory causes it to collide with a wall or the floor of the landscape, the entire system is rotated about the object's end-location such that the resultant test-system looks exactly the same but the start location and the end location are on the same Y coordinate and we can define a rectangular path along which the object is moving. We then, knowing the X coordinates of the start location and end location on the surface, test each rotated landscape cell against this rectangle to see if any of these obstruct the object's motion. Because the moving object's position is approximated using both a point and a radius the rectangle of motion does not test the object's final location completely so we'll also have to test the end circular position against these same walls, more on that later.
When inside a cave, the program first tries to find the end-point inside a cell nearest the start cell by moving away from the known start cell in either direction and not going beyond the number of cells equal to the total distance travelled (straight line from start to end) divided by the constant
K (not its real name but
K's manager insists we refrain using its true identity!). If the end-point is not found it is assumed that the object is not only colliding into a cave wall but actually passes through and is therefore outside the cave. When this happens, the maximum number of cells are counted up and down away from the start cell, the direction of travel is evaluated using the start cell's angle of 'descent' to the next cell as a standard of comparison and the testing starts from the nth cell away from the known start cell and travels 2n cells in the opposite direction (following the direction of motion) and assumes that something will block the object from reaching the point it knows is not inside the cave. As I write this I'm realizing that if I know the direction its travelling I probably do not need to go back n steps and can safely assume its will be blocked moving forward from there. I'll test this theory later and tell you more about it in the next article.
Once we have defined the limits of our test system, we create the rectangle of motion by rotating this test system around the end-point. Then, going over each cell along the path which the object is travelling we rotate these into our test system and see if they traverse the rectangle of motion. When they do, we get to figurin'!
What happens next is all the magic. First we draw a line (figuratively) through the center horizontal line between the start and end positions at an angle normal to the wall/floor we're colliding with. At a distance of
R (radius of moving object) away from this center line we look at the value of Y and determine whether or not this point is on the test line. If it is then we have ourselves what is known in the computer space game and solar simulation biz as a "tangential collision point". Because, if you remember your tenth grade geometry, trig or whatever, a line touching a circle's circumference is perpendicular to the line drawn from the center of the circle to the point where this line makes contact with it. A line that barely touches the edge of a circle is known as a tangent line. Draw a line away from a circle's center and then a perpedicular line to it at the circumference and you've got yourself a tangent. And so, by doing this test we can see whether or not the "ball" or moving object hits the line "squarely" (Sponge-Bob square pants has lost his exclusive rights to the word "square" and pending his lawyers' resubmission to the Supreme Court we retain the privilege of using it here!) and so this ball then bounces neatly off the wall. More on that later...
Here below is a giant oversized replica of our ball bounces off the wall in a tangential-collision.
In the diagram above you can see the blue rectangle of motion and the one black line called "test-line" that makes a tangent with the red circle. This red circle is NOT the intended end position but rather the position at which the ball is obstructed from going any further. You can see this by the fact that the blue rectangle reaches to the edge of the image where the center location of the intended end point is. In this example, the test-line collides with the object and a tangent line through the center line places the tangent point of collision on the line and therefore the object hits the line squarely. Knowing the test-line's normal angle and the object's approximate radius we can calculate the tangent collision point which we then use to place the object into it's final position. What the program actually does is calculate the distance between the end-position in the rotated test system and keeps track of the nearest collision until all tests are made and then places the object that distance away from the original un-rotated position in the game-world in the object's actual direction of motion.
In the image below you'll see a different kind of collision, a non-tangential collision. These are a little bit more complicated but not entirely over anyone's head. I'm sure you'll see the logic, though I struggled for a month to get this working properly you will no doubt appreciate the simplicity of the solution. Like I mentioned above the difference between a tangential-collision and a non-tangential collision is when the line's tangent does not actually make contact with the test-line. Let's look at the diagram below :
If you look at the black line at the bottom left of the image you'll notice that extending it upwards to infinity would cut through the object's end position. That is because it doesn't quite reach far enough into the rectangle to stop it squarely in a simple tangential collision. The first thing we need to do is determine whether or not the collision is tangential. To do this we draw a line away from the circle's center and (step 1) find the point at which it reaches the perimeter of the object's radius. You can see this on the green circle on the right. The gray line extending away from the extended-test line and traversing that circle's center point strikes the green circle at a point labelled "Point Normal Intersection" in this image. This point's Y coordinate tells us that it is actually above the test line and the test-line does NOT make contact with it. And that is how we know that we have a non-tangential collision. What we then need to do next is calculate the difference between the horizontal center line and the top corner of the test line(step 2). Step 3 : knowing dyCollision (as labelled in the above diagram) and the radius of the object we can then figure out the angle of collision and add Pi to it to get the deflection angle (the deflection angle is the same as the normal angle in tangential collisions) which we will then use to deflect the object's trajectory after the collision. For now, all we want to do is place the object at the location furthest to the left, inside the rectangle of motion, and to the right of the nearest obstruction. Knowing dy and R and the angle of collision its a simple matter to figure out dx, subtract that value from the X-coordinate of the test-line's end-point and calculate the distance from the start location then see if this is the nearest collision.
tada! and you learned all that in high-school too!
The above calculations are used when the object's rectangle of motion collides with a test-line. But in some cases the end location exceeds the boundaries of the game-world by less than a distance equal to the circle's radius. When this happens outside the rectangle of motion we have what is called a "Circle Collision" or an "End-point Collision", the International Engineering Standards and Collision-Naming committee is still discussing the issue but for the sake of this article we will simply refer to them as "Circle Collisions". The
classMath has a static function which calculates whether or not a line collides with a circle and if so at what point of intersection.
public static bool LineIntersectsCircle(classDoubleCartesian ptCircleCenter, double dblRadiusCircle, classDoubleCartesian pt1, classDoubleCartesian pt2, ref classDoubleCartesian ptIntersection)
It requires either a
classRadian or a
classDoublePoint to be passed by reference and returns a boolean set to true only if the line intersects with the circle. So testing a collision with the object's end-position is only a matter of sending the circle's description along with the two end points of the line and it tells us whether and where there is a collision. The circle/line point of intersection is a matter of interest here as it is defined by this function as the point at which a line drawn perpendicular to the test-line and going through the circle's center intersects with the test-line. This value is set whether or not the circle and line actually do collide and is used by this program to calculate the angle of deflection.
speaking of which ...
Angle of Deflection
In order to calculate the deflection angle we need to know two things :
- The original trajectory angle
- the angle normal to the plane our object makes contact with
but you'll say "non-tangential collisions" do not make contact with a plane! Which I'm sure was the first thing you were all thinking, and you'd be correct. But in the case of the non-tangential collisions we just add pi to the collision angle and forget about the whole thing when we pass on our information to our trusty pal Mr.DeflectedCollisionAngle()
public</span /> static</span /> void</span /> DeflectedCollisionAngle(double</span /> dblNormalToLineOfCollision, ref</span /> classMath.classRadialCoor cRadVel)
</span />double</span /> dblNeededRotation = classMath.cleanAngle(1</span />.5</span /> * Math.PI - dblNormalToLineOfCollision);
double</span /> dblRotatedTrajectory =classMath.cleanAngle( cRadVel.angle + dblNeededRotation);
double</span /> dblResultantAngle = 0</span />;
dblResultantAngle = classMath.cleanAngle(-dblRotatedTrajectory);
double</span /> dblMagnitudeFactor = Math.Abs(Math.Cos(dblResultantAngle)) * .7</span />;
cRadVel.angle = classMath.cleanAngle(dblResultantAngle - dblNeededRotation);
cRadVel.radius *= dblMagnitudeFactor;
this is in fact a static helper function of which there are a few that do a lot of the rotating and array manipulations. What we do here is rotate this system so that the
NormalToLineOfCollision value is made vertical and our ball is necessarily bounding downwards towards an imaginary horizontal line off of which it needs to bounce. Then its as easy as calculating the bounce of the golf ball off the edge of the miniput simulator game you made when you were thirteen (or was I the only one who spent his summer months tinkering with graphics on an Apple ][ computer?), calculate the change in angle in the rotated system and add it to the referenced radial coordinate describing the angle and magnitude. What's interesting to note here is that the ball will not bounce like the Flubber ball you won't find at Wal-Mart or any SuperBall from your local drugstore. The idea here is that an object colliding with a wall at an acute angle will skim the surface and continue on its way deflected but otherwise unperturbed in its intended direction. However, should you crash head on into a mountain side then your ship won't be so resilient and the impact will smush it down somewhat and reduce its speed considerably. The calculation here is a simple one named
Cos(). Because the system is rotated with the normal facing upwards a head-on collision looks like a vertical drop. The
Cos() of an angle close to + or - pi/2 approaches zero and the
Cos() of an acute angle near zero or pi approaches one. Therefore, tack on a .7 drop and multiply that by the
Cos() and you have yourself a nifty way of reducing the speed of those nasty crashes while maintaining the strong heading for the odd unintentional bounce.
a Bad Bounce
but things aren't always perfect. I'm still devising ways to fix this but every now and then you'll hit a wall and say "that bounce was funny". this, I believe, happens when the ball bounces directly onto a point, hits and edge and does the funny freaky thing the wrong way. it happens in golf, it happens in hockey and by-golly it'll happen here too... it doesn't happen very often and I have a plan to use the line's normal as the deflection angle whenever a non-tangential collision occurs on the edge between two lines with nearly parallel normals. I'll tinker with that but you must be made aware that the warranty that comes with this code protects its designers from any such bad-bounces. read the fine print, buddy!
When a Push is required
Sometimes you just want things to slide. So you give them a shove. If you place your ship on the side of a hill it shouldn't just sit there. It should slide down the hill to the bottom. The deflection magnitude thing I mentioned in some previous paragraph helps that a lot but if the ship is stalled gravity will pull it down and a shove in the normal direction away from the wall pushes it sideways a little bit and then it falls and gets shoved and falls and gets shoved and so on until it winds up at the bottom. Collisions should place the object exactly where it belongs. Theoretically. But, unfortunately that just isn't the case, sometimes you have to ensure that your game world doesn't fall apart on some undetected obscure weird case collison that winds up sticking things where they don't belong. In any case, the
Push() was made to ensure that any ship falling through the landscape winds up on the surface again and though I've fixed those problems enough to happily say its not really an issue anymore I keep it around just for old-time's sake happy that it got me through the rough patch when debugging a pesky problem was getting me down. Sometimes, however, a push from one wall results in shoving the ship half-way through the adjacent wall. This can be a problem, and so every such situation along the landscape and along the walls of the caves is detected at the build time so that every wall and every landscape cell has a pointer to the cell towards which they push. When these two walls push the object back and forth a few times the program then
wedges() the moving object between them. This is done easily enough with a dividing line between the two walls and a bit of trig to figure out the distance away from the common point along this dividing line the wedged object needs to be.
You can see this in the
Wedge() function. I was going to implement the same thing in the
classCave but found it unnecessary.
I've already got the solar-system simulator up and running. I've downloaded a bunch of images from Nasa's various unmanned missions posted on the internet and plugged those into a
classRotatedImage which has them rotating 90 times per quarter turn so that you can actually see the planets and moons spinning on themselves as they travel about their primaries. There are hundreds of moons in the solar system and only a few of them have their own images and the rest use the same few default images but it already looks pretty good. I'll be adding the landscapes to the solar system simulator next. And then what I have to do right away is work on the game-save/load functions which are crucial if this whole project will be worth playing later. So I guess that's what's next.
Pending later will be the resources, astronauts and their proficiences and the actions they can do with the resources given their proficiences. Such as "Compress atmosphere" or "dig for resources", or "build radio" or whatever. When I have a peace-time system up and running we'll be looking at an actual war-game with fighter ships you pilot in a Defender type game setting, or Vanguard in the caves with turret fire, alien ships, missiles, bombs and all the rest which will require you to build your armaments, research technologies, mine for resources and train your pilots and engineers. I figure I'll be at this for the next year. An article every two months or whenever and we'll see how it goes.
to all you Space-cadets out there, "shoot for the moon and you just might hit the stars!"
Mars Mission (2) : Explore the Solar System