Gönderen Konu: 6502 ASM Sprite Animasyon rutini  (Okunma sayısı 4722 defa)

0 Üye ve 1 Ziyaretçi konuyu incelemekte.

Çevrimdışı wizofwor

  • RAAT
  • Tedavideki Retromanik
  • *
  • İleti: 398
6502 ASM Sprite Animasyon rutini
« : 23 Kasım 2015, 14:34:49 »
Öğle arasında kahvemi içerken yazdığım 30 satırlık (boş satırlar dahil) karşınızdayım.

Amaç bir oyun içinde kullanılacak sprite animasyonu rutini.

İki zero page adresi kullandım STATE=$01 hangi animasyonun gösterileceği bilgisini tutacak. Yürüme, koşma, zıplama gibi state'ler için programın farklı adreslere dallanmasını istiyorum. Bunun için indirect indexed adresleme modundan faydalanmak istedim.

FRAME=$02 ise o anki STATE için gösterilecek animasyon karesini tutuyor. 

Kod: [Seç]
STATE = $01
FRAME = $02
SPRITE_POINTER = SCREEN_MEM+$03f8

LOOP: LDA STATE
ASR
TAX
JSR (JUMP_TABLE,X)
  INC STATE
AND #$02
JMP LOOP

Ana döngü içersinde STATE değerini iki ile çarpıp jump table'da işeret ettiği adrese dallanıyorum. İki ile çarpmamın nedeni adress bilgisinin iki byte olması.

Döngünün devamında STATE değerini bir arttırıp, sonucun 2'den büyük olmasını istemiğimden (sadece iki farklı durum modelleğim için) AND #$02 yapıyorum.

 

Kod: [Seç]
STATE1:
LDX FRAME
LDA SPRITE_POINTERS_TB,X
STA SPRITE_POINTER
INX
AND #$03
RTS
STATE2:
LDA #$03
STA SPRITE_POINTER
RTS

İkinci kısımda duruma göre FRAME değerini ilerletip sprite pointer'ı güncelliyorum.       

Kod: [Seç]
* = JUMP_TABLE
!by >STATE1,<STATE1,>STATE2,<STATE2

* = SPRITE_PONITERS_TB
!by = 0,1,2,3

Son kısımda da tablolarım var.

Tavsiyelerinizi her zaman olduğu gibi yine dört gözle bekliyorum. Programı çalıştırmayı denemedim. Özellikle JUMP_TABLE kısmının çalışıp çalışmadığından emin değilim.

Kodun bütünü için github linki burada:
https://github.com/wizofwor/C64-assembly-examples/blob/master/beginner-routines/sprite-animation/sprite-anim.asm
Gosub ile gidilen yerden goto ile dönen adam

Çevrimdışı nightlord

  • RAAT
  • Tedavideki Retromanik
  • *
  • İleti: 389
    • Night Network
Ynt: 6502 ASM Sprite Animasyon rutini
« Yanıtla #1 : 23 Kasım 2015, 17:26:13 »

Eline saglik. cok vaktim yok. hemen birkac not:

Alıntı
Kod: [Seç]
STATE = $01
FRAME = $02

Zero page de kullanmak icin guvenli adresler 02'den baslar. o yuzden state icin $01 adresini kullanma. 0 ve 1 6510 ram/rom mapping olaylarini kontrol ediyor.

Alıntı
Kod: [Seç]
LOOP: LDA STATE
ASR
TAX
ASR degil ASL

Alıntı
Kod: [Seç]
JSR (JUMP_TABLE,X)
benim bildigim indirect addressing mode destekleyen komut jmp. jsr desteklemiyor normalde (illegal opcode varsa bilmem)

Alıntı
Kod: [Seç]
  INC STATE
AND #$02
JMP LOOP
lda state
clc
adc #$01
and #$01
sta state
jmp loop

ya da sadece iki state olacagi garanti ise:
lda state
eor #$01
sta state


