Forums

Sega Master System / Mark III / Game Gear
SG-1000 / SC-3000 / SF-7000 / OMV
Home - Forums - Games - Scans - Maps - Cheats - Credits
Music - Videos - Development - Hacks - Translations - Homebrew

View topic - Implement horizontal scrolling to a sonic-like platform

Reply to topic Goto page Previous  1, 2
Author Message
  • Joined: 03 Dec 2021
  • Posts: 54
Reply with quote
Post Posted: Sat Dec 10, 2022 9:18 pm
Ok, my code is working now (!!!)

It turned out the source of my issues came from the various defines/variables and their addresses I was setting:

; other variables before
hspeed equ $03              ; player horizontal speed
scroll equ $c104            ; vdp scroll register buffer.
HScrollReg equ $08          ; horizontal scroll register
NextRowSrc equ $c105        ; store tilemap source row address (2 bytes)
NextColSrc equ $c107        ; store tilemap source col address (2 bytes)
NextRowDst equ $c109        ; store tilemap row address in VRAM (2 bytes)
NextColDst equ $c10b        ; store tilemap col address in VRAM (2 bytes)
CurrentColScreen equ $c10d  ; store screen column (0-32)
; other variables after


(@sverx already warned me about switching to "ramsection", but I don't have it in VASM > and I know it's a pain but I am learning also, I will one day make the switch I promise!)

So turns out changing "CurrentColScreen" to another (greater) address solved all my issues. Strangely, I did check coming back to the value I was using before ($c10d, which seemed fine to my calculations), recompiled, and .... it works....

I sincerely don't know what might have happened, but sure I know I have a functioning horizontal scrolling (to the right now ;) ) even at steps different than 8 pixels at a time.

Next step: make it work for the left scroll as well, but this should be easier.

I will soon update my public repository so if any of you feel like having a look at my messy scrolling code it will be possible (first I will clean it a bit before committing).*

HUGE thank you (once more)

EDIT
*updated: https://github.com/umbe1987/cinos
  View user's profile Send private message
  • Joined: 06 Mar 2022
  • Posts: 598
  • Location: London, UK
Reply with quote
Post Posted: Sat Dec 10, 2022 10:53 pm
Last edited by willbritton on Sat Dec 10, 2022 11:07 pm; edited 1 time in total
umbe1987 wrote
HScrollReg is simply $08, which I was using to set the H scroll register.

I thought I could be using it to read its value as well but I just realized it was simply stupid as it is not really register at address 8 (anyway, Iwould be interested if possible how to actually get the value of HScrollReg if possible)

Yeah okay. I'd say that the most common convention for constant values you'll likely see around is ALL_CAPS, usually as a define preprocessor directive, so you might have (and apologies if this doesn't work in vasm):

.define VDP_REG_HSCROLL = $08


Then you can load the value using like

ld a, VDP_REG_HSCROLL

which is equivalent to
ld a, $08


Otherwise, as far as reading the VDP register values is concerned, it's just not possible, so you'll have to shadow the value in a variable in RAM (your
scroll
variable I believe) every time you update it if you want to know what the value is.

umbe1987 wrote
Morevore, I did not know I could call a subroutine with condition (like "call nz, drawColumn"). This is why I was confused as to how to conditionally call it like I could with labels and JUMPs.

Yeah it's definitely worth familiarising yourself with all the various things you can do conditionally as it's hard to remember what you can and can't do. As a fairly classic CISC processor, the Z80 always surprises me in terms of what instructions are available. Modern RISC processors typically allow one to combine the basic elements of register manipulation much more freely.

The conditional call instructions can be useful but don't come up quite as much as I expect them to, I think because it's often the case that registers need to be set to pass parameters, so you might be more likely to see something like this:


; some operation that sets a flag, e.g. z
jr nz, condition_skip_lbl
; set up some registers for parameters to my_routine
call my_routine
; do something else
condition_skip_lbl:
; condition skip code


I feel certain that vasm doesn't have automatic labels, but with an assembler that does, e.g. wla-dx you can write that imo more semantically and concisely as:


; some operation that sets a flag, e.g. z
jr nz, +
; set up some registers for parameters to my_routine
call my_routine
; do something else
+
; condition skip code


I know a number of us have tried to convince you to "upgrade" to wla-dx now, so I won't push too hard, but maybe something for a rainy day? :)
You won't regret it!

EDIT: just saw your last post - haha!
  View user's profile Send private message Visit poster's website
  • Joined: 06 Mar 2022
  • Posts: 598
  • Location: London, UK
Reply with quote
Post Posted: Sat Dec 10, 2022 11:06 pm
Last edited by willbritton on Wed Dec 28, 2022 1:03 am; edited 1 time in total
umbe1987 wrote
Ok, my code is working now (!!!)

Amazing - well done!

Maybe post a video when you have something we can see working?

umbe1987 wrote
(@sverx already warned me about switching to "ramsection", but I don't have it in VASM > and I know it's a pain but I am learning also, I will one day make the switch I promise!)

Okay, well here's a little something to help you on your way: a very slimmed down version of my regular WLA-DX template that I think should be simple enough to get you going with. You should just need WLA-DX and GNU make to get started.

https://github.com/retcon85/template-sms-wladx-lite
  View user's profile Send private message Visit poster's website
  • Joined: 05 Sep 2013
  • Posts: 3768
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Sun Dec 11, 2022 3:25 pm
the LD instruction cheat sheet I made some time ago... may be useful!
LD_cheat_1.png (101.71 KB)
LD cheat sheet part 1/2
LD_cheat_1.png
LD_cheat_2.png (66.67 KB)
LD cheat sheet part 2/2
LD_cheat_2.png

  View user's profile Send private message Visit poster's website
  • Joined: 03 Dec 2021
  • Posts: 54
Reply with quote
Post Posted: Mon Dec 12, 2022 9:24 pm
willbritton wrote
Maybe post a video when you have something we can see working?


I made a little video to show the progress I made so far and what is still missing.

In summary:
1) right scroll at 8 pixels step (1 tile)
2) right scroll at 3 pixels step (<1 tile)
3) right scroll at 13 pixels step (>1 tile) - NOT READY YET (?)
4) left scroll - NOT READY YET
5) using a very big image - NOT READY YET

