Jump to content
IGNORED

Help with XB256 game


Sergioz82

Recommended Posts

Hi,

I'm working a "proof of concept" code for a game I have in mind. 

I reached an interesting result however I ran into some pesky problems that I hope you can help me to address.

 

First, the idea is to have a randomly generated line of blocks that falls from the top of the screen and that is added to the existing lines which are then moved one line down (windows + scrldn routines). The game over is when the player at the bottom is hit by the blocks. The player can shoot a projectile toward the blocks.

 

Now the annoying part:

 

1) The block area is 10x20 characters, that is ten characters per line that can be either occupied by blocks or empty. To generate the line string I use a 1 to 10 for loop with a RND call that decides if the character is empy or not. Problem is, the cycle slows down the game loop and gives me problems with hit detection of the projectile sprite and the controls reactivity. So first question is: is there a faster way to generate this random string?

 

 

2) The lines are drawn with vwrite. This gives me a problem with the 96 offset: I use character 140 for blocks and without offset their image is replaced with commas. If I add the offset the problem is solved but.. I use vread for collision detection and with the offset the returned string is empty so no collision. How can I align the results? One idea is to use display at in place of vwrite. That way the vread routine should be fine. Is display at slower than vwrite?

 

3)Now the most painful part, the collision detection.. I can't use a sprite for each block so I simply check with vread what's in position() of the projectile (top-center of the sprite). The idea is that once the sprite enters a block area it returns me the char 140. Problem is: position returns me the pixels, but vwrite needs a character position. I have to convert the data from position into charater with int(row/8) and int(col/8) before calling vread. It seems to do the trick but I don't really like it. Is there a more elegant (and precise) solution?

 

Thanks in advance

Edited by Sergioz82
Title change
Link to comment
Share on other sites

! Dimension two arrays for characters to pixels conversions to help with sprite collision

10 DIM SX(24),SY(32) ! but you won't need ALL of this array it's just the full screen

20 N=0 :: FOR L=1 TO 24 :: SX(L)=N :: N=N+8 :: NEXT L :: SX(1)=1

30 N=0 :: FOR L=1 TO 32 :: SY(L)=N :: N=N+8 :: NEXT L :: SY(1)=1

 

Now , if you have a player missile that goes up the screen, you can use this, let's say PM=0 or PM=1 for player missile on or off, and we'll use a GCHAR to detect a hit; but only do something IF there is a hit;

 

Example line of code could be;

4000 IF PM=1 THEN 4010 ELSE 4099

4010 PMX=PMX-1 ! Player missile's X position decremented by one (8 pixels, one char)

4020 IF PMX<1 THEN 4030 ELSE 4040

4030 PM=0 :: CALL DELSPRITE(#1) :: GOTO 4099 ! Player missile turned OFF if it's got to the top of screen

4040 CALL LOCATE(#1,SX(PMX),SY(PMY)) ! Place the player missile sprite in it's new position

4050 CALL GCHAR(PMX,PMY,CH) :: IF CH>32 THEN 4060 ELSE 4099 ! Checks for anything

4060 GOTO 4100 ! Branch to new code instead of bogging this code down

4099 RETURN

 

This code assumes your block is character 104, but in reality it could be anything you've coded it for;

4100 IF CH=104 THEN 4110 ELSE 4199 

4110 CALL LINK("DISPLY",PMX,PMY," ") ! Blank space where the block was

4120 SCORE=SCORE+10 ! Or whatever you want your score to be?

4130 GOSUB 4200 ! Update the score (this is depending on how you do it though)

4140 GOTO 4030 ! Back to the original sub but the part that gets rid of the player missile

4199 goto 4099 ! Because we gosubbed to 4000, otherwise there'll be issues

 

This code just shows the score;

4200 CALL LINK("DISPLY",24,1,STR$(SCORE)) :: RETURN

 

I hope this gives you some help.  It's cumbersome with 8 pixel movement of player sprite but in fact appears faster and is deadly accurate when it comes to sprite vs character detection.  It was used in Avaris.  I'm not sure how you'd be doing the blocks falling into the line thing, but this'll help with the player missile bit?

  • Like 3
Link to comment
Share on other sites

Hi Retrospect, thanks for the reply.

 

This is a very interesting solution! 

I'm going to implement it in the game and I'll let you know.

 

About the blocks, they work like a reverse tetris: the projectile stops once it hits a block in the line above. Then the sprite is deleted and replaced with a block character. When the row is full it is cancelled (I still have to implement this part) and all the rows below shift up one line. So I don't really want for the above blocks to fall into the line, that's convenient for me :)

 

  • Like 2
Link to comment
Share on other sites

6 hours ago, Sergioz82 said:

1) The block area is 10x20 characters, that is ten characters per line that can be either occupied by blocks or empty. To generate the line string I use a 1 to 10 for loop with a RND call that decides if the character is empy or not. Problem is, the cycle slows down the game loop and gives me problems with hit detection of the projectile sprite and the controls reactivity. So first question is: is there a faster way to generate this random string?