Alıntı
Kod: [Seç]
STATE1:
LDX FRAME
LDA SPRITE_POINTERS_TB,X
STA SPRITE_POINTER
INX
AND #$03
RTS

frame pointer x registerinde. and islemini a ile yapiyorsun. o yuzden bekledigin gibi calismayacak


Çevrimdışı wizofwor

  • RAAT
  • Tedavideki Retromanik
  • *
  • İleti: 398
Ynt: 6502 ASM Sprite Animasyon rutini
« Yanıtla #2 : 23 Kasım 2015, 17:59:55 »
Hızlı cevap için teşekkürler.

Kodu tavsiye ettiğin düzeltmelerle beraber güncelledim ve aralara comment ekledim.

eor rutinini kullanmadım zira sadece olayı fazla dallanıp budaklandırmamak için iki durumla yetinmiştim. Gerçek bir oyunda iki durumdan fazlası olacaktır.

Merak ettiğim ACME altında aşağıdaki şekilde yazdığım jump table çalışacak mı?

* = JUMP_TABLE
!by >STATE1,<STATE1,>STATE2,<STATE2


Yapmak istediğim subrutinlerin başlangıç adreslerini little endian formatında tabloya yazmaktı. Ancak bu şekilde yapıldığından emin değilim.

Güncellediğim kodu tekrar yapıştırıyorum.
 
Kod: [Seç]
STATE = $02
FRAME = $03
LIMIT = #$01
SPRITE_POINTER = SCREEN_MEM+$03f8


loop: LDA STATE ;load state
ASL ;mulitply by 2
TAX ;and jump to this adress
JMP (JUMP_TABLE,X)
return: LDA STATE ;increase state
CLC ;value in memory
ADC #$01 ;till STATE>LIMIT
AND LIMIT ;then roll over
STA STATE
jmp loop

;animation frames for each state
STATE1:
LDX FRAME  ;animation state 1 has three frames
LDA SPRITE_POINTERS_TB,X
STA SPRITE_POINTER
LDA FRAME ;increase
ADC #$01 ;animation
AND #$03 ;frame
STA FRAME ;index
JMP return
STATE2:
LDA #$03 ;animation state 2 has only one frare
STA SPRITE_POINTER
JMP return

* = JUMP_TABLE
!by >STATE1,<STATE1,>STATE2,<STATE2

* = SPRITE_PONITERS_TB
!by = 0,1,2,3
Gosub ile gidilen yerden goto ile dönen adam

Çevrimdışı nightlord

  • RAAT
  • Tedavideki Retromanik
  • *
  • İleti: 389
    • Night Network
Ynt: 6502 ASM Sprite Animasyon rutini
« Yanıtla #3 : 23 Kasım 2015, 18:24:45 »
Merak ettiğim ACME altında aşağıdaki şekilde yazdığım jump table çalışacak mı?

* = JUMP_TABLE
!by >STATE1,<STATE1,>STATE2,<STATE2


Yapmak istediğim subrutinlerin başlangıç adreslerini little endian formatında tabloya yazmaktı. Ancak bu şekilde yapıldığından emin değilim.

evet calisacak. o > < operatorlerinin acmedeki temel amaci bu kullanim.

Yalniz su asagidakinin calisacagini zannetmem

LIMIT = #$01
...
and LIMIT

bunun yerine

LIMIT = $01
and #LIMIT

demen lazim. cunku degiskenlere numerik deger atanabilir. # isareti burada degiskenin degil komutun parcasi (and ve and# iki ayri komut yani)


Alıntı
loop:   LDA STATE       ;load state
   ASL          ;mulitply by 2
   TAX          ;and jump to this adress
   JMP (JUMP_TABLE,X)    
return:   LDA STATE       ;increase state
   CLC          ;value in memory
   ADC #$01       ;till STATE>LIMIT
   AND LIMIT       ;then roll over
   STA STATE
jmp loop

