News:

Printed Amstrad Addict magazine announced, check it out here!

Main Menu
avatar_ervin

My CPCRetroDev2015 entry - RUN"CPC"

Started by ervin, 13:08, 28 June 15

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

ervin

Hi everyone.

It's been several months since I have touched any CPC development.

Some of you may have noticed my Chunky Pixel Curator thread go quiet... unfortunately I just ran out of steam on it.  :'(
I looked at how much was left to do, and I just couldn't bear the thought of going on with it any more... it was just too ambitious, and I felt burned out.
Even today, if I look at the source code, I just crawl away into a corner and hide.

The good thing is that I achieved what I wanted to achieve from it, on a technical level at least.
I'm still very proud of what I was able to do regarding real-time sprite scaling on a CPC.

And then ronaldo announced CPCtelera... and I was fascinated.
I've never done any dev in C, and I've always wanted to learn it.
When the CPCRetroDev2015 compo was announced, I decided it was time to dive in and give CPCtelera a try.

And I'm glad I did!
It's absolutely brilliant.
If the devs back in the 80s/90s had access to something like this (even on the *much* slower PCs of the day), things would have been very interesting indeed.

Any devs around here that are curious about CPCtelera should give it a try. I think it's absolutely awesome.
And the dev (ronaldo) is super responsive and supportive, and is always willing to listen to suggestions.

So! I'll have a go at coding a simple into-the-screen auto-runner.
The idea is that simple sprites (composed of tiles) will advance toward the player at high speed, and the player's job is simply to avoid crashing for as long as possible.
It's inspired by the glut of very simple and very difficult games flooding mobile.

I'm making slow but steady progress, as I'm learning C while I write this.
I've already thrown away a lot of code, but now I've got a box being drawn on screen, aligned with a horizontal line representing road movement.
The box grows and shrinks as the player moves forward and backward.

The road line's position, and the box's position and size, are calculated using proper 3d-to-2d projection.
The calculations appear to be quick enough in SDCC, which is a good thing, as I won't be using any asm in the project at all.
I'm gonna see how fast I can make it run using pure SDCC (and my poor C skills) along with CPCtelera's *wonderful* framework.

It's horribly unoptimised, and only uses character printing for test purposes for the time-being.
But it's a start, and because the game idea is so simple, I can already see light at the end of the tunnel.
8)

I've gotta give a big round of applause to CPCtelera's fast character printing functions - they are very very nice indeed.

If you're interested in following where this project goes, the first DSK is attached.
The game will (rather pretentiously) be called RUN"CPC".
;D

[EDIT] I forgot to mention that the "game" is controlled with the arrow keys.


arnoldemu

How about storing a few prescaled sprites like they did back in the day? Then choose them based on distance from the viewer
? It may help to avoid being concerned with the how and let you concentrate on the game play. Hint, those runner type games often appear to have a set of prepare sections which are randomly placed after each other. This allows the game to be possible without making it totally random and potentially impossible if the random choice is bad.
My games. My Games
My website with coding examples: Unofficial Amstrad WWW Resource

arnoldemu

I am looking forward to playing  your game. I always thought a runner game would suite cpc.
My games. My Games
My website with coding examples: Unofficial Amstrad WWW Resource

ervin

#3
Quote from: arnoldemu on 13:25, 28 June 15
How about storing a few prescaled sprites like they did back in the day? Then choose them based on distance from the viewer
? It may help to avoid being concerned with the how and let you concentrate on the game play. Hint, those runner type games often appear to have a set of prepare sections which are randomly placed after each other. This allows the game to be possible without making it totally random and potentially impossible if the random choice is bad.

Indeed I will use prepared sections, as otherwise things could become a bit messy for the player!
:)

As for the sprites, I'm sort of going to use pre-scaled sprites.
My obstacles will have 21 different sizes, depending on the distance from the player.
Each one will simply be composed of data such as width, height, and then a list of tile references.
So each obstacle will be constructed using tiles, instead of colour values for every pixel.

Using a dirty buffer approach, I should hopefully achieve a playable speed (at full-screen).
8)

ervin

Quote from: arnoldemu on 13:27, 28 June 15
I am looking forward to playing  your game. I always thought a runner game would suite cpc.

Thanks man.
I certainly hope it will suit the cpc.
8)

ronaldo

Thank you for your kind words, @ervin. I'm glad to help :)

