Thursday, June 16. 2005Faxanadu collision detection - Part III (Sprite behaviour II)![]() Seven of the eight top-level rules from the sprite description table will be discussed in this update, the most complicated one gets an update for itself. Let's start with the first top-level rule (ID: $00). This rule is used as the first top-level rule of nearly all sprites and basically initializes a sprite. The code of this rule is located between the offsets $A6FF and $A72B and between $9917 and $9946. A new sprite data array ($2E4 - $2EB) is introduced here. I call it the phase counter array. A phase is a distinct part in a sprite's behaviour. Monster $2A for example has four phases: Short crouch, short jump, long crouch, long jump. The array keeps track of the phase the monster is currently in. Every time the following code is executed the phase counter is increased by one. CODE: <br />$A6FF-> BD D4 2: LDA $2D4,X ; Get sub-rule
<br />$A702-> 30 3: BMI $A707 ; Check if there's an active sub-rule
<br />$A704-> 4C 71 A7: JMP $A771 ; Return
<br />$A707-> FE E4 2: INC $2E4,X
<br />
In the following code there's a call to a very important function ($A879) which is frequently called when handling information from the sprite description table. Containing only seven instructions the function is as trivial as important though. It only adds the value passed in register A to the pointer stored in $CA/$CB (I call $CA/$CB the rule pointer). It's used to navigate through the sprite description table. There are two more values read from the sprite description table. The first one is the sub-rule to be executed after the execution of the main rule finished. The second one (stored in $364 - $36B) is the duration of the phase that the sprite entered. For example the value written to $364,X for the short crouch of sprite $2A is 5 while the value for the long crouch is $14. CODE: <br />$A70A-> A9 1: LDA #$1
<br />$A70C-> 20 79 A8: JSR $A879 ; Move rule pointer
<br />$A70F-> A0 0: LDY #$0
<br />$A711-> B1 CA: LDA ($CA),Y
<br />$A713-> 9D D4 2: STA $2D4,X
<br />$A716-> C8: INY
<br />$A717-> B1 CA: LDA ($CA),Y
<br />$A719-> 9D 64 3: STA $364,X
<br />$A71C-> A9 2: LDA #$2
<br />$A71E-> 20 79 A8: JSR $A879 ; Move rule pointer
<br />
At the end there's a check whether the sub-rule to execute next is 6. If that's the case there's a jump to some more initialization data. This sub-type is however used one single time and for a monster (sprite ID $12) that isn't even part of the game. The distinguishing feature between this monster and all other sprites in the game is that this monster has body parts that move quasi-independently from each other while the bodies of all other sprites move as one "block". CODE: <br />$A721-> BD D4 2: LDA $2D4,X
<br />$A724-> C9 6: CMP #$6
<br />$A726-> D0 3: BNE $A72B
<br />$A728-> 4C 17 99: JMP $9917
<br />$A72B-> 60: RTS
<br />
The body of monster $12 has four different parts which are initialized in the code at $9917. The initialization code is pretty interesting because it features five sprite data arrays which have not yet been mentioned. The first of these arrays is $304 - $30B. It contains a value that's used to determine the size of a sprite. How exactly that works is discussed at a later point. This value is probably set to zero as the sprite is treated differently than all other sprites. The second array ($C2 - $C9) contains the vertical position of the active sprites. In the code below the vertical position of three of the four body parts of the sprite are set to the vertical position of the main body part - n times $10 and stored in $37C - $37E. The third array ($BA - $C1) contains the horizontal position of the active sprites. The three secondary body parts are set to the same horizontal position as the main body part. The fourth array ($2EC - $2F3) holds another counter that's used to determine the sprite's behaviour. It's exact meaning is sprite-specific. The last array ($2F4 - $2FB) is used to calculate how a sprite moves in vertical direction (jumps or flies). CODE: <br />$9917-> A9 0: LDA #$0
<br />$9919-> 9D 4 3: STA $304,X ; Sprite size
<br />$991C-> 38: SEC
<br />$991D-> B5 C2: LDA $C2,X ; Y position of sprite
<br />$991F-> E9 10: SBC #$10
<br />$9921-> 8D 7C 3: STA $37C ; Y position of 2nd body part
<br />$9924-> E9 10: SBC #$10
<br />$9926-> 8D 7D 3: STA $37D ; Y position of 3rd body part
<br />$9929-> E9 10: SBC #$10
<br />$992B-> 8D 7E 3: STA $37E ; Y position of 4th body part
<br />$992E-> B5 BA: LDA $BA,X ; X position of sprite
<br />$9930-> 8D 79 3: STA $379 ; X position of 2nd body part
<br />$9933-> 8D 7A 3: STA $37A ; X position of 3rd body part
<br />$9936-> 8D 7B 3: STA $37B ; X position of 4th body part
<br />$9939-> A9 0: LDA #$0
<br />$993B-> 9D EC 2: STA $2EC,X
<br />$993E-> 9D F4 2: STA $2F4,X
<br />$9941-> A9 0: LDA #$0
<br />$9943-> 9D E4 2: STA $2E4,X ; Phase counter
<br />$9946-> 60: RTS
<br />
Let's have a look at the main rule with ID $01 now which is considerably shorter than the one we've already discussed. This rule is used for unconditional jumping to another main-rule. One byte after the rule identifier there's a pointer that points to the next rule to be executed. This pointer is loaded into the rule pointer addresses and the next main rule is loaded (the one the rule pointer now points to). This rule is actually used just a single time, for the sprite of the old man next to springs. CODE: <br />$A72C-> BD D4 2: LDA $2D4,X ; Get sub-rule
<br />$A72F-> 30 3: BMI $A734 ; Check if there's an active sub-rule
<br />$A731-> 4C 71 A7: JMP $A771
<br />$A734-> A9 1: LDA #$1
<br />$A736-> 20 79 A8: JSR $A879 ; Move rule pointer
<br />$A739-> A0 0: LDY #$0
<br />$A73B-> B1 CA: LDA ($CA),Y
<br />$A73D-> 48: PHA
<br />$A73E-> C8: INY
<br />$A73F-> B1 CA: LDA ($CA),Y
<br />$A741-> 85 CB: STA $CB ; Set new upper byte
<br />$A743-> 68: PLA
<br />$A744-> 85 CA: STA $CA ; Set new lower byte
<br />$A746-> 20 AC A8: JSR $A8AC ; Mark phase as complete
<br />$A749-> 4C BC A6: JMP $A6BC ; Load next main rule
<br />
The next main rule ($02) is the most complex of all main rules. So complex in fact that it deserves an update on it's own. Therefore the next rule I'm going to present here is the rule with ID $03. This rule is used to select one of two sub-rules depending on the distance between the player and the sprite. This is, for example, used for the king sprite which stands up from his throne when the player comes close and sits down again when the player leaves. CODE: <br />$A7E5-> A0 1: LDY #$1
<br />$A7E7-> B1 CA: LDA ($CA),Y
<br />$A7E9-> A: ASL A
<br />$A7EA-> A8: TAY
<br />$A7EB-> B9 F5 A7: LDA $A7F5,Y
<br />$A7EE-> 48: PHA
<br />$A7EF-> B9 F4 A7: LDA $A7F4,Y
<br />$A7F2-> 48: PHA
<br />$A7F3-> 60: RTS
<br />
The first of the two sub-rules of main rule $03 checks whether or not the sprite is within a certain distance to the player on the X axis. CODE: <br />$A7F8-> BD D4 2: LDA $2D4,X
<br />$A7FB-> 10 8: BPL $A805 ; Check if there's an active sub-rule
<br />$A7FD-> A9 7: LDA #$7
<br />$A7FF-> 20 79 A8: JSR $A879 ; Move rule pointer
<br />$A802-> 4C BC A6: JMP $A6BC ; Load next main rule
<br />$A805-> 20 F8 82: JSR $82F8 ; X distance between player and sprite
<br />$A808-> A0 2: LDY #$2
<br />$A80A-> D1 CA: CMP ($CA),Y ; Compare with distance from table
<br />$A80C-> 90 F: BCC $A81D
<br />
Load pointer to the first alternative main-rule: CODE: <br />$A80E-> A0 4: LDY #$4
<br />$A810-> C8: INY
<br />$A811-> B1 CA: LDA ($CA),Y ; Lower byte of first alternative
<br />$A813-> 48: PHA
<br />$A814-> C8: INY
<br />$A815-> B1 CA: LDA ($CA),Y ; Upper byte of first alternative
<br />$A817-> 85 CB: STA $CB
<br />$A819-> 68: PLA
<br />$A81A-> 85 CA: STA $CA
<br />$A81C-> 60: RTS
<br />
Load pointer to the second alternative main-rule: CODE: <br />$A81D-> C8: INY
<br />$A81E-> B1 CA: LDA ($CA),Y ; Lower byte of second alternative
<br />$A820-> 48: PHA
<br />$A821-> C8: INY
<br />$A822-> B1 CA: LDA ($CA),Y ; Upper byte of second alternative
<br />$A824-> 85 CB: STA $CB
<br />$A826-> 68: PLA
<br />$A827-> 85 CA: STA $CA
<br />$A829-> 60: RTS
<br />
The second of the two sub-rules checks whether or not the sprite is within a certain distance of the player on the Y axis. This rule is used significantly less often than the first rule. It's used in only two sprites and one of them is a monster that's not even used in the actual game. It's also very interesting that the code of this rule is way shorter than the code of the first sub-rule even though both could have been written like the second rule. CODE: <br />$A82A-> BD D4 2: LDA $2D4,X
<br />$A82D-> 10 8: BPL $A837 ; Check if there's an active sub-rule
<br />$A82F-> A9 7: LDA #$7
<br />$A831-> 20 79 A8: JSR $A879 ; Move rule pointer
<br />$A834-> 4C BC A6: JMP $A6BC ; Load next main rule
<br />$A837-> 20 1B 83: JSR $831B ; Y distance between player and sprite
<br />$A83A-> A0 2: LDY #$2
<br />$A83C-> D1 CA: CMP ($CA),Y ; Compare with distance from table
<br />$A83E-> 90 2: BCC $A842
<br />$A840-> A0 4: LDY #$4 ; Skip to address correct alternative
<br />$A842-> C8: INY
<br />$A843-> B1 CA: LDA ($CA),Y ; Lower byte of selected alternative
<br />$A845-> 48: PHA
<br />$A846-> C8: INY
<br />$A847-> B1 CA: LDA ($CA),Y ; Upper byte of selected alternative
<br />$A849-> 85 CB: STA $CB
<br />$A84B-> 68: PLA
<br />$A84C-> 85 CA: STA $CA
<br />$A84E-> 60: RTS
<br />
Main rule $04 is extremely short. It only completes the current phase by setting the MSB bit in the sub-rule byte that belongs to the sprite. CODE: <br />$A86E-> A9 1: LDA #$1
<br />$A870-> 20 79 A8: JSR $A879 ; Move rule pointer
<br />$A873-> 20 AC A8: JSR $A8AC ; Set phase to complete
<br />$A876-> 4C BC A6: JMP $A6BC ; Load next main rule
<br />
The code of main rule $05 ($A754 - $A771) equals the code of main rule $01 except for two instructions. Before loading the next main rule the byte of the sprite in the phase counter array ($2E4 - $2EB) is reset to zero. Main rule $06 is definitely the weirdest of all main rules. I know what it does but I'm not sure why it does what it does. Let's have a look. At first two more bytes forming an offset are loaded from the sprite description table and stored in $02/$03. Afterwards another byte is loaded from that table and added to the offset the pointer points to (plus Y, the number of the current sprite). So it's basically used to add a pre-defined value from the sprite description table to a value from one of the sprite data arrays. In practice the only sprite data array used by this kind of main rule is $BA - $C1, the X coordinates of the sprite. Now why would adding a predefined value to the sprite's X position value ever be necessary? I already have a decent idea but I'm not 100% sure. The precision of the sprite placement meta-data is 16 pixels (1 block). A higher precision is necessary for some sprites though, for example for NPCs sitting at a table. That's where main rule $06 kicks in and adjusts the exact position of the sprite I guess. CODE: <br />$A84F-> A0 1: LDY #$1
<br />$A851-> B1 CA: LDA ($CA),Y ; Load lower byte of offset
<br />$A853-> 85 2: STA $2
<br />$A855-> C8: INY
<br />$A856-> B1 CA: LDA ($CA),Y ; Load upper byte of offset
<br />$A858-> 85 3: STA $3
<br />$A85A-> C8: INY
<br />$A85B-> B1 CA: LDA ($CA),Y ; Load value to add
<br />$A85D-> 48: PHA
<br />$A85E-> 8A: TXA
<br />$A85F-> A8: TAY
<br />$A860-> 68: PLA
<br />$A861-> 18: CLC
<br />$A862-> 71 2: ADC ($2),Y ; Add value
<br />$A864-> 91 2: STA ($2),Y
<br />$A866-> A9 4: LDA #$4
<br />$A868-> 20 79 A8: JSR $A879 ; Move rule pointer
<br />$A86B-> 4C BC A6: JMP $A6BC ; Load next main rule
<br />
Unless I made a mistake the last main rule (ID: $07) isn't actually used by any sprite at all. It's very short and easy to understand though. It just loads a value representing a sprite phase from the sprite behavior table and stores that value in the sprite data array where the phases of the active sprites are stored. CODE: <br />$A6E9-> BD D4 02: LDA 2D4,X
<br />$A6EB-> 30 3: BMI $A6F0 ; Check if there's no active sub-rule
<br />$A6ED-> 4C 71 A7: JMP $A771
<br />$A6F0-> A0 1: LDY #$1
<br />$A6F2-> B1 CA: LDA ($CA),Y ; Load phase value
<br />$A6F4-> 9D E4 2: STA $2E4,X ; Store phase value
<br />$A6F7-> A9 2: LDA #$2
<br />$A6F9-> 20 79 A8: JSR $A879 ; Move rule pointer
<br />$A6FC-> 4C BC A6: JMP $A6BC ; Load next main rule
<br />
Trackbacks
Trackback specific URI for this entry
No Trackbacks
|
Calendar
QuicksearchArchivesContact
Links
Errorserendipity error: could not include serendipity_plugin_topexits:9e394f6ce1233c944505729bbd323460 - exiting.
Blog AdministrationPowered byCategories |