Skool Daze

Want to talks about games you like, would like to see developed on the Oric, it's here.
User avatar
Iapetus
Flying Officer
Posts: 135
Joined: Thu Mar 19, 2009 10:47 pm

Post by Iapetus »

Awesome!

I hope you can and have time to continue in the same vein of the first posts(explaining things).
User avatar
Chema
Game master
Posts: 3013
Joined: Tue Jan 17, 2006 10:55 am
Location: Gijón, SPAIN
Contact:

Post by Chema »

Will do, of course. (Not sure anybody was interested, but it was good to have it written somewhere :) )

Development is progressing, btw. Anybody interested could try to build it from the SVN repository, but beware it is a development version (not too stable and with tests here and there).
User avatar
Chema
Game master
Posts: 3013
Joined: Tue Jan 17, 2006 10:55 am
Location: Gijón, SPAIN
Contact:

Post by Chema »

Okay, this one is going to be looong. You have been warned.

Chapter 4: And now our characters

Following the game philosophy, our sprites will also be sectioned in tiles. Skool Daze characters have a maximum size of 24x32 pixels, originally sectioned in a grid of 3x4 tiles (8x8 pixel tiles, of course). In this case we can make an easy equivalence for the Oric and arrange the characters in grids of 4x4 tiles of 6x8 pixels; the same as our Skool tiles.

A sprite graphic will be, then, an array of 4x4 bytes, each one being an ID for a tile graphic and the corresponding mask. This way some graphics can be reused and some memory saved. For each animation frame of the character (walk, jump, fire catapult, sit down, etc) we will have a different 4x4 array. Following the naming conventions in pyskool.ca (see link in my post with chapter 2), we’ll call this the animatory state, and we’ll keep a pointer to this structure for each of our characters in the game that will be updated whenever the character moves.

To make things easy, the character ID will be the index in all the arrays with data for each character, and as it is usual in the 6502 instead of having something like:

Code: Select all

data_character1
	.word xxxx ;Pointer to animatory state
	.byt xx ;pos_col
	.byt xx ;pos_row
...
data_character2
	.word xxxx;  Pointer to animatory state
	.byt xx ;pos_col
	.byt xx ;pos_row
...
It is better to have

Code: Select all

as_pointer_high
	.byt xx,xx,… ; high byte pointer for character 0,1,2,3..
as_pointer_low
	.byt xx,xx,… ; low byte pointer for character 0,1,2,3..


pos_col
	.byt xx,xx,... ; column position of character 0,1,2,...
pos_row	
	.byt xx,xx,... ; row position of character 0,1,2,...
(For C users, do not use arrays of structures struct sprite_data sprites[N_SPRITES], but separate arrays for each field -better if they are all bytes- such as char pos_col[N_SPRITES], char pos_row[N_SPRITES], etc)

So we can have the character ID in reg X (for instance) and access each field with pos_col,x or pos_row,x. So, for our animatory states:

Code: Select all

as_pointer_high 
	.byt >Eric_anim_states,>Einstein_anim_states,>Angelface_anim_states,>BoyWander_anim_states
	.byt >Boy_anim_states,>Boy_anim_states,>Boy_anim_states,>Boy_anim_states,>Boy_anim_states
	.byt >Boy_anim_states,>Boy_anim_states,>Boy_anim_states,>Boy_anim_states,>Boy_anim_states
	.byt >Boy_anim_states,>Creak_anim_states,>Rockitt_anim_states,>Wacker_anim_states,>Withit_anim_states
	.byt >Pellet_anim_states,>Pellet_anim_states
as_pointer_low
	.byt <Eric_anim_states,<Einstein_anim_states,<Angelface_anim_states,<BoyWander_anim_states
	.byt <Boy_anim_states,<Boy_anim_states,<Boy_anim_states,<Boy_anim_states,<Boy_anim_states
	.byt <Boy_anim_states,<Boy_anim_states,<Boy_anim_states,<Boy_anim_states,<Boy_anim_states
	.byt <Boy_anim_states,<Creak_anim_states,<Rockitt_anim_states,<Wacker_anim_states,<Withit_anim_states
	.byt <Pellet_anim_states,<Pellet_anim_states
Our animatory states for Eric are something like:

Code: Select all

