Asmusr Posted July 7, 2022 Share Posted July 7, 2022 What's the reason you're using 8x8 magnified sprites instead of 16x16 sprites? Quote Link to comment Share on other sites More sharing options...
Sergioz82 Posted July 7, 2022 Author Share Posted July 7, 2022 57 minutes ago, Asmusr said: What's the reason you're using 8x8 magnified sprites instead of 16x16 sprites? Mainly because I didn't want to use memory, after all pacman is a yellow ball, I didn't think I needed more details (but giving it two shades of yellow for example could be nice). Would it benefit the movement/eating management (basically collision detection) if I used a 16x16 sprite? Quote Link to comment Share on other sites More sharing options...
apersson850 Posted July 7, 2022 Share Posted July 7, 2022 It would benefit the resolution of the sprites. Are you using that much memory for other things that you can't afford to define the larger sprites? Now you overlap sprites with the creatures in the "parking place" i the middle. Thus one disappear when the eating sprite moves by. Why are they parked as sprites? Are they going out on a hunt later? 1 Quote Link to comment Share on other sites More sharing options...
Sergioz82 Posted July 7, 2022 Author Share Posted July 7, 2022 8 minutes ago, apersson850 said: It would benefit the resolution of the sprites. Are you using that much memory for other things that you can't afford to define the larger sprites? Now you overlap sprites with the creatures in the "parking place" i the middle. Thus one disappear when the eating sprite moves by. Why are they parked as sprites? Are they going out on a hunt later? I have in mind some special items pacman can use but I don't know if there will be enough memory to implement them. I was also thinking to mess with voice module so I'm being cautious with space. Yes, the ghosts in the middle will move. They can partially do it already but I commented the call to the movement routine to focus on pacman. They disappear indeed, actually there are 4 parked there but two are not visible due to hw limitations. My next step is to take care of such issue. I was thinking about drawing one at a time while others stay outscreen. Hopefully they'll get a flickering effect that would do as ghostly transparency. 1 Quote Link to comment Share on other sites More sharing options...
apersson850 Posted July 8, 2022 Share Posted July 8, 2022 Another way is to redefine them as character graphics while they are stationary anyway. Doesn't work when they start moving, but if not all move at the same time, it could work. Quote Link to comment Share on other sites More sharing options...
HOME AUTOMATION Posted July 8, 2022 Share Posted July 8, 2022 In the original game, only 3 ghosts can occupy the box. 1 Quote Link to comment Share on other sites More sharing options...
apersson850 Posted July 8, 2022 Share Posted July 8, 2022 Probably for a good reason! 1 1 Quote Link to comment Share on other sites More sharing options...
XLERB Posted July 8, 2022 Share Posted July 8, 2022 I have to say, reading this thread is like a master class in handling sprites in assembly (and Forth!) 1 Quote Link to comment Share on other sites More sharing options...
Sergioz82 Posted July 9, 2022 Author Share Posted July 9, 2022 (edited) Here's the flickering effect I had in mind. For the moment the ghosts just bounce around, now I have to make them move as they should. Ideally I'd like a different behavior for each one, but maybe I'm asking too much from myself Anyway, if you guys are interested I'll gladly share the complete code for ghost movement/drawing. Meanwhile, the trick I used is to hide the ghost sprites at each game loop. I use a byte for a count of 4. Each loop only the ghost corresponding to count number is drawn on screen, the others are moved at row C0 and so on, in rotation. I can foresee an issue with this method: as the game loop gets loaded with instructions it will inevitably slow down so the flickering will slow down as well and it could become annoying to the eye. However, the loop is actually slowed down with a count of 1000 so I should have some margins. Spacm.mp4 Edited July 9, 2022 by Sergioz82 2 Quote Link to comment Share on other sites More sharing options...
ti99iuc Posted July 9, 2022 Share Posted July 9, 2022 Good solution Sergio! This was the same solution used for Midnite Mason in 1984, So I think you will be able to manage a good speed for the full game. 1 Quote Link to comment Share on other sites More sharing options...
Asmusr Posted July 9, 2022 Share Posted July 9, 2022 This way the ghosts will always flicker - even when there is no vertical overlap. The more common way to handle this is to rotate the order of the ghosts in the sprite attribute list each frame. Then they will only flicker if there are more than 4 sprites on a line. You can also order all the eyes after all the bodies. Then it's only the eyes that will flicker most of the time. 3 2 Quote Link to comment Share on other sites More sharing options...
Asmusr Posted July 9, 2022 Share Posted July 9, 2022 As a general comment, the way I always work with sprites now, which is different from when I started programming games for the TI, e.g. Titanium, is that I maintain a list of (logical) sprite data in RAM, which is not bound to hardware sprites at all. The data stored for each sprite include things like position, velocity, size (if the logical sprites can be bigger than a hardware sprite), pattern(s), animation settings, color, whether the sprite is visible, and any other attributes required. Then, each frame or game loop, I rewrite the entire sprite attribute table in VDP RAM by going through the logical sprites list, generating data for hardware sprites (y, x, pattern, color, early-clock) as needed, stopping if the 32 sprite limit is reached, or maybe ending before that. This makes it easy to create a flicker routine by traversing the logical sprites in a different order each frame, and it also facilitates things like animation, bigger sprites, sprite movement, adding and deleting sprites, having more the 32 logical sprites, etc. It can also be faster than using VSBW to change hardware sprite attributes directly. But my method is somewhat bound to having the 32K RAM available, because storing logical sprite data in VDP RAM would be a lot slower. 5 1 Quote Link to comment Share on other sites More sharing options...
Sergioz82 Posted July 10, 2022 Author Share Posted July 10, 2022 @Asmusr Thank youvery much for your aid. 12 hours ago, Asmusr said: This way the ghosts will always flicker - even when there is no vertical overlap. The more common way to handle this is to rotate the order of the ghosts in the sprite attribute list each frame. Then they will only flicker if there are more than 4 sprites on a line. You can also order all the eyes after all the bodies. Then it's only the eyes that will flicker most of the time. Ok, I'll use this method. Please tell me if I got it right: In RAM I have an attribute list in this order: Ghost 1,2,3 and 4 (to be more precise, it's Ghost 1 - Eyes 1, Ghost 2 - Eyes 2 and so on) With your method each time I update VDP I rotate the order this way: VDP attribute list at game loop 1: Ghost 1,2,3,4 VDP attribute list at game loop 2: Ghost 4,1,2,3 VDP attribute list at game loop 3: Ghost 3,4,1,2 VDP attribute list at game loop 4: Ghost 2,3,4,1 VDP attribute list at game loop 5: Ghost 1,2,3,4 and so on. Correct? About your second comment, I'll rewrite my VDP update system. At the moment I VMBW at both routines for pacman and for ghosts. Instead I'm going to BL to a single VDP update routine (which as you suggest will update the whole sprite attributes table at once) at the end of each loop after all the game logic calculations are done. I had in mind that RAM and VDP data should have 1:1 correspondence but you shown me that VDP data are for screen drawing, they don't contribute to game logic calculations. 2 Quote Link to comment Share on other sites More sharing options...
Asmusr Posted July 10, 2022 Share Posted July 10, 2022 1 hour ago, Sergioz82 said: VDP attribute list at game loop 1: Ghost 1,2,3,4 VDP attribute list at game loop 2: Ghost 4,1,2,3 VDP attribute list at game loop 3: Ghost 3,4,1,2 VDP attribute list at game loop 4: Ghost 2,3,4,1 VDP attribute list at game loop 5: Ghost 1,2,3,4 and so on. Correct? Yep Quote Link to comment Share on other sites More sharing options...
apersson850 Posted July 10, 2022 Share Posted July 10, 2022 3 hours ago, Sergioz82 said: ... VDP data are for screen drawing, they don't contribute to game logic calculations. That sounds like a very good conclusion. Optimize data for its purpose. The game logic is probably more complicated than a translation from logic data to VDP data format. 2 Quote Link to comment Share on other sites More sharing options...
Sergioz82 Posted July 12, 2022 Author Share Posted July 12, 2022 (edited) On 7/10/2022 at 8:43 AM, Asmusr said: Yep Here's my basic routine to do the trick. I explain the idea behind it so hopefully it can be useful to other beginners like me: I define a temporary attribute list in RAM. Each game loop I rotate the position of the sprites according to an offset that is incremented each game cycle up to "Last position of Temporary Attribute List - 1". Each position is 4 bytes long, corresponding to the attributes of a single sprite: 2 for sprites coordinates and 2 for pattern and color. Each time I do a double MOV with autoincrement. MOV increments two, therefore first MOV copies coordinates and points to next word, now second MOV copies the pattern and the color. With second increment R0 and R1 point to next sprite in RAM A.T. The offset works this way: at first cycle it's 0. The routine just copies 1:1 of what's in my RAM attribute table into temporary table, let's say order of sprites is 1 2 3 4 so in temporay table it will be 1 2 3 4 too. At second cycle it's 4 (4 bytes increment). Now it copies RAM data starting to second sprite entry in temporary table. If R1 goes over the end of the temporary table it's resetted back to position 1. Meanwhile there's still one sprite to copy from RAM A.T to Temp A.T but now it will be copied in position 1. Now Temp A.T is 4 1 2 3 ... Let's skip to offset 12: 1 2 3 4 in RAM A.T is copied starting from position 4 in Temp. A.T, therefore now it's 2 3 4 1. At this point, offset is cleared to 0 so next cycle it's back to 1:1 scenario, that is, 1 2 3 4 in Temp. A.T It works, but obiviously not as well as it should.. the sprites glitch like crazy, in between correct frames I see random horizontal lines here and there. I can't post a video now I'll do it later but I have pictures with corrupted sprites. I used debugger to check what happens at >0300 in VDP and it seems the routine does its job. Do you guys see some fault in the code? Maybe a broken increment/jump? Quote UPDVDP LI R0,ATTLST *Load Sprite attribute table start address (RAM) LI R1,SPSHFT *Load address of a shift area in RAM, allocated with BSS 40 MOV @SWPOFF,R3 *Load rotation offset A R3,R1 *Add offset to swap area SHIFT MOV *R0+,*R1+ *Move word in *R0 to *R1, implicit +2 increment (move sprite coordinates word) MOV *R0+,*R1+ *Move word in *R0 to *R1, implicit +2 increment (move sprite pattern and color word) CI R1,SPSHFT+40 *Is R1 over the last position of the shift area? JLT GO1 *No, keep incrementing LI R1,SPSHFT *Yes, point back to first position GO1 CI R0,ATTLST+40 *Is R0 over the last word in RAM attribute table? JLT SHIFT *No, keep moving AI R3,4 *Increment offset by 4 (4 bytes of sprite attributes) MOV R3,@SWPOFF *Save the result for the next game loop CI R3,40 * Is the offset over last position in swap area? JLT WVDP *No, keep the value CLR @SWPOFF *Yes, no offset WVDP LI R0,>0300 *Write the rotated sprite table in VDP sprite attributes table LI R1,SPSHFT LI R2,40 BLWP @VMBW B *R11 glitch1.bmp glitch2.bmp glitch3.bmp Edited July 12, 2022 by Sergioz82 2 Quote Link to comment Share on other sites More sharing options...
Willsy Posted July 12, 2022 Share Posted July 12, 2022 (edited) Are you seeing simple 'tearing' - adjusting the sprites when they are actually being drawn? One way to get around that would be to try and do it in the vertical blank period. That would depend on when the VDP interrupt fires I guess - beginning or end of frame etc. Edited July 12, 2022 by Willsy typo 1 Quote Link to comment Share on other sites More sharing options...
+mizapf Posted July 12, 2022 Share Posted July 12, 2022 TMS9918 specs: 2.1.6 VDP Interrupt The VDP INT output pin is used to generate an interrupt at the end of each active-display scan, which is about every 1/60 second for the TMS9918A/9928A and 1/50 for the TMS9929A. ... 2 2 Quote Link to comment Share on other sites More sharing options...
Sergioz82 Posted July 12, 2022 Author Share Posted July 12, 2022 @Willsy @mizapf Thank you. I'll try to figure out how to check and wait the vertical blank. Are there any topics on this forum? Quote Link to comment Share on other sites More sharing options...
PeteE Posted July 12, 2022 Share Posted July 12, 2022 (edited) 4 hours ago, Sergioz82 said: I'll try to figure out how to check and wait the vertical blank. Are there any topics on this forum? Lots. A search for "VSYNC" should get you what you're looking for. Here is what I've settled on: VSYNC CLR R12 ; CRU Address bit 0002 - VDP INT VWAIT TB 2 ; CRU bit 2 - VDP INT JEQ VWAIT ; Loop until set MOVB @VDPSTA,R12 ; Clear interrupt flag manually since we polled CRU The 9918A VDP sets an interrupt signal at the start of vertical blank (59.94 times per second) which is immediately after the 24th line of characters at the bottom of the visible portion of the screen, where it starts displaying solid border color. The VDP interrupt is connected 9900 CPU interrupt, and also to the 9901 chip which is connected to the CRU signals on the CPU. To read the interrupt from the 9901 we need to read CRU address 2, using the TB (test bit) instruction relative to the address in the R12 register. The JEQ spins in a loop until the interrupt occurs, but then interrupt must be cleared by doing a read of the VDP status register. Normally the ISR (interrupt service routine) would handle reading the status register after the interrupt, but I always run with the CPU interrupts off and the ISR disabled, by using "LIMI 0" at the start of my program. Edited July 12, 2022 by PeteE 1 1 Quote Link to comment Share on other sites More sharing options...
+mizapf Posted July 12, 2022 Share Posted July 12, 2022 One could also use IDLE in this case, then check for the VDP INT. 1 Quote Link to comment Share on other sites More sharing options...
PeteE Posted July 12, 2022 Share Posted July 12, 2022 (edited) 3 hours ago, mizapf said: One could also use IDLE in this case, then check for the VDP INT. Wouldn't that also allow the ISR to run? In my programs, I avoid the ISR because I'm using as much of the scratchpad RAM as possible for my own use. And the ISR changes the address register in the VDP, which if it occurs between your own code setting the address and writing the data, could very easily be causing the glitches described. Edited July 12, 2022 by PeteE 1 Quote Link to comment Share on other sites More sharing options...
+mizapf Posted July 12, 2022 Share Posted July 12, 2022 Correct, you have to enable interrupts (at least LIMI 1). I just wanted to point out that this is probably a reasonable use case for the IDLE command. Quote Link to comment Share on other sites More sharing options...
+TheBF Posted July 12, 2022 Share Posted July 12, 2022 2 hours ago, PeteE said: Lots. A search for "VSYNC" should get you what you're looking for. Here is what I've settled on: VSYNC CLR R12 ; CRU Address bit 0002 - VDP INT VWAIT TB 2 ; CRU bit 2 - VDP INT JEQ VWAIT ; Loop until set MOVB @VDPSTA,R12 ; Clear interrupt flag manually since we polled CRU The 9918A VDP sets an interrupt signal at the start of vertical blank (59.94 times per second) which is immediately after the 24th line of characters at the bottom of the visible portion of the screen, where it starts displaying solid border color. The VDP interrupt is connected 9900 CPU interrupt, and also to the 9901 chip which is connected to the CRU signals on the CPU. To read the interrupt from the 9901 we need to read CRU address 2, using the TB (test bit) instruction relative to the address in the R12 register. The JEQ spins in a loop until the interrupt occurs, but then interrupt must be cleared by doing a read of the VDP status register. Normally the ISR (interrupt service routine) would handle reading the status register after the interrupt, but I always run with the CPU interrupts off and the ISR disabled, by using "LIMI 0" at the start of my program. Thanks @PeteE This is very concise and going into my system's library. 1 Quote Link to comment Share on other sites More sharing options...
HOME AUTOMATION Posted July 12, 2022 Share Posted July 12, 2022 2 hours ago, mizapf said: I just wanted to point out that this is probably a reasonable use case for the IDLE command. But, doesn't the use of IDLE, violate the 4A's, hardwiring? Quote Link to comment Share on other sites More sharing options...
Recommended Posts
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.