You might try IRND to create the random numbers. From the manual:

CALL LINK(“IRND”,limit,variable[,.........])
Returns a random number as an integer from 0 to limit-1. The results are the same as
variable=INT(limit*RND) but IRND is much faster than the slow XB random
number generator. Up to 8 random numbers can be created with one call to IRND.

 

2) The lines are drawn with vwrite. This gives me a problem with the 96 offset: I use character 140 for blocks and without offset their image is replaced with commas. If I add the offset the problem is solved but.. I use vread for collision detection and with the offset the returned string is empty so no collision. How can I align the results? One idea is to use display at in place of vwrite. That way the vread routine should be fine. Is display at slower than vwrite?

i'm not quite sure what you mean here. The following program creates a 10 byte long string made up of ASC 140 characters. It prints it to the screen using VWRITE. It reads it from the screen using VREAD. Then line 20 shows that the length of the string is 10 bytes and the first character is ASC 140 (all the rest will be 140 as
5 A$=RPT$(CHR$(140),10)
10 CALL LINK("VWRITE",1,A$)
15 CALL LINK("VREAD",1,10,A$)
20 PRINT LEN(A$),ASC(A$)

You might find DISPLY to be useful. The following program sets a 4 column wide window at the left side of the screen, then uses DISPLY to print A-P within the window.


100 CALL LINK("WINDOW",1,1,24,4)
110 CALL CLEAR 
120 FOR I=1 TO 20 
130 CALL LINK("DISPLY",I,1,"    ABCDEFGHIJKLMNOP",0)
140 NEXT I
150 GOTO 110 
6 hours ago, Sergioz82 said:

 

 

  • Like 1
Link to comment
Share on other sites

Hi Senior_falcon,

 

thank you very much for your replies. 

 

About point #1 and the suggestion of using disply I'll surely give it a try.

 

About the second point, I see commas in your code as well.

 

 

Please consider this code:

 

100 CALL CLEAR
110 CALL CHAR(140,"0000545474545400")

120 ST$=""
130 FOR I=1 TO 10

140 IF RND<=0.5 THEN ST$=ST$&CHR$(140)ELSE ST$=ST$&CHR$(32)
150 NEXT I
160 CALL LINK("VWRITE",11,ST$)
170 CALL LINK("VREAD",11,10,S$)
180 DISPLAY AT(20,2):S$
190 GOTO 120

 

At 150 I see on top of the screen a series of "," in place of my defined character.

At 170 I correctly see the defined char.

 

If I change 150 to DISPLAY AT(1,12):ST$ I see the right char, but at 170 I see nothing.

 

If I change 170 to use "VWRITE",609,S$ I see commas as displayed on screen with VWRITE, but if I add the offset at 140 CHR$(140+96) then everything works

 

 

 

 

 

 

Edited by Sergioz82
Link to comment
Share on other sites

1 hour ago, Sergioz82 said:

100 CALL CLEAR

110 CALL CHAR(140,"0000545474545400")

120 ST$=""
130 FOR I=1 TO 10

140 IF RND<=0.5 THEN ST$=ST$&CHR$(140)ELSE ST$=ST$&CHR$(32)
150 NEXT I
160 CALL LINK("VWRITE",11,ST$)
170 CALL LINK("VREAD",11,10,S$)
180 DISPLAY AT(20,2):S$
190 GOTO 120

 

At 150 I see on top of the screen a series of "," in place of my defined character.

At 170 I correctly see the defined char.

 

If I change 150 to DISPLAY AT(1,12):ST$ I see the right char, but at 170 I see nothing.

 