It's quite curious the scrolling effect you've made out of characters. It remembers me to lots of games I've made using characters :D. Character and string drawing functions are indeed fast, if you take into account they are reading characters from ROM. However, it is much much faster drawing sprites than characters (you can check it on the measures included in the documentation). Even drawing boxes (with cpct_drawSolidBox) is a much faster alternative. In the end, the fastest functions are those which draw tiles.

Will be following your progress and expecting your great results ;)



Gryzor

Thanks for the update mate!!

ervin

Quote from: ronaldo on 17:05, 28 June 15
Thank you for your kind words, @ervin. I'm glad to help :)

It's quite curious the scrolling effect you've made out of characters. It remembers me to lots of games I've made using characters :D . Character and string drawing functions are indeed fast, if you take into account they are reading characters from ROM. However, it is much much faster drawing sprites than characters (you can check it on the measures included in the documentation). Even drawing boxes (with cpct_drawSolidBox) is a much faster alternative. In the end, the fastest functions are those which draw tiles.

Will be following your progress and expecting your great results ;)

At the moment, I'm trying to get a dirty buffer working, where only changed character blocks are printed, and no clearscreen is needed. Once I've got that working, the next step will indeed be to use tiles instead.
8)

ervin

Alrighty, I've added test_2.zip to the original post.

It still contains a box drawn with characters, but now uses a dirty buffer approach, so I no longer clear the screen between frames, and I can now print a filled box instead of just an outline.

It's still very slow of course, but as a quick test I'm very happy with it.

Now I'll try to figure out how to use CPCtelera's excellent tile features!
8)

ronaldo

Did you forget to attach it to the post? :)

[EDIT]: Sorry, I didn't see you attached it to the first post, not to this last one :)

McKlain

This is a bit like the second level in Savage, isn't it?

ervin

Quote from: McKlain on 13:58, 29 June 15
This is a bit like the second level in Savage, isn't it?

A little bit, but without the shooting.
And probably nowhere near as smooth either!  ;D

ervin

#12
I've uploaded test_3.zip, which contains 2 large white squares that can be moved closer or further away (up/down arrow keys).
This one gives a good idea of the effect I'll be trying to achieve in my game, though of course the graphics will look a bit different.

I've gotta say, I'm very happy with cpctelera's tile functions.
They are very fast, and they make creating this sort of program a joy.
8)

Also, some questions for any SDCC devs out there.
(I'm still learning C and probably not doing things optimally).

This is the code that compares frameBuffer with screenBuffer.
(They are both arrays of 1000 bytes).

    for (buffIdx=0;buffIdx<1000;buffIdx++){
        if (frameBuffer[buffIdx]!=screenBuffer[buffIdx])
            screenBuffer[buffIdx]=frameBuffer[buffIdx];
    }


Does anyone know of a way to make following things quicker?
- the FOR loop (buffIdx is an unsigned int)
- the != comparison
- copying frameBuffer[buffIdx] to screenBuffer[buffIdx]

Thanks.


AMSDOS

#13
Quote from: ervin on 08:41, 30 June 15
Does anyone know of a way to make following things quicker?
- the FOR loop (buffIdx is an unsigned int)
- the != comparison
- copying frameBuffer[buffIdx] to screenBuffer[buffIdx]


"!=" means not equal to.
"Unsigned Ints" are always Positive values.
You might be able to make things quicker by using a different kind of loop, I cannot say with any absolute certainty.


Also usually the notation for increments to variables is ++buffIdx.
I'm unsure if you can get away without the Brackets after the FOR statement, C can allow a simple statement after a FOR, though it doesn't say if that simple statement can be a IF <Condition> <Simple Statement>; condition.


In Assembly, if you can embed that into your code, it's merely a comparison check you need to do and if it equals the same, you would skip the code. I'm not sure what it should look like, all really depends on how C interacts with the Assembly, so it may not look like this:



ld a,(frameBuffer[buffIdx])
ld hl,(screenBuffer[buffIdx])
cp (hl)
jr z,skip ;; if HL = A Skip
ld (hl),a
.skip



Of course it might have to be modified to ld hl,screenBuffer[buffIdx] to obtain the address of screenBuffer[buffIdx] rather than the contents of it, contents are check on comparison.
* Using the old Amstrad Languages :D   * with the Firmware :P
* I also like to problem solve code in BASIC :)   * And type-in Type-Ins! :D

Home Computing Weekly Programs
Popular Computing Weekly Programs
Your Computer Programs
Updated Other Program Links on Profile Page (Update April 16/15 phew!)
Programs for Turbo Pascal 3

ervin