I still don't know why 5) is not working, but I guess it has to do with the image being too big in size to fit the ROM space (it is in raw (.inc) format, 7872x512 pixels big). I imagine I have to compress it, but I still don't know how and if this is really the issue.

Anyway, if you have time, enjoy the little video :)

https://drive.google.com/file/d/1CWBKy9qvkkfypL7TyRp5zk9OFo4aoj-T/view?usp=share...
  View user's profile Send private message
  • Joined: 06 Mar 2022
  • Posts: 598
  • Location: London, UK
Reply with quote
Post Posted: Mon Dec 12, 2022 10:31 pm
A-mazing! Looks really great, @umbe, very well done indeed.

I think the problem with moving more than 8 pixels is because you've written your DrawColumn routine to simply queue up the next column each time - it just increases the pointer by 32. You should find that when you compare the top 5 bits as you are, those top bits will tell you how many columns you need to move by, as well as in which direction, so should fix the left scrolling issues too.

And yes, I think the problem with the very large tileset is indeed ROM capacity. At 7872x512 uncompressed I think you're talking about 125,952KB which is way too big to hold in ROM without banking - another good reason to look at either wla-dx or devkitsms, although you could have a crack at handling the banking manually. If you store it uncompressed in ROM, a map that big should fit in 8x 16KB banks if I've got my sums right.

Well done again! Looking forward to seeing what comes next!
  View user's profile Send private message Visit poster's website
  • Joined: 03 Dec 2021
  • Posts: 54
Reply with quote
Post Posted: Thu Dec 29, 2022 2:16 pm
Hello and merry Christmas everyone!

willbritton wrote
A-mazing! Looks really great, @umbe, very well done indeed.


Thank you!!!!!

willbritton wrote
I think the problem with moving more than 8 pixels is because you've written your DrawColumn routine to simply queue up the next column each time - it just increases the pointer by 32. You should find that when you compare the top 5 bits as you are, those top bits will tell you how many columns you need to move by, as well as in which direction, so should fix the left scrolling issues too.


This is driving me crazy. I am actually trying to compare the top 5 bits of the next scroll position with the current one which I stored in a variable called "CurrentColScreen". "CurrentColScreen" holds a copy of the scroll register value with the bottom bits all 0.

When I am trying to move right (i.e. scroll left), I do this:


MovePlayerRight:
; Test if player wants to move and scrolls background in the opposite direction to give sense of movement.
    ld a,(input)                ; read input from ram mirror.
    bit 3,a                     ; is right key pressed?
    jp nz,MovePlayerLeft        ; no, then check for left movement

    ld a,(scroll)               ; get scroll buffer
    sub hspeed                  ; sub constant speed to scroll
    ld (scroll),a               ; update scroll buffer
   
    ld b,HScrollReg             ; make the scroll happening
    call SetRegister

    ; draw new column only if needed (in case the updated column is different than the one shown on screen)
    ld a,(scroll)               ; get updated scroll buffer
    and %11111000               ; get top 5 bits
    ld hl,(CurrentColScreen)    ; get old CurrentColScreen
    ld b,l
    cp b
    call nz,DrawColumn