If I change 170 to use "VWRITE",609,S$ I see commas as displayed on screen with VWRITE, but if I add the offset at 140 CHR$(140+96) then everything works

 

Now I see the problem. It's that pesky screen offset. Normally when strings are put on the screen each character has 96 (>60) added to it. PRINT, DISPLAY AT, CALL LINK("DISPLY"...) all do this automatically. However, VREAD and VWRITE do not use the screen offset. The intention was to make it so you could read or write a series of bytes to/from any location in VDP, not necessarily just to/from the screen. If you are reading from the color table or character patterns you would not want to use the offset. From my end, the easiest fix would be to add that information about the screen offset to the manual. Another possibility would be to apply the screen offset only when reading from/to the screen but not from other locations in VDP.

 

At least for now, you have to add the screen offset yourself:

140 IF RND<=0.5 THEN ST$=ST$&CHR$(236)ELSE ST$=ST$&CHR$(128) and to be compiler friendly you could do this:

140 IF RND*2<1 THEN ST$=ST$&CHR$(236)ELSE ST$=ST$&CHR$(128)

 

Now when line 170 reads the string the offset will already be included in the string. Line 180 DISPLAY AT will add the offset again, and so it will not be displayed correctly, but I think you have enough information do deal with this.

  • Like 2
Link to comment
Share on other sites

19 hours ago, senior_falcon said:

Now I see the problem. It's that pesky screen offset. Normally when strings are put on the screen each character has 96 (>60) added to it. PRINT, DISPLAY AT, CALL LINK("DISPLY"...) all do this automatically. However, VREAD and VWRITE do not use the screen offset. The intention was to make it so you could read or write a series of bytes to/from any location in VDP, not necessarily just to/from the screen. If you are reading from the color table or character patterns you would not want to use the offset. From my end, the easiest fix would be to add that information about the screen offset to the manual. Another possibility would be to apply the screen offset only when reading from/to the screen but not from other locations in VDP.

 

At least for now, you have to add the screen offset yourself:

140 IF RND<=0.5 THEN ST$=ST$&CHR$(236)ELSE ST$=ST$&CHR$(128) and to be compiler friendly you could do this:

140 IF RND*2<1 THEN ST$=ST$&CHR$(236)ELSE ST$=ST$&CHR$(128)

 

Now when line 170 reads the string the offset will already be included in the string. Line 180 DISPLAY AT will add the offset again, and so it will not be displayed correctly, but I think you have enough information do deal with this.

Understood. Thank you for the explanation.

Link to comment
Share on other sites

@Retrospect @senior_falcon

 

Restrospect, I had to make a double call to a sub with your collision routine.

I had the problem that it worked half the time. 

It happened because the scroll down block -> check collision -> move missile up sequence in the game loop works only when the space (in characters) between missile and block is even.

When it's odd the block goes down and when the check sub is called the gchar read a void space.

My solution is check collision -> scroll down block -> check collision -> move missile. Probably there's a better solution, meanwhile I'm glad it works and I can proceed with the development.

 

Senior_falcon, I used the IRND function for the first string and now the block movement is more fluid. 

 

 

Classic99.gif

Edited by Sergioz82
  • Like 3
Link to comment
Share on other sites

Game is proceeding, I have added graphics and some of the gameplay mechanics I had in mind. 

It will need 32K expansion + assembly compiler but I knew that and I'm confident once compiled it will be a fast enough game.

 

I have another couple of questions:

 

1) I realized I'm adding up stuff without exactly considering the memory limitation . I have a general idea of such limit but I'm not doing a precise calculation. How do you guys use to work? Have you got a method to plan the requirements?

 

2) I already tried a compiled version and it runs too fast and it's unplayable. How do I synchronize all the events in the game loop at the pace I want?  Is there some documentation about timing techniques? I tried to search the forum but words like timing and timer are very common so I get too many unrelated result.

 

Thanks in advance

  • Like 1
Link to comment
Share on other sites

2 hours ago, Sergioz82 said:

Game is proceeding, I have added graphics and some of the gameplay mechanics I had in mind. 

It will need 32K expansion + assembly compiler but I knew that and I'm confident once compiled it will be a fast enough game.

 

I have another couple of questions:

 