#14
Quote from: AMSDOS on 11:11, 30 June 15
"!=" means not equal to.
"Unsigned Ints" are always Positive values.
You might be able to make things quicker by using a different kind of loop, I cannot say with any absolute certainty.

Also usually the notation for increments to variables is ++buffIdx.
I'm unsure if you can get away without the Brackets after the FOR statement, C can allow a simple statement after a FOR, though it doesn't say if that simple statement can be a IF <Condition> <Simple Statement>; condition.

In Assembly, if you can embed that into your code, it's merely a comparison check you need to do and if it equals the same, you would skip the code.

Yep, the "!=" and the Unsigned Int parts are cool. I was just wondering if anyone knew of something quicker than what I was doing in those statements.

I've researched other loop types (while, do while, etc.), and the general consensus seems to be that we should leave it to the compiler, as these days compiler optimisation is very good (and SDCC does indeed seem to do an excellent job in this regard).

The lack of braces is an interesting one. I initially thought I'd need them after every single FOR, IF etc.
But it seems that if there is only a single statement following FOR or IF, it will execute correctly.

In the case of nesting an IF inside the FOR as I have done, it looks as though no braces are needed at all, if the IF only has one statement after it. And it looks as though the compiler treats that IF (and its one statement) as one statement after the FOR. Weird, but it seems to work!  8)

[EDIT] I had tried a bit of assembler, but then I thought no no no... I don't want to head down that path of staring at assembly for hours on end trying to get a bit more speed out of it. It's very satisfying when you find that extra speed, but I did enough of that during Chunky Pixel Curator to last a lifetime!  :P


ronaldo

I assume that you have 2 tile buffers and compare changes between one and the other. This approach is not much more improvable: you can go down to assembly and improve it, or do some C hacks, but that's not a good way to address this issue.

What you should do at this point is changing your drawing algorithm. You should have a dirty buffer which only stores changes (not the entire screen). Then, when looping through the dirty buffer, you dont have to make hundreds of comparisons to decide that nothing should be done (unchanged tiles). You only have to worry about changes.

Other thing you should do to improve your code is not using unsigned int unless you require them. If your values are going to be in the range [0,255] you should use only 8-bits per tile, and not 16-bits (the size of a unsigned int). For that, you can use unsigned char. In CPCtelera you have some aliases for types to fastly identify their size and use what you require. In your case, you should use u8 type (unsigned 8-bits).

If your tiles are going to be in a narrower range, you can use CPCtelera's bitarrays. What you do here is defining a normal u8 array, but using it as 1-bit, 2-bits or 4-bits elements, instead of 8-bits. So, you can do things like this:

// Array with 8 elemnts of 8-bits or 16 elements of 4-bits each
u8 array4bits[8];
// Fill 16 4-bits elements with the numbers from 0 to 15
u8 i;
for(i=0; i < 16; ++i)
   cpct_set4Bits(array4bits, i, i);

In this case, you can save a lot of memory, not loosing to much performance (always depending on your needs) :).

ervin

Quote from: ronaldo on 12:53, 30 June 15
I assume that you have 2 tile buffers and compare changes between one and the other. This approach is not much more improvable: you can go down to assembly and improve it, or do some C hacks, but that's not a good way to address this issue.

What you should do at this point is changing your drawing algorithm. You should have a dirty buffer which only stores changes (not the entire screen). Then, when looping through the dirty buffer, you dont have to make hundreds of comparisons to decide that nothing should be done (unchanged tiles). You only have to worry about changes.

Other thing you should do to improve your code is not using unsigned int unless you require them. If your values are going to be in the range [0,255] you should use only 8-bits per tile, and not 16-bits (the size of a unsigned int). For that, you can use unsigned char. In CPCtelera you have some aliases for types to fastly identify their size and use what you require. In your case, you should use u8 type (unsigned 8-bits).

Thanks ronaldo.
You've given me an idea...
8)


ervin

I've uploaded test_4.zip to the first post in this thread.

Oh yeah! That's more like it!
Got some *really* nice speed happening now, all with pure SDCC+cpctelera.
This has now confirmed that I can totally stay away from any assembler coding for this project - I'm really happy about that!  ;D

Please ignore the horrendous colours - the graphics are just placeholders for now.

AMSDOS

Quote from: ervin on 02:47, 01 July 15

This has now confirmed that I can totally stay away from any assembler coding for this project - I'm really happy about that!  ;D


