This is going to be a monster tutorial that'll take a while for me to write, so here are the first five parts:

Arrays and Bullet Code

Axe doesn't natively support any data structures, but that doesn't mean you can't use any in your Axe programs. Because Axe allows you to manipulate the bytes and bits of your calculator all you want, you can make your structures, made in whatever way best suits you. Axe is a versatile tool.

One of the most useful data structures (by far) is the array. An array is just a list of data elements, which could be anything—bullets, enemies, lobsters, etc. In fact, if you think about it, a tilemap is a type of array (a two-dimensional one).

So how do you make an array in Axe? Well, first you need to decide where to put it. Any safe RAM area (L1, L2, etc.) will do. Just make sure it's reasonably big enough. Arrays can take a lot of memory.

Now decide how each element is going to be stored. What are you representing with each element? Just for the sake of example, let's say you're making a program to keep track of some squares floating around the screen but always going in one direction. You'd need to keep track of its X and Y values, as well as how fast it's moving horizontally and vertically. That would be four bytes per element. (You could probably cut it down to three or even two if you desparately neede to, but I'll keep things simple.) The array could look like this:



Like with everything in Axe, we start counting from 0 because it makes our lives much, much easier.

To start working on our example, make a program called ASQUARE as follows:





This will be our main program. It should be pretty simple to understand: it first initializes some data (the code for which we're going ot put in a subprogram called ASQUI), then goes in a loop where it draws the squares until you quit. prgmASQUR will hold our subroutines.

For program ASQUI, just put this in:



That's it. L will hold the current size (length, number of elements) of the array. It'll get updated whenever we modify the array (in the subroutine itself).

Displaying objects in the array

An array of data (or any other data structure) is pretty much useless unless you do something with it. Here we'll make the subroutine DA that displays all the elements in the array.

Draw all elements wrote:

Variables:
The only global variable you really need will be one giving you the size (length) of the array (L).

L - current length of array (initialized to 0 at the beginning of the program and updated by the routine itself)

Input:
None.

Output:
None. It draws the sprites to the buffer, but that's pretty much it.

Code:





You probably don't need too much explaining, so I'll keep it short. First, there's "If L." This is to avoid an (almost) infinite loop when L happens to be zero. (We loop from 1 to L and subtract four more instead of from L-1 to L-1 because it's a lot faster to check each pass of the loop.) Then the routine loops through every element, using Pt-Change( to display each one.

We use Pt-Change( here because it's the easiest to work with when you want to draw a moving object. It works like this: Just before the main program calls DispGraph, we draw all the squares onto the screen so they appear, and once the screen finishes displaying, all the squares get removed with the same routine to make it "clean" again. This makes things easier if you're making a complex tilemap game where redrawing the entire screen every frame takes too much time.

Anyway, now that you've defined the subroutine, you can compile your program now! And run it! what does it do?

Nothing. It waits until you press CLEAR. That's because you haven't added any elements to the array yet. We'll get there in the next step.

Manipulating the array

Now a routine to add (push) an element to the end of the array. Put this in program ASQUR.

Pushing an element wrote:

Variables:
The only global variable you really need will be one giving you the size (length) of the array. Let's call it L.

L - current length of array (initialized to 0 at the beginning of the program and updated by the routine itself)

Input:
Since each element holds four values, let's make each one an argument.

r1 - X-value of new square
r2 - Y-value of new square
r3 - X-speed of new square
r4 - Y-speed of new square

Output:
None. (Of course, you could easily modify it to return a useful value, such as a pointer to the element added.)

Code:



Scary? Fine, I'll break it down.

Before it does anything, it checks if there are 177 elements in the list. This is because L1 can only hold 714 bytes of data, which is approximately 178*4 elements. If you're using any safe RAM location, make sure you change this limit accordingly. Having too many elements is called an overflow, which could mess up whatever data comes after L1 (in this case the variables A through Θ.

First look at the very inside of the mess of braces, at the line "L+1→L*4+L1-4." The real action starts there. First, it increments L by one (you probably know why). Since that command returns the value of L, you can keep doing operations on it (multiplying by four in this case). Since each element in our example is four bytes long, L*4 gets the offset of the next element in the array. But there's a problem here: Since you incremented L already, this now points four bytes ahead of where you're supposed to be. We take care of this by adding only L1-4 to the total.

That gives us a pointer to where to store the first byte of the element, so "r1→{L+1→L*4+L1-4}" would store the first byte there.

Now here's the fun part: By storing to a variable location, the pointer you stored to is returned in HL. That means that you can keep on storing to the byte after it by simply adding one! That's why the line above is enclosed by "r2→{ ... +1}": you just add one to get the next byte, then store to it. You can keep going like this for as long as you want; it's the single most optimized way to store a mass of variable data in Axe!

Next up we'll actually add the pretty squares. Promise.

Actually doing something

Finally we're going to add the actual enemies (squares). Here are some ideas for how they should be added:

    [li]Enemies always start at the center of the screen. That would be (44, 2Cool.[/li]
    [li]Enemies should move in a random direction.[/li]
    [li]Enemies spawn at random times.[/li]


To do this, we need to back to the main program, into the main loop (Repeat getKey(15):End). Change prgmASQUARES into this:





All the changes come just before the first sub(DA).

You probably understand this too. If a random two-byte integer is less than 4096 (that's a chance of 4096/65536, or 1/16), stick a square in the middle of the screen and give it a random X- and Y-speed. Then quit when the user presses CLEAR.

Before we compile, there's one thing that's missing. Something that every enemy/bullet system needs to have—movement. So change prgmASQUARES again:









All the new additions are right after the ones you just added. The point here is that it has to be before the first sub(DA). Otherwise, you'd be changing the squares' position after they get drawn but before they get erased, so they'd end up being "erased" from a different location.

Well? Compile and run!



It works! Amazing, eh? You can let it run for as long as you want, and even though it slows down quite a bit with a lot of enemies on the screen, they all move on their own!

The only problem now is that they keep wrapping around the screen. Usually that's not what you want to happen, since if an enemy or bullet goes off the screen, it should stay off the screen. We'll take care of that in the next section.

Getting rid of the extras

To make the squares more realistic enemies, we're going to remove them when they go off the screen. That calls for a new routine:

Removing an element wrote:

Variables:
And yet again, you need L.

L - current length of array (initialized to 0 at the beginning of the program and updated by the routine itself)

Input:
Well, we need to know which element ot remove.

r1 - Index of element to remove

Output:
None. (Again, you can modify it to return something useful, but we won't here.)

Code:



You can probably tell that it's a copy statement. What we're trying to do is copy everything after the element to be removed four bytes back—overwriting the element you want to remove. The routine also takes care of the array length variable (L) by subtracting one.

The only thing that should seem weird here is the extra "+1" at the end. It might not make sense, but it takes care of the case where you're trying to remove the very last element. If that extra little bit of code weren't there, the Copy( statement would try to copy 0 bytes backwards, which gets translated to a copy a 65536 bytes, which is definitely not what you want. This extra bit doesn't do us any harm besides slowing the program down a tiny, tiny bit, but it takes care of that scenario for us.

So now that you have the routine down, let's actually use it. Go back into the main program and change it to this:









All the changes are in the For(I,1,L) loop. It basically tests the X- and Y-values after they're changed to see if they're completely off the screen, and if so, they get removed. The reason I jump to a lable RM instead of writing the code twice is because it's smaller and even gets rid of any chance of some certain nasty coincidences I won't talk about here. The point is it works.



Next: Imma FIRIN' M-- How to make those squares do something other than move in a straight line.

Comments and suggestions and rants? Do I explain things well enough?
Just so you know, your [quote] tags are broken. You have to do [quote="text_here].

As for your tutorial, it's very well written. However, I find it much easier to delete an element from an array by filling it with zeroes. Then, in my display loop, I skip over the deleted elements. I also overwrite the deleted element with a new element when I add a new element into the array.
[quote="souvik1997"]Just so you know, your
Quote:
tags are broken. You have to do [quote="text_here].

As for your tutorial, it's very well written. However, I find it much easier to delete an element from an array by filling it with zeroes. Then, in my display loop, I skip over the deleted elements. I also overwrite the deleted element with a new element when I add a new element into the array.


How do you keep track of which ones are deleted? Shock

And thanks for telling me about the quote tags. Fixing.

EDIT: That looks a lot better Very Happy
I just fill the element's data with zeroes, and I skip over those elements when I parse the array.
How do you know which elements you can write back to, though? Do you parse it for the first null element every time you add an element?
I do exactly that. Your method seems to be faster than mine though, I'll try to use your method in my game and I'll see if there is any noticeable speed difference.
This looks like an extremely thorough tutorial; I'll have to re-read it in some more detail wen I have more time. Thanks for sharing, and keep up the good work!
kalan_vod on RevSoft suggested I write a tutorial with an actual example, so here it is.

This is a more specific tutorial on how to make SHMUP games. Some of the info overlaps with my Arrays in Axe tutorial, though, so I might just delete that someday.

After I finish this tutorial I'll probably work on one to make platformers Very Happy

How to make a shoot-em-up

Space Invaders, Phoenix, Phantom Star—you've seen shoot-em-up games before. They can come in a variety of styles, but in general they're games where you play as a single ship with unlimited firepower firing recklessly and indiscriminately on groups of "enemies" that are only trying to defend their home planet. Cruelty is fun.

This tutorial will guide you step by step in making a simple shoot-em-up. First, as with any game, we need to decide what our game should look like. Let's just stick with one level for the purposes of this tutorial, but we can have a lot of fun with how enemies and bullets move. Let's get creative; maybe something like this?

[epic screenshot goes here]

Looks good? Like with all my programs, the code's split into sections, because it makes debugging that much easier. We start my making the main program ASHMUP that sets everything up:





prgmASHMUPD will hold our data, prgmASHMUPI will set up (initiate) the variables, and prgmASHMUPR will hold all our subroutines. The actual game goes in the loop, so the user can quit at any time by pressing CLEAR. prgmASHMUPK handles user keypresses (and moves the ship accordingly), while prgmASHMUPM moves and tests the enemies and bullets.

The home ship

First let's make our ship move. Create program ASHMUPD and put this in:



That's going to be our character.

Now let's code the actual movement. Since our ship's just one ship, we won't put it in an array. We need to keep track of its X- and Y-positions; for simplicity, how about just X and Y? Here's prgmASHMUPK:





We move two pixels each time.

We haven't given it an initial position yet, so put that in prgmASHMUPI. The ship should start in the center of the screen, maybe a closer to the bottom edge. (44, 40) should work.



But how will we display the ship and all the other stuff in the actual game? For such a simple game, let's just clear the screen and redraw everything every time. It's actually faster than the alternatives in this case because everything on the screen is dynamic. So change the main program to this:







I've also commented the lines "prgmASHMUPM" and "prgmASHMUPR" since we haven't actually made them yet.

Well, how does it look? Compile it and see for yourself.



It's a bit fast, but it works well enough for now. It'll slow down after we add the enemies and bullets anyway. That's what we'll do next.

Chargin' laz0rz

All right, now for the fun stuff: bullets! Since there can be more than one bullet at a time, we need to store it in an array. Arrays are basically lists of elements with a similar structure; see our guide to arrays for more information.

Before we make the subroutines, though, we need to decide what the array's going to look like. An array is just a list of elements, so we need to figure out the most efficient format to store a bullet in. What information do you need about a bullet?

    [li]Bullet type - Not really important now, but by adding this you're allowing yourself the opportunity to add cannons, laz0rz, and all that fun stuff (later on).[/li]
    [li]Bullet speed - again, this would allow you to have different kinds of bullets.[/li]
    [li]X-value of the bullet[/li]
    [li]Y-value of the bullet[/li]


So that's it. Four bytes per element. We can store the bullets in L4, which gives us 323 bytes (80 elements) of memory to work with. First, like with all arrays, we need to set a variable up to store the number of elements. It should be initialized in the init code.



Now you can add the bullet-adding subroutine to put in prgmASHMUPR:





Again, see http://ourl.ca/9288/176368 for a full-length explanation on how this works.

Of course, bullets should only be added when the user presses a button to fire. One option could be 2ND. But we don't want the ship to fire too rapidly, so first set up a counter in the main program. We'll be using this counter for lots of things, here to make sure the fire button doesn't repeat too quickly.







The only new line of code is a "C+1→C" right after the main loop starts. We don't really have to initiate C because since we're not using the whole number anyway, it won't make much of a difference.

What does matter though is how C affects firing. Add this code to the end of prgmASHMUPK:







Just in case you don't understand this code completely, it first checks if 2ND is held down (if didn't memorize the keycode table, it's 54). Then it tests to see if the counter is a multiple of 8. This adds a little pause between consecutive bullets; otherwise, a bullet is added each pass of the loop, and you'd quickly get a continuous stream of bullets!

And if the conditions are right, the program adds a "type 1" bullet with a speed of 3 pixels per pass (a bit faster than the ship at 2) placed at a position three pixels to the right of the ship and four pixels up, which positions a 2- by 4-pixel bullet nicely. Don't bother compiling yet, since we haven't added any of the actual bullet-drawing code. That's in the next section.

Bullets can move

Let's take a break from coding. Put this in ASHMUPD.



That's the sprite for the bullet, of course (2 by 4).

All right, break's over. Back to the code!





That goes in a new program called ASHMUPM (you can check in the main program to see where the code'll be run).

It might look confusing at first, but that's because of how optimized it is. [Insert self-congratulating remarks here.] First of all, the For( loop goes from 1 to L, not 0 to L-1, unlike most indexes, which start at zero. This is because since I is checked against the maximum value (either L or L-1) every pass of the loop, there's a speed optimization in using L. We'll be taking care of that extra 1 in the actual code.

The next line is a gigantic if statement that already moves the bullet. It looks complicated, but just look at the inside first: "{I*4+L4-1→J}." It takes I (the current index being parsed), multiplies it by four (the number of bytes per element), and adds L4-1. Why L4-1? If you just add L4, you get the next element's first byte (since we're starting the loop at 1), so if you subtract one from that, it would be the current element's Y-value. The pointer to that is stored to J for later reference.

Then the {J-2} is subtracted. Since J points to the current Y-value, two bytes before it would be the speed of the bullet. And since our bullets go upwards, we subtract. All of this gets stored back to {J}.

Now here's the (über1337) optimization: Since the store statement is storing to a non-constant pointer (thanks to the J), it returns the pointer stored to, which you can use for further operations. In this case we wrap the whole thing with braces to get the value again and test if it's greater than 127 (because if its signed value is less than zero, its unsigned value is greater than 127; we actually save a few bytes by not having to use sign{). If it did, we call Lbl RSB, which we'll set up next.

Finally, a working gun

Lbl RSB is a subroutine, so that means we go back to prgmASHMUPR. Here's the code:





All this does is remove the element from the list. For a full explanation, see the guide to arrays.

Now there's one last thing we need to do before the program works. Back when we finished the first little demo we commented out all the lines importing programs that didn't exist (yet). Now that we've made them, take all the periods out. You probably don't need a screenshot for this.

Finally, it's time to actually display the bullets. Since they're in an array, the program would have to loop through the array and display each one, but conveniently, we already have one such loop set up in prgmASHMUPM. Let's just edit that to save ourselves another loop:





The code is pretty similar to the stuff in prgmASHMUPM (in fact, we could have saved a few more bytes by combining the two), so I won't explain it here. If you want a review, it's here.

And it works!



Next: Enemies
Quote:
Space Invaders, Phoenix, Phantom Star—you've seen shoot-em-up games before.
. . .
You forgot Raven Wink I'll read this article a little later Smile
A great addition, Deep Thought; thanks!
_player1537 wrote:
Quote:
Space Invaders, Phoenix, Phantom Star—you've seen shoot-em-up games before.
. . .
You forgot Raven Wink I'll read this article a little later Smile

And Falcon. Razz
Added.

And I fixed the broken-link imgs in the first post (I moved the tutorial files to http://clrhome.co.cc/tutorials/arrays/ but didn't change the URLs).
Very Happy Thanks Deep Thought!
Deep Thought wrote:
Added.

And I fixed the broken-link imgs in the first post (I moved the tutorial files to http://clrhome.co.cc/tutorials/arrays/ but didn't change the URLs).
Neat-o, thanks. Did you / will you make this a PDF as well? Sorry that I didn't get a chance to TeX your Crash guide yet; I still want to do so at some point.
I still haven't done that yet for any of my tutorials. I don't really have a theme for it, so I still have to figure out what I want my PDF to look like.
Deep Thought wrote:
I still haven't done that yet for any of my tutorials. I don't really have a theme for it, so I still have to figure out what I want my PDF to look like.
Gotcha, that makes a good deal of sense. Smile You don't necessarily need to actually turn them into PDFs, I suppose; they seem happy enough sitting on your blog.
I actually haven't even mentioned these tutorials on my blog yet. I should...

I've been looking around for HTML to PDF services (because I'm that lazy), but I haven't found a working one yet.
Deep Thought wrote:
I actually haven't even mentioned these tutorials on my blog yet. I should...

I've been looking around for HTML to PDF services (because I'm that lazy), but I haven't found a working one yet.
I'd be happy to generate a PDF for you, or you could just install something like CutePDF that allows you to print to PDF.
  
Register to Join the Conversation
Have your own thoughts to add to this or any other topic? Want to ask a question, offer a suggestion, share your own programs and projects, upload a file to the file archives, get help with calculator and computer programming, or simply chat with like-minded coders and tech and calculator enthusiasts via the site-wide AJAX SAX widget? Registration for a free Cemetech account only takes a minute.

» Go to Registration page
Page 1 of 1
» All times are UTC - 5 Hours
 
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum

 

Advertisement