MovePlayerEnd:
; end of player movements

    ; Update player sprites in the buffer.
    call UpdateSATBuff

    ld a,(scroll)               ; get updated scroll in VDP
    and %11111000               ; get top 5 bits
    ld (CurrentColScreen),a     ; update CurrentColScreen

    jp Loop             ; jump back to start of main loop


This is working so far, but now as @willbritton rightly suggested, I'd like to store in register A the amount of columns I need to scroll (if any), and check the S flag to see in which direction to do that.

I have few issues to achieve this. As you can see, when the player wants to move right, the first thing I do is SUBtracting "hspeed" from the scroll value (which is initialized as 0). By doing so, its value at the first movement is set to 0 - hspeed (e.g. 8) = 0 - 8 = 248 ($F8). I understand the result is wrapped since an 8 bit register can only store 0-255 values.

Then I AND it with $F8 and compare it with the "CurrentColScreen" (also ANDed with $F8 all the time I update it to store it with the bottom bits all 0).

If instead of comparing the two values, I SUBtract CurrentColScreen, I thought I could 1) store the number of columns I am supposed to load (if any), and 2) know if I need to scroll right or left by looking if the S flag is set.

However, the result of the subtraction is difficult to understand for me. First of all, it's never a value between 0 and 32, and second it is always very large when I would expect to get something like 1 or 2 (columns) as a result.

This is just an example of the steps and results I get by inspecting Emulicious debugger:

; STEP 1 (ld a,(scroll))
A = 234 ($EA, %11101010)

; STEP 2
AND $F8
A = 232 ($E8, %11101000)

; STEP 3 (SUB CurrentColScreen)
B = 240 ($F0, %11110000)
A - B
A = 248 ($F8, %11111000)


Now, I am not good in math, but if I guess correctly, at STEP 2 I would like to have 29, since 232/8 is 29. Then, B should be 30, since 240/8 is 30. When SUBtracting B from A, I should get A-B = 29-30 = -1, which means 1 column to draw with negative sign. These are the key info I need.

I could always keep the result I am getting as is, and divide it by 8, but I thought this was the job of getting the 5 top bits with "AND $F8".

Is there any way I can get the first 5 bits of (e.g.) $F8 without having to divide by 8 or even a better and more clever way to do this?
  View user's profile Send private message
  • Joined: 05 Sep 2013
  • Posts: 3768
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Thu Dec 29, 2022 2:42 pm
first of all you have to see if it's possible for your game to move more than 1 column - it means the screen will be moving faster than 8 pixels/frame, which is a lot

anyway, let's suppose you can. So take the difference between your old_colum and new_column variables, say it's 248 (%11111000). Do a signed right shift by 3 positions and you'll get %11111111 which is -1, which means you're moving left one column.
  View user's profile Send private message Visit poster's website
  • Joined: 06 Mar 2022
  • Posts: 598
  • Location: London, UK
Reply with quote
Post Posted: Thu Dec 29, 2022 2:42 pm
umbe1987 wrote
Hello and merry Christmas everyone!

And to you umbe!

You have to decide whether or not you're talking about pixels or about columns. The relationship is x8 as you have pointed out.

Currently both your scroll variable and CurrentColScreen are in pixels, not columns. The AND $F8 zeros out the bottom three bits, so it truncates to a multiple of 8, but it doesn't divide by 8 - you are still left with a value in pixels, even though it can now only take the value of the first pixel of one of 32 columns, e.g. 0, 8, 16, 24, etc.

umbe1987 wrote
Is there any way I can get the first 5 bits of (e.g.) $F8 without having to divide by 8

I rather suspect that dividing by 8 is exactly what you need to do here, assuming that you want a value in columns that you can then use as an offset to modify a memory pointer.

Dividing by 8 is extremely easy to do. Assuming you have a value in register a:


sra a    ; divides by 2
sra a    ; divides by 2 again (= 4)
sra a    ; divides by 2 again (= 8)


Using sra (as opposed to srl) should mean that, treating your original value as signed, the signed-ness will be preserved, example:


ld a, %01110000  ; i.e. +112d
sra a
sra a
sra a            ; result should be %00001110, i.e. +14d

ld a, %11110000  ; i.e. -16d
sra a
sra a
sra a            ; result should be %11111110, i.e. -2d


Then you can just add this value to your column offset - no special treatment of sign should be required, because the addition will safely overflow if the number is negative and look like a subtraction.

