Сайт Влада

Как добавить Spin Dash

Автор гида: vladikcomper
Основано на гиде «SCHG How-to:Add Spin Dash to Sonic 1/Part 1»

Spin Dash — самая известная способность Соника, которая дебютировала в Sonic 2. В Первом Сонике этой способности не было, но ходят слухи, что подобие Spin Dash все-таки было, так как в РОМе найдены неиспользованные спрайты Соника, занимающегося Спин Дэшем. Однако правда до сих пор неизвестна, а спрайты могли использоваться для других целей.

Движок Sonic the Hedgehog 2 во многом сходен с движком Sonic 1, так что можно портировать Спин Дэш из Sonic 2, немного адаптировав его код для работы на другом движке. Все эти адаптации уже сделаны для Вас в этом гиде, вы даже можете проверить сходства предоставленных кодов, откопав суброутину Спин Дэша в коде Второго Соника.

Вставка основы Спин Дэша

Найдите в исходном коде лейбел Obj01_MdNormal и добавьте прямо после него строчку:

		bsr.w	Sonic_SpinDash	; ++ branch to Spin Dash

Теперь каждый раз, когда ежик стоит, будет запускаться суброутина Спин Дэша, которая в свою очередь будет проверять, можно ли выполнять Спин Дэш.

Настало время вставлять саму суброутину Sonic_SpinDash. Вставьте ее после суброутины Sonic_JumpHeight. Найдите конец суброутины, «; End of function Sonic_JumpHeight» и вставьте после него код:

; ---------------------------------------------------------------------------
; Subroutine allowing Sonic to do Spin Dash
; ---------------------------------------------------------------------------

; ||||||||||||||| S U B	R O U T	I N E |||||||||||||||||||||||||||||||||||||||

Sonic_SpinDash:
		tst.b	$39(a0)			; is Spin Dash flag set?
		bne.s	SpinDash_Process	; if yes, branch
		cmpi.b	#8,$1C(a0)		; is animation Duck?
		bne.s	Sonic_SpinDash_Rts	; if not, branch
		move.b	($FFFFF603).w,d0
		andi.b	#$70,d0			; is A/B/C pressed?
		beq.w	Sonic_SpinDash_Rts	; if not, branch
		move.b	#2,$1C(a0)		; use Spin Dashing animation
		move.w	#$BE,d0
		jsr	(PlaySound_Special).l
		addq.l	#4,sp			; avoid Sonic_Jump call
		move.b	#1,$39(a0)		; set Spin Dash flag
		move.w	#0,$3A(a0)
		bsr.w	Sonic_LevelBound
		bsr.w	Sonic_AnglePos
 
Sonic_SpinDash_Rts:
		rts	
; ---------------------------------------------------------------------------

SpinDash_Process:
		move.b	($FFFFF602).w,d0
		btst	#1,d0			; is Down pressed?
		bne.w	SpinDash_Charge		; if yes, branch
		
SpinDash_Release:
		move.b	#$E,$16(a0)		; decrease Sonic's height
		move.b	#7,$17(a0)
		move.b	#2,$1C(a0)		; use rolling animation
		addq.w	#5,$C(a0)		; add 5 to Y coordinate
		move.b	#0,$39(a0)		; clear Spin Dash flag
		moveq	#0,d0
		move.b	$3A(a0),d0
		add.w	d0,d0
		move.w	DashSpeeds(pc,d0.w),$14(a0) ; get Spin Dash speed
		btst	#0,$22(a0)		; is Sonic facing right?
		beq.s	SpinDash_ReleaseSound	; if yes, branch
		neg.w	$14(a0)			; negate inertia

SpinDash_ReleaseSound:
		bset	#2,$22(a0)
		move.w	#$BC,d0
		jsr	(PlaySound_Special).l
		bra.s	loc_1AD78

; ---------------------------------------------------------------------------
DashSpeeds:	dc.w  $800		; 0
		dc.w  $880		; 1
		dc.w  $900		; 2
		dc.w  $980		; 3
		dc.w  $A00		; 4
		dc.w  $A80		; 5
		dc.w  $B00		; 6
		dc.w  $B80		; 7
		dc.w  $C00		; 8
; ---------------------------------------------------------------------------
 
SpinDash_Charge:
		tst.w	$3A(a0)		; test charge counter
		beq.s	loc_1AD48	; if it's zero, branch
		move.w	$3A(a0),d0	; move it to d0
		lsr.w	#5,d0		; divide by 32
		sub.w	d0,$3A(a0)
		bcc.s	loc_1AD48
		move.w	#0,$3A(a0)
 
