### Making an enemy move along surfaces

Posted:

**Wed Sep 21, 2022 12:17 pm**I'm trying to write code for making an enemy follow along all surfaces, including ground, ceilings, walls, and both ground and ceiling slopes. Unfortunately, no matter what I do, I can't quite get it to work right, and I've been trying for months. The only data I have available for what surfaces it's touching or near is what block numbers are nearby, though I can use that to look up what surface type it is based on a table index. I haven't even gotten as far as testing it with slopes; I can't even get the basic straight surface interaction to work. Here is the code that I currently have:

Yes, the X and Y position high and low bytes are in separate 8-bit tables. It's dumb, but I can't change it because this is for a ROM hack. But I don't know how I'd do it in a homebrew either. In fact, I don't even know how I'd do it in a

Code: Select all

```
; CurrentSurface: What surface the sprite is currently on. Same values as the ones listed in the tables. $FF = in air.
; Speed: Current speed, unsigned 4.4 ($10 = move 1 pixel per frame).
; Direction: Current movement direction. 0 = clockwise, 1 = counterclockwise.
; TransitionTimer: Timer for transitions. Is decremented by the current speed value.
; NextSurface: Surface to change to when the transition timer runs out.
; BlockedStatus: Which sides the sprite is blocked on, asb-udlr (udlr = primary foreground above, below, left, right; asb = secondary interactive layer above, side, below)
; $00-$0F, $8A-$8F: Scratch RAM.
; Scratch1, Scratch2: Longer-term scratch RAM that isn't used by any subroutines.
; $005000 etc.: Breakpoints.
; checked block position diagrams; C = current position, 1, 2, 3 = position of first, second, and third blocks to check
; C 2 2 C 1 3 3 1 2 1 C 3 3 C 1 2
; 3 1 1 3 2 C C 2 C 3 2 1 1 2 3 C
; data for block checking
; 3 blocks per surface, X and Y offsets; 2 directions
BlockCheckData:
; 00: ground
dw $0010,$0010 : dw $0010,$0000 : dw $0000,$0010
dw $FFFF,$0010 : dw $FFFF,$0000 : dw $0000,$0010
; 01: ceiling
dw $FFFF,$FFFF : dw $FFFF,$0000 : dw $0000,$FFFF
dw $0010,$FFFF : dw $0010,$0000 : dw $0000,$FFFF
; 02: outer left edge
dw $0010,$FFFF : dw $0000,$FFFF : dw $0010,$0000
dw $0010,$0010 : dw $0000,$0010 : dw $0010,$0000
; 03: outer right edge
dw $FFFF,$0010 : dw $0000,$0010 : dw $FFFF,$0000
dw $FFFF,$FFFF : dw $0000,$FFFF : dw $FFFF,$0000
; 04: steep slope left
; 05: steep slope right
; 06: normal slope left
; 07: normal slope right
; 08: gradual slope left
; 09: gradual slope right
; 0A: very steep slope left
; 0B: very steep slope right
; 0C: steep slope left, upside-down
; 0D: steep slope right, upside-down
; 0E: normal slope left, upside-down
; 0F: normal slope right, upside-down
; table of values to multiply the speed by for different surfaces, in 4.4 fixed-point - X, Y; counterclockwise, clockwise
SpeedMultiplierTable:
; 00: ground
db $F0,$00 : db $10,$00
; 01: ceiling
db $F0,$00 : db $10,$00
; 02: outer left edge
db $00,$10 : db $00,$F0
; 03: outer right edge
db $00,$F0 : db $00,$10
; 04: steep slope left
db $F0,$10 : db $10,$F0
; 05: steep slope right
db $F0,$F0 : db $10,$10
; 06: normal slope left
db $F0,$08 : db $10,$F8
; 07: normal slope right
db $F0,$F8 : db $10,$08
; 08: gradual slope left
db $F0,$04 : db $10,$FC
; 09: gradual slope right
db $F0,$FC : db $10,$04
; 0A: very steep slope left
db $F0,$20 : db $10,$E0
; 0B: very steep slope right
db $F0,$E0 : db $10,$20
; 0C: steep slope left, upside-down
db $10,$10 : db $F0,$F0
; 0D: steep slope right, upside-down
db $10,$F0 : db $F0,$10
; 0E: normal slope left, upside-down
db $10,$08 : db $F0,$F8
; 0F: normal slope right, upside-down
db $10,$F8 : db $F0,$08
; what the "next" surface is when going around an outside corner
; ground, ceiling, outer left edge, outer right edge; clockwise, counterclockwise
NewSurfaceAtCorner:
db $03,$02,$00,$01 : db $02,$03,$01,$00
; what the "next" surface is when going around an inside corner
; ground, ceiling, outer left edge, outer right edge; clockwise, counterclockwise
NewSurfaceAtWall:
db $02,$03,$01,$00 : db $03,$02,$00,$01
; most of the main code starts here
LDA Speed,x
CMP #$11
BCS .ExtraHighSpeed
.NormalSpeed
STA Scratch0
JSR CheckBlocks
JSR MoveAlongBlocks
LDA CurrentSurface,x
BPL .Return
LDY #$00
LDA BlockedStatus,x
BIT #$84
BNE .HitSurface
INY
BIT #$28
BNE .HitSurface
INY
BIT #$41
BNE .HitSurface
INY
BIT #$42
BEQ .Return
.HitSurface
STZ CurrentSurface,x
.Return
RTS
.ExtraHighSpeed
STA Scratch1
LDA #$10
STA Scratch0
.Loop
JSR CheckBlocks
JSR MoveAlongBlocks
LDA Scratch1
SEC
SBC #$10
CMP #$11
BCC .NormalSpeed
STA Scratch1
BRA .Loop
;------------------------------------------------
; check the specified blocks
;------------------------------------------------
; If 1 is solid, check if 2 is a wall and transition to that wall if it is. If 1 is a slope, transition to that slope. If 1 is air, check if 2 is air, and if it is, check if 3 is solid, and if it is, transition to corner. If 2 is a slope, transition to that slope.
CheckBlocks:
; if the timer is currently set, don't check any blocks yet and just continue the same way
LDA TransitionTimer,x
STA $005004
BEQ .Continue
; otherwise, decrement it by the speed amount and move on when it's at or below 0
SEC
SBC Scratch0
STA TransitionTimer,x
BEQ .Continue2
BCC .Continue2
RTS
.Continue2
; zero out the timer in case it didn't come out evenly, then set the new state
STZ TransitionTimer,x
LDA NextSurface,x
STA CurrentSurface,x
.Continue
STA $005006
; concatenate the X and Y positions for 16-bit referencing (why is SMW so stupid)
LDA XPosLo,x
STA $8A
LDA XPosHi,x
STA $8B
LDA YPosLo,x
STA $8C
LDA YPosHi,x
STA $8D
; first, multiply the current surface type by 24 to get the index to the tables
LDA CurrentSurface,x
STA $005008
REP #$20
AND #$00FF
ASL #3
STA $00
ASL
ADC $00
; add 12 to the index if it's moving the other direction
LDY Direction,x
BEQ .Direction0
CLC
ADC #$000C
.Direction0
REP #$10
TAY
STY $8E
STA $00500A
; store the position of the first block to RAM for the Map16 check routine
LDA $8A
CLC
ADC BlockCheckData,y
STA $00
LDA $8C
CLC
ADC BlockCheckData+2,y
STA $02
SEP #$30
; Input: $00-$01 = block X position, $02-$03 = block Y position
JSL FindBlockNumber
REP #$30
STA $04
STA $00500C
; store the offsets for the second block so that they can be used in multiple branches
LDY $8E
LDA $8A
CLC
ADC BlockCheckData+4,y
STA $00
LDA $8C
CLC
ADC BlockCheckData+6,y
STA $02
LDY $04
SEP #$20
STA $00500E
; now it's time to check what surface type the first block is
LDA SurfaceType,y
SEP #$10
BMI .Air1
CMP #$04
BCS .SetNewSurface
; if the first block is solid, check if the second block is a perpendicular blocking surface, and if it is, transition to that surface; if not, don't change anything
.Solid1
JSL FindBlockNumber
REP #$30
TAY
STA $005010
SEP #$20
; SurfaceType = lookup table that tells which surface each block is (same values as the current surface type table)
LDA SurfaceType,y
STA $00
SEP #$10
CMP #$04
BCS .Return
LDA Direction,x
ASL #2
ORA CurrentSurface,x
TAY
LDA NewSurfaceAtWall,y
LDY #$0F
; surfaces 00-03 all count as solid, while anything else is either air or a slope
.SetNewSurface
STA NextSurface,x
TYA
STA TransitionTimer,x
.Return
RTS
; if the first block is air, check what the second block is
.Air1
JSL FindBlockNumber
REP #$30
TAY
STA $005012
SEP #$20
LDA SurfaceType,y
SEP #$10
BMI .Air2
; if the second block is a slope, transition to that slope
CMP #$04
LDY #$7F
BCS .SetNewSurface
RTS
; if the second block is air, check if the third block is solid, and if it is, transition to the next surface around a corner
.Air2
REP #$30
LDY $8E
LDA $8A
CLC
ADC BlockCheckData+8,y
STA $00
LDA $8C
CLC
ADC BlockCheckData+10,y
STA $02
SEP #$30
JSL FindBlockNumber
REP #$30
STA $005014
; this time, we need to check the block number/acts-like setting itself
CMP #$0100
; if it's somehow less than #$0100, it acts like air
BCC .Air3
; blocks 100-110 are solid only on the top, while 111 and beyond are either completely solid or are slopes
CMP #$0111
BCS .Corner3
; if it's on a passable ledge, fall off it
.FallOffLedge
STA $005016
SEP #$30
LDA #$FF
TAY
BRA .SetNewSurface
; if it's in the air, fall immediately
.Air3
STA $005018
SEP #$30
LDA #$FF
STA CurrentSurface,x
RTS
; if it's going around a corner, check which surface to transition to based on the current surface and direction
.Corner3
STA $00501A
SEP #$30
LDA CurrentSurface,x
STA $00
CMP #$04
BCC .NotOnSlope
; if it's currently on a slope, then count all regular slopes as ground and all ceiling slopes as ceiling
STZ $00
CMP #$0C
BCC .NotOnSlope
INC $00
.NotOnSlope
LDA Direction,x
ASL #2
ORA $00
TAY
LDA NewSurfaceAtCorner,y
LDY #$FF
JMP .SetNewSurface
;------------------------------------------------
; movement routine
;------------------------------------------------
MoveAlongBlocks:
STA $005020
LDY Scratch0
LDA CurrentSurface,x
BPL .NotInAir
JSL UpdatePositionWithGravity
.NotInAir
CMP #$04
BCC .MoveStraight
JMP .OnSlope
.MoveStraight
CMP #$02
BCS .OnWall
.OnFloorOrCeiling
EOR Direction,x
LSR
BCS .MoveLeft
.MoveRight
TYA
ASL #4
CLC
ADC XPosFrac,x
STA XPosFrac,x
PHP
TYA
LSR #4
PLP
ADC XPosLo,x
STA XPosLo,x
LDA XPosHi,x
ADC #$00
STA XPosHi,x
RTS
.MoveLeft
TYA
EOR #$FF : INC
TAY
ASL #4
CLC
ADC XPosFrac,x
STA XPosFrac,x
PHP
TYA
LSR #4
ORA #$F0
PLP
ADC XPosLo,x
STA XPosLo,x
LDA XPosHi,x
ADC #$FF
STA XPosHi,x
RTS
.OnWall
EOR Direction,x
LSR
BCS .MoveDown
.MoveUp
TYA
EOR #$FF : INC
TAY
ASL #4
CLC
ADC YPosFrac,x
STA YPosFrac,x
PHP
TYA
LSR #4
ORA #$F0
PLP
ADC YPosLo,x
STA YPosLo,x
LDA YPosHi,x
ADC #$FF
STA YPosHi,x
RTS
.MoveDown
TYA
ASL #4
CLC
ADC YPosFrac,x
STA YPosFrac,x
PHP
TYA
LSR #4
PLP
ADC YPosLo,x
STA YPosLo,x
LDA YPosHi,x
ADC #$00
STA YPosHi,x
RTS
.OnSlope
LDA XPosLo,x
STA $00
LDA XPosHi,x
STA $01
LDA YPosLo,x
AND #$F0
STA $02
LDA YPosHi,x
STA $03
JSL FindBlockNumber
REP #$30
; blocks 16E-1D7 are slopes
CMP #$01D8
BCS .Return
CMP #$016E
BCC .Return
SBC #$016E
TAY
; [$82] points to a table that indicates the distance between the top of the block and the top of the slope for each pixel of each slope tile
LDA [$82],y
AND #$00FF
ASL #4
STA $00
LDA $94
AND #$000F
ORA $00
TAX
SEP #$20
LDA SlopeDataTbl,x
SEP #$10
AND #$0F
ORA $02
STA YPosLo,x
.Return
RTS
```

*modern*engine, and Google was no help. If I'm going about this task all wrong and there's a much better way to do it, feel free to provide alternative suggestions, or even code; I literally could not pay people to program this for me, so any meaningful help is appreciated.