Just for comparison, if you used srl instead you would get the "wrong" result for signed numbers:

ld a, %01110000  ; i.e. +112d
srl a
srl a
srl a            ; result should be %00001110, i.e. +14d

ld a, %11110000  ; i.e. -16d (or +240d unsigned)
srl a
srl a
srl a            ; result should be %00011110, i.e. 30d - okay for unsigned, bad for signed


EDIT: The only caveat is that +15 or -16 columns are really the limits of your movement. Any more than that and you need more than 8 bit storage, but as sverx points out, even moving as many as 8 pixels in a frame is pretty fast!

Hopefully that's the right direction, try it out maybe and let us know.
  View user's profile Send private message Visit poster's website
  • Joined: 06 Mar 2022
  • Posts: 598
  • Location: London, UK
Reply with quote
Post Posted: Thu Dec 29, 2022 3:11 pm
Also worth noting that you could maybe have done the shift right instead of the and $f8, since shifting will drop those bottom 3 bits in any case.

You can still conditionally call your DrawColumn routine, because if the result is 0 then you don't need to call it.

Something like:


ld a,(scroll)               ; get scroll buffer
sub hspeed                  ; sub constant speed to scroll

; here you probably want to save to RAM and set the VDP register as you are currently doing, or else save the value for later

sra a
sra a
sra a                       ; a now contains the column shift value, from -16 to +15
jr nz, DrawColumn           ; if column shift is 0, don't need to do this
  View user's profile Send private message Visit poster's website
  • Joined: 03 Dec 2021
  • Posts: 54
Reply with quote
Post Posted: Thu Dec 29, 2022 3:13 pm
Thank you as always for your precious hints @sverx @willbritton!

One thing though: how do I "treat" my values as signed as opposed to unsigned? So far I think I have always used unsigned ones, at least since I am getting correct results using srl as opposed to sra.

Very basic question I guess, but I never thought of this just until now...
  View user's profile Send private message
  • Joined: 06 Mar 2022
  • Posts: 598
  • Location: London, UK
Reply with quote
Post Posted: Thu Dec 29, 2022 3:24 pm
umbe1987 wrote
Thank you as always for your precious hints @sverx @willbritton!

One thing though: how do I "treat" my values as signed as opposed to unsigned? So far I think I have always used unsigned ones, at least since I am getting correct results using srl as opposed to sra.

Very basic question I guess, but I never thought of this just until now...

Most of the time you don't need to do anything different with signed numbers. By "treat" as signed we generally mean to like set your own expectations of what those numbers mean.

e.g. in the example I gave of %11110000, you (as the programmer) can either decide that this number means -16d or +240d. The CPU doesn't care which one you decide.

Adding and subtracting numbers works exactly the same as in "real life", example:


ld b, %00001010 ; i.e. 10d
ld a, %11110000 ; could be either +240d or -16d
add a, b  ; result is %11111010, i.e. either +250d or -6d


The same applies to subtraction, since the CPU really just subtracts by negating and adding at the same time.

There are a few places you need to be careful though. The sra / srl difference I pointed out above is one place. You should use sra whenever you are expecting to deal with signed numbers (as opposed to unsigned numbers or bit patterns) and you will almost certainly be fine. The other place is what happens when you get an overflow, e.g. you want to add two unsigned numbers together which would generate a result larger than 255d for an 8 bit register, or larger than 65,535d for a 16 bit register; OR if you want to add two signed numbers together which would generate a result larger than +127d for an 8 bit register, or *lower* than -128d for an 8 bit register, etc.

In that case, you as the programmer need to decide whether or not you care about those possibilities, and if so, use the flags and/or the special carry-aware operations to detect and correct.

In your scrolling example, I think you can safely add your signed column offsets. If you get an unanticipated overflow, the scroll will wrap around, i.e. a scroll of 33 columns will look like a scroll of -1 column. I think this is okay, only because you are very unlikely to ever want to scroll that much!
  View user's profile Send private message Visit poster's website
  • Joined: 06 Mar 2022
  • Posts: 598
  • Location: London, UK
Reply with quote
Post Posted: Thu Dec 29, 2022 4:15 pm
Oh, one other thing to be careful of with shifting is that the bits that are "lost" to shifting right cause the result to always be truncated *down* - i.e. towards negative infinity. This might lead to rounding errors as usually you expect rounding to always round towards zero.

e.g.


ld a, %00101010 ; i.e. +42d
sra
sra
sra             ; result is 00000101, i.e. +5d (5.25 rounded towards negative infinity)

ld a, %11010110 ; i.e. -42d
sra
sra
sra             ; result is 11111010, i.e. -6d (-5.25 rounded towards negative infinity)


