Ok, I've used Celius's code to display some simple text on the background, and I even made it blink with my CPU expensive, lazy and newbie way (Simply having to code twice, one with text, the other blank, and a counter)
But this method will be a real mess if I'm to write a lot of text, which I will...
So, the real question is, how can I add more text without making such a mess, and having it change after a certain time?
At the timing, I'm thinking on adding a variable, and increase it by 1 each frame.
And when it reaches 60 (One second), change the text.
Celius's code:
Code:
lda #$21 ;position to write text in name table
sta $2006 ; mess around with values to get a feel
lda #$91 ; for positioning
sta $2006
lda #$00
sta $2005
sta $2005
ldy #10 ;number of characters in hello world string
ldx #0
loadtext:
lda text1, x
sta $2007
inx
dey
bne loadtext
text1:
.db 177,178,179,180,0,179,181,182,183,181 ;Bad placing of characters in the CHR
; P U S H S T A R T
If you have a variable that's counting to 60, you should have it count from 60 to 0 instead of 0 to 60. If you do it 0 to 60, it may look like:
label
blah blah blah
inc Variable
cmp #60
bne label
Where if you count from 60, it would look like:
label
blah blah blah
dec Variable
bne label
Which just takes less time. And besides, that way you could easily change the value of the counter without a big shindig.
I'm not so sure what you're looking for with the text though. Are you making some sort of text engine to stick into a game?
Celius wrote:
I'm not so sure what you're looking for with the text though. Are you making some sort of text engine to stick into a game?
Something like that.
I want to be able to change a fixed text field when ever I want, with some predefined text.
I'm not that used to Assembly, but I'm thinking on just doing a modification to your code, into something like this:
Code:
lda text[textNum], x
And then just make it loop.
And counting DOWN from 60 was clever, I'm going to change it right away, so I can just copy that whenever needed
Well what you're going to want is a kind of function or routine that you can feed values. Here's what I'd recommend.
Take a look at this routine:
Code:
WriteText:
lda NTHigh
sta $2006
lda NTLow
sta $2006
ldx TextLength
ldy #1
-
lda (TextAddress),y
sta $2007
iny
dex
bne -
rts
You feed this routine 5 bytes: NTLow, NTHigh, TextLength, TextAddress, and TextAddress+1. I'd recommend storing the text as is with the first byte representing the length. So you'd have a string like:
Text:
.db 13
.db "Hello, World!"
It would read the first byte as 13, and store that into "TextLength". Then you store the High and Low parts of the address of the label "Text" into TextAddress and TextAddress+1. It's up to your engine to figure out where on the name table to put it.
And notice that I start the loop with Y equal to 1. This is because the first entry of the string of text represents how long it is, so we don't want to write that value to the background. This limits the size of the text to be 255 characters.
Celius wrote:
You feed this routine 5 bytes: NTLow, NTHigh, TextLength, TextAddress, and TextAddress+1.
When and how should I change those values?
Just change them before jumping to WriteText?
Also, I'm a little confused on the TextAddress.
How I'm I supposed to set it/find the high and low values of the label?
Lot of questions
Yes, you should change them before jumping to WriteText. It's up to your game engine to determine what name table address to write the data at, and what piece of text to write.
Getting the address of the text probably requires a table of pointers. You may have a table that looks like:
TextArrays:
.dw Text1, Text2, Text3, Text4, etc.
And just to be clear, .dw will place two bytes at that location, the first being the low part of the address, and the second being the high one. So .dw Text1 is placing the address of "Text1" (a label) at that location in ROM first the low byte, and then the high.
Your game engine may say that it wants to write the text that's located at the address 4 bytes into that table (Text3, in the case above). So you'd have some code that is given an index for that table, and stores the address pointed to in "TextAddress" and "TextAddress + 1".
For example, say the routine was fed a value of 4. It would take that value, put it into X, and put TextArrays,x into TextAddress, inx, then TextArrays,x into TextAddress+1. After that, you'd do:
ldy #0
lda (TextAddress),y
sta NumberOfCharacters
Then all you'd have left is the name table address to figure out.
How about accepting the X/Y location as a single byte in A, with the horizontal limited to multiples of 4, and using a zero-terminated string? That allows you to pass everything in registers:
Code:
example:
; Call print_at manually, to show how
; it works. Prints test at h=8 v=4.
lda #(4*32 + 8)/4
ldx #>test
ldy #<test
jsr print_at
; Use macro to make little text box
print_at 12,10,"+-------+"
print_at 12,11,"| Hello |"
print_at 12,12,"+-------+"
test: .byte "test",0
.zeropage
temp: .res 2 ; 2 bytes of temporary space
.code
; Prints 0-terminated string at specified location
; A -> (v*32+h)/4 (x pos must be multiple of 4)
; X -> high byte of string address
; Y -> low byte of string address
print_at:
; Set PPU address to (A+$800)*4
sta temp
lda #$20/4
asl temp
rol a
asl temp
rol a
sta $2006
lda temp
sta $2006
; Set pointer to string
sty temp
stx temp+1
ldy #0
beq @first ; always branches
; Write characters until zero
@loop: sta $2007
iny
@first: lda (temp),y
bne @loop
rts
; Prints str at h,v. H is rounded down to
; multiple of 4.
.macro print_at h, v, str
.local str_addr ; avoids duplicate names
lda #(v*32+h)/4
ldx #>str_addr
ldy #<str_addr
jsr print_at
; Place string in RODATA segment, which
; doesn't mix with code
.rodata
str_addr:
.byte str
.byte 0 ; terminate with 0
.code
.endmacro
Oh, that's actually a pretty cool idea. So you're going with a kind of a C-esque string termination (right? or am I just being stupid?). I've never thought of doing that. Though for some reason, I'm willing to use $FF as a string ender, though $00 makes a lot more sense because you can detect the value with a single instruction (beq/bne with no cmp).
So where exactly is the string defined when the macro is called? It looks like its defined right where the code is, which seems you have to jump over it somehow...
The ".rodata" directive puts the string in the RODATA segment, which doesn't mix with the code. During linking, the assembler puts each segment's data into the file, as a contiguous chunk. Oh wait, I already put a comment to this effect.
Ok, I'm having trouble trying to get your code working in NESASM, blargg.
Code:
;----------------------Write Text----------------
; Prints 0-terminated string at specified location
; A -> (v*32+h)/4 (x pos must be multiple of 4)
; X -> high byte of string address
; Y -> low byte of string address
print_at:
; Set PPU address to (A+$800)*4
sta temp
lda #$20/4
asl temp
rol a
asl temp
rol a
sta $2006
lda temp
sta $2006
; Set pointer to string
sty temp
stx temp+1
ldy #0
beq firstWrite ; always branches
; Write characters until zero
loopWrite: sta $2007
iny
firstWrite: lda (temp),y
bne loopWrite
rts
;---------------MACRO------------------
printText .macro
;h, v, str
.local str_addr ; avoids duplicate names
lda #(\2*32+\1)/4
ldx #low(str_addr)
ldy #high(str_addr)
jsr print_at
; Place string in RODATA segment, which
; doesn't mix with code
.rodata
str_addr:
.byte \3
.byte 0 ; terminate with 0
.code
printText .endm
;---------------MACRO------------------
;------------------Write text End------------------
I got no compile errors,but I guess it's just big logic flaw here
Also, I have no clue how NESASM wants the input for the macro.
printText(12,11,177) is my suggestion, but doesn't work.
The macro can work if you use the .data section, basically the equivalent of .rodata in ca65:
Code:
print_at .macro ; h, v, str
; \@ suffix ensures unique names when
; macro is invoked multiple times
lda #(\2*32+\1)/4
ldx #high(str_addr\@)
ldy #low(str_addr\@)
jsr print_at_
.data
str_addr\@:
.byte \3
.byte 0
.code
.endm
I found you have to first set up the .data section to be at a different address, otherwise the .code overwrites it (without any warnings, of course!):
Code:
; (at beginning of file, before code and data)
; Put data at $A000
.data
.bank 1
.org $A000
; Put code at $8000
.code
.bank 0
.org $8000
After doing this exercise, I realized the "at" could be separated from the "print". This makes everything simpler:
Code:
; Prints string at current location
print_str .macro ; str
...
.endm
; Moves to specified location on screen
move_to .macro ; x, y
lda #high(\2*32 + \1 + $2000)
sta $2006
lda #low( \2*32 + \1 + $2000)
sta $2006
.endm
; Prints string at specified location
print_at .macro ; x, y, str
move_to \1, \2
print_str \3
.endm
BTW, another lovely nesasm feature that just bit me: if you do something like LDA (foo),y, it quietly treats it as LDA foo. Absolutely idiotic; for one, it should never quietly ignore something, and for another, since (foo),y is the official syntax, it should definitely give an error when it's used.
Full sources for above two examples, with test driver:
print_str_nesasm.zip
Ok, I've been able to add your code, blargg, but I'm stuck on one problem.
How can I input multiple raw hex and/or decimal?
I've tried "text1: .db $b1,$b2,$b3,$b4,$00,$b3,$b5,$b6,$b7,$b5", "print_at 0,26,177,178" and, believe it or not, "print_at 0,26,"±²³´�³µ¶·µ"".
So, the closest I've come is the last try there, but it also display some unwanted tiles in front of every character.
I could place all the characters at the 'right' places in the chr, but then I have a lot of graphics I have to move.
Suggestions?
I believe his code terminates the string when it encounters the entry $00... So that first one would only store the first 4 characters. What exactly does it put on the screen when you do "print_at 0,26,177,178" or actually any of the other ones?
At the first AND second, it simply displays the first character, which is a P in my case.
Nothing more, and nothing less..
The third one adds a lot of random tiles before printing the next characters I want. (Not completely random, it follows some sort of pattern, but I'm clueless about it..)
About the $00.
With your code I can successfully type "0" for a blank spot.
I used hex at the 3rd attempt, because I did something wrong when typing the decimals, so I just skipped it..
Ok, I've got this working smoothly (Changed the CHR, so the letters are at right position)
But on to my other problem, timing.
I want the text to change after a certain time after the player push start.
I got the controller working, but I'm completely lost on the timing.
Also, I want to change the text often, but how can I avoid getting a mess with branching? (I suppose I have to branch after a comparing)
What are you confused about with the timing? And what timing are you referring to?
Also, you say that you need to avoid getting a mess of branching. What do you mean by this?
BTW I'm sorry I didn't see that you posted here on the 16th, I probably would have posted a reply.
Timing - After a certain amount of frames (VBlank), do stuff.
I have tried counting upwards, because I want things to happend after 8, 40, 70 frames. (More to come
)
But I failed, because the NSF stopped playing, and the text showed up instantly.
Mess of branching - Count upwards, compare, branch if correct frame.
In other words, if I have a lot of text I want to change, there would be a lot of branching.
Unless you can say something like , If this frame is equal to X, do the next line, else, don't do the next line.
And don't worry about you didn't see I posted. It made me do a lot of experiments on my own
Okay, about the NSF. It sounds like you're having RAM conflicts with that. Make sure you aren't using the same variables in RAM that the NSF uses. Otherwise that will screw things up.
Secondly, why exactly is happening after 8, 40, 70, etc. frames? Are you writing 1 character or a string of characters to the screen? Because there are better ways to go about this. One would be:
Code:
ldx WaitIndex ;starts off as 0
lda FrameCount
cmp WaitTable,x
bne NotWaitedEnough
inx
stx WaitIndex
...Whatever you do once you've waited a correct amount
NotWaitedEnough:
....Whatever you do if you haven't waited the correct amount.
WaitTable:
.db 8, 40, 70 ....
And the best thing about that is that you can still keep the value as 8 bits, and you can wait anywhere between 1 and 255 frames.
Celius wrote:
Okay, about the NSF. It sounds like you're having RAM conflicts with that. Make sure you aren't using the same variables in RAM that the NSF uses. Otherwise that will screw things up.
I doubt I have RAM conflicts, because I haven't added any new variables after I've added the NSF (I have a couple of unused variables), and have made sure the NSF have enough space.
Also, I want it, after the frames, to print short strings onto the screen (Under 20 chars)
I find your code there good enough to use, but how do I make it print the correct string?
Code:
print_at 0,26,text[WaitIndex] ;(text1,text2,text3 ext.)
Not sure exactly how you do it in Assembly, but maybe have it load the text, equal to the WaitIndex value?
Using WaitIndex would definitely make sense. But you'd have to have a table of pointers to the strings, probably, since each string has variable length. I seem to recall you had a table of pointers, right? Well if not, you'll need to make one:
Code:
PointerTable:
.dw Text1, Text2, Text3
Where .dw defines the low and high bytes of the label.
Perhaps ditch the macro for now and do it in straight assembly. It'll be easier to deal with.