Simdi soyleyecegimin zaten farkinda olabilirsin, ama ben thread'e faydasi olacagini dusundugum icin yine de soyleyeyim. Bunu simdilik loop olarak dusunmen OK ama aslinda bir oyunda boyle bir loop olmayacak. asil ilk 4 komut (jmp dahil) ve state1 state2 vs senin asil animasyon rutinin olacak ve rts ile bitecekler. oyunun ana loop'u baska zilyon tane is yaparken zamani geldiginde bu rutine atlayacak. Bu rutin state'e gore (frame'i update ederek veya etmeyerek) sprite pointer'lari update edecek. main loopun baska yerlerinde sen joystick girdisine veya ai durumlarina bakarak "state" leri update edeceksin.

Çevrimdışı wizofwor

  • RAAT
  • Tedavideki Retromanik
  • *
  • İleti: 398
Ynt: 6502 ASM Sprite Animasyon rutini
« Yanıtla #4 : 23 Kasım 2015, 18:33:08 »
LIMIT = #$01
...
and LIMIT

bunun yerine

LIMIT = $01
and #LIMIT

demen lazim. cunku degiskenlere numerik deger atanabilir. # isareti burada degiskenin degil komutun parcasi (and ve and# iki ayri komut yani)

Ben bu değişken olayına çok farklı anlamlar yüklemişim. Şimdi taşlar yerine oturdu galiba.

Simdi soyleyecegimin zaten farkinda olabilirsin, ama ben thread'e faydasi olacagini dusundugum icin yine de soyleyeyim. Bunu simdilik loop olarak dusunmen OK ama aslinda bir oyunda boyle bir loop olmayacak. asil ilk 4 komut (jmp dahil) ve state1 state2 vs senin asil animasyon rutinin olacak ve rts ile bitecekler. oyunun ana loop'u baska zilyon tane is yaparken zamani geldiginde bu rutine atlayacak. Bu rutin state'e gore (frame'i update ederek veya etmeyerek) sprite pointer'lari update edecek. main loopun baska yerlerinde sen joystick girdisine veya ai durumlarina bakarak "state" leri update edeceksin.

Bardağa dolu tarafından baksak? Bu loop'un içine oyunun ai, collision vb. diğer hesaplarıyla ilgili kodlar da gelecek ve birlikte main loop'u (voltran) oluşturacaklar desek?
Gosub ile gidilen yerden goto ile dönen adam

Çevrimdışı witchdoktor

  • RAAT
  • Normalleşmiş Retroman
  • *
  • İleti: 757
Ynt: 6502 ASM Sprite Animasyon rutini
« Yanıtla #5 : 23 Kasım 2015, 19:39:14 »
Neden bu kadar komplike bir kod oluşturdun? Animasyon kareleri ve bunların kaç frame süreceği vb ile ilgili dizileri içeren veri tabloları işini görmez miydi? Nightlord'un kodu basitleştirme önerisi getirmesini beklerdim ;) Üstad alternatif kod metodları önerirse sevinirim. 20 yıldır kod yazmayan bünyeme iyi gelecektir...

Çevrimdışı wizofwor

  • RAAT
  • Tedavideki Retromanik
  • *
  • İleti: 398
Ynt: 6502 ASM Sprite Animasyon rutini
« Yanıtla #6 : 24 Kasım 2015, 12:58:51 »
Bu kadar komplike bir kod ortaya çıkmasının sebebi başlangıç noktası olarak BASIC'deki `ON GOTO` deyimini almış olmam. ON GOTO ile yazacağım basic rutinin ASM'ye çevirmeye çalıştım.

Witchdoctor'un söylediği mantıktan yola çıkınca aşağıdaki sonuca ulaştım. İlk örnekte STATE'i sürekli arttırdığım için animasyon doğru çalışmayacaktı. En iyisi kodu loop'tan çıkarmak olduğunu düşündüm. Ana döngü başka bir yerlerde STATE değerini güncelleyip buraya dallanıyor diye düşündüm.

