News:

Printed Amstrad Addict magazine announced, check it out here!

Main Menu
avatar_lachlank

CPC Hill Climber WIP

Started by lachlank, 22:55, 18 July 15

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

lachlank

Well I got somewhat bored with Minecraft, so thought it might be easy to knock up a Hill Climb Racing clone for CPC.





I started out playing with hardware scrolling as had never looked into this before, but rapidly ran into problems, such as most sprite drawing routines don't take offset video start addresses into account.


So I switched to a software scroll which actually works OK, columns of width 1 byte, and only redrawing the difference between adjacent columns. Redrawing all 80 only requires about half the cycles for a full frame refresh so still plenty of time to draw sprites etc within single frame (this runs 50hz with no double buffer and hopefully enough cycles left over for game logic and some basic physics).


All C (CPCTelera) with no assembler at this point. Now to figure out how to do some 2d physics (or fake it) on the CPC  :'(


Lachlan

ronaldo

The animation of the car looks very funny :D. It's very nice for being version 0.01. That could be a very funny game.

Quote from: lachlank on 22:55, 18 July 15
I started out playing with hardware scrolling as had never looked into this before, but rapidly ran into problems, such as most sprite drawing routines don't take offset video start addresses into account.
You've got to pass them the pointer to the start address.

  u8* scrLocation = cpct_getScreenPtr(STADDR, x, y);

When screen hasn't been scrolled, STADDR=0xC000. If you scroll the screen, change STADDR to the present staring point of your video memory or backbuffer, and you can get your screen locations to draw :).

Anyways, hardware scrolling has some other difficulties to deal with, but that's a start :)