I don't think this is a problem with the suggestion I made earlier though, since you want it to always choose the column to the "left" of where you are scrolling to.
  View user's profile Send private message Visit poster's website
  • Joined: 03 Dec 2021
  • Posts: 54
Reply with quote
Post Posted: Sat Dec 31, 2022 10:17 am
willbritton wrote
In your scrolling example, I think you can safely add your signed column offsets. If you get an unanticipated overflow, the scroll will wrap around, i.e. a scroll of 33 columns will look like a scroll of -1 column. I think this is okay, only because you are very unlikely to ever want to scroll that much!


Sorry for the late reply, but the chances I have time to code are few and I take sometime to understand new concepts :)

It won't probably work this way, since in my "drawColumn" function I would need both the index of the destination column (the screen) and the source one (in tilemap coordinates). This latter does not wrap. I would need just the number of columns (which should be 1 most of the times as long as I don't go for the "more than 8 hspeed" route), and the sign to understand where to draw them.

Therefore, I am in the process of making my code less optimized but more readable for the time being and as long as I will get more confident with all the assembly concepts.

What I am trying to do now is simply understanding if my SUBtraction (of the next scroll value with the current one) has a negative sign. I think to achieve this I can either look at the Sign flag or the Carry flag or even the oVerflow one. The SUB instruction should set all these flags, so I guess I am fine.

As for the idea I want to implement: in my previous example, at STEP 3 I had A - B = 232 - 240 = 248 = -8. SUB A,B (or simply SUB B) should set the S, C, and V flag to 1. I guess if I control this with something like JR C (for the Carry), JR PE (for the oVerflow), or JR M (for the Sign) I would be able to understand the sign. Then I would probably convert my positive result to a negative one (using Two's Compliment from what I understand by reading some documents), and divide it by 8 to have my final result.

So to wrap up, building on the previous example (in pseudo code):

; STEP 1 (ld a,(scroll))
A = 234 ($EA, %11101010)

; STEP 2
AND $F8
A = 232 ($E8, %11101000)

; STEP 3 (SUB CurrentColScreen)
B = 240 ($F0, %11110000)
A - B
A = 248 ($F8, %11111000)

; STEP 4 (check the sign)
JR C,ConvertSign
ConvertSign:
A = 248 -> -8

; STEP 5 (divide by 8 to get n of columns to draw)
SRA A
SRA A
SRA A
A = -1


I hope this all makes sense, I will try to code this soon :)
  View user's profile Send private message
  • Joined: 25 Mar 2019
  • Posts: 20
  • Location: Nagoya, Japan
Reply with quote
I envy your progress.
Post Posted: Wed May 10, 2023 4:12 am
Hello, Dear umbe1987.

I read your post with great interest.
I too am having trouble with horizontal scrolling on large maps.
I am a beginner programmin, so I don't know what to do.
How can I build your code with wla-dx?
  View user's profile Send private message
  • Joined: 03 Dec 2021
  • Posts: 54
Reply with quote
Post Posted: Thu May 11, 2023 8:28 am
hogel wrote
Hello, Dear umbe1987.

I read your post with great interest.
I too am having trouble with horizontal scrolling on large maps.
I am a beginner programmin, so I don't know what to do.
How can I build your code with wla-dx?


Hello @hogel.

I wrote my code to be assembled with VASM, not WLA-DX.
Although I think it is not so difficult to port it to WLA-DX, I never had the chance to do so and unfortunately I am no longer working on it.

Anyway, I wrote to the owner of VASM and he agreed that I insert the binary in my project so that anybody can build it.

When I have a bit of time I will 1) update my code in the public repository to reflect the latest improvements I did 2) upload the compiled game and 3) upload the VASM binary.

I will post here when it's done so that you know :)

Umberto
  View user's profile Send private message
  • Joined: 25 Mar 2019
  • Posts: 20
  • Location: Nagoya, Japan
Reply with quote
Post Posted: Thu May 18, 2023 3:27 am
Hell @umbe1987

I look forward to your posts.
  View user's profile Send private message
  • Joined: 25 Mar 2019
  • Posts: 20
  • Location: Nagoya, Japan
Reply with quote
Post Posted: Sun Sep 24, 2023 1:46 pm
I was able to eliminate the errors one by one and managed to get it to work with wla-dx, but it seems like there is something wrong somewhere.
I will continue to study so that I can fix it.
screenshot01.jpg (260.75 KB)
screenshot01.jpg

  View user's profile Send private message
Reply to topic Goto page Previous  1, 2



Back to the top of this page

Back to SMS Power!