loc_1AD48:
		move.b	($FFFFF603).w,d0
		andi.b	#$70,d0		; is A/B/C pressed?
		beq.w	loc_1AD78	; if not, branch
		;move.w	#$1F00,$1C(a0)
		move.w	#$BE,d0
		jsr	(PlaySound_Special).l
		addi.w	#$200,$3A(a0)
		cmpi.w	#$800,$3A(a0)
		bcs.s	loc_1AD78
		move.w	#$800,$3A(a0)
 
loc_1AD78:
		addq.l	#4,sp		; avoid Sonic_Jump call
		cmpi.w	#$60,($FFFFEED8).w
		beq.s	loc_1AD8C
		bcc.s	loc_1AD88
		addq.w	#4,($FFFFEED8).w
 
loc_1AD88:
		subq.w	#2,($FFFFEED8).w
 
loc_1AD8C:
		bsr.w	Sonic_LevelBound
		bsr.w	Sonic_AnglePos
		move.w	#$60,($FFFFF73E).w	; reset looking up/down
		rts

; End of subroutine Sonic_SpinDash

Отлично! Теперь у нас есть подобие Спин Дэша, с виду похожее на Спин Дэш из Sonic CD.

Исправление багов

Камера не всегда может успевать прокручиваться вниз за Соником, особенно при прохождении двойных S-образных труб в GHZ. К тому же у камеры в Первом Сонике есть врожденный баг. Он заключается в том, что если Соник зайдет ниже своей высоты за нижнюю границу камеры, игра посчитает что он свалился в пропасть и убьет его. Чтобы исправить этот баг, идите к суброутине Boundary_Bottom и замените ее на это:

Boundary_Bottom:
		move.w	($FFFFF726).w,d0
		move.w	($FFFFF72E).w,d1
		cmp.w	d0,d1			; screen still scrolling down?
		blt.s	Boundary_Bottom_locret	; if so, don't kill Sonic
		cmpi.w	#$501,($FFFFFE10).w	; is level SBZ2 ?
		bne.w	KillSonic		; if not, kill Sonic
		cmpi.w	#$2000,($FFFFD008).w
		bcs.w	KillSonic
		clr.b	($FFFFFE30).w		; clear lamppost counter
		move.w	#1,($FFFFFE02).w	; restart the level
		move.w	#$103,($FFFFFE10).w	; set level to SBZ3 (LZ4)
 
Boundary_Bottom_locret:
		rts

Еще один заметный баг, который вскроется после исправления Spike Bug (смотрите гид «Как исправить Spike Bug») — отказ Соника прекращать Спин Дэш, после того как он накололся на иголках (Spikes). Для исправления бага, идите к лейбелу Hurt_ChkSpikes и вставьте прямо за ним строчку:

		move.b	#0,$39(a0)	; clear Spin Dash flag

Следующий баг, который предстоит исправить, заключается в том, что враг может спокойно повредить Соника, когда он совершает разгон на месте. Чтобы его исправить, идите к суброутине Touch_Enemy и перед строчкой «cmpi.b #2,$1C(a0) ; is Sonic rolling?» вставьте проверку на Спин Дэш:

		cmpi.b	#$1F,$1C(a0)	; is Sonic Spin Dashing?
		beq.w	loc_1AF40	; if yes, branch

Постройте РОМ и убедитесь, что эти баги исправлены.

Вставка анимации Спин Дэша

Чтобы Спин Дэш смотрелся как надо, сделать ему нормальную анимацию, чем мы сейчас и займемся.

Для начала, нужно портировать арт Спин Дэша из Sonic 2. Это я уже за Вас сделал, скачайте этот архив: spindash.zip. Достаньте из него spindash.bin и поместите этот файл в папку artunc.

Теперь в исходном коде игры найдите лейбел Art_Sonic и после строчки «incbin artunc\sonic.bin» вставьте:

		incbin	artunc\spindash.bin	; Spin Dash art

Арт Соника, особенный, так как каждый раз динамически подгружается нужный спрайту набор тайлов. Новый арт требуется вписать в список динамически загружаемых тайлов для дайльнешей работы с ним.