; Animatory states for children
Eric_anim_states
; Animatory state 0 (1-Eric00.png)
.byt 0, 0, 0, 0
.byt 0, 1, 2, 0
.byt 3, 4, 5, 0
.byt 0, 6, 7, 0
; Animatory state 1 (1-Eric01.png)
.byt 0, 0, 0, 0
.byt 15, 16, 17, 0
.byt 18, 19, 20, 0
.byt 21, 22, 23, 0
And we have our graphics and masks aligned this way:

Code: Select all

.dsb 256-(*&255)
.dsb 8	
children_tiles
; Tile graphic 1
.byt $0, $f, $7, $5, $0, $7, $2, $7
; Tile graphic 2
.byt $0, $20, $30, $30, $10, $38, $3c, $3e
…
.dsb 256-(*&255)
.dsb 8
children_masks
; Tile mask 1
.byt $70, $60, $60, $70, $70, $70, $78, $70
; Tile mask 2
.byt $5f, $4f, $47, $47, $47, $43, $41, $40
We need another table with data including the base address with the tiles for each character (either children or teacher). Again taking into account the amount of graphics it will exceed 256 different tiles easily, so splitting them is a good idea.

Aligning everything carefully to page boundaries, we only need the high bytes, therefor for our 21 characters:

Code: Select all

; Tables with base pointers to tiles for characters
tab_tiles
	.dsb 15, >(children_tiles-8)
	.dsb 3,  >(teacher_tiles-8)
	.dsb 1,  >(teacher2_tiles-8)
	.dsb 2,  >(children_tiles-8)
tab_masks
	.dsb 15, >(children_masks-8)
	.dsb 3,  >(teacher_masks-8)
	.dsb 1,  >(teacher2_masks-8)
	.dsb 2,  >(children_masks-8)
Ok. We have everything prepared (I made a small Matlab script to create the graphical data from the png files; else it is quite of a nightmare). Now it is time to see how the sprites are rendered. Remember we only draw those tiles flagged in the SRB. Basically whenever a tile is flagged we perform three steps: drawing the skool background in the backbuffer (draw_skool_tile), adding the sprites (draw_sprites) and dumping the backbuffer. We need to check if any of our sprites intersects with the tile being drawn and, if so, render the corresponding graphic (and mask) for the sprite tile using the animatory state. We will use the position of the sprite (column/row of the upper-left tile of the sprite – column/rows are, of course, in skool tile coordinates).

Here is the code:

Code: Select all

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Loops through the character list and updates the
; back buffer with corresponding the character sprite tile.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