1) I realized I'm adding up stuff without exactly considering the memory limitation . I have a general idea of such limit but I'm not doing a precise calculation. How do you guys use to work? Have you got a method to plan the requirements?

 

2) I already tried a compiled version and it runs too fast and it's unplayable. How do I synchronize all the events in the game loop at the pace I want?  Is there some documentation about timing techniques? I tried to search the forum but words like timing and timer are very common so I get too many unrelated result.

 

Thanks in advance

 

For point 2, you need to add some delays in your code. In particular on input and, if needed, when you move your hero, enemies on screen.

 

At the beginning of your program add a:

 

CALL LOAD(-1,3)

 

the second parameters sets 3 frames (1/16 sec on NTSC machines) of delay when you use the CALL LINK("SYNC") routine from XB.

 

So, for input you can use:

 

CALL KEY(0,K,S)::CALL LINK("SYNC")

 

to add a delay of 48ms to the routine, that should be enough in general.

 

You can also use the  CALL LINK("DELAY", VAL) call to add a specific delay VAL. So, the previous example can be written:

 

CALL KEY(0,K,S)::CALL LINK("DELAY",48)

 

For graphics, just add the same delay after the needed CALL VCHAR / CALL HCHAR. Sprites, in general do not need any delay instruction, in general.

 

This in general. Of course, it's depend on your code where to add additional delays, but this infos could be a good starting point.

 

 

 

  • Like 2
  • Thanks 1
Link to comment
Share on other sites

I'm not a big fan of adding delays that do nothing. I think it can lead to sluggish keyboard response. For example, if you scan the keyboard/joystick and then have a delay you can tap the keyboard during the delay and nothing will register.

To me a better way is to set up a loop to scan the keyboard some number of times, lets say 20x. If any one of those CALL KEYs detects a keypress then the key is stored and when you exit the key scanning loop that key is provided to the program. This way it is much harder to miss a keypress or joystick input.

10 KK=0

20 FOR I=1 TO 20

30 CALL KEY(0,K,S)

40 IF K<>-1 THEN KK=K

50 NEXT I

60 ! if KK<>0 then a key was pressed

 

How can you determine the delay without a lot of trial and error?

Do your development in XB so the compiled program loads from XB. (DSK1.PROGRAM-X)

In PROGRAM-X have line 1 CALL HCHAR(1,1,20) Here 20 will be the delay to test out

In the compiled program have the first line be 1 CALL GCHAR(1,1,DELAY)

You have passed the value for Delay from the XB loader to the compiled program

1 CALL GCHAR(1,1,DELAY)

10 KK=0

20 FOR I=1 TO DELAY

30 CALL KEY(0,K,S)

40 IF K<>-1 THEN KK=K

50 NEXT I

60 ! if KK<>0 then a key was pressed

You can try different values in the CALL HCHAR until you find the one that works best for the program. Let's say 46 works best.

Once you know that you can go back and make line 1 DELAY=46

 

 

 

  • Like 3
  • Thanks 1
Link to comment
Share on other sites

The thing that may not be obvious is that, when running a compiled program from XB, the compiled program is just a very large assembly subroutine running from XB. Try this program out:


10 CALL GCHAR(1,1,G)
20 PRINT "You passed the number";G;"to the compiled program" 

Compile this program and it will read the screen character at 1,1 and print it out. (Usually that will be the edge character 31)

The compiled program will be:


10 CALL INIT :: CALL LOAD(8192,255,158):: CALL LINK("RUN")  plus a bunch of embedded code that you cannot see.

Add line 1 CALL HCHAR(1,1,53) and run the program. XB puts 53 at 1,1 on the screen, then starts the compiled program, which reads 53 from the screen and can use it in the compiled program.

 

This doesn't apply to what you're doing, but when the compiled program ends it returns to XB to the first line after 10. Usually there is no line, so the program ends. But you can have XB do anything that is legal after returning, such as running another program, printing something, or ????

 

  • Like 1
  • Thanks 2
Link to comment
Share on other sites

10 hours ago, senior_falcon said:

The thing that may not be obvious is that, when running a compiled program from XB, the compiled program is just a very large assembly subroutine running from XB. Try this program out:



10 CALL GCHAR(1,1,G)
20 PRINT "You passed the number";G;"to the compiled program" 

