In the previous article, we created a tile map and did a simple scrolling screen using Screen Copy.
While it worked, and demonstrated some cool aspects of AMOS, it wouldn’t really be efficient for use in an action/arcade game, is there a better option? Also, can we load the map from disk?
There is also another option for scrolling the screen we haven’t investigated, let’s look at that first.
Previous AMOS Basic Tutorials in this series:
AMOS has a neat feature where you can define scrollable areas, and then specify which area you wish to scroll.
When I think of parts of the screen scrolling, my mind immediately goes to the kind of parallax scrolling where the strip of background closest to you scrolls faster than the most distant.
Here is a simple example:
Here is the code to implement this effect:
' Set up the screen Screen Open 1,320,200,32,Lowres Screen Open 0,320,200,32,Lowres Flash Off : Hide : Paper 0 : Pen 4 : Cls ' Define the scrolling Def Scroll 1,0,0 To 320,8,-1,0 Def Scroll 2,0,8 To 320,16,-2,0 Def Scroll 3,0,16 To 320,24,-4,0 Def Scroll 4,0,24 To 320,32,-8,0 Def Scroll 5,0,32 To 320,56,-16,0 ' Put something on screen For LINE=1 To 15 Print "Hello World! "; Next LINE ' Scroll Loop While Inkey$="" Screen Copy 0,0,0,16,60 To 1,0,0 Wait Vbl Scroll 1 Scroll 2 Scroll 3 Scroll 4 Scroll 5 Screen Copy 1,0,0,16,60 To 0,304,0 Wend
The main part to take note of is where we “define the scrolling”:
Def Scroll 1,0,0 To 320,8,-1,0
This tells AMOS that we want to set up scrolling area 1, that it starts in the top, left, goes as far as the right of our screen, but only 8 lines down. The scroll direction is -1, so it will scroll left one pixel. On the vertical it doesn’t scroll at all.
We also set up another four zones, getting incrementally faster.
To actually activate the scroll we have to enter
Where 1 is the number of the zone to scroll. Each time this is called, it will scroll that area of screen by the amount we set up in the definition.
You might also notice that to make it an infinite scroll, we copy the area of screen we are about to scroll off screen to a hidden screen and back again. Otherwise as the screen scrolls we would have a gap where the text used to be.
Under the hood, the Def Scroll approach is simply using the blitter to screen copy in a similar way to what we implemented in the previous tutorial.
Is there a better way?
I am no expert, I only got an Amiga for the first time in 2020, so I am still learning. That said, I have to believe copying large amounts of data on each frame is not going to be the best route.
So far my best results have come from mixing the first example we looked at, using screen offset, and the tile-based approach.
Scrolling Tiles with Offset
Before we get into the code, I need to drop some theory.
If you recall, setting the screen offset moves our viewport, rather than the screen data. It’s like moving your phone camera and seeing the updated view on your phone display. The scene didn’t move, your view of it did.
So we set up a screen much larger than the viewable screen on your monitor, then to get the scroll effect we move the viewport up this virtual screen. When we get to the top we start over at the bottom.
Obviously, like in the example at the top of the page, we need to take care of the gaps left behind, and make it so there doesn’t seem to be a jolting jump.
For now I take a full copy of the screen and paste it down at the bottom before starting the scroll area. Not the best but it’s progress!
I am not entirely sure if the screen copy approach I use in my example code below is better or if I should have tried pasting the tiles twice instead. Let me know if you have a better approach!
AMOS and Loading the Map Data File from Amiga Disk Drive
Before we get into the code, we should talk about loading the map and how AMOS handles text file access.
We set up a simple ASCII text file with our map data, that way for now we can use a text editor to edit our map.
Then later we open the file and start reading it, line by line
' Load the map file from disk Open In 1,MAPDATA$ ' Read the lines and fill the map array MAP_TILE_ROW=0 Print "Loading ..." While Not Eof(1) ' Read a line at a time Print MAP_TILE_ROW," "; Line Input #1,THIS_LINE$
As each line is read it, we interpret the string by grabbing characters using Mid$ (each tile number is 3 digits, eg. 004, followed by a space).
We increment the row counter and loop, until the end of the file in which case we close the file and continue.
MAP(9,MAP_TILE_ROW)=Val(Mid$(THIS_LINE$,37,4)) Inc MAP_TILE_ROW Wend ' Close the file Close 1
It’s not super fast, but it is convenient and human-readable. There are other options available to us, but it works!
The code is still quite raw, proof of concept rather than a finished piece, but hopefully it gives you an idea as a starting point for how a vertically scrolling shooter can have backdrop implemented.
Rem ************************************** Rem Rem Vertically scrolling shooter demo Rem Rem by Maker Hacks Rem Rem 2020 Rem Rem *************************************** ' Need a bigger buffer for a large map Set Buffer 150 ' Make a double-height screen + 32px Screen Open 0,320,512+32,16,Lowres Screen Hide : Curs Off : Flash Off : Cls 0 Colour Back $0 : Hide : Screen Show Screen Display 0,135,60,320,200 ' Debug/Score info area Screen Open 1,320,20,16,Lowres Screen Display 1,135,40,320,20 Hide : Curs Off : Flash Off : Cls 13 Screen 0 ' Load map tiles Load "tile.abk" ' Load sprites Load "invader.abk" Get Sprite Palette ' Map file (ascii for now) MAPDATA$="vertical_map_2.txt" ' Set up a list of colours Palette ,,,,,,,,,,,,,,,,Colour(0),Colour(1),Colour(2),Colour(3),Colour(4),Colour(5),Colour(6),Colour(7),Colour(8),Colour(9),Colour(10),Colour(11),Colour(12),Colour(13),Colour(14),Colour(15) ' DEBUG the palette X=0 For I=0 To 15 Ink I,I,I X=X+8 Bar X,40 To X+8,48 Next ' Background to black Paper 0 ' Print colours and numbers For I=0 To 15 Pen I Print I; Next ' See the bob on screen Paste Bob 170,100,2 '---------------------- ' Title screen pause Locate 15,12 Print "Press a key" Wait Key Cls '---------------------- ' Set up map in memory ' Fixed map length for now MAP_HEIGHT=100 Dim MAP(9,MAP_HEIGHT) ' Globals - not elegant but works Global MAP_HEIGHT,PLYER_X,PLYER_Y,ENEMY_X,ENEMY_Y,SCENERY_X,SCENERY_Y Global _SCROLL_OFFSET,MAP(),MAP_TILE_ROW,MAPDATA$,THIS_PIXEL_ROW,ROW_COUNTER ' Initialise environment INIT ' Where the magic happens MAIN Rem Initial setup Procedure INIT ' Load and Draw the initial map MAP_INIT ' Initial coords for the player sprite PLYER_X=150 : PLYER_Y=200 ' Initial coords for enemy sprite ENEMY_X=100 : ENEMY_Y=10 ' Start line to draw at THIS_PIXEL_ROW=288 ' Set the screen to start conditions RESTART_SCROLL ' Uncomment to turn off automatic Bob updates for speed 'Bob Update Off End Proc Rem Tile painting routine Procedure _PAINT_ROW ' Paint a full row of tiles Paste Icon 0,THIS_PIXEL_ROW,MAP(0,MAP_TILE_ROW) Paste Icon 32,THIS_PIXEL_ROW,MAP(1,MAP_TILE_ROW) Paste Icon 64,THIS_PIXEL_ROW,MAP(2,MAP_TILE_ROW) Paste Icon 96,THIS_PIXEL_ROW,MAP(3,MAP_TILE_ROW) Paste Icon 128,THIS_PIXEL_ROW,MAP(4,MAP_TILE_ROW) Paste Icon 160,THIS_PIXEL_ROW,MAP(5,MAP_TILE_ROW) Paste Icon 192,THIS_PIXEL_ROW,MAP(6,MAP_TILE_ROW) Paste Icon 224,THIS_PIXEL_ROW,MAP(7,MAP_TILE_ROW) Paste Icon 256,THIS_PIXEL_ROW,MAP(8,MAP_TILE_ROW) Paste Icon 288,THIS_PIXEL_ROW,MAP(9,MAP_TILE_ROW) End Proc Rem Map loading and drawing routine Procedure MAP_INIT ' Load the map file from disk Open In 1,MAPDATA$ ' Read the lines and fill the map array MAP_TILE_ROW=0 Print "Loading ..." While Not Eof(1) ' Read a line at a time Print MAP_TILE_ROW," "; Line Input #1,THIS_LINE$ ' Put the file data into the array MAP(0,MAP_TILE_ROW)=Val(Mid$(THIS_LINE$,1,4)) MAP(1,MAP_TILE_ROW)=Val(Mid$(THIS_LINE$,5,4)) MAP(2,MAP_TILE_ROW)=Val(Mid$(THIS_LINE$,9,4)) MAP(3,MAP_TILE_ROW)=Val(Mid$(THIS_LINE$,13,4)) MAP(4,MAP_TILE_ROW)=Val(Mid$(THIS_LINE$,17,4)) MAP(5,MAP_TILE_ROW)=Val(Mid$(THIS_LINE$,21,4)) MAP(6,MAP_TILE_ROW)=Val(Mid$(THIS_LINE$,25,4)) MAP(7,MAP_TILE_ROW)=Val(Mid$(THIS_LINE$,29,4)) MAP(8,MAP_TILE_ROW)=Val(Mid$(THIS_LINE$,33,4)) MAP(9,MAP_TILE_ROW)=Val(Mid$(THIS_LINE$,37,4)) Inc MAP_TILE_ROW Wend ' Close the file Close 1 ' LOADED LINES = NEW MAP HEIGHT MAP_HEIGHT=MAP_TILE_ROW ' Select the start tile row to put on screen MAP_TILE_ROW=MAP_TILE_ROW-16 ' Screen full of tiles For THIS_PIXEL_ROW=1 To 512 Step 32 ' Paint the row of tiles '_PAINT_ROW ' Increment the tile row Inc MAP_TILE_ROW Next THIS_PIXEL_ROW End Proc Rem Main loop Procedure MAIN ' Loop forever unless told otherwise ... Repeat ' Debug info Screen 1 : Pen 2 : Paper 0 Locate 0,0 : Print " " Locate 0,0 : Print "OFFSET: ",_SCROLL_OFFSET," ROW: ",MAP_TILE_ROW Screen 0 ' Get player input _PLAYER_INPUT ' Decrement the scroll offset Add _SCROLL_OFFSET,-2 ' If we get to zero then reset the scroll, otherwise ' scroll by setting the screen offset If _SCROLL_OFFSET>0 ' Paint row of tiles above viewable area Add ROW_COUNTER,2 If ROW_COUNTER>=32 ROW_COUNTER=0 Dec MAP_TILE_ROW THIS_PIXEL_ROW=_SCROLL_OFFSET-32 _PAINT_ROW End If ' The offset is how we get the scrolling Screen Offset 0,0,_SCROLL_OFFSET Else ' We got to the top of the virtual screen ' So we need to start over at the bottom Bob Off Screen Copy 0,0,-32,320,200 To 0,0,256 RESTART_SCROLL Dec MAP_TILE_ROW THIS_PIXEL_ROW=_SCROLL_OFFSET-32 _PAINT_ROW Screen Offset 0,0,_SCROLL_OFFSET End If ' Draw player and enemy sprites SPR_DRAW ' Wait for vertical blank Wait Vbl ' If mouse pressed then quit, otherwise back to the loop! Until Mouse Key End Proc ' Reset the screen offset Procedure RESTART_SCROLL ' Screen offset counter _SCROLL_OFFSET=288 ' Reset screen offset to screen bottom Screen Offset 0,0,_SCROLL_OFFSET ' Set the row in MAP array to count from If MAP_TILE_ROW=<10 Then MAP_TILE_ROW=MAP_HEIGHT-1 ' Reset the pixel row counter ROW_COUNTER=0 End Proc Rem Get player input Procedure _PLAYER_INPUT ' If cursor left or right then move player If Key State(79)=True and PLYER_X>112 Then PLYER_X=PLYER_X-4 If Key State(78)=True and PLYER_X<418 Then PLYER_X=PLYER_X+4 ' Fire button If Key State(64)=True Then Shoot End Proc ' Draw player and enemy sprites Procedure SPR_DRAW ' Set the obstacle x, y SCENERY_Y=512-_SCROLL_OFFSET+4 If SCENERY_Y>512 Then SCENERY_X=Rnd(288) ' Bobs are drawn onto the screen memory Bob 10,SCENERY_X,SCENERY_Y,4 Bob 20,SCENERY_X,SCENERY_Y+32,5 Bob 30,SCENERY_X+32,SCENERY_Y,6 Bob 40,SCENERY_X+32,SCENERY_Y+32,7 ' Sprites are on a layer above the regular ' viewable screen so are much easier Sprite 0,PLYER_X,PLYER_Y,2 ' Sprite 8 upwards is a special "computed" sprite Sprite 8,ENEMY_X+100,ENEMY_Y,1 ' Move enemy down the screen Add ENEMY_Y,6 ' If the enemy drops off bottom ' start at a new random X coord If ENEMY_Y>300 ENEMY_X=Rnd(300)+30 ENEMY_Y=30 End If End Proc