draw_sprites
.(
	; Use self-modifying code to have these values
	; as immediate addressing and speed the loop up.
	lda tile_col
	sta smc_tilecol+1
	lda tile_row
	sta smc_tilerow+1

	; Loop thorugh all the characters in the game
	ldx #MAX_CHARACTERS-1	
loop
	; Check if the character overlaps the current tile
	; start with the col
smc_tilecol
	lda #0	;tile_col
	sec
	sbc pos_col,x
	bmi skip
	cmp #4
	bcs skip
	sta smc_row+1	; Save this for later, again self-modifying code

	; now the row
smc_tilerow
	lda #0	;tile_row
	sec
	sbc pos_row,x
	bmi skip
	cmp #4
	bcs skip
If the sprite does overlap, we need to get the ID of the tile which needs to be drawn. We have in register A the row and stored the column using self modifying code in the adc #x instruction below, so it is easy to get the tile code from row*4+column and the pointer to the current animatory state (as_pointer_low/high). If the tile code is zero we will skip drawing. Surely you already noticed there are quite a lot of empty tiles. Else the pointer to the graphic and mask will be the base pointer for tiles/masks plus the ID*8 (8 bytes per tile).

Notice how we obtain the pointer making use of the fact that everything has been aligned in memory at our convenience; and how we store it using self-modifying code as the parameter of the instruction at graphic_p (the ora) and mask_p (the and)

Code: Select all

	; This sprite overlaps... time to draw it
	; get tile offset in reg Y
	
	asl
	asl
	; Carry is clear here
smc_row
	adc #0
	tay

	; Get the UDG number 
	lda as_pointer_low,x
	sta tmp	
	lda as_pointer_high,x
	sta tmp+1	
	lda (tmp),y
	beq skip	; If it is 0, then don't do anything

	; Good, now get the pointer to the graphic and mask, that is multiplying index by 8
#ifdef FULLTABLEMUL8
	tay
	lda tab_mul8hi,y
	sta tmp+1
	lda tab_mul8,y
#else
	ldy #0
	sty tmp+1	
	asl			
	rol tmp+1	
	asl			
	rol tmp+1	
	asl			
	rol tmp+1
#endif

	; If tiles are arranged smartly, we can do this...

	; Carry must be clear here
	sta graphic_p+1
	sta mask_p+1
	lda tmp+1
	adc tab_tiles,x
	sta graphic_p+2
	lda tmp+1
	adc tab_masks,x
	sta mask_p+2
The multiplication by 8 can be done using a table (#define FULLTABLEMUL8), which will take memory or with rotations (which is slower). For now, I am using the former.

Here is the drawing loop. A few of things to notice here: first the usual sprite masking thingy (take background, and mask and ora graphic) and second the trick posted by Twilighte in another thread to support AIC coloring mode (basically when the background has the inverse bit set to attain more colors). This is the easy route: remove the bit (done in the mask) and invert the pixels. Some clashing will occur, of course. In fact we want this part of the code to be as fast as possible and those extra cycles do not help, but they are inevitable.

Code: Select all

	; ... and copy it
	ldy #7
loopcopy
	lda backbuffer,y

#ifdef AIC_SUPPORT
	bpl ScreenNoInverse
	eor #63
ScreenNoInverse
#endif

mask_p
	and $1234,y
graphic_p
	ora $1234,y
	sta backbuffer,y
	dey
	bpl loopcopy
Now go for the next character. Unfortunately “loop” is beyond a page, so a bpl loop cannot be used.

Code: Select all

; Time to get another character
skip
	dex
	bmi end
	jmp loop
end
	rts
.)
As a side note it is desirable to set up the code to avoid the jsr/rts as much as possible.

And that is all… nearly. We want to update the screen when our sprite moves, don’t we? This means marking the SRB correctly. Quite simple: flag the tiles occupied by our sprite as dirty, then move it (update animatory state and coordinates if necessary), and flag the new tiles occupied by our sprite as dirty aswell. Then let the engine do his work. We can move many sprites which may overlap. All of them will flag the needed bits in the SRB. In the rendering step we will redraw what is needed. Quite fast indeed!

This is the routine which flags the SRB bits for a sprite passed in reg X. It is quite straightforward, although may look ugly. Just remember we only flag a bit if the tilecode is not zero and that a sprite is 4x4. We operate column by column, keep two zero page pointers tmp0 pointing to the animatory state and tmp1 to the SRB byte and keep the correct bitmask for flagging the bit in tmp2. For each column we flag the bit for each of the 4 rows (I have unrolled this part).

Code: Select all

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Updates the SRB, marking tiles which need to
; be redrawn for a given sprite (reg X), depending
; on his animatory state.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
update_SRB_sp
.(
	; First check if sprite is visible
	lda pos_col,x
	sec
	sbc first_col
	cmp #$FD ; (-3)
	bmi endme		
	cmp #LAST_VIS_COL+1
	bmi doit
endme
	rts
doit
	; Save col-first_col for later
	sta sav_col+1
	; Get the pointer to the animatoy state
	lda as_pointer_low,x
	sta tmp0
	lda as_pointer_high,x
	sta tmp0+1

	; Prepare pointer to SRB
	lda pos_row,x
	asl
	asl
	adc pos_row,x
	adc #<SRB
	sta smc_srbpl+1
	lda #>SRB	
	adc #0
	sta smc_srbph+1

	lda #4	; Iterate through the 4 columns
	sta tmp
loop
	; Get pointer to SRB at the correct byte
	; and also the correct bitmask
sav_col
	lda #0
	bmi skip
	cmp #FIRST_VIS_COL
	bcc skip
	cmp #LAST_VIS_COL+1
	bcs skip
	lsr 
	lsr 
	lsr
	clc
smc_srbpl
	adc #0
	sta tmp1
	lda #0
smc_srbph
	adc #0
	sta tmp1+1

	lda sav_col+1
	and #%00000111
	tay
	lda tab_bit8,y
	sta tmp2

	ldy #0
	lda (tmp0),y
	beq skip1	; Skip if tilecode is zero

	; Mark the corresponding bit for this SRB
	;y = 0 here
	lda (tmp1),y
	ora tmp2
	sta (tmp1),y

skip1
	ldy #4
	lda(tmp0),y
	beq skip2

	; Mark the corresponding bit for this SRB
	ldy #5
	lda (tmp1),y
	ora tmp2
	sta (tmp1),y

skip2	
	ldy #8
	lda(tmp0),y
	beq skip3


	; Mark the corresponding bit for this SRB
	ldy #10
	lda (tmp1),y
	ora tmp2
	sta (tmp1),y

skip3
	ldy #12
	lda(tmp0),y
	beq skip
	
	; Mark the corresponding bit for this SRB
	ldy #15
	lda (tmp1),y
	ora tmp2
	sta (tmp1),y


skip
	inc tmp0
	bne nocarry
	inc tmp0+1
nocarry
	inc sav_col+1
	dec tmp
	bne loop
end
	rts
.)
From here the rest is quite easy. At least in concept. I keep variables with the animation frame number (so I cycle through 0,1,2,3,0,1… increasing/decreasing the pos_col variable at even values and use others for jumping, firing, hitting…) and keep pointers updated. I have a base pointer with animatory state 0 which may point to the one corresponding to the character looking left or looking right. Have a look at step_character or change_direction in engine.s or update_animstate in script.c, which updates the animatory state for a character passed in reg X to the one passed in reg A updating the SRB.

Did I leave anything behind? Any questions or comments? What do you want to read about next?
User avatar
Chema
Game master
Posts: 3013
Joined: Tue Jan 17, 2006 10:55 am
Location: Gijón, SPAIN
Contact:

Post by Chema »

Oh, btw... I forgot to post an image of the latest working version.
Image

The AI system is complete (I think, surely there are some details left, but...), the 'special playtimes' (Angelface's mumps, hidden pea-shootter, stampedes, etc.) are in. You are given lines in all the possible situations and you are expelled from the school when reaching 10000 lines.

You can perform every possible action (except hitting shields or opening the safe), even jumping over kocked children.

Although you cannot hit the shields yet, by tweaking the code, I have added even when teachers reveal their safe combination letters.

However there are many many details left. You still cannot rename the characters, there are no sfx, no music, no colors... and the worst thing is that I have a little more than 1K left, so I am afraid I will suffer to make it fit in a cassete system. But I'll try...

Cheers
User avatar
Chema
Game master
Posts: 3013
Joined: Tue Jan 17, 2006 10:55 am
Location: Gijón, SPAIN
Contact:

Post by Chema »

Quick update. I am testing a different set of colors and the possibility of using white paper in some ocasions. Indeed it is possible (set everything as ink, then inverse bit), but there is a huge colour clash, so it is usable only in areas where sprites are not drawn. For instance on speech bubbles (which are protected to avoid the bug in the original game where they were corrupted).

Also removed the x8 big table to make some space. The saving was not too much and I need the memory the tables used.

Here is an screenshot.

Image

Let me know if you like it, or what you prefer. Other ideas are welcome.
User avatar
Iapetus
Flying Officer
Posts: 135
Joined: Thu Mar 19, 2009 10:47 pm

Post by Iapetus »

Awesome work Chema! Are you translating some routines(say AI) directly from Z80 to 6502?
User avatar
Chema
Game master
Posts: 3013
Joined: Tue Jan 17, 2006 10:55 am
Location: Gijón, SPAIN
Contact:

Post by Chema »

Thanks Algarbi.

Indeed I am translating the AI nearly directly from the commented z80 sources in the link I posted above. Not only because I did not know how they were done or what was in, but also to make them behave as closer to the original game as possible.

However I had to make some changes and optimizations. I want to say that the original code is full of dirty tricks far away from what we consider today good programming techniques: lateral effects, many different entry points to the routines, plays with the return address in the stack, etc. All to achieve the needed complexity.

And it is quite of a masterpiece.

BTW I am playing with the inverse bit on shields to give some colours. For now I have to do that by hand (which is tedious and won't be usable once the background changes and tiles are regenerated), but that way I am learning and studying the possibilities. The preliminar results look quite good, though the game still looks quite monochrome :(

I am really worried with the size. I wanted this to be a cassete based game, moreover when you cannot save your game and it nearly fits in memory, but I am not sure how to gain the memory I need. Dbug suggested some ideas and I will try them, but still...
User avatar
barnsey123
Flight Lieutenant
Posts: 379
Joined: Fri Mar 18, 2011 10:04 am
Location: Birmingham

Post by barnsey123 »

Hi Chema,

definitely keep publishing the code details (I don't understand it yet but I'm hoping it'll make sense later!) :D

The color is subjective (everyone has a different preference) but I think I prefer a monochrome main screen with a colored score panel. That's my tuppence worth. :D
User avatar
Chema
Game master
Posts: 3013
Joined: Tue Jan 17, 2006 10:55 am
Location: Gijón, SPAIN
Contact:

Post by Chema »

barnsey123 wrote:Hi Chema,

definitely keep publishing the code details (I don't understand it yet but I'm hoping it'll make sense later!) :D

The color is subjective (everyone has a different preference) but I think I prefer a monochrome main screen with a colored score panel. That's my tuppence worth. :D
Hi Barnsey, thanks for the input. I will try to add as much color as possible to the main screen, but with my lack of ability in the graphics area I am not sure I will be able to do much. But at least something will be done, making use of the alternate paper color shown in the screenshots (for instance dotted patterns on even/odd rows) and probably using the inverse bit on some small details.

And I will also work a little more on the score panel, following your advice. What you see is a copy of the original games's panel, but at least something will be done.

Thanks again for your input. I will post some downloadable demo as soon as I get something more complete. Also I will post more internal details as soon as I have time to write it. I am sure you will understand the code perfectly soon!

Cheers
User avatar
Chema
Game master
Posts: 3013
Joined: Tue Jan 17, 2006 10:55 am
Location: Gijón, SPAIN
Contact:

Post by Chema »

Okay, I've been working on possible color combinations with this "AIC mode". It is not easy at all to obtain good results (Twilighte does magic, you know), but I think I got something quite nice and usable.

Image
Image

Still needs tweaking, but you can see how it looks like. Do you like it?

All this process is semi-automated: I draw the b&w pic with a windows program, use a Matlab script to create the tiles and map data, copy it by hand to the source files (I could automate this, but need to put each section where it should be, so not as easy as simply adding the data to a .s file) and then carefully edit the selected tiles on the sources for adding the inverse bit effects where needed. I test the effects previously with HIDE to see how they look like.

As you can see quite tedious and long. And I still need to start working on the score panel.

I also did some bug hunting so the game is shaping up :) And I need to find time to write the next chapter about the internals... probably facing the AI system this time...

Comments and suggestions are welcome, as usual.

Cheers.
User avatar
ibisum
Wing Commander
Posts: 1643
Joined: Fri Apr 03, 2009 8:56 am
Location: Vienna, Austria
Contact:

Post by ibisum »

>Do you like it?

I really, really like it.
User avatar
Iapetus
Flying Officer
Posts: 135
Joined: Thu Mar 19, 2009 10:47 pm

Post by Iapetus »

Looks great!
User avatar
kenneth
Squad Leader
Posts: 514
Joined: Fri Nov 26, 2010 9:11 pm
Location: France PdD
Contact:

Post by kenneth »

the screen like pop art comics is a good idea. 8)
User avatar
maximus
Flying Officer
Posts: 203
Joined: Thu Feb 23, 2006 7:45 pm
Location: Nimes, France

Post by maximus »

:D those colors give an amazing retro atmosphear, I love that !
User avatar
Chema
Game master
Posts: 3013
Joined: Tue Jan 17, 2006 10:55 am
Location: Gijón, SPAIN
Contact:

Post by Chema »

Just a quick note this time to state that development is progressing, though not as quickly as I would like it to.

Have been doing some bug hunting and adding missing details. The most important thing is that now you can hit the shields in the game (so you can actually do something else than just moving around). Instead of setting them to flash, as in the speccy version, I am turning the shield upside-down. I think it looks quite nice.

Also corrected some collision detection issues with pellets.

Still fighting with the code size, finding room here and there to fit the new code, and I have to start experimenting with some (simple) sfx to include in the game. Probably the kind of effects that can be generated with Twilighte's SDES and not using anything more advanced. BTW, if anybody has any interesting effect that I could use, just post the register values. It would help me.

Cheers.
Post Reply