Compile this program and it will read the screen character at 1,1 and print it out. (Usually that will be the edge character 31)

The compiled program will be:


10 CALL INIT :: CALL LOAD(8192,255,158):: CALL LINK("RUN")  plus a bunch of embedded code that you cannot see.

Add line 1 CALL HCHAR(1,1,53) and run the program. XB puts 53 at 1,1 on the screen, then starts the compiled program, which reads 53 from the screen and can use it in the compiled program.

 

This doesn't apply to what you're doing, but when the compiled program ends it returns to XB to the first line after 10. Usually there is no line, so the program ends. But you can have XB do anything that is legal after returning, such as running another program, printing something, or ????

 

Ah, now I understand, I tried the code and it read the passed value correctly.

Thanks again!

Link to comment
Share on other sites

  • 2 weeks later...
1 hour ago, Sergioz82 said:

I made a mess in the previous post and I can't delete it.

 

I believe you have only an hour to edit a post. If you become a subscriber to AtariAge, you can edit your posts after a longer delay, generally, but indefinitely in this “Development” subforum.

 

...lee

  • Like 2
  • Thanks 1
Link to comment
Share on other sites

7 hours ago, senior_falcon said:

That is absolutely beautiful!!!!! Such a simple concept, elegantly done!

Thank you very much! ;-)

I couldn't have made it without XB256 (the scroll down routine in particular is fundamental for the game, but also disply helped me a lot)

 

The game mechanics is complete but there is still some work to do: it lacks menu,sounds, score, some tweaks (i.e in the second video the blue monster is not supposed to move like that). I hope I can complete it in the next weeks.

Edited by Sergioz82
  • Like 2
Link to comment
Share on other sites

Here I am again.. 

The game is proceeding, menu is complete and now it has joystick support (I realized that keyboard control is too tiring for the hand, it's not good for the gaming experience)

I have another couple of questions:

 

1) How do I implement a 6 digits score? I tried a basic for loop that assigns to a variant a starting value of 1000000 and increments it and it had no problem with printing the result so I thought it was managed by basic itself.

I used then a variant in the game for the score and it switches to negative values after 32768. 

 

2) Is there any guide about how to make sounds? I read some pdf scans of old manuals but they're mainly focused on music while I'm looking for sound effects. In those manuals they go like "this is the sound of explosion", "this is the sound of a bell chime" and so on but I'd rather like to read the principles behind it. For example how to choose the right number of frequencies to use in a single call sound,  how you actually choose the frequencies you need to replicate a bell and so on. I hope I explained myself ?

 

Thanks in advance 

 

 

Link to comment
Share on other sites

5 minutes ago, Sergioz82 said:

2) Is there any guide about how to make sounds? I read some pdf scans of old manuals but they're mainly focused on music while I'm looking for sound effects. In those manuals they go like "this is the sound of explosion", "this is the sound of a bell chime" and so on but I'd rather like to read the principles behind it. For example how to choose the right number of frequencies to use in a single call sound,  how you actually choose the frequencies you need to replicate a bell and so on. I hope I explained myself ?

 

Thanks in advance 

 

 

I studied electronic music way back in the age of analog Moog synthesizers where you have to build sounds with multiple oscillators and "voltage controlled" amplifiers. :) 

I think your question is outside the realm of programming and more in the realm of the physics of sound and music.

Once you understand what the sound is composed of you can code it or something close to it.  The alternative is experimenting (a lot) and listening carefully to the results.

 

Some concepts to put in your tool box.  "Envelope" of a sound refers to the shape the amplitude (volume) a sound would make over a period of time. 

You can think of the envelope in terms of "attack"; how fast the volume comes up, sustain; how long the sound stays at the same volume and decay; how fast the sound falls to silence.

 

To get the frequencies required to make a sound can be hard.  Artists use their ears and their wet-ware spectrum analyzer. (Everybody has that but with different specs) :) 

Technicians might require an electronic spectrum analyzer but that does not tell the entire story because a real sound unfolds over time with different parts coming into the mix over the envelope time ie: each part could have a separate envelope.  In computer games this level of detail is typically not needed but some sounds must have an envelope. Bells for example: fast attack with a long decay.

 

Apology if you know this already. If it is somewhat new then code examples and modification, trial and error, will probably be the way to go.

  • Like 1
  • Thanks 1
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...