Probably better that way, always seems to be fiddly incorporating assembly within C code.
* Using the old Amstrad Languages :D   * with the Firmware :P
* I also like to problem solve code in BASIC :)   * And type-in Type-Ins! :D

Home Computing Weekly Programs
Popular Computing Weekly Programs
Your Computer Programs
Updated Other Program Links on Profile Page (Update April 16/15 phew!)
Programs for Turbo Pascal 3

andycadley

#19
Quote from: ervin on 08:41, 30 June 15
Also, some questions for any SDCC devs out there.
(I'm still learning C and probably not doing things optimally).

This is the code that compares frameBuffer with screenBuffer.
(They are both arrays of 1000 bytes).

    for (buffIdx=0;buffIdx<1000;buffIdx++){
        if (frameBuffer[buffIdx]!=screenBuffer[buffIdx])
            screenBuffer[buffIdx]=frameBuffer[buffIdx];
    }


Does anyone know of a way to make following things quicker?
- the FOR loop (buffIdx is an unsigned int)
- the != comparison
- copying frameBuffer[buffIdx] to screenBuffer[buffIdx]

Thanks.

The IF is probably the biggest problem there, it's a lot more effort for the Z80 to read both numbers and compare them then do the copy than it is to blindly copy them and, since you aren't doing anything special, the end result will be the same.

ervin

Quote from: andycadley on 18:34, 01 July 15
The IF is probably the biggest problem there, it's a lot more effort for the Z80 to read both numbers and compare them then do the copy than it is to blindly copy them and, since you aren't doing anything special, the end result will be the same.

Yes, you're right.

However, that IF is what determines whether or not a tile is actually printed to the screen.
I left out the rest of the code so that it would be clear what I was talking about... except that doing so removed context.
:-[


ervin

#21
test_5.zip has been uploaded to the original post.

This one contains more colour, more giant squares, and more speed!
I'm *very* happy with how fast it's running.
There is some screen tearing, but I'll live with it for now.

The squares aren't being sorted yet, so there is some incorrect overlapping of squares, but as a concept demo this is fine.
8)

Just wondering, who thinks I should keep the road stripe?
I'm thinking I may not need it...



ronaldo

It's really nice, man :).

Maybe its time for you to start considering Double Buffer to stop flickering and visual redrawing :).

Road stripe coud be changed or visually improved, but I think it helps the visual 3D effect.

Nice work!

ervin

#23
Quote from: ronaldo on 12:52, 02 July 15
It's really nice, man :) .

Maybe its time for you to start considering Double Buffer to stop flickering and visual redrawing :) .

Road stripe coud be changed or visually improved, but I think it helps the visual 3D effect.

Nice work!

Thanks ronaldo.

Another question for any SDCC devs out there...

How is SDCC with putting things below 0x4000?
(I've got the firmware disabled, if that helps).

I'll need to put my tiles somewhere, and if I use double-buffering, I might run out of RAM.
Where can I put my tiles, and how would I tell SDCC to put them at (for example) 0x1000?

Also, I'd need to move my code to 0x8000, but cpctelera compiles to 0x4000 by default.
How do I change that?

Thanks for any help.
:)

[EDIT] I think I found how to put the code at a different address.
In build_config.mk I'd say
Z80CODELOC := 0x8000

Is that right?

ronaldo

You can put things in memory wherever you want. There is no restriction at all for that, and has nothing to do with SDCC, in fact. However, you must take into account some thigs:

       
  • First 64 bytes of memory are reserved for RST vectors (0x0000-0x0039)
  • The Stack is usually placed at 0xBFFF (just before the start of default video memory) and grows inversely (from there to 0x0000)
  • If you use firmware, you should take it into account. It lives in 0xA6F0-0xBF80 aprox (up to the stack).
  • If you wanted to respect BASIC, you should take care of its working area (variables mainly) 0x0040-0x0170 and the program (0x0170-HIMEM)
  • If you use firmware functions, lower ROM will be enabled, shadowing the first 0x4000 bytes of RAM. Reads below 0x4000 will come from ROM instead of RAM, and any code placed there will be unaccessible. That happens with drawChar and drawString functions of CPCtelera: their code MUST be placed beyond 0x4000 or the computer will likely hang when calling them.
Provided that, you can place your program wherever you wanted. There are some CPCtelera examples that place code start at 0x100, for instance.
You can easily select where you want your code to start as an option of cpct_mkproject script, or just editing the file cfg/build_config.mk of your project and changing 0x4000 for your desired code start address:

Z80CODELOC := 0x4000



Powered by SMFPacks Menu Editor Mod