was war denn gestern los hier in Berlin, dass solche hoher Besuch da war?
Promo-Tour für Thimbleweed Park, siehe hier
Du bist in Begriff, Forum64 zu verlassen, um auf die folgende Adresse weitergeleitet zu werden:
Bitte beachte, dass wir für den Inhalt der Zielseite nicht verantwortlich sind und unsere Datenschutzbestimmungen dort keine Anwendung finden.
letzter Beitrag von 8R0TK4$T3N am
was war denn gestern los hier in Berlin, dass solche hoher Besuch da war?
Promo-Tour für Thimbleweed Park, siehe hier
Das wurde im Thimbleweed Newsletter und Blog aber lange angekuendigt.
Auf nach London am Thursday, datt kann man noch schaffen!
War echt ganz cool in Berlin
Stefan Höltgen sprach mich Anfang des Jahres mal speziell auf den Aspekt 'Spiel im Spiel' bei Caren an. Also die Implementierung des Pong Spiels welches man in Caren spielen kann am Atari 2600.
(ein World First, hehe
Ich hatte dazu einige Sachen rausgesucht und beschrieben und gebe das auch gerne hier einmal wieder gekuerzt.
Die specs zu den SFX kommen direkt hierher:
http://cs.au.dk/~dsound/Digita…enfoot/Pong.dir/Pong.html
Ich las diesen Artikel und das triggerte erst die Idee, das als Spiel-im-Spiel umzusetzen.
Ich draengte Jammer dazu, die 100% so umzusetzen - trotz der extremen Zeitnot zur deadline damals.
Wenn man Kamil kennt weiss man, dass er da auch Perfektionist ist, so dass er dazu noch einen Filter
drumbastelte damit es klingt als kaeme es aus SO EINEM TV-Lautsprecher wie im Spiel
Also alles hochwissenschaftlich, versteht sich
MrSid zeigte sich von Anfang an interessiert an unserem Caren-Projekt. Die ein oder andere Sache
hatte ich mit ihm bereits diskutiert und ich fragte, ob er ein 'Pixel Pong' als Gast-Auftritt coden moechte
in hires chars als 4x3 Feld (Ja, 4:3 Ratio MUSS sein natuerlich
Meine Vorgabe war diese Char-Anordnung
0123
4567
89ab
und eben separate Aufrufe 'pro Spielframe'. Das heisst ich muss die Pong-Engine einpaar pro Frame aufrufen
koennen und sie darf dann nicht mehr als vielleicht ein Drittel des Frames verbraten an Zeit.
MrSid machte daraus ein
0369
147a
258b
Hielt sich aber sonst komplett an die Vorgabe
Die Caren Engine startet pro Raum ein Script.
Hier ist ein laenglicher Ausschnitt aus diesem Script. Kuerzungen markiere ich mit "(...)"
Das ist einer der ersten Raeume und das Script etwas unaufgeraeumter als spaeter.
script_caren1f.sh:
name: tv
box: 121 42 179 78
scripts: _scp_handle_tv _lookattv _scp_handle_tv _deny
variables: on=0 null2=0 null3=3 frame=0
name: vcs
box: 143 88 168 103
scripts: _lookvcs _lookvcs _vcs_use _scp_handle_vcs
variables: cart=0 score=0 joy=0 frame=0
(...)
_init_room:
.asm .byte $ff
call _load_stuff
set game_control 0
set game_play_tune 0
setcurrent tv
setcurrent fridge
setcurrent vcs
setcurrent oven
setcurrent trapdoor
set game_num_objects_in_room 9
set game_box_num 10
set game_bg_col0 9
set game_bg_col1 12
set game_bg_col2 0
set game_npc_x 0
set game_mask_active 1
set game_mask2_col 12
set game_mirror_active 0
set game_mirror_col 6
launch _watch_masks every 3 frames in 3 frames
place_caren 82 103
set game_current_box 1
call game_face_se
set game_control 1
if fridge_open reconfig_area fridge 296 89 319 115
if oven_open reconfig_area oven 214 111 239 118
if not oven_open reconfig_object pot click none
if oven_open reconfig_object pot click _lookpot
if pot_taken reconfig_object pot click none
if trapdoor_open enable_exit trapdoor
if trapdoor_open reconfig_area trapdoor 1 1 51 40
if oven_open reconfig_area oven 212 111 239 125
break for 3 frames
call game_screen_on
end
(...)
_vcs_use:
if vcs_cart goto label_playthe2600
ifown cartridge goto label_owning
say "Irgendwo habe ich noch ein Modul.",0,"I have a cartridge for it somewhere.",0
end
#
label_owning
say "Noch steckt kein Modul drin.",0,"I need to plug a cartridge in first.",0
end
#
label_playthe2600
if not vcs_joy goto label_joystickmissing
goto _play_pong
end
#
label_joystickmissing
say "Ohne Joystick ist das doof.",0,"There's no joystick, that's no fun.",0
end
_play_pong:
walk2xy 180 122
keep_walking
say "Ok, 5 Punkte mache ich mal schnell.",0,"Ok, I'll have a quick 5 point game.",0
keep_listening
set game_control 0
call game_face_nw
break for 3 frames
set game_current_mood 2
animate tv every 1 frames: 7
break for 3 frames
/asm-------------
lda #$00
sta $5800+6*40+16+$0
lda #$03
sta $5800+6*40+16+$1
lda #$06
sta $5800+6*40+16+$2
lda #$09
sta $5800+6*40+16+$3
lda #$01
sta $5800+6*40+16+$28
lda #$04
sta $5800+6*40+16+$29
lda #$07
sta $5800+6*40+16+$2a
lda #$0a
sta $5800+6*40+16+$2b
lda #$02
sta $5800+6*40+16+$50
lda #$05
sta $5800+6*40+16+$51
lda #$08
sta $5800+6*40+16+$52
lda #$0b
sta $5800+6*40+16+$53
\asm------------
.asm jmp playlabel
.asm #include "tvpong3.asm"
.asm playlabel
/asm------------------
lda #11
sta Player1Y
sta Player2Y
lda #$00
sta PointsPlayer1
sta PointsPlayer2
jsr initBall
\asm------------------
.asm label_mainp
.asm jsr updatePong
.asm jsr renderPong
.asm inc FrameCount
.asm lda PointsPlayer2
.asm sta caren1f_vcs_score
break for 1 frames
animate tv every 1 frames: 7
if vcs_score<5 goto label_mainp
set game_control 1
say "Geschafft! Ich schalte wieder aus.",0,"Great! I'd better turn it off again.",0
keep_listening
say "Diese Version war von_",0,"This version was by_",0
keep_listening
say "Andreas Varga.",0,"Andreas Varga.",0
/asm-------------
ldx #$00
stx $5800+6*40+16+$0
inx
stx $5800+6*40+16+$1
inx
stx $5800+6*40+16+$2
inx
stx $5800+6*40+16+$3
inx
stx $5800+6*40+16+$28
inx
stx $5800+6*40+16+$29
inx
stx $5800+6*40+16+$2a
inx
stx $5800+6*40+16+$2b
inx
stx $5800+6*40+16+$50
inx
stx $5800+6*40+16+$51
inx
stx $5800+6*40+16+$52
inx
stx $5800+6*40+16+$53
\asm------------
animate tv every 1 frames: 5 6 5 6 5 5 6 6 6 5 5 6 5 5 6 5 6 6 5 6 6 6 5 5 6 5 0
end
------------------------------------------------------
Interessant in Bezug auf Pong ist es ab hier:
_play_pong:
walk2xy 180 122
keep_walking
say "Ok, 5 Punkte mache ich mal schnell.",0,"Ok, I'll have a quick 5 point game.",0
keep_listening
set game_control 0
call game_face_nw
break for 3 frames
set game_current_mood 2
animate tv every 1 frames: 7
Klartext:
Caren geht zu x,y = 180,122
Die Engine wartet bis sie dort ist (andere Dinge im Hintergrund laufen aber weiter, nur DIESES Skript wartet)
Caren sagt etwas
Die Engine wartet bis sie fertig ist damit
Der Spieler verliert die Kontrolle (Cursor aus)
Caren schaut nach nord-west
Mini-Pause.
Caren freut sich (auch wenn man das von hinten nicht sieht
Der TV-Bildschirm wird auf frame 7 animiert (siehe g_caren1f_tv_4-3.png)
Dann kommt etwas inline assembler, ganz stumpf um die char-matrix nach MrSids vorgaben anzulegen.
Hinterher setze ich sie wieder zurueck, so dass die uebrige TV Animation passt.
Das ginge eleganter, aber so ging es damals schnell und ich habe das nicht mehr veraendert danach
Zur Deadline war ja massiv Stress.
.asm jmp playlabel
.asm #include "tvpong3.asm"
.asm playlabel
Hier wird der komplette pong-code in inline assembler eingebungden und uebersprungen.
Es folgt die PONG game loop:
.asm label_mainp
.asm jsr updatePong
.asm jsr renderPong
.asm inc FrameCount
.asm lda PointsPlayer2
.asm sta caren1f_vcs_score
break for 1 frames
animate tv every 1 frames: 7
if vcs_score<5 goto label_mainp
Das "break for 1 frames" ist der Clou dabei.
Dazu gleich.
Die Loop laesst sich direkt lesen denke ich.
Ich schrieb einen eigenen Scriptcompiler fuer Caren. Das ist keine VM sondern Caren fuehrt direkten 6502-code aus in Scripten.
Spannend ist zB das hier:
"break for 1 frames" wird compiliert zu:
ldx #<(*+9)
ldy #>(*+7)
lda #01 ;in that many frames
jmp _break
X,Y werden auf die Stelle NACH dem jmp _break gesetzt.
Einmal +9, einmal +7 weil ja ein LDX #XX dazwischen kommt
_break ist entscheidend und sieht so aus (Teil der Engine selbst).
_break
.(
sta frames_delay
stx script_data+5 ;X
sty script_data+6 ;Y
jsr find_free_slot
lda #1
sta slots_msb,x
frames_delay=*+1
lda #1
sta slots_lsb,x
txa
asl
asl
asl
tax
ldy #0
loop
lda script_data,y
sta slots_data,x
inx
iny
cpy #8
bne loop
end
rts
script_data ; a x y
.byte 0,0,<_enter_script, >_enter_script, 9, 9, 9 ,0
.)
;---------------------------------------
_enter_script
.(
stx target+1
sty target+2
target
jmp $babe
.)
Break sucht also einen freien multi-threading slot und setzt dann die funktion _enter_script
dort hinein. Sie hat zwei benutzte Parameter, naemlich einen 16bit pointer auf genau die Stelle nach jmp _break.
Das ganze passiert nach frames_delay frames welches im Akku an _break uebergeben wird.
Im naechsten frame arbeitet die Engine also alle 32 Slots ab, zaehlt den 16bit counter runter, trifft nach frames_delay
auf einen unterlauf, fuehrt den slot aus und laedt dabei A,X,Y so wie es im Slot gesetzt wurde.
So wie ich _break umgesetzt habe, beleiben also keine CPU-Flags oder Register erhalten.
Das war zuerst drin, erwies sich dann aber in der Praxis als unnoetig - der grosse Vorteil wenn man in Assembler programmiert.
In C wuerde man sich das nicht trauen
Nach 5 Punkten erhaelt der Spieler die Kontrolle zurueck, Caren sagt ihren Text, die Charmatrix fuer den TV wird
wieder zurueckgesetzt und
animate tv every 1 frames: 5 6 5 6 5 5 6 6 6 5 5 6 5 5 6 5 6 6 5 6 6 6 5 5 6 5 0
Flippt zwischen den beiden noise-frames 5 und 6 (siehe wieder g_caren1f_tv_4-3.png) hin und her.
Das g_ in g_caren1f_tv_4-3.png verraet meiner Engine, dass es eine Animation ist die pro Frame ein eigenes Farbram mitbringt,
so dass ich white noise und natuerlich Pong in hires darstellen kann.
Ein wichtiges Detail ist diese Zeile im Script:
animate tv every 1 frames: 7
nach:
.asm jsr updatePong
.asm jsr renderPong
Das heisst die Pong-Engine plottet nicht in den Bildschirm, sondern in reserviertes RAM.
Erst danach wird dieses Speicherbereich dann in den aktuell angezeigten Zeichensatz kopiert.
Das erledigt: animate tv every 1 frames: 7
D.h. heisst soviel wie, fuelle diejenigen Zeichen welche per Maske fuer den 'TV' reserviert sind mit dem Inhalt des 7. Frames und zwar im naechsten
Engine-Frame, d.h. also sofort (das ist das 'every 1').
Die Zeile: animate tv every 1 frames: 5 6 5 6 5 5 6 6 6 5 5 6 5 5 6 5 6 6 5 6 6 6 5 5 6 5 0
wechselt also in 50Hz zwischen Fraems 5 und 6 (das ist das statische Rauschen) und am Ende zeigt es Frame 0, das entspricht dem ausgeschalteten Fernseher.
(siehe auch angehaengtes PNG)
Vielleicht interessiert diese Herangehensweise oder ein wenig Hintergrund zu Caren ja
Das fand ich jetzt aber auch interessant, wusste ich alles gar nicht!
Btw, die kleine Pong engine hat mich circa 3h gekostet. Die vertikale Anordnung der Chars macht das Rendern der Schläger um vieles einfacher, daher hab ich das so gewählt.
Sehr interessant. Vielen Dank für die schöne Erklärung, Enthusi.
Das ist keine VM sondern Caren fuehrt direkten 6502-code aus in Scripten.
Eine Frage hätte ich aber noch, wenn's erlaubt ist: Wie ist das obige Zitat zu verstehen? Bedeutet dies, daß das komplette Script von Deinem Compiler nach 6502 kompiliert wird, indem z. B. ein Befehl wie "CALL _load_stuff" umgewandelt wird in "JSR _load_stuff"? Für diesen Fall, aber auch für den inline-Assemblercode taucht dann nämlich die Frage auf, ob der verwendete Code für eine absolute Adresse erzeugt wird oder relokatibel angelegt ist (z. B. mittels Header, der die zu patchenden Befehle enthält wie beim AmigaOS). Und sollte mal irgendwann jemand ähnlich wie bei SCUMM auf die Idee kommen, einen Caren-Interpreter für andere Maschinen zu schreiben, müßte der dann nur einen 6502-Emulator schreiben oder bedarf es einer gesonderten Interpreter-Engine?
Caren-Interpreter für andere Maschinen
Caren für Z80 ? Oob, komm schnell!
Für jeden Raum entsteht am Ende ein komplettes Assemblerlisting. Auch alle globalen Variablen sind dort via label eingebunden. Zu dieser Zeit ist das also noch voll relativ an jede Stelle assemblierbar. Im Spiel selbst wurde es aber absolut für DAS Scriptoffset im Spiel aSsembliert. Für z80 würde man keinen 6502 emu bauen, sondern die scripte in z80 compilieren. Allerdings verwende ich bei Bedarf direkt 6502 inline Assembler auch im script. Daher die enorme Flexibilität gegenüber einer VM. Meist sind das aber geschlossene Effekte oder Routinen die man dann separat in z80 umsetzen müsste und könnte. z80 hab ich mir ja mal beibringen müssen
Heute konnte ein haesslicher bug der zwar nur einmal auftrat aber dennoch sehr stoerte gefixt werden \o/
Ich BIN kein häßlicher Bug...häßlich OK, aber sechs Gliedmaßen habe ich nicht!
Ich BIN kein häßlicher Bug...häßlich OK, aber sechs Gliedmaßen habe ich nicht!
Du heißt ja auch sicherlich nicht Gregor Samsa
Heute konnte ein haesslicher bug der zwar nur einmal auftrat aber dennoch sehr stoerte gefixt werden \o/
Es geht fleißig voran
Gestern kam meine ReGame Ausgabe an
Sehr schoen geworden, freut mich.
Recht ausfuehrlich zu technischen Details bei Caren aber auch nette Interviews von anderen. Z.B Peiselullis und Vetos Donkey Kong!
Habe ein kleines Tool/GUI gecodet um das Einbinden neuer Raeume zu erleichtern.
Ich hatte das Gefuehl, dass wir es noch sehr oft brauchen werden
Vor allem die Verbindungen der Tueren aber auch wo Caren steht und ich welche Richtung sie schaut abhaengig davon woher sie kam.
Die Animationsframes der Tueren werden auch gleich mit beruecksichtigt.
Klasse
Ich denke mal, dass die Entwicklung damit um einiges beschleunigt wird, oder?
Wenn Caren II dann mal fertig ist, könnte man ja ein Adventure-CK daraus machen
Mit was gemacht? C# Python?
python natürlich
Tkinter für das GUI. Für schnelle Sachen ist das ideal (für mich).
Wird Caren 2 auch die Maus 1351 unterstützen (ich meine nicht die Joystickemulation)?
Bitte... würde sich bestimmt besser steuern lassen als mit dem Joystick..
ohja bitte 1351 Maus...
Hm. Bin ich persönlich kein Freund von. Das ganze Steuerungskonzept ist auf direkte Interaktion mit Objekten und 4-Wege-Menüführung ausgelegt. Das müsste man komplett auf links drehen, wenn das sich halbwegs gut anfühlen soll und ich sehe dort kaum Mehrwert bis auf zusätzliche Einschränkungen bei den Ressourcen.
Bzw was wir in der Entwicklung bereits vorantreiben, ist eine Verschlankung der Steuerung. Der Doppelklick ist beispielsweise nicht mehr notwendig. Hotspots bekommen etwas Spielraum. Solche Dinge...