------------------------------------------------------------------------ -- Astro Smash Coding: Eric Askilsrud (Redlense@u.washington.edu) -- -- A <10k program written with Amos 1.3, Jun 1992 -- -- -- -- Tutorial file on AstroSmash's creation. -- ------------------------------------------------------------------------ Hey, I like the idea of the Amoner editors' for this 10k program thing. This way I can hack out fun little routines w/o having to do painstaking waste-of-my-time polishing work! Anyways, Astro Smash is really the first 10k of a much bigger program that I may be writing soon. AstroSmash has a little bit of everything - and - as a bonus - it is actually playable! So I decided to write this tutorial on it's creation for intermediate AMOS programmers! ----------------------------------------------------------------------------- First some background - The game uses the 8 hardware sprites, with sprites 0 and 4 being the spaceships. The graphics are in a sprite bank that was saved along with the program. They were originally drawn with Dpaint in 4 color mode - it is very important that you pick up sprites from a 4 color screen, otherwise they will be treated like computed sprites until you ERASE them! object 1-16 different rotations of the spaceship, w/ object 1 being a space- ship pointing upwards, and each succesive object being the spaceship rotated 22.5 degrees further clockwise 17 the bullet 18 explosion ---------------------------- Algorithm: setup while playing -title screen/wait to start while alive for player=0 to 1 -check joystick -update sprites next wend -game over sequence wend --------------------------- Looking at the code: -------------------- >> Dim XDIR#(1),YDIR#(1),X#(1),Y#(1),LK(1),I(1),AX#(16),AY#(16) This line reserves all the memory for all the arrays in the game. An array is used for every variable that needs a seperate version for each of the two spaceships - the index of the array represents the player number (0 & 1). xdir#() - holds the horizontal component of velocity ydir#() - holds the vertical component of velocity x#() - holds x coord of ship y#() - holds y coord of ship lk() - holds the last direction the joystick was pressed i() - holds the image number of ship (1-16) Notice that the '#' after x,y,xdir,ydir means that they are floating point variables - that means they contain a decimal point (see manual) ax#() and ay#() are used to store a table of values, they will be explained shortly >> Flash Off : Hide : Curs Off : Paper 0 : Degree These commands turn the flashing off, hide the mousepointer, turn the cursor off, set the background to black, and set the trigonomic functions to degree mode(see below) respectively. >> For ANGLE=0 To 15 >> AX#(ANGLE+1)=0.5*Sin(ANGLE*22.5) >> AY#(ANGLE+1)=-0.5*Cos(ANGLE*22.5) >> Next These statements fill the table of the aforementioned AX#() and AY#(). This table is a holds the 'x-acceleration' and 'y-acceleration' for the ship for any given spaceship image number (1-16). I've set the acceleration to .5 - We can use trigonometry to figure the x and y components of accel. For example, if the ship was thrusting 45 degrees clockwise from straight up (image#3) ax# would be .5 * sin45 and ay# would be .5 * cos45. Notice the negative in front of the calculation for AY# - this is because 'up' on the computer screen is negative and not positive. Negating the accel y-component will enable us to comply. So, the loop precalculates x,y components of acceleration for each image number. Eg. ax#(3) returns accel x-component for image #3 >> Make Mask Make mask arround images for collision detection >> While QUIT=0 'Keep doing every thing between here and WEND until user quits' >> TYPE=0 : For Z=0 To 1 : XDIR#(Z)=0.0 : YDIR#(Z)=0.0 : Next >> X#(1)=10.0 : Y#(1)=10.0 : I(1)=7 : X#(0)=300.0 : Y#(0)=180.0 : I(0)=15 >> INGAME=0 : Cls This initializes all the stuff for a game: TYPE=0 --> TYPE holds Warp/Bounce mode - 0 means that we don't know yet XDIR#(), YDIR#() = 0 --> set velocity vectors to zero x=10, y=10, etc --> gives spaceships their starting coords i(1)=7 i(0)=15 --> gives spaceships their initial images INGAME=0 --> this means that nobodys died in current game yet >> Locate ,2 : Centre "Astro Smash" >> Locate ,4 : Centre "(Written in Amos 1.3)" >> Locate ,8 : Centre "1) Reflective Walls " >> Locate ,10 : Centre "2) Warp Space" >> Locate ,13 : Centre "Q) Quit" >> While TYPE=0 : A$=Inkey$ : If A$="1" Then TYPE=1 >> If A$="2" Then TYPE=2 >> If Upper$(A$)="Q" Then End >> Wend This draws the game selection menu and waits for the user to enter a TYPE. Keeps getting keys until TYPE other than 0 is given, or, by the selection of 'q', the program is ended. Notice the use of upper$, which lets it accept capital or lower-case Q's - see manual. >> Cls : If TYPE=1 Then Box 0,0 To 319,199 Clears the screen and, if in a Bounce game, draw the border >> While INGAME=0 'Keep doing every thing between here and WEND until somebodys dead' >> For P=0 To 1 P represents the player number - so, do each of the following for each player: >> K=Joy(P) Read the JoyStick for current P >> If K>15 Then K=K-16 : XDIR#(P)=XDIR#(P)+AX#(I(P)) : YDIR#(P)=YDIR#(P)+AY#(I(P)) : Gosub CHSPEED If K>15 that means that that player is pressing FIRE button k=k-16 just means 'now we know the players pressed the button, and we'll take care of it' - subtracting 16 from k just leaves us with joystick direction values w/o the button (see manual) Since the 'thrust' was pressed, we will add the the component-acceleration values from the AX and AY tables explained above (notice the player indexing). XDIR#(P)=XDIR#(P)+AX#(I(P)) : YDIR#(P)=YDIR#(P)-AY#(I(P)) And lastly, Gosub CHSPEED which limits the spaceship from going too fast (see explanation of routine below). >> If K=8 Then If Chanan(P)=0 Then I(P)=I(P)+1 : Gosub CHLOOP : Amal P*4,"A2,("+Str$(I(P))+",1)" : Amal On P*4 If K=8 that means that the player is pressing right on the joystick, which means that the spaceship should be rotated right - meaning image number is increased - but what if we are at image 16 (337.5 degrees) and we rotate 22.5 degrees further? Since 360 degrees = 0 degrees, we switch back to image number 1 - this is exactly what Gosub CHLOOP performs. Once we figure out what new Image the sprite should be, we set up an AMAL string to Animate it to that image - Why all this Chanan (see manual!!) and AMAL stuff? why not just change the sprite? Because that would make the ship rotate too fast! So, we say with an amal string, to change the image number in 2 vbl's - With the Chanan(P) function check that the image is not being animated before incrementing the image! Amal P*4,"A2,("+Str$(I(P))+",1)" ^^ this little tricky string addition trick, the string is seen as: "A2,(#,1)" where # represents the image number to animate to. Expirement with this trick of using the str$() function to create amal strings!!! This method is much better than writing independent amal strings for small speed or distance differences OR by using the Amreg function to pass it along!! >> If K=4 Then If Chanan(P)=0 Then I(P)=I(P)-1 : Gosub CHLOOP : Amal P*4,"A2,("+Str$(I(P))+",1)" : Amal On P*4 This is the same story as the line before, except this time we are dealing with joystick left >> If K=1 and LK(P)<>K Then Gosub BULLET If the joystick is pressed up AND the last direction the joystick was pressed was NOT up (to ensure only ONE bullet is released every time the joystick is pressed up) then Gosub BULLET (see below). >> On TYPE Gosub BOUNCE,WARP Now depending on the type of game do either 1) bounce off walls 2) warp to other side of screen (see below) >> X#(P)=XDIR#(P)+X#(P) : Y#(P)=YDIR#(P)+Y#(P) After getting the joystick info, we can now finally update the sprites. The new x coord will equal the old one plus the x-comp of velocity(xdir) The new y coord will equal the old one plus the y-comp of velocity(ydir) >> Sprite P*4,X Hard(X#(P)),Y Hard(Y#(P)),I(P) Put the sprite in its place - Sprite 0 is used for player 0 Sprite 4 is used for player 1 Therefore there is a neat little formula that gives sprite number given player number: player# * 4 !!! (wow) Notice the current player index is accessed for x,y and i >> Gosub CHECKBULLS Also we should check to make sure their are no bullets standing still on the playfield (see below) >> LK(P)=K store the current k into 'last k' (lk) for that player this is used by the fire bullet routine (see above) >> If Sprite Col(P*4)<>0 Then Boom : Sprite P*4,X Hard(X#(P)),Y Hard(Y#(P)),18 : INGAME=1 : Amal Off Check for sprite collisions - sprite number for collision detection is calculated from our previously derived formula, sprite# = p*4. If there is a collision, then make a Boom sound, change that sprite image into an explosion! (image 18) turn amal off, and let the WHILE loop know that somebody has died (ingame=1) >> Next End of 'Player' loop... >> Wait Vbl Wait a Vbl to wait for screen to draw >> Wend Once we get passed this WEND, that means that somebody has DIED! >> For Z=0 To 1 >> If Sprite Col(Z*4)<>0 Then Sprite Z*4,X Hard(X#(Z)),Y Hard(Y#(Z)),18 >> Next Check Collision of the two spaceships once more, just incase they collided into one another - otherwise only one would turn into an explosion. If there is a collision, turn that sprite into an explosion image #18 as above >> Sprite Update : Wait Vbl : Locate ,11 : Centre "Game Over" : Wait 40 : While Joy(1)<16 : Wend This is more end-of-game stuff - update the sprites to ensure they show an explosion, print 'game over', wait for player 1 to press his button >> For Z=0 To 7 : Sprite Off Z : Next : Sprite Update Finally, turn all the sprites off, as we are done with them for this game >> Wend >> End Once we are out of this WEND, that means the person has quit!! Which means the next command is End! -------------------------------- Routines called by the main loop -------------------------------- >> CHLOOP: >> If I(P)>16 Then I(P)=1 >> If I(P)<1 Then I(P)=16 >> Return This routine sets image# back to 1 if it gets greater than 16 and up to 16 if it gets below image number 1 (see above) >> CHSPEED: >> If XDIR#(P)>4.0 Then XDIR#(P)=4.0 >> If XDIR#(P)<-4.0 Then XDIR#(P)=-4.0 >> If YDIR#(P)>4.0 Then YDIR#(P)=4.0 >> If YDIR#(P)<-4.0 Then YDIR#(P)=-4.0 >> Return This routine limits how big or small a component of velocity can be. You can see that if the absolute value of the component of speed is greater than 4 it is set back to 4. >> BOUNCE: >> If X#(P)>304.0 Then X#=304.0 : XDIR#(P)=0.0-XDIR#(P) : Bell 1 >> If X#(P)<-3.0 Then X#=-3.0 : XDIR#(P)=0.0-XDIR#(P) : Bell 1 >> If Y#(P)>184.0 Then Y#=184.0 : YDIR#(P)=0.0-YDIR#(P) : Bell 1 >> If Y#(P)<-2.0 Then Y#=-2.0 : YDIR#(P)=0.0-YDIR#(P) : Bell 1 >> Return This routine is used by the Bounce mode of the game - if the x or y components of the ships go off the screen, then the velocity-component that is perpendicular to that of the boundary it exceeds is reversed. If such a collision is detected, the awesome 'Bell 1' sound effect is executed for that extra arcade realism. >> WARP: >> If X#(P)>304.0 Then X#(P)=0.0 >> If X#(P)<0.0 Then X#(P)=304.0 >> If Y#(P)>184.0 Then Y#(P)=0.0 >> If Y#(P)<0.0 Then Y#(P)=184.0 >> Return This routine is used by the Warp mode of the game - if the x or y components exceed the maximum in the region, they are set to the minimum and vise versa. >> BULLET: >> Q=0 >> If P=0 Then For Z=1 To 3 : If Chanmv(Z)=0 Then Q=Z : Next >> If P=1 Then For Z=5 To 7 : If Chanmv(Z)=0 Then Q=Z : Next The Bullet routine is called whenever the joystick is pressed up again - First we must cycle thru the bullet Sprites that belong to the current player, to see if any are stationary - which means they are not on the screen and can be used. Sprites 1 to 3 are bullets belonging to player0 sprites 5 to 8 are bullets belonging to player1 q holds the last found currently-available bullet - It is initially set to zero, therefore, if no bullets are found it is left 0 >> If Q=0 Then Return >> Shoot So, if no bullets are available, RETURN (do nothing) Otherwise, make the neat 'Shoot' sound fx, and do the following: >> Sprite Q,X Hard(X#(P)+7)+30*AX#(I(P)),Y Hard(Y#(P)+8)+30*AY#(I(P)),17 This command gives the bullet its initial position. The initial position is going to be the ships x,y plus a displacement value (x+7,y+8) that gives the coordinate of the center of the ship. To the center of the ship's coords are added multiple of the vector that points in the direction of the ship ---- the components of this vector are the same as that of the accel vector, our good buddies AX and AY! To initially get the bullet good and far away from the ship, this vector is multiplied by 30 (determined expire- mentally). Image number 17 is assigned, as this is the 'bullet' object >> Amal Q,"M "+Str$(Int(300*AX#(I(P))))+","+Str$(Int(300*AY#(I(P))))+",50" : Amal On Q This gives the bullet its Movement command. Again, this uses string addition to produce the AMAL string. Looks pretty gross, don't it?? What it basically does is: "M 300*ax(i),300*ay(i),50" which means Move (in 50 vbl's) : 300 times the x-comp of accel in the x- direction, and 300 times the y-comp of accel in the y-direction. The reason for all those parethesis is: 1) We have them around the Str$(), 2) We use Int() around the string to Str$() - this is because AMAL does't like floats - so we must convert the numbers to Str$() into integers 3) player indexing Hopefully it isn't too mind boggeling - Remember, when looking at things like this it is very helpful to watch your order of operations - start from the inside of the parenthesis and work your way out EX) Amal Q,"M "+Str$(Int(300*AX#(I(P))))+","+Str$(Int(300*AY#(I(P))))+",50" AMAL Q,"M " + Str$(Int(300*ax#(2))) + "," + Str$(Int(300*AY#(2))) + ",50" AMAL Q,"M " + Str$(Int(300*.38)) + "," + Str$(Int(300 * .92)) + ",50" AMAL Q,"M " + Str$(114) + "," + str$(276) + ",50" AMAL Q,"M " + "114" + "," + "276" + ",50" AMAL Q,"M 114,276,50" >> Return >> CHECKBULLS: >> For Z=1 To 7 >> If Z<>4 and Chanmv(Z)=0 Then Sprite Off Z >> Next >> Return This routine makes sure that there are no bullets standing still on the play- feild. Bullets are given a starting location and a certain movement string by the BULLET routine above - but there is nothing clearing them once their movement pattern runs out. This checks each of the sprites 1-7 except 4 (sprites 0 and 4 are ships) to see if they are moving with the Chanmv command (see manual). If they are not moving, this routine just turns the sprite off. ------------------------------------------------------------------------------- Okay, that's it! Hopefully that's been of some interest to you. Why don't you drop me a line and let me know what you think, (don't tell me I spel bad or nuthin grammer like) Thanks, -Eric (redlense@milton.u.washington.edu)