Откройте файл _inc/Sonic dynamic pattern load cues.asm и вставьте эти строки перед лейбелом SonPLC_Blank:

		dc.w SonPLC_SpinDash1-SonicDynPLC	;58
		dc.w SonPLC_SpinDash2-SonicDynPLC	;59
		dc.w SonPLC_SpinDash3-SonicDynPLC	;5A
		dc.w SonPLC_SpinDash4-SonicDynPLC	;5B
		dc.w SonPLC_SpinDash5-SonicDynPLC	;5C
		dc.w SonPLC_SpinDash6-SonicDynPLC	;5D

Эти номерки справа от строк стоят для удобства. Они обозначают номер кадра, который полезно знать для создания покадровой анимации. Мы только что сделали ссылки на лейбелы, в которых содержится информация о подгружаемых тайлах, теперь нужно вставить сами лейбелы.

Почти в самом конце файла, перед строкой «even» вставьте нужные лейбелы:

SonPLC_SpinDash1:	dc.b 1, $F5, $10	; 01 F 510
SonPLC_SpinDash2:	dc.b 1, $F5, $20	; 01 F 520
SonPLC_SpinDash3:	dc.b 1, $F5, $30	; 01 F 530
SonPLC_SpinDash4:	dc.b 1, $F5, $40	; 01 F 540
SonPLC_SpinDash5:	dc.b 1, $F5, $50	; 01 F 550
SonPLC_SpinDash6:	dc.b 1, $F5, $60	; 01 F 560

В комментариях справа от строк дано более удобное представление информации. Данные о подгружаемых тайлах имеют формат NN(STTT), где N - количество следуемых за ним данных о тайлах STTT, S - количество погружаемых тайлов (от 1 до 16), T - номер тайла, с которого начинается подгрузка.

Вся эта динамическая подгрузка нужна для экономии VRAM (видеопамяти). Ее всего 64 Кб, если загрузить туда весь Сониковский арт, а он весит 40 Кб, то не хватит места на остальное.

Но вернемся к делу. Теперь нам нужно слепить спрайты из блоков 8х8, предоставленных в арте.

Откройте файл _maps/Sonic.asm и над строчкой «byte_21292: dc.b 0» вставьте:

		dc.w byte_spdh1-Map_Sonic, byte_spdh2-Map_Sonic
		dc.w byte_spdh3-Map_Sonic, byte_spdh4-Map_Sonic
		dc.w byte_spdh5-Map_Sonic, byte_spdh6-Map_Sonic

Теперь надо прописать вышеперечисленные лейбелы. Опять, прописывать будем почти в конце файла, перед строкой «even»:

byte_spdh1:	dc.b 1			; Spin Dash 1
		dc.b $F8, $F, 0, 0, $F0
byte_spdh2:	dc.b 1			; Spin Dash 2
		dc.b $F8, $F, 0, 0, $F0
byte_spdh3:	dc.b 1			; Spin Dash 3
		dc.b $F8, $F, 0, 0, $F0
byte_spdh4:	dc.b 1			; Spin Dash 4
		dc.b $F8, $F, 0, 0, $F0
byte_spdh5:	dc.b 1			; Spin Dash 5
		dc.b $F8, $F, 0, 0, $F0
byte_spdh6:	dc.b 1			; Spin Dash 6
		dc.b $F8, $F, 0, 0, $F0

Спрайты готовы. То, что мы добавили, называется Спрайтовыми Маппингами (Sprite Mappings), об их устройстве вы можете посмотреть в справочниках.

Последнее, что осталось сделать — скрепить все спрайты анимацией, которая будет воспроизводится. Откройте _anim/Sonic.asm и перед строкой «SonAni_Walk: dc.b $FF, 8, 9, $A, $B, 6, 7, $FF» вставьте:

		dc.w SonAni_SpinDash-SonicAniData	;1F

Справа записан порядковый номер анимации для справки. Теперь перед строкой «even» в конце файла вставьте:

SonAni_SpinDash:	dc.b 0, $58, $59, $58, $5A, $58, $5B, $58, $5C, $58, $5D, $FF

В этой строке перечислены по порядку номера кадров, включенные в анимацию.

И нам осталось почти ничего, всего лишь отредактировать код Спин Дэша, чтобы он использовал новую анимацию. Идите к суброутине Sonic_SpinDash и замените «move.b #2,$1C(a0)» на:

		move.b	#$1F,$1C(a0)	; changed from #2

Теперь идите к лейбелу loc_1AD48 и раскомментируйте строчку «move.w #$1F00,$1C(a0)»

Постройте РОМ, все должно заработать.