Kod: [Seç]
STATE = $02     ;current animation case (like jump,hit,run etc.)
FRAME = $03     ;current frame within the case
SPRITE_POINTER = SCREEN_MEM+$03f8


spriteAnimation:
      ;increase animation frame
      inc FRAME               ;increase animation frame
      ldx STATE               ;and compare with frameCount
      lda frameCount,x        ;for this state
      cmp FRAME               
      bne +
      lda #$00
      sta FRAME
+     
      ;calculate sprite pointer
      lda frameInit,x       ;calculate sprite pointer to use
      clc                   ;frameInit + FRAME
      adc FRAME             ;
      sta SPRITE_PONITER    ;set animation frame
     
      rts


frameCount:
!by 3,1           ;number of frames for each animation case
frameInit:
!by 0,3           ;beginning frame for each animation case

Hak geçmemesi için ilk örneği de benzer şekilde güncelledim. Eğer rutinimiz sadece animasyon karelerini oynatacaksa witchdoctor'un alternatifi çok daha temiz bir çözüm oldu. Ancak orjinal kod da ortalığı fazla karıştırmadan araya kod eklemeye müsait. Daha esnek. İlerde duruma göre ikisini de kullanabilirim.

Kod: [Seç]
STATE = $02 ;current animation case (like jump,hit,run etc.)
FRAME = $03 ;current frame within the case
SPRITE_POINTER = SCREEN_MEM+$03f8

spriteAnimation:
lda STATE ;load state
asl ;mulitply by 2
tax ;and jump to this adress
jmp (JUMP_TABLE,X)
rts

;animation frames for each state
state1:
ldx FRAME  ;animation state 1 has three frames
lda SPRITE_POINTERS_TB,X
sta SPRITE_POINTER
lda FRAME ;increase
adc #$01 ;animation
and #$03 ;frame
sta FRAME ;index
jmp return
state:
lda #$03 ;animation state 2 has only one frame
sta SPRITE_POINTER
jmp return

* = JUMP_TABLE
!by >state1,<state1,>state1,<state2

* = SPRITE_PONITERS_TB
!by = 0,1,2,3
Gosub ile gidilen yerden goto ile dönen adam

Çevrimdışı nightlord

  • RAAT
  • Tedavideki Retromanik
  • *
  • İleti: 389
    • Night Network