Quote from: lachlank on 22:55, 18 July 15
All C (CPCTelera) with no assembler at this point. Now to figure out how to do some 2d physics (or fake it) on the CPC  :'(
Have a look at Platform Climber source code. It has some 2D physics implemented (very basic, but that's a start) :).

lachlank

Quote from: ronaldo on 23:16, 18 July 15

You've got to pass them the pointer to the start address.

  u8* scrLocation = cpct_getScreenPtr(STADDR, x, y);

When screen hasn't been scrolled, STADDR=0xC000. If you scroll the screen, change STADDR to the present staring point of your video memory or backbuffer, and you can get your screen locations to draw :) .



Hi, thanks for the feedback. After scrolling (adding 1 to offset each time), I can get the new draw address fine (as above), I think the problem lies with the sprite drawing, the routine assumes that the screen mem starts on a page boundary (the standard ones, 0xC000, 0x8000 etc). As such this logic doesn't work once the memory is offset:



add  hl, bc    ;; [11] We add 0x800 minus the width of the sprite (BC) to destination pointer
   ld    b, a     ;; [ 4] Save A into B (B = A)
   ld    a, h     ;; [ 4] We check if we have crossed video memory boundaries (which will happen every 8 lines).
                  ;; .... If that happens, bits 13,12 and 11 of destination pointer will be 0
   and   #0x38    ;; [ 7] leave out only bits 13,12 and 11
   ld    a, b     ;; [ 4] Restore A from B (A = B)
   jp   nz, ds_drawSpriteWidth_next ;; [10] If that does not leave as with 0, we are still inside
                                    ;; .... video memory boundaries, so proceed with next line


   ;; Every 8 lines, we cross the 16K video memory boundaries and have to
   ;; reposition destination pointer. That means our next line is 16K-0x50 bytes back
   ;; which is the same as advancing 48K+0x50 = 0xC050 bytes, as memory is 64K
   ;; and our 16bit pointers cycle over it
   ld   bc, #0xC050        ;; [10] We advance destination pointer to next line
   add  hl, bc             ;; [11]  HL += 0xC050
   jp ds_drawSpriteWidth_next ;; [10] Continue copying



Am I correct in thinking we would need a more complex routine for calculating the next line (based on address of vid mem start) for drawing sprites when the screen has been scrolled?




ronaldo

#3
No, that routine is made that way on purpose to work at any given location on the memory. It might be faulty, but it does not assume a starting location.

The problem you may be facing could be due to the way scroll works. If you increment offset by 1, you are moving the screen 2 bytes, not 1. If you take this into account, you can use hardware scrolling and all the drawing functions from CPCtelera should work.

I have coded a little example to show this. This is a text which scrolls horizontally as you press left and right cursor keys.



#include <cpctelera.h>

#define SCR_VMEM  (u8*)0xC000
#define CHAR_LINE 0x050

// Scroll offset (0-255)
const u8  g_offset = 0;


void waitNVSYNCs(u8 n) {
   do {
      cpct_waitVSYNC();
      if (--n) {
         __asm__("halt");
         __asm__("halt");
      }
   } while(n);
}


void drawCompleteText(u8* str){
   u8 line;
   for (line=0; line < 10; line++) {
      u8* ptr = SCR_VMEM + (line+5)*CHAR_LINE;
      u8 c = str[line + 40];
      str[line + 40] = 0;
      cpct_drawStringM1( (str+line), ptr, 1, 0);
      str[line + 40] = c;
   }
}

void scroll(u8* str, i8 move, u16 erase_offset, u16 draw_offset, u8 char_offset) {
   u8* poff  = &g_offset;
   u8* pvmem;
   u8  c;

   // Move screen, changing offset and recalculate pointer
   *poff = *poff + move;
   cpct_setVideoMemoryOffset(*poff);
   pvmem = SCR_VMEM + *poff * 2;

   // Remove characters that exit from screen
   cpct_drawSolidBox(pvmem + erase_offset, 0x00, 2, 80);

   // Draw new characters that enter the screen
   pvmem += draw_offset;
   for (c=*poff + char_offset; c < *poff + char_offset + 10; c++) {
      cpct_drawCharM1_f( pvmem, (c & 0x03) | 0x01 , 0, str[c]);
      pvmem += 0x50;
   } 
}

#define SCROLL_LEFT   scroll(str,  1, 5*CHAR_LINE - 2, 6*CHAR_LINE - 2, 39)
#define SCROLL_RIGHT  scroll(str, -1, 6*CHAR_LINE    , 5*CHAR_LINE    ,  0)

void main(void) {
   const u8 str[121] = "This is a scrolling text that will be using hardware scrolling for moving. Check how it works, moving and drawing chars.";
   u8* poff = &g_offset;

   // Initialization
   cpct_disableFirmware();
   cpct_memset(SCR_VMEM, 0, 0x4000);
   cpct_setBorder(0);

   // Draw the initial text to scroll
   drawCompleteText(str);

   // Loop forever
   while(1) {
      cpct_scanKeyboard_f();

      waitNVSYNCs(1);
      if (*poff && cpct_isKeyPressed(Key_CursorLeft))
         SCROLL_RIGHT;
      else if (*poff < 71 && cpct_isKeyPressed(Key_CursorRight))
         SCROLL_LEFT;
   }
}

You can do a finer grained scroll using double buffer and having one buffer displaced 1 byte with respect to the other.

lachlank

#4
Quote from: ronaldo on 01:29, 19 July 15
No, that routine is made that way on purpose to work at any given location on the memory. It might be faulty, but it does not assume a starting location.

Thanks... Can you explain to me what I am doing wrong here (set offset of 150, then draw some boxes)?



void testScrolled() {
   u8 *scrStart = 0xc000 + 150 * 2;
   u8* scrLocation = cpct_getScreenPtr(scrStart, 1,1);
     
   cpct_setVideoMemoryOffset(150);
   
   cpct_drawSolidBox (scrLocation, 0xff, 20, 20);
   scrLocation = cpct_getScreenPtr(scrStart, 59,0);
   cpct_drawSolidBox (scrLocation, 0xff, 20, 200);
   
   while(1) {};
}



EDIT: Maybe this article is of relevance, specifically the section "Moving around the screen using screen addressess with a hardware scrolling screen":
Screen memory addressess

ronaldo

You're doing nothing wrong. In fact, there is a problem with drawing routines, but a little bit different from what you were pointing out previously. The problem is considering that next screen byte is +1 from present screen byte. That works most of the time, but not all the time, as you may see here:

void main(void) {
   cpct_disableFirmware();     
   cpct_setBorder(1);
   
   // Moves 0xC000 to the last visible character of video memory
   cpct_setVideoMemoryOffset(25);

   // Draws a 2-chars-wide box 4 chars from the end
   cpct_drawSolidBox ((u8*)0xC7FD, 0x0F, 2, 4);
   
   // Draws a 2-chars-wide box 2 char from the end
   cpct_drawSolidBox ((u8*)0xC7FF, 0xFF, 2, 4);
   
   // Highlights the first 4 pixels of the last character
   *((u8*)0xC000)=0xF0;
   
   while(1) {};
}

When screen is scrolled 0x??FF locations have this problem. Incrementing 1 does not move to the next screen byte, but to the next screen byte  on the next pixel line. Take, for instance, 0xC7FF, which is used in this example. Drawing a box there fills 0xC7FF and 0xC800 with 0xFF (first line of the box). However, the correct locations to do that are 0xC7FF and 0xC000.

I didn't took this into account when designing functions. This problem is much difficult to solve without penalizing performance, and is distributed accoss drawing functions.

Right now, the best you can do is taking it into account when using drawing functions, while we design a solution not to penalize performance.

lachlank

Quote from: ronaldo on 11:54, 19 July 15

Right now, the best you can do is taking it into account when using drawing functions, while we design a solution not to penalize performance.
Great, it would be useful in due course to have routines in parallel to the existing drawing functions to handle the scrolled screen. Your task list must be getting very long!


Apologies for pointing to the wrong part of your routine; I was initially under the impression that offsetting the screen simply pushed the start address forward, and all character lines would then run sequentially. Now after reading some more I realize that in fact each of the 8 characters lines always occupies the same 0x800 block of memory, and these wrap around. Just the draw offset is changed for each line.

Powered by SMFPacks Menu Editor Mod