Retrojen Forum
Dijital Sanat => Kodlama => Konuyu başlatan: wizofwor - 11 Aralık 2013, 21:14:12
-
Merhabalar ilk mesajımda belirttiğim üzere kafa ütülemeye başlıyorum.
İlk parallax scroller kodumu yazmaya çalışıyorum. Hedeflerim
- Ekranı üç banda ayıracağım. en üst bant en yavaş, orta bant hızlı alt kısım ise en hızlı olacak.
- Tek yönlü sağdan sola kaydırma yapacağım
- Scroll hiç durmayacak
- Ayrı bir yede tutacağım bir değere göre scroll hızı değişecek
Bunu başarmak için timer ve raster kesmelerini beraber kullanmayı düşündüm. kaydırma hızını bir hafıza adresine yazacağım. Timer IRQ hep aynı hızda çalışacak ve kaydırma hızı verisine göre kaydırma zamanına kadar verecek. (Başka bir hafıza adresine değer yazacak.) eğer kaydırma zamanı ise raster IRQ oluştuğunda atlanan subrutin kaydırma yapacak.
Mantıklı olan pratikte uygulanan sistem bu mudur? Kurguladığım algoritmada bir mantık hatası var mı? Timer IRQ'yu unutup herşeyi raster'a bağlamak daha mı mantıklı? Kafamı kurcalayan sorunlar bunlar.
-
platform hangisi c64 mü? Her donanımın kendine has yöntemleri var.
-
Bence timer IRQ olayına hiç girme, tüm zamanlamayı rasterline üzerinden yap.
3 ayrı kademede kaydırma için 3 ayrı ekranı splitscreen olarak kullanıp, ilgili rasterline'da uygun ekranı aktifleyip bu ekranın da sadece görünen kısımlarını değişken hızlarda olarak kaydırabilirsin. Bellekte de ekran için tahsis edilen 1K'lık alanların görünen alan dışındaki kısımlarını sprite görüntüleri veya her türlü veri için kullanabilirsin.
Bu durumda bu alanlar üzerinde sadece sprite'ları kullanabilirsin.
-
@ref:
Biraz fazla hızlı bir giriş oldu galiba. Ne üzerinde çalıştığımdan hiç bahsetmemişim. C64 için bir yarış oyunu projem var. Yani evet C64'ten bahsediyoruz.
@witchdoktor:
Bu timer olayı çok temel bir karar galiba. Anladığım kadarıyla olsa da olur olmasa da. Timer veya raster ikisinden birini seçip o yolda devam etmek gerekiyor gibi. Aynı hadiseyle actionscript öğrenirken de yaşamıştım. AS'de de timer ve refresh event'lerini kullanan oyunlar var. İkisiyle de aynı sonuca varılıyordu.
İlerde elime ayağıma dolaşmaması için timer olayını rafa kaldırayım olmazsa, tabi bu seferde delay rutinleri girecek işin içine.
-
@ref:
Biraz fazla hızlı bir giriş oldu galiba. Ne üzerinde çalıştığımdan hiç bahsetmemişim. C64 için bir yarış oyunu projem var. Yani evet C64'ten bahsediyoruz.
@witchdoktor:
Bu timer olayı çok temel bir karar galiba. Anladığım kadarıyla olsa da olur olmasa da. Timer veya raster ikisinden birini seçip o yolda devam etmek gerekiyor gibi. Aynı hadiseyle actionscript öğrenirken de yaşamıştım. AS'de de timer ve refresh event'lerini kullanan oyunlar var. İkisiyle de aynı sonuca varılıyordu.
İlerde elime ayağıma dolaşmaması için timer olayını rafa kaldırayım olmazsa, tabi bu seferde delay rutinleri girecek işin içine.
Timer IRQ ile yapacağın zamanlamalarda mutlaka rasterline veya frame kayıpları olacaktır. Tüm IRQ'ları işlerken diğer IRQ'ları kapatacağın için, çakışan durumlarda kaymalar illa ki olacak.
Tüm işlemleri rasterline kesmelerinde gerçekleştirip programın ana döngüsünü sonsuz boş döngü olarak kullanabileceğin gibi, hiç IRQ kullanmayıp tüm işleri ana döngü içinde rasterline'ları bekleyerek de gerçekleştirebilirsin. Sonuçta iş, her yapacağın işin ne kadar rasterline'lık CPU cycle'ı yiyeceğinde düğümleniyor.
-
Tabii c64 ile pek alakam yok ama bildiğim kadarıyla vic'de bir scroll registeri var 0xd016'da. Ayrıca bir raster irq var, programlanabilir bir kesme isteği. Ben olsam, eğer ekranın üstteki 3 plane'i hep aynı şekilde ise, bir kere cycle sayıp tüm planelerin kaydırılmalarının kaç cycle tuttuğunu hesaplar, programlanabilir kesme isteğine göre paralax'ımı tek raster'a yerleştirdiğim kesme ile tamamlardım. Geri kalan vakte de oyun logic'ini koyardım.
Yani, örnek, diyelim ki ekranın ilk 3 karakter satırı gökyüzü, ikinci 3lü satırı tarlalar, üçüncü üçlü satırı yol kenarındaki ağaçlar olsun.
-x=1 olsun
-0. raster satırına irq kur (http://www.c64-wiki.com/index.php/Raster_interrupt (http://www.c64-wiki.com/index.php/Raster_interrupt) )
-bitmask'ı kontrol et, bit set ise,
--d016'da x pixel kaydır, cycle say, 3 sütun geçsin.
--d016'da x pixel kaydır, cycle say, 3 sütun geçsin.
--d016'da x+x pixel kaydır, cycle say, 3 sütun geçsin.
--8 pixel kaydı ise, karakterleri kaydır.
böylece 1.katman 1 pixel, 2. katman 2 pixel, 3.katman 4 pixel kaymış olur, bence işi görür.
Vic'i 8 pixelde bir geri alman gerekiyor. Aynı zamanda arka plan grafiklerini bir karakter kaydırıp tazelemen gerekiyor.
Cycle say'dan kastım, bir loop ile bekleyebilirsin. Ya da burada her zaman aynı cycle tutan bir işlemi yapabilirsin. Ya da zaman yetiyorsa yeniden raster kesmesi programlayabilir ve bekleyebilirsin. (vic'in 1 raster satırı kaç cycle tutuyor, border kaç cycle tutuyor bilmiyorum. Eğer çok büyükse bu süre, o zaman bayağı cycle harcamış olursun. Bu durumda en iyisi 3 kere raster interrupt kurmak daha mantıklı olur herhalde.)
Hızı yavaşlatmak için bir adet bitmask kullanabilirsin. yani diyelim ki #10101010 (170) bu senin mask'ın olsun. 8 irq'nun sadece 4'ünde kaydırma yapılacak. Ya da 00010001 olunca 2 kaydırma. 11111111 (255) olursa 8'inin 8'inde kaydırma var. Hızlandırmak için, bitmask 255'e geldikten sonra, x'i artırıp bitmask'ı yine düşüğe ayarlayarak hızı arttırırsın.
Burada hızı değiştirmek için iki değişken var, x ve bitmask. Zaten kaydırma rutini her ikisini de kontrol ettiği için hızı değiştirmek için bu değerleri modifiye etmen yeterli. Bitmap için bir tablo tutarsın hızlıdan yavaşa,
0-10000000
1-10001000
2-10010010
3-10101010
4-11010110
5-11011011
6-11101110
7-11111111
şeklinde bu tablodan çekersin 8 bitmask yavaşlama seviyesi, ve 8 (x) seviyesi 8x8 64 hız seviyesi eder. Bunları bile bir tabloya koyabilirsin. 64 girdili bir tablo yap, ordan direk yerleştirirsin.
Tabii şimdi ben bir spectrumcu olarak biraz işleri dolaylı yapıyor olabilirim. Lütfen c64'cüler atlasın konuya :D Yani görüldüğü üzere timer falan kullanmadım, sadece 1 raster irq kullanarak tüm kaydırmaları yaptım.
Bilmem anlatabildim mi :)
edit:Mantığı anlatmak için mecburen speccy'den örnek vericem.
Speccy'de 1 raster 228 ts tutuyor. Raster border'ı çizerken 80 ts var. Kaydırmayı border alanında yapmak akıllıca. d016'yı border çizilirken ayarlarsın, sonra 228x24 beklemek gerek (5400 cycle beklemek için sabit tstate tutan bir loop yapılabilir, bu sürede iş de yapılabilir.) Sanırım PAL c64'de 1 raster 63 cycle tutuyormuş (kaynak: http://unusedino.de/ec64/technical/misc/vic656x/pal-ntsc.html (http://unusedino.de/ec64/technical/misc/vic656x/pal-ntsc.html)). Bu durumda 3 karakter satırı için, 63x24= 1512 cycle beklemek gerek. Eğer bu süre senin için değerliyse ozaman en iyisi irq programlayıp işe geri koyulmak gerek.
Haa tabii ben bütün bunları 8x8'lik karakter ekranı için söyledim, 8x4'lük ekran modunda, ona göre ayarlamak lazım.
-
.....
lda #$32
RASTER1 cmp $d012
bne RASTER1
jsr SCROLL1
lda #$74
RASTER2 cmp $d012
bne RASTER2
jsr SCROLL2
lda #$b2
RASTER3 cmp $d012
bne RASTER3
jsr SCROLL3
....
diğer rutinler
....
jsr $1003 ;müziksiz olmaz :)
jmp $ea81 ;veya jmp $ea31
;---------------------------
SCROLL1 .....
.....
rts
SCROLL2 ....
....
rts
SCROLL3 ....
.....
rts
;------------------
c64'te uzun zaman oldu intro kodlamayalı.
-
Herkese mutlu yıllar. Kendi adıma 2014'ün ilk meyvası olarak, oldukça ilkel olmakla beraber, bir scroller yazmayı ve kazasız belasız çalıştırmayı başardım. Karakter tabanlı kaydırma yaptığım için gözlere biraz zararlı olsa da zamanlama olayını iyi kotardığımı düşünüyorum.
İlgileneler için .prg ve .asm dosyaları ekte
Yazdığım kodda hoşuma gitmeyen kısım aşağıda örneğini vermiş olduğum move_row alt rutininden 4 tane yazmış olmam. Satır numarasını parametre olarak gönderebilseydim daha mutlu olurdum. Ama bunun oluru yok herhalde.
!zone move_row1{
move_row1:
ldx #$00
ldy SCREEN_RAM+40*14
.loop
lda SCREEN_RAM+40*14+1,x
sta SCREEN_RAM+40*14,x
inx
cpx #40
bne .loop
sty SCREEN_RAM+40*14+39
rts
}
Bir diğer sorun da hız. Amacım bütün ekranı kaydırmaktı ama buna zaman kalmıyor malesef. IRQ'yu ekranın hemen altından başlatıyorum ($FB). Şu anda bile kaydırma işleri bitene kadar raster ekranın tepesine dayanıyor. Gerçi bununla ilgili bazı hinlikler düşünüyorum. Ama önce smooth scroll olayıyla ilgilenmem lazım.
-
Emeğine sağlık.
Bir video gibi birsey koyman mumkun mu?
-
Emeğine sağlık.
Bir video gibi birsey koyman mumkun mu?
vice c64 emülatör:
http://vice-emu.sourceforge.net/index.html#download (http://vice-emu.sourceforge.net/index.html#download)
Dejavu, bu programı kur, .prg dosyasını sürükle bırak.
@wizofwor
Hayırlı olsun :D
Karakter tabanlı olması biraz göze rahatsız edici gelse de sanırım doğru yoldasın. Diğer taraftan arka planda olacaksa bu grafikler o kadar da önemli olmayabilir.
Yarın öğleden sonra biraz incelerim, kodu paylaşmayı düşünüyor musun?
Bu arada eğer 7dx'e katılacaksan, bu scroller oyun haline gelmeye başladığında forumlara koyma, skate sonra kabul etmiyor. Saçmalığın daniskası ama ille de gizli saklı kodlanacak bu release'ler :D
-
Kodu paylaştım zaten. Biraz uzunca olduğu için mesajın içine gömmek istemedim ama ek olarak paylaştım.
Belli bir aşamayı geçtikten sonra en büyük sıkıntı neyi nasıl yapacağımı öğrenmek. Sanal ağda assembler ve VIC yazmaçlarını başlangıç seviyesinde öğrenmek için bol miktarda kaynak var. Sonrası el yordamıyla gidiyor. Örneğin akşam her şeyi hallettiğimi düşünürken efektin zamanlamasını ayarlayamadığımı fark ettim. Ne yaparsam yapayım sabit olarak 5sn.'de bir adım atlıyordu. IRQ rutimi kontrol ederken imdadıma Nightlord yetişti. Google'da 6510, IRQ, $d01a vs. ararken Nightnetwork denk geldi ve kodları karşılaştırınca CIA IRQ'ları kapatmadığımı fark ettim.
Kaynak oluşturması açısından bu tip basit kodları özellikle yayınlamak istiyorum. Hatta ilerde vaktim olursa İngilizce olarak da yayımlayabilirim. Tabi bunlar birebir oyun içinde kullanılacak kodlar değil. Kullansam bile basit işleri halleden alt rutinler olduğu için sorun çıkarmayacağını düşündüm. Kabul etmezlerse de canları sağolsun. Şu parallax'ı istediğim seviyeye getireyim sıra sprite muliplexer'a gelecek. Kurguladığım oyunda multiplexer'e gerek yok ama acayip heveslendim sıralama algoritmalarını vs. okumaya başladım bile.
-
Kodu paylaştım zaten. Biraz uzunca olduğu için mesajın içine gömmek istemedim ama ek olarak paylaştım.
...
Kaynak oluşturması açısından bu tip basit kodları özellikle yayınlamak istiyorum. Hatta ilerde vaktim olursa İngilizce olarak da yayımlayabilirim. Tabi bunlar birebir oyun içinde kullanılacak kodlar değil.
Valla öğrenme kaynakları çok gibi görünüyor ama kaynağın "kendine uyanını" bulmak zor. Bu sebepten yayınladığın kaynak kodu bence çok değerli. Şimdi dalıyorum içine bakalım neymiş 6502 :)
-
@wizofwor
Güzel bir girişim olmuş, ellerine sağlık.
Ekranın genelde 25 satırını da kaydırmak çoğunlukla gerekmez, skor paneli vb alanlar nedeniyle. Bunun dışında, doublebuffering ile 2. bir ekranda her frame'de ekranın belli bir kısmını (örneğin ekran kayma hızı 1 pixel/frame ise, her frame'de ekranın 1/8'lik kısmını) kopyalayarak CPU gücünden tasarruf edebilirsin.
Sprite multiplexer işi de güzel bir 'challenge' olur bu arada.
Hayırlı kodlamalar...
-
Doublebuffering tekniğini okudum. Ama uygulaması nasıl yapılır hiç kafa yormadım. 4 satır farklı hızla kayarken biraz kafa karıştırıcı olur gibi geldi. Hız sorununun üstesinden gelmek için farklı sayaç değerlerinde farklı satırları kaydırmayı düşünüyorum. Ama önce smooth scrolling olayını halletmem lazım.
@Dejavu: Video olayı için hazırda pratik bir yöntemim yok ama bir yolunu bulur video'da eklerim.
-
Ellerine sağlık wizofwar. Ben bu thread'i de önceden kaçırmışım. Birkaç naçizane notum olacak
Yazdığım kodda hoşuma gitmeyen kısım aşağıda örneğini vermiş olduğum move_row alt rutininden 4 tane yazmış olmam. Satır numarasını parametre olarak gönderebilseydim daha mutlu olurdum. Ama bunun oluru yok herhalde.
!zone move_row1{
move_row1:
ldx #$00
ldy SCREEN_RAM+40*14
.loop
lda SCREEN_RAM+40*14+1,x
sta SCREEN_RAM+40*14,x
inx
cpx #40
bne .loop
sty SCREEN_RAM+40*14+39
rts
}
Bu soru iki türlü algılanabilir:
1- kodu 4 kere yazmaktan rahatsızsın ama bellekte 4 kere yer kaplaması OK
2- kodun bellekte de 4 kat yer kaplamasından rahatsızsın
Birincisinin basit bir çözümü var: makro kullanmak
!macro moveRow .row{
ldx #$00
ldy SCREEN_RAM+40*.row
.loop
lda SCREEN_RAM+40*.row+1,x
sta SCREEN_RAM+40*.row,x
inx
cpx #40
bne .loop
sty SCREEN_RAM+40*.row+39
rts
}
; sonra ileride
moveRow1:
+moveRow 14
moveRow2:
+moveRow 15
moveRow3:
+moveRow 16
moveRow4:
+moveRow 17
Hatta ayni macroyu MoveLogo rutininde de kullanabilirsin. Bellekteki açılım şu anki haliyle aynı ama en azından kopyala yapıştır hatalarından seni korur.
Eğer bir rutinim olsun bellekte diyorsan, o zaman daha esnek bir rutin yazmak lazım. Burada problem verilen parametrenin (.row) çarpma işlemine girecek olması. o çarpmayı da tek rutin olduğu zaman Acme'ye yaptırmak mümkün değil. Bu yüzden çarpmayı compile time değil runtime da yapmak zorundasın.
Her C64 coder'ı "çarpma", "bölme" gibi kelimeler duyduğunda bir durup, bundan nasıl kurtulurum diye bakmalı. Burada da hemen row'un kaç değişik değer alabileceğine bakıyoruz. 25. hemen 25 değerlik bir tablo yapıyoruz. Daha doğrusu alt baytlar ve üst baytlar için iki tablo yapıyoruz:
scr_rows_lo:
!by <(SCREEN_RAM + 40 * 0)
!by <(SCREEN_RAM + 40 * 1)
...
!by <(SCREEN_RAM + 40 * 23)
!by <(SCREEN_RAM + 40 * 24)
scr_rows_hi:
!by >(SCREEN_RAM + 40 * 0)
!by >(SCREEN_RAM + 40 * 1)
...
!by >(SCREEN_RAM + 40 * 23)
!by >(SCREEN_RAM + 40 * 24)
...
;row is passed in X
moveRow:
!zn{
lda scr_rows_lo, x
sta .r + 1
clc
adc #1
sta .w + 1
lda scr_rows_hi, x
sta .r + 2
sta .w + 2
ldx #$00
.r:
lda $1234,x
.w:
sta $1234,x
inx
cpx #40
bne .r
rts
}
Tabii butun bunları yaptığında başlangıçtan daha da çok bellek harcamış olabilirsin :) Bu yüzden böyle bir tasarım kararının bir diğer ayağı da bu rutinin kaç değişik yerden çağırılıyor olduğu. n yerden çağırılan ve basit hali x bayt harcayan bir rutinin daha esnek ve karmaşık hali y bayt harcıyorsa (y > x), o zaman nx ile y'yi karşılaştırıp ona göre karar vermek lazım. n yeterince büyükse bellek kazanacaksın demektir.
Bir de en son moveLogo rutininle ilgili bir yorumum var. orada 5 ayrı loop kullanmak yerine hepsini tek loopta halledebilirsin. Bunun için birinci harfi 39. lokasyona koymak icin y registerini kullanmaktan vazgeçmen gerekir. Ben olsam önce 5 satirin birinci lokasyonlarındaki harfi alır bir yerlere yazardım (zero page olabilir). sonra bir loopta ard arda 5 tane "lda foo+1,x sta foo, x" yapar ardindan da en son o 5 sakladigim karakteri 39. pozisyonlara koyardim.
-
İşin içine $d016'yı da ekleyerek scroller'ı biraz daha smooth hale getirdim. Bayağı bir optimizasyon da yaptım. Yine de sormak istediğim hoşuma gitmeyen bazı şeyler var. Öncelikle kaynak kodlarının github linkleri:
scroller.asm (https://github.com/wizofwor/C64-assembly-examples/blob/master/beginner-routines/scroller-03/scroller.asm)
scroller-subroutines.asm (https://github.com/wizofwor/C64-assembly-examples/blob/master/beginner-routines/scroller-03/scroller-subroutines.asm)
prg dosyası github'da da var. İsterseniz aşağıda ekler kısmından da alabilirsiniz.
Programın başında kullanacağım zero page adreslerini $02'den başlayarak sıralıyorum. Kernal IRQ'ları kapattığım için sorun olmuyor. Eğer kernel IRQ'ları kullanacak olsaydım. Hangi ZP adreslerini kullanabilirim?
Kullandığım counter rutininden memnun değilim. Örneğin part2 ilk iki frame'de kayıyor, sonraki iki frame bekliyor. Eğer bir frame kayıp, bir frame beklese efekt daha yumuşak olacak. Farklı bir bekleme/sayaç mantığı nasıl kurulabilir bilemedim.
.delay inc COUNTER ;increase the COUNTER
ldx COUNTER
cpx #05
bne .delay2
ldx #00
stx COUNTER
.delay2 cpx #04 ;Skip clculation parts as per
beq .part2 ;delay value
cpx #03
beq .part3
cpx #02
beq .part4
cpx #01
beq .part5
Bu soru iki türlü algılanabilir:
1- kodu 4 kere yazmaktan rahatsızsın ama bellekte 4 kere yer kaplaması OK
2- kodun bellekte de 4 kat yer kaplamasından rahatsızsın
Üniversitedeki C sınıfından mı, yoksa çok eskiden okuduğum bir makaleden mi bilmiyorum aklıma kazınmış "Bilgisayarın yapabileceği rutin işi sen yapma" şeklinde bir söz var. Bunu ben hep aynı şeyi iki kere yazıyorsan optimizasyon hatası yapıyorsun olarak algıladım. 6502 evreninde işlerin tam bu şekilde yürümediğin yavaş yavaş kavramaya başlıyorum. Kodun bellekte 4 kere yer kaplamasını yarı hızda çalışmasına yeğliyorum.
Makro konusundaki örnek için de çok teşekkürler. Şimdilik makrolara girmek istemedim. Çünkü şu kodu çalıştırıp, bir de internete yüklemek için 6502'nin yanında ayrıca giriş seviyesinde bash, git ve vim öğrenmem gerekti. Bu yaştan sonra bünye kaldırmıyor. Daha sonra koda renk geçişleri eklemeyi düşünüyorum. Artık makrolara bir da o zaman bakarım.
-
İşin içine $d016'yı da ekleyerek scroller'ı biraz daha smooth hale getirdim.
....
Üniversitedeki C sınıfından mı, yoksa çok eskiden okuduğum bir makaleden mi bilmiyorum aklıma kazınmış "Bilgisayarın yapabileceği rutin işi sen yapma" şeklinde bir söz var.
Biraz değil, smooth olmuş. Eskisi karakter tabanlı scrolling yapıyordu. Çok da güzel olmuş.
z80 dünyası da unrolled loop'larla dolu. Başta ben de yadırgamıştım, 2-3kb dolusu manuel kopyalama komutunu görünce, LDIR'in nesi vardı diyorsun ama sonra aradaki hız farkının tam iki kat olduğunu görünce doğru hesabı yapıyor insan. Asmp ekran modunun z80 kodunu yazarkende mecburen böyle bir işe girişmiştim, makro ile oluşturulan kod 6kb'ı buluyordu. Bu sebepten, bizim gibi yavaş cpu'lara asm yazan kişilerin ilk öğrenmesi gereken şey makro dili. iyi kalpli Nightlord zaten söylemiş nasıl kullanacağını, makro olmadan bu tür 4-5 kere yazılmış kodun bakımını yapmak çok zor oluyor.
-
amanin ikinci joker vakasi :) ellerine saglik wizofwor. ilk firsatta koda daha detayli bakip yorumlarimi aticam
-
Bu basligi daha yeni gordum, guzel konuymus, kacirmisim.
Genel bir sey eklemek istedim sadece: C64'te (ve muhtemelen raster duzeyinde ekrani update edebildiginiz diger sistemlerde), ekranla ilgili yapilan degisikliklerin mutlaka ekranin gorunebilir alani disinda olmasi gerekmiyor (titremeleri onlemek, efekti smooth yapabilmek adina). Bunun icin tek bir kaide var; bulundugunuz raster satirinda o satiri degistirmemek. Yani 40x25 scrolling yaparken ekranda tearing (titreme vb) olmasini engellemek icin kaydirmaya border'dan baslamak yerine kaydirilan video/color ram satirinin bir alt satirindan baslamak sorunu ortadan kaldiracaktir (her bir ekran satirini islemenin yine bir ekran satiri kadar raster zamani harcadigi ve butun bu islemi de 1 frame suresi icinde tamamladiginiz varsayimi ile). Ilk satir zaten gosterildigi icin ikinci satirda ilk satiri degistirmeniz ancak bir sonraki frame'de gorunebilir olacaktir, bu islemi tum kalan satirlar icin yaptiginizda da gorsel olarak bir problem yasamazsiniz.
Eger update yapan rutin raster'in ilerleme hizina yetisiyorsa o zaman yukaridakinden biraz farkli olarak ilk baslayacaginiz satiri daha ileri almak durumunda kalabilirsiniz. (Yani birinci satiri ikinci satir civarinda update etmek yerine, 5. ya da 10. satirda update etmeye baslamak isteyebilirsiniz).
Biraz kotu anlattim ama dilerim ilgilenen arkadaslara bir faydasi olur.
-
C64 memory map'de zeropage'de 10-15 byte'lık serbest alan görülüyor.
http://sta.c64.org/cbm64mem.html (http://sta.c64.org/cbm64mem.html)
-
10-15 byte boş görünüyor dediğin $03-$12 arası mı? Bu arada BASIC rutinlerinin kullandığı alanlar var. Eğer BASIC rutinlerini kullanmıyorsak $90'a kadar yardıramaz mıyız?
-
evet once basic'ten vazgec ve $2'den $90'a kadar yardir. sonra kernel'den de vazgec (su an kernelden kullandigin tek sey irq entry ve exit rutinleri yani ff48 ve ea81. bunlara bakarsan yaptiklari tek seyin registerlari hatirlamak oldugunu goreceksin. bu kodu kendin yazip irq vektor olarak 314 315 yerine fffe ffff kullanirsan kerneli kapatip butun zero page'i de kullanabilirsin)
daha timing counter'larin ile ilgili yorum yazicam ama bu gece firsat olmadi
-
senin kodunu nacizane analiz edeyim ve bence neden zorlandigini aciklamaya calisayim. sonra da timing olayini ben olsam nasil cozerdim onu aciklamaya calisayim.
analiz:
su anki halinde yasadigin problem bence scrollar.asm 74-121. satirlar arasindaki rutinin yeterince parcalanmamis olmasindan kaynaklaniyor. kodun bu bolumu ard arda dizilmis islemler iceriyor ve siralari onemli. 56-71. araligindaki kosullarla 74-121 araliginda tek noktaya atliyorsun ve ondan sonrasi calismak zorunda kaliyor. bu part1, part2 part3 vs'nin birbirinin arkasindan calisiyor olmasi bana gore gereksiz bir "coupling" halbuki bu partlarin yaptigi isler birbirinden bagimsiz isler.
burada hemen nacizane bazi rename onerilerimi sunacagim, bunlar sonraki cozum onerimin daha kolay anlasilmasini saglayacak.
genel olarak butun yazilim islerinde isimlere cok onem vermek lazim. iyi isimlendirmeye onem verirsek dusunusumuz de berraklasiyor. iyi bir isim o parcanin ne yaptigini berrakca anlatan en kisa ifade olmali. her zaman super isimler bulmak kolay degil ama cabanin bile kendi basina faydasi var. ben genel prensip olarak fonksiyon/method/subroutine isimlerine "fiil + nesne ismi" gibi bir format kullanmaya calisirim. "calculateDelay" "scrollLine" "moveChars" "initEffect" vs gibi... Bazen bunun hafif disina cikarim daha aciklayici oldugunu hissedersem. super lokal looplarda atlama yeri etiketleri falan icin cok kasmam ama subroutine isimleri onemli.
Bu dogrultuda part1 part2 gibi isimler biraz generic/ambiguous kaliyor. keza shift1 shift2 gibi isimler de ilk bakista ne yaptigini isminden cikaramadigim seyler. shift2 mesela karakterleri kaydiriyor ama d016 kaydirmasini da yapiyor olabilirdi. shift2'nin koduna bakmadan bunu anlamak mumkun degil. Bu yuzden ben olsam shift1 shift2 vs isimlerini, charScrollLine1 charScrollLine2 falan yaparim. part1 part2 falan bolumlerini de her birini bagimsiz bir subroutine haline getirir ve isimlerine pixelScrollLine1 pixelScrollLine2 falan yaparim. yani mesela
pixelScrollLine2:
!zone{
lda offset2 ;Calculate offset for line 2
sec
sbc #01
and #07
sta offset2
cmp #07 ;when offset value is not 7
bne .end ;skip char shifting part
jsr charScrollLine2
.end:
rts
}
timing cozum: birinci adim yukaridaki gibi part1,part2 vs'yi birbirinden bagimsiz pixel scroll rutinleri haline getirmek. Bu rutinlerin yaptigi is artik bagimsiz olarak tanimli pixelScrollLine2 cagirdigim zaman biliyorum ki line2 bir pixel kayacak (eger icinde zamani galmisse karakterler de kayacak ama bu kaygi artik pixelScroll rutininin icindeki bir ic detay haline geldi. pixelScroll rutininin musterisi artik bunu dusunmek zorunda degil.)
ikinci adim da 56-71 arasinda atlanilan yerlerde yapilan isi degistirmek. Burada 5 durumlu bir counter kurmus durumdasin. Bu 5 durumun her birinde ne olmasini istiyorsan bunu artik tek tek yapabilirsin. her durumda hangi satirlari scroll edip hangilerini etmemeyi kontrol etmek cok kolay. mesela
1 nolu durum: logo scroll ama satirlar beklesin
2 nolu durum: satir 4 scroll etsin digerleri beklesin
3 nolu durum: satir 2 ve satir 4 scrol etsin
vs...
bu durumda yine bi onemsiz gorunen rename. beq ile atladigin yerleri part1 part2 diye isimlendirmek yerine "state1, state2 vs" ya da counter oldugunu ifade etmek icin "cycle1, cycle2 vs" diye isimlendirmek bence daha seffaf
diger bir ufak oneri genelde bu tip counter tabanli loop gorunumlu seylerde "off by 1" hatasi yapmak cok kolaydir. Bunu engellemenin en iyi yolu genel olarak hep ayni yapiyi kullanmak. yani counter'i ne zaman artiriyoruz ne zaman kontrol ediyoruz. vs. ben asagida bunu hafif degistiriyorum
.delay
ldx COUNTER
cpx #00
beq .cycle0
cpx #01
beq .cycle1
cpx #02
beq .cycle2
cpx #03
beq .cycle3
cpx #04
beq .cycle4
; if we are here Counter == 5 and it's time to reset.
ldx #00
stx COUNTER
.cycle0:
...
jmp .end
.cycle1:
...
jmp .end
.cycle2:
...
jmp .end
.cycle3:
...
jmp .end
.cycle4:
...
jmp .end
.end:
inc COUNTER
; then go on to the action part
simdi en basta bahsettigin herhangi bir satirin "2 kaydi 2 kaymadi yerine bir kayip bir kaymadi" olayini yapabilirsin. 5 cycle'dan hangilerinde kaymasini istiyorsan (mesela cycle 1, 3, te kaysin 0 2 4'de kaymasin) o cycle'larin bolumune birer jsr pixelScrollLineX yerlestirmen yeterli.
Burada yaptigimiz sey temel olarak su: "Bilgisayar bilimlerindeki pek cok problem bir seviye fazla araci katarak cozulebilir" (most problems in computer science can be solved by adding a level of indirection). burada scroll yapan rutinleri birbiri ardina dizip cycle'a bakarak aradaki bir noktaya atlamak yerine, scroll rutinlerini bagimsiz rutinler haline getirip araya o rutinleri dogru cycle'larda cagiran bir araci seviye (.cycleN rutinleri) ekledik. Bu bize her cycleda hangi satirlari istersek kaydirabilme sansi verdi.
-
Değerli tavsiyelerin için teşekkürler. Çok faydası olduğuna emin olabilirsin.
-
Burada yaptigimiz sey temel olarak su: "Bilgisayar bilimlerindeki pek cok problem bir seviye fazla araci katarak cozulebilir" (most problems in computer science can be solved by adding a level of indirection).
Güzel bir öğütmüş, kulağımıza küpe yapalım. Yalnız burada "ne zaman bir yönlendirme daha eklemeliyim?" sorusunun cevabı yok, sanırım cevap "tecrübe" olsa gerek.
edit: Bu konu bölünmüştür. Konunun bölünmüş başlığı aşağıdaki bağlantıdan takip edilebilir:
http://retrojen.org/pano/index.php?topic=763.msg6321;topicseen#msg6321 (http://retrojen.org/pano/index.php?topic=763.msg6321;topicseen#msg6321)
-
Güzel bir öğütmüş, kulağımıza küpe yapalım. Yalnız burada "ne zaman bir yönlendirme daha eklemeliyim?" sorusunun cevabı yok, sanırım cevap "tecrübe" olsa gerek.
"Ne zaman" sorusunun cevabi bu sozun baglaminda su: yonlendirmeyi eklemeden problemi cozemedigin zaman.
Cunku bu soz konuya fonksiyonel dogruluk acisindan yaklasiyor. Amac once yapmak istedigin seyi fonksiyonel olarak yapabilmek (perf olarak yavas olabilir o ayri konu). Sorunu cozdukten sonra perf optimizasyonuna gidecegin soylenebilir. Baska deyisle: Oncelikle "calistir" sonra "optimize et"
isin tecrube ile ilgili olan kismi bazi durumlarda bazi indirection'larin performansi kabul edilemez kilacagini bastan bilmek ve o yola bastan sapmamak. Bazen bu "tecrube" geri de tepebilir. yani calistirmadan optimize etmeye calisanlarin basina belalar geldigi cok gorulmustur. Tabi CS camiasinda bunun icin de ozlu soz var. Knuth Baba'dan tum sevenlere gelsin:
"Vaktinden once yapilan optimizasyon butun kotuluklerin anasidir" (Premature optimization is the root of all evil)
-
Herkese mutlu yıllar. Bu başlığa ilk çalışan kodu geçen yılın ilk günü atmışım. Geriye dönüp bakınca benim için oldukça verimli bir yıl oldu diyebilirim. Belki alışkanlık haline getiririm temennisiyle, biraz aceleye getirsem de yine çalışan bir şeyler paylaşıyorum.
Scroller #04 (https://github.com/wizofwor/C64-assembly-examples/tree/master/beginner-routines/scroller-04)(github link)
Bu sefer bir önceki programdan tamamen farklı bir yaklaşım izledim. Metin yerine basit bir petscii garafik hazırladım. Irq rutinini de elimden geldiğince basit tutmaya çalıştım. Bu rutini her satır için (25 defa) tekrar çağırıyorum.
;====================================================================
; IRQ routine:
; Sets x-scroll register and for each raster zone
; and sets scrollFlag on Each screen refresh
; ===================================================================
irq:
!zone {
;dec $d020
;-------------/ wait for next raster for stable effect /-
ldx irqIndex
lda scrollRasterLo,x
clc
adc #$01
- cmp $d012
bne -
;--------------/ set XSCROLL /--------------------------
lda scrollOffset,x
sta $d016
;--------------/ increase irqIndex /--------------------
inx
cpx #25
bne + ; index != 25, skip
ldx #00 ; reset index
lda #01 ; set scrollFlag
sta scrollFlag
+ stx irqIndex ;store updated index value
lda scrollRasterLo,x ;update raster row for next irq
sta $d012
.end
;inc $d020
dec $d019 ;acnowledge IRQ register
jmp $ea81 ;return to kernel
}
Geri kalan hesaplamaları main loop içinde yaptırdım. Main loop ile irq arasındaki senkronizasyon için zeropage'de bir byte'ı bayrak olarak kullandım. Hareketi yavaşlatmak için Ref'in önerdiği maske sistemini kullanmayı denedim.
ldy scrollSpeed,x ;chech the speed mask
lda speedMask,y
bit counter
bne +
...
...
scrollSpeed:
!by 4,3,2,1,0,0,0,0,0,0
!by 0,0,0,0,0,1,2,1,2,3
!by 2,3,4,3,4
speedMask
!by %111111,%110111,%101010,%001001,%000000
Sonuçta sağda solda pek çok glitch olsa da çalışan bir şeyler etmeyi başardım. Şimdilik kodu paylaşmakla yetiniyorum. Sorularım daha sonra gelecek.
-
Eline sağlık wiz, süper olmuş. Dalgaları bu kadar basit bir şekilde çözebilmen de etkileyici. Bir göz aldanması var ve işe yarıyor.
Yukarıdaki bulutlarda bazı "glitch"ler var, timing ile ilgili büyük ihtimal. Bad raster işleri falan vardı c64'te bu tür bir sorun mu acaba?
Müzik kime ait?
Kodu inceliyeceğim çünkü orta kısımdaki karakter tabanlı grafik hi-res şekilde 1 pixellik bir kayma yapıyor, bakıcam oraya. Benim mask sistemini anlamış olmanın yanı sıra, uygulamış olmanı heyecanla karşıladım :D Lakin ben bile dönüp okuduğumda neden bahsettiğimi pek anlayamıyorum :) Niye bitmaskla uğraşıp zaman kaybetmeyi önermişim acaba? Hayırlamıyorum valla :D Ama işe yaramış galiba.
Diğer taraftan ekran görüntüsü niye pdf onu pek çözemedim işte :D
-
Benim mask sistemini anlamış olmanın yanı sıra, uygulamış olmanı heyecanla karşıladım :D Lakin ben bile dönüp okuduğumda neden bahsettiğimi pek anlayamıyorum :) Niye bitmaskla uğraşıp zaman kaybetmeyi önermişim acaba? Hayırlamıyorum valla :D Ama işe yaramış galiba.
Senin mask sistemini anlayabildiğine göre oldukça kabiliyetli biri olmalı wizofwor, daha bir saygı duydum şimdi. Sen bile zaman zaman zorlanıyorsun. Ayrıca gönderilerinle ilgili bir 'brain buffer' tutsan güzel olacak, zaman zaman 'Memento' vari senaryolar çıkıyor ortaya :)
Gizli Not: Bu zamanlama hilesini mucidi açıklamadan kullanmayı düşünüyorum. Yakalarsan kızar mısın? ;)
-
Müzik son dakikada hvsc'den rastgele indirilmiş bir şey. Kafa ütülemekten başka bir işe yaramıyor galiba. Küçük bir itirafta bulunmam gerekirse bitmask'la ilgili olarak ne önerdiğini ilk başta anlamamıştım. Ama kafamda oluşan fikirler, biraz deneme yanılmayla beni gördüğün sonuca ulaştırdı. Hala da nasıl çalıştığından tam emin değilim. Maskedeki kapalı bitleri arttırdıkça efekt hızlanıyor. Halbuki ben tam tersini yapmak istemiştim. Yalnız bunun önceki switch/case tarzı koda göre çok daha efektif bir sistem olduğunu rahatlıkla söyleyebilirim. Her karakterin satırının hızını ayrı tutuyorum. Eski sistemle çok uzun bir kod yazmam gerekirdi.
Zamanlama sıkıntılarına gelince. İki ayrı sorun var. Birincisi bulutlarda ekran kaydırma için istediğim satırı bir türlü tutturamıyorum. Irq'ya bir satır erken girip, asıl satırı beklemek için aşağıdaki kodu eklediysem de irq rutinim stable falan olmadı.
ldx irqIndex ;irq sıra numarası
lda scrollRasterLo,x ;irq satır numarası
clc
adc #$01
- cmp $d012
bne -
İkincisi ise karakter kaydırma rutiniyle ilgili bir sıkıntı. O gördüğün kaymanın VIC'in karakter kopyalayan rutine tur bindirmesinden kaynaklandığını düşünüyorum. Sonuçta bu rutin irq dışında çalışıyor ve zamanlamasını kontrol etmiyorum.
-
Küçük bir itirafta bulunmam gerekirse bitmask'la ilgili olarak ne önerdiğini ilk başta anlamamıştım. Ama kafamda oluşan fikirler, biraz deneme yanılmayla beni gördüğün sonuca ulaştırdı.
Haha, eh, aklın yolu bir dostum :) Tam olarak önerdiğim şeyi uygulamış olman benim açımdan süper olmuş. 10 puan!
Diğer soruların cevapları nightlord, wisdom ya da skate'te bulunuyor. Bakalım gören olacak mı.
-
Şimdi gördüm bu başlığı, süper iş çıkarmışsın wizofwor, tebrikler kardeşim.
Şu anda çok detaylı giremeyeceğim konuya, çok az vaktim var. Ancak bir iki temel noktaya parmak basacağım.
1) Bunu bir oyun projesi için planladığını söylemişsin. Tüm postları dikkatle okuyamadım ama daha önce uyaran olmadıysa ilk uyarıyı yaparak başlayayım. Spritelar ve zamanlamalar. :) Oyunda kullanacağın spritelar scrolllerin hassas biçimde yakalamış olduğun zamanlamalarını etkileyeceklerdir. Yani sen bu ekranın üzerinde serbestçe bir ya da birkaç sprite gezdirdiğinde zamanlamaların birbirine girdiğini göreceksin. Elbette ki bahsettiğim spriteların dikey pozisyon değişimleri, yatayda pek bir sorunla karşılaşmazsın. Yani oyunda spriteların gezineceği alanları iyi planlaman lazım.
Bir de iş zamanlamaya geldiğinde spriteları ekranda kaç adet olduklarına göre değil o anki bulunduğun raster satırına kaç sprite denk geldiğine göre değerlendirmen lazım. Bu konu çok derin bir konu, çeşitli çözümler var ancak dediğim gibi öncelikle senin ne planladığın önemli.
2) Stable IRQ rutini biraz daha karmaşık bir konu. Bu çok iyi bir örnek değil aslında stackle gereksiz oynaşıyor ama yine de mantığını anlaman için güzel bir örnektir.
http://www.ffd2.com/fridge/vic/stableraster.txt (http://www.ffd2.com/fridge/vic/stableraster.txt)
Yorumları okursan tam olarak stable zamanı nasıl yakalıyor anlayacaksın.
Stable raster yakaladıktan sonra işin biraz daha kolaylaşacak. Ama badlinelara, spritelara dikkat etmen lazım.
3) Scroll'ün nerede yapılacağı konusuna gelirsek... Basitçe kural şu. Scroll eden alan dışındaki herhangi bir yer, istersen parçalara bölerek. Ama burada da dikkat edilmesi gereken bir mevzu var. Scroll eden alanın üstünde kalan kısımda yapılan kaydırma işlemleri o framede, altında yapılanlar ise bir sonraki framede çizilir. Yani diyelim ki 8 karakter yüksekliğinde bir bloğu kaydıracağız. Alanın üstündeki alan en fazla 5 satının kaydırılması için yeteri cycle'a sahip, alt 3 satırı ise alanın altında kaydırmamız gerekiyor. Bu şekilde parçalayabilirsin, sorun yok. Ancak vereceğin scroll değerleri arasında 1 framelik bir faz farkı olması gerekiyor. Eğer aynı scroll pozisyonlarıyla kaydırmayı denersen alt üç satır 1 frame geriden gelecektir.
Bir diğer mevzu ise örneğin demin bahsettiğim 8 satırlık alanın içersinde o satırları kaydırma işlemi yapamayabilirsin ancak diğer scrollleri bu alanda yapmanda bir sakınca yok. Müzik falan da bu alanda çalabilirsin. Yeter ki toplam cycleların o alanı aşmayacağı garanti olsun.
Son olarak benim "beam kovalamaca" diye tabir ettiğim bir durum var. O da şöyle. Diyelim ki 8 satırın tamamını kaydırmak için yeterli zamanın kalmadı. Tek zaman kalan bölge yine 8 satılırlık alanın kendisi. Bu durumda şöyle bir trick kullanabiliyorsun. Diyelim ki ilk 6 satırı bölgenin dışında kaydırdın. Kalan son iki satırı burada kaydırabilirsin. Ancak dikkat! bu satırların kaydırıldığı bloğun satırların kendilerine denk gelmemesi gerekiyor. Yani mesela ilk 6 satır dışardan kaydırıldıysa, bu 6 satırlık bölgede takip eden iki satırı kaydırabilirsin.
Teorik olarak şu da mümkün. 8 satırlık bölgenin ilk 4 satırında alt 4 satırını, son dört satırında ilk 4 satırını faz farkıyla kaydır. Ama böyle bir şey için zamanlamaların fazlasıyla kusursuz olması gerekiyor. Tavsiyem bu kadar kritik zamanlamalara mecbur kalmadıkça girmemen yönünde olacaktır.
Şimdilik bu kadar. Daha sonra vaktim olduğunda daha teknik açıklamalarla yardımcı olmaya çalışacağım.
Not: Tüm karakter ekranını yukardan aşağı ve aşağıdan yukarı scroll eden iki rutin yazmayı denerseniz birini kolayca yapabildiğinizi, diğerinde bir türlü zamanlamaların yetişmediğini göreceksiniz. Burada bahsettiğim "beam kovalamaca" dediğim mevzu devreye giriyor. Her iki yönde de scroll'ü temiz biçimde yapmayı başardığınızda büyük ölçüde yukarda anlattıklarımı pratikte kullanmış oluyorsunuz.
-
Tavsiyelerin için teşekkürler. Merak etme, buradaki örnekleri bire bir oyun içinde kullanmayı düşünmüyorum. Maksat konsepleri oturtmak. Sensei'lerden tavsiye alırken, çekirgelere ip ucu bırakmak. Başarılı da oluyor. İlk ve son kod örneklerime bakacak olursan, kod yapısı, commentler, label kullanımı vb. bayağı bir fark göreceksin.
Aslında buraya oyun görseliyle ilgili bir mockup koyaibilsem, çok daha basit bir scroll istediğimi gösterebilirdim. Ama oyunu yetiştirmeye çalıştığım partinin kuralları çok katı olduğu için oyunda kullanacağım hiç bir şey paylaşmamaya çalışmıyorum. Scrolla ilgili tek sorun hızı değişken yapmaktı. Onu da en azından kafamda çözdüm. Refmask tekniğiyle halledeceğim.