Ynt: 6502 ASM Sprite Animasyon rutini
« Yanıtla #7 : 25 Kasım 2015, 22:56:13 »
stateFrames tablosu yapip onu calan bir rutin yapmak benim de aklima gelmisti (vizontele'deki mukremin tonlamasiyla okuyabilirsiniz :) ). ama ote yandan ben kodu biraz da indirect adressing ile jump tablosu yapma egzersizi olarak algilamistim. O yuzden o halini calisir hale getirecek yorumlarda bulundum.

Bana kalsa ben bir adim daha ileri giderim. state animasyonu basina dusen frame sayisini sabitlerim (mesela 4). state'leri temsil eden sayilari da 4'un katlari olarak secerim (run = 128, kick = 132, die = 136 vs...). yani bir bayt icinde hem state'i hem frame'i represent ederim. ust 6 bit state, alt 2 bit frame olur. boylece kod suna indigenir

Kod: [Seç]
    lda STATE
    ora FRAME
    sta SPRITE_PTR

    and #$03 ; same as lda frame
    clc
    adc #1
    and #$03
    sta FRAME
    rts

Eger birden fazla sprite anime edilecekse loop eden animasyonlardakiler ayni FRAME degiskenini kullanabilir. o zaman state/frame birlestirme kodunu butun spritelara loop ettirip, frame artirma kodunu loop disinda bir kere yapabiliriz.

Eger her animasyon 4 frame kullanmiyorsa, iki yoldan biri takip edilebilir: ya olan frame'leri bellekte tekrar etmek gerekir. ya da  her animasyonu 4 frame haline getirebiliriz. Eger cok fazla state yoksa ikincisini tercih ederim.

Çevrimdışı Ref

  • Yönetici
  • Özgür Retrocu
  • *
  • İleti: 2970
  • Advanced User Simulator
    • ae unutmadan
Ynt: 6502 ASM Sprite Animasyon rutini
« Yanıtla #8 : 25 Kasım 2015, 23:20:44 »
yani bir bayt icinde hem state'i hem frame'i represent ederim. ust 6 bit state, alt 2 bit frame olur. boylece kod suna indigenir

nightlord, bu kadar teferruat ne için?
yerden kazanmak mı yoksa hız kazanmak mı?

spectrumda birsürü pahalı shift ve and işleminden kaçmayı ve 8+8 16bit olarak saklamayı tercih eder birçok programcı böylece tek bir komutla iki data da (state+frame) registerlere yerleşmiş olur. elbette bu aynı sonucu almak için 1 byte fazla harcamak demek oluyor. Ama kaç farklı sprite olabilir ki? var sayalım 64 tane olsun, topu topu 64 byte ziyan etmiş olursun, zeropage bile dolmaz :D


Çevrimdışı nightlord

  • RAAT
  • Tedavideki Retromanik
  • *
  • İleti: 389
    • Night Network
Ynt: 6502 ASM Sprite Animasyon rutini
« Yanıtla #9 : 26 Kasım 2015, 00:13:40 »
sonucu bir bayta indirgemeye mecbur eden sey gidecegi destinasyonun bir byte olmasi. sprite pointerlar bir bayt. benim dedigim metod state ve frame gibi iki ayri bayt'ta yasayan bilgilerin bir bayta indirgenmesinin en ucuz (hem bellek hem zaman tasarrufu acisindan) yolu.

Çevrimdışı wizofwor

  • RAAT
  • Tedavideki Retromanik
  • *
  • İleti: 398
Ynt: 6502 ASM Sprite Animasyon rutini
« Yanıtla #10 : 26 Kasım 2015, 10:38:38 »
Ben kodu biraz da indirect adressing ile jump tablosu yapma egzersizi olarak algilamistim. O yuzden o halini calisir hale getirecek yorumlarda bulundum.

Evet ana fikir indirect indexed adresleme kullanarak BASIC'teki ON ... GOTO tarzı bir uygulama yapmaktı. Tamamen farklı bir yaklaşım önermek yerine benim kodumdaki hataları göstermen çok daha yapıcı bir yaklaşım. Bu sayede hem yanlış bildiğim bazı şeylerin doğrusunu öğrendim. Hem de withchdoctor'un önerisiyle beraber aynı işi yapmanın ikinci bir yöntemini de somutlaştırmış oldum.

Her durum için frame sayısını sabitlemek başta iyi fikir gibi görünse frame sayısı çok değişkenlik gösteriyorsa sorun olabilir. 'run' 8, kick 2 die 1 frame ise 11 yerine 24 pointer harcanır. FRAME ve STATE'i birbirinden ayırırken bunu düşünmüştüm.

@Ref: Senin iki datayı birşeltirme yöntemi temelde benim ikinci yöntemin aynısı değil mi? Sadece biz fakir Commodore'cular 16 bit yazmacımız olmadığı için değerleri zeropage'a atıyoruz.
Gosub ile gidilen yerden goto ile dönen adam

Çevrimdışı witchdoktor

  • RAAT
  • Normalleşmiş Retroman
  • *
  • İleti: 757
Ynt: 6502 ASM Sprite Animasyon rutini
« Yanıtla #11 : 26 Kasım 2015, 11:34:44 »
nightlord daha bir etkin hale getirmiş ki 6502 kodlamasının temeli kodu yavaşlatmıyorsa olabildiğince 'size' ve 'memory' etkin programlamaya dayanıyor. Gerçi diğer byte da 'attribute' ve x-koordinatı 8. biti gibi amaçlarla kullanılabilirdi.