Jump to content

  • Log In with Google      Sign In   
  • Create Account

New Old Things



Adventurous Adventure

Posted by , 28 July 2015 - - - - - - · 1,077 views
C64 adventure
I'm currently attending yet another C64 game creation competition: Forum64.de's Adventure Competition 2015.

I always wondered how the guys/gals at Lucasfilm Games managed to cram Maniac Mansion and Zak McKracken onto a few small disks. So this competition was the perfect opportunity to test drive my ideas.


How they did it

Fortunately today the internet provides all kind of technical info at my finger tips. So it's no problem finding some of the old programmers spilling their beans. They actually created a Lisp like language that allowed them scripting even with a kind of multi threading. Incredible! Obviously the script was compiled to byte code which barely fit into memory. So that's the way I chose to follow.
Fun link of the day: Ron Gilbert on Scumm (http://grumpygamer.com/scumm_notes)


How I faked it

Tables. Tables with pointers to more tables. All verbs are represented by indices; so are items and static objects (items on screen)
Every item/object has a list of verbs to scripts. Item 5 with verb 7? Run this script. No entry for object 14 and verb 9? Show the dreaded "That doesn't work."

And there's action areas with type "room exit" or "script trigger". Room exits are handled directly while script triggers start their attached script.


Where's the beef?

It's in the script! All puzzles are handled by various script actions. A script action can set/clear flags, run a different script depending on a flag state. Characters can be "walked", get or lose an item, branch if a certain combination of items/objects is used, if the controlled character is anybody specific, etc.

This script together with rooms made up of elements (of which some may depend on certain flag states) makes for a quite small memory footprint. At the current state there's 65 flags, 39 rooms, 55 static objects and 18 items making up a total of 30355 bytes (graphics included).

Since that's all pretty abstract, here's a few excerpts.


Action table for a static object (badge reader in elevator)
SOA_BADGE_BUTTON_IN_LIFT
          !byte PA_EXAMINE, <CS_EXAMINE_BADGE_BUTTON, >CS_EXAMINE_BADGE_BUTTON
          !byte PA_LAST_ENTRY_FLAG | PA_USE, <CS_USE_BADGE_BUTTON_IN_LIFT, >CS_USE_BADGE_BUTTON_IN_LIFT
Starts a sequence when a button is used in the elevator, referenced in the table above

CS_USE_BADGE_BUTTON_IN_LIFT
          !byte CSA_SET_HAPPEN,2
          !byte CSA_REDRAW_SCREEN
          !byte CSA_WAIT,50
          !byte CSA_IF_HAPPEN_SET,4,<CS_ACCIDENT,>CS_ACCIDENT
          !byte CSA_CLEAR_HAPPEN,2
          !byte CSA_TOGGLE_HAPPEN,3
          !byte CSA_LAST_ENTRY_FLAG | CSA_REDRAW_SCREEN</pre>



The Week of Awesome II - Post Mortem

Posted by , 02 October 2014 - - - - - - · 1,330 views

Post Mortem to "Building Blocks"

Attached Image

So I entered the Week of Awesome II competition last week. I simply love this tight deadline competitions. And I couldn't help it again.
The theme came up as "The Toys are alive". Yay.

Usually I don't jump into coding right away to think about the game to be. And as usual I always go for the nearest idea. It's very rare that I try to outwit the theme and do something really creative. And so it was decided, a simple Lemmings style game, with tin soldiers walking about building blocks.

The Good

Level design

The common start is to think of how to keep the level in memory. The base of the level is tile based. I did think of bigger blocks right away, and this worked out fine after a few tries. This led me to the next point pretty fast, the


Level editor

Attached Image

Esp. for these competitions, when I go level based, I need a way to churn out levels very fast. Since this competition lasted for a week I went out of my way to make a decent editor. It was a bit more work in the beginning, but helped me tremendously during the last two days to play with level design and add some more stages. I wouldn't have managed 10 stages without it.


Base Library

For Ludum Dare we have to provide the full source. This makes me hesitate to actually use my home grown engine and I work with a very simple cut down framework. For the GameDev compo I can use my full framework and it does help quite a lot. I've got game assets, GUI and game states from the start, and especially, I'm comfortable with it.


Generated Music

Previous to the last Ludum Dare I was on the search for music generators once more. I encountered cgMusic, which made some really nice songs. It's a bit piano centric, and I feel that some song parts to repeat, but it was good enough for me. Adding a midi to ogg converter I could generate songs in a few minutes.


Early Preview

As suggested I put up an early version. And it was a good idea. I had some very valuable feedback, as there were a few things I didn't even think of. I think I did add all suggestions.


The Bad

Collision system

The sloped tiles were made out as a first issue to tackle. For some reason I thought using polygons would work out nice. Since I have a math library with polygon collision code I wrote polygon creators for the different blocks. Collision with a unit's bounds rectangle (=polygon) worked very nicely.
However problems appeared when I actually implemented the movement system. I had my units move in pixel units. Obviously mixing pixel based movement with "exact" mathematics (think of the slope of a diagonal block) does not work out. Units fell one pixel, ended up inside the block polygon and were stuck.

I ended up adding a simple hack: return the pixel based height of a tile depending on the x position in the tile. Ugly, but worked.


Ugly bug

When I had the first preview out someone mentioned that the game crashes on exit. I despise things like that (Gamemaker games, I'm looking at you). In the end the real bug was a bug in my level block handling. I managed to access out of bounds memory and as usual this crashed at a totally unrelated position. I had the luck that some code changes led to the bug appearing right away after the wrong access. It took a few seconds to fix, but I noticed that due to that bug some stages had broken data saved.

I added a simple hack: On loading levels I auto fixed these broken parts.



The Ugly

Windows 7 auto volume adjustment

Windows 7 does some automatic volume adjusting. If there's only very low volume music, it turns it up. But if the music suddenly gets louder it gets adjusted down. I do understand the reasoning behind this feature, but it doesn't lend itself to gaming too well. I wish there was a way for my game to tell Windows to not mess with my volume.

This feature is really really annoying. I'm not entirely sure how to work around it beside starting to play a loud sound effect at the start.



TL;DR

As final thought I'm pretty happy how the game turned out. It's not my best, but also not my worst. I wish I had more stages, as the first 5 are simple tutorials. At least the last two stages are a bit of a puzzle though.


Soooo, when does the next compo start?


A C64 Game - Step 99

Posted by , 10 May 2013 - - - - - - · 1,433 views

And of course lots of little bugs were found and fixed Posted Image

-Live number display was off on a new level.
-Beams would sometimes not be removed on the final boss
-Disable screen output when saving scores (IRQs go nuts if using kernal save routine)
-Cleaned up "extro" text


Have fun!



Previous Step Next Step

Attached Files




A C64 Game - Step 25

Posted by , 30 September 2011 - - - - - - · 1,194 views

Time to spruce up the title screen again.


This time we split the title screen display, upper part will be a bitmap logo, lower path text mode.


Attached Image

Some clarification: The graphic chip of the C64, the VIC II, has some neat features. It displays line per line whatever mode currently is set. This means, with skilled modification you can change the mode while a screen is being displayed.

The VIC II aids the programmer with a raster interrupt. You can provide a line number where a interrupt should occur. This avoids having to actively wait. Since we need to show two modes we need to toggle modes two times.

Once the raster is above the inner visible area we activate the bitmap mode. Once the raster hits the end of the bitmap we want to display switch to text mode again. There's a bit more to it if you want exact splits, but the way the image is set up allows us to approach that rather naively.


The bitmap data itself was created by a Lua script from an actual image. Note that a bitmap is compiled of several parts: The actual image data, the color ram part (for 1 of the three possible colors per cell) and the screen data (for the other two possible colors per cell).

Bitmap data cannot be put in memory randomly, there are only a handful of aligned locations where they need to reside. Nice trick: Since the layout of the code is completely fixed you can actually force the compiler to put data at a specific location. This way you avoid to having to copy the bitmap data somewhere else.

Here we store the bitmap data at $2000:

;place the data at a valid bitmap position, this avoids copying the data        
* = $2000        
TITLE_LOGO_BMP_DATA
        !byte 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
        !byte 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
        ...


One of the color parts can also be fixed in memory:

* = $2c00
TITLE_LOGO_SCREEN_CHAR
        !byte 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
        !byte 0,0,0,0,0,0,0,0,0,0,32,32,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
        ...

The final part is copied to its required target location on demand:

TITLE_LOGO_COLORRAM
        !byte 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
        ...




During settup up the title screen state we need to copy the color information to the color RAM. Since we only need 64 pixel height for the logo we only copy 320 bytes:

          ldx #0
.FillColor      
          lda TITLE_LOGO_COLORRAM,x
          sta SCREEN_COLOR,x
          inx
          bne .FillColor

.FillColor2
          lda TITLE_LOGO_COLORRAM + 256,x
          sta SCREEN_COLOR + 256,x
          inx
          cpx #( 320 - 256 )
          bne .FillColor2


Now to set up the initial raster interrupt. Note that before doing that we call jsr WaitFrame once so the raster line is at a specific location. You don't want to hit the interrupt on the wrong side.

;-----------------------------------
;init IRQ
;-----------------------------------
!zone InitTitleIRQ
InitTitleIRQ
            
          sei

          lda #$37 ; make sure that IO regs at $dxxx
          sta $1 ;are visible

          lda #$7f ;disable cia #1 generating timer irqs
          sta $dc0d ;which are used by the system to flash cursor, etc

          lda #$1 ;tell VIC we want him generate raster irqs
          sta $d01a

          lda #$10 ;nr of rasterline we want our irq occur at
          sta $d012

          lda #$1b ;MSB of d011 is the MSB of the requested rasterline
          sta $d011 ;as rastercounter goes from 0-312

          ;set irq vector to point to our routine
          lda #<IrqSetBitmapMode
          sta $314
          lda #>IrqSetBitmapMode
          sta $315

          ;acknowledge any pending cia timer interrupts
          ;this is just so we are 100% safe
          lda $dc0d 
          lda $dd0d 

          cli
          rts




Note the routine IrqSetBitmapMode (and IrqSetTextMode). These routines set their respective mode and setup the raster interrupt for the other part.

;-----------------------------------
;IRQ Title - set bitmap mode
;-----------------------------------
!zone IrqSetBitmapMode
IrqSetBitmapMode
          ;acknowledge VIC irq
          lda $d019
          sta $d019

          ;install top part
          lda #<IrqSetTextMode
          sta $314
          lda #>IrqSetTextMode
          sta $315

          ;nr of rasterline we want our irq occur at
          lda #$71 
          sta $d012

          ;bitmap modus an
          lda #$3b
          sta $D011 

          ;set VIC to bank 0
          lda $DD00
          and #$fc
          ora #$3
          sta $dd00

          ;bitmap to lower half, screen char pos at 3 * 1024 ( + 16384)
          lda #%10111000
          sta $D018

          JMP $ea31


;-----------------------------------
;IRQ Title - set text mode
;-----------------------------------
!zone IrqSetTextMode
IrqSetTextMode

          ;acknowledge VIC irq
          lda $d019
          sta $d019

          ;install scroller irq
          lda #<IrqSetBitmapMode
          sta $314
          lda #>IrqSetBitmapMode
          sta $315

          ;nr of rasterline we want our irq occur at
          lda #$10
          sta $d012

          ;disable bitmap mode
          lda #$1b
          sta $D011 

          ;set VIC to bank 3
          lda $DD00
          and #$fc
          sta $dd00

          ;bitmap to lower half, screen char pos at 3 * 1024 ( + 16384)
          lda #%00111100
          sta $D018

          jmp $ea31



Once the title screen state is left the interrupt needs to be disabled:

;-----------------------------------
;release IRQ
;-----------------------------------
!zone ReleaseTitleIRQ
ReleaseTitleIRQ
            
          sei

          lda #$37 ; make sure that IO regs at $dxxx
          sta $1 ;are visible

          lda #$ff ;enable cia #1 generating timer irqs
          sta $dc0d ;which are used by the system to flash cursor, etc

          ;no more raster irqs
          lda #$00 
          sta $d01a

          lda #$31
          sta $314
          lda #$EA
          sta $315

          ;acknowledge any pending cia timer interrupts
          ;this is just so we are 100% safe
          lda $dc0d 
          lda $dd0d 

          cli
          rts


Previous Step Next Step

Attached Files




A C64 game - Step 24

Posted by , 24 September 2011 - - - - - - · 1,072 views

What use are highscores if they aren't saved?



With this step highscores are saved and loaded from your previously used medium (tape or disk). On startup the currently used medium is stored, save and load is done via bog standard kernal routines.


Attached Image


The additions for this are rather miniscule. Both save and load routines work on a continious piece of memory. Fortunately both highscore names and scores are right next to each other.



For saving note that the auto-delete function in the common floppy drive is borked. Therefore we manually delete the file first and save it afterwards.
The "S0:" in front of the file name is the special code for Scratching the file.

HIGHSCORE_DELETE_FILENAME          
          !text "S0:HIGHSCORE"
HIGHSCORE_DELETE_FILENAME_END


!zone SaveScores
SaveScores

          ;delete old save file first
          lda #HIGHSCORE_DELETE_FILENAME_END - HIGHSCORE_DELETE_FILENAME
          ldx #<HIGHSCORE_DELETE_FILENAME 
          ldy #>HIGHSCORE_DELETE_FILENAME

          jsr KERNAL_SETNAM
          
          lda #$0F      ; file number 15
          ldx DRIVE_NUMBER
          ldy #$0F      ; secondary address 15
          jsr KERNAL_SETLFS

          jsr $FFC0     ; call OPEN
          ; if carry set, the file could not be opened
          bcs .ErrorDelete

          ldx #$0F      ; filenumber 15
          jsr $FFC9     ; call CHKOUT (file 15 now used as output)

.close
          lda #$0F      ; filenumber 15
          jsr $FFC3     ; call CLOSE

          ldx #$00      ; filenumber 0
          jsr $FFC9     ; call CHKOUT (reset output device)

          jmp .SaveNow

.ErrorDelete
          ;Akkumulator contains BASIC error code

          ;most likely errors:
          ;A = $5 (DEVICE NOT PRESENT)

          ;... error handling for open errors ...
          lda #65
          sta $cc00
          jmp .close    ; even if OPEN failed, the file has to be closed

.SaveNow
          lda #9
          ldx #<HIGHSCORE_FILENAME
          ldy #>HIGHSCORE_FILENAME
          jsr KERNAL_SETNAM
          
          lda #$00
          ldx DRIVE_NUMBER
          ldy #$00
          jsr KERNAL_SETLFS

          lda #<HIGHSCORE_SCORE
          sta $C1
          lda #>HIGHSCORE_SCORE
          sta $C2

          ldx #<HIGHSCORE_DATA_END
          ldy #>HIGHSCORE_DATA_END
          lda #$C1      ; start address located in $C1/$C2
          jsr $FFD8     ; call SAVE
          
          ;if carry set, a save error has happened
          ;bcs .SaveError    
          rts


Loading the score back in means mostly inserting the corresponding load calls:
;--------------------------------------------------
;load high scores
;returns 1 if ok, 0 otherwise
;--------------------------------------------------
!zone LoadScores
LoadScores
          ;disable kernal messages (do not want to see load error etc.)
          lda #$00
          jsr KERNAL_SETMSG
          
          ;set logical file parameters
          lda #15 
          ldx DRIVE_NUMBER
          ldy #0
          jsr KERNAL_SETLFS
          
          ;set filename
          lda #9
          ldx #<HIGHSCORE_FILENAME
          ldy #>HIGHSCORE_FILENAME
          jsr KERNAL_SETNAM
          
          ;load to address
          lda #$00                             ; 0 = load
          ldx #<HIGHSCORE_SCORE
          ldy #>HIGHSCORE_SCORE
          jsr KERNAL_LOAD
          bcs .LoadError                        ;flag whether ok or not is set into the Carry flag
          lda #1
          rts

.LoadError
          lda #0
          rts


Previous StepNext Step

Attached Files




A C64 game - Step 23

Posted by , in Uncategorized, C64 17 September 2011 - - - - - - · 1,012 views

Highscore display is all nice and dandy, but we want to see our score and name in there! So this step adds name entry. For this we rely on the Kernal (it's with 'a' on the C64) functions to read the pressed keys from the keyboard.

Adding to the score moving we find the spot for the name, shift all lower score names down and let the player enter his name.



Attached Image


The Kernal routine we're using is called GETIN and is placed in the ROM at $FFE4.



The following huge code snippet is placed in the old CheckForHighscore routine. For explanation reasons it's separated.



The first block moves the name entries of lower scores downwards. Remember, the name entries are stored continously in the location at HIGHSCORE_NAME.


          ;move names down
          ;shift older entries down, add new entry
          lda #( HIGHSCORE_ENTRY_COUNT - 1 )
          sta PARAM2
          
          ;y carries the offset in the score text, position at start of second last entry
          ldy #( ( HIGHSCORE_NAME_SIZE + 1 ) * ( HIGHSCORE_ENTRY_COUNT - 2 ) )
          
.CopyName
          lda PARAM2
          cmp PARAM1
          beq .SetNewName
          
          ;copy name
          ldx #0
          
.CopyNextNameChar
          lda HIGHSCORE_NAME,y
          sta HIGHSCORE_NAME + ( HIGHSCORE_NAME_SIZE + 1 ),y
          
          iny
          inx
          cpx #HIGHSCORE_NAME_SIZE
          bne .CopyNextNameChar
          
          tya
          sec
          sbc #( HIGHSCORE_NAME_SIZE + HIGHSCORE_NAME_SIZE + 1 )
          tay
          dec PARAM2
          jmp .CopyName



Here's the interesting part. First the proper offset of the new name inside the big text is calculated and the old name cleared out.

.SetNewName
          
          ;calc y for new name offset
          ldy PARAM1
          
          lda #0
.AddNameOffset          
          cpy #0
          beq .NameOffsetFound
          clc
          adc #( HIGHSCORE_NAME_SIZE + 1 )
          dey
          jmp .AddNameOffset
          
.NameOffsetFound          
          tay  

          ;clear old name
          ldx #0
          sty PARAM3
          lda #32
          
.ClearNextChar
          sta HIGHSCORE_NAME,y
          iny
          inx
          cpx #HIGHSCORE_NAME_SIZE
          bne .ClearNextChar


Here's the meat, the actual name entry. Of course with Backspace support and Enter to finalize the entry. Out of sheer lazyness the newly entered char is only inserted in the text and the text then fully displayed.

          ldy PARAM3
          
          ;enter name
          ldx #0
          stx PARAM3
          
          jmp .ShowChar
          
.GetNextChar
          sty PARAM4
          
          ;use ROM routines, read char
          jsr KERNAL_GETIN
          beq .GetNextChar

          ;return pressed?
          cmp #13
          beq .EnterPressed

          ;DEL pressed?
          cmp #20
          bne .NotDel
          
          ;DEL pressed
          ldy PARAM4
          ldx PARAM3
          beq .GetNextChar
          dec PARAM3
          dey
          dex
          lda #32
          sta HIGHSCORE_NAME,y
          jmp .ShowChar

.NotDel   
          ldy PARAM4
          ;pressed key >= 32 or <= 96?
          cmp #32
          bcc .GetNextChar
          cmp #96
          bcs .GetNextChar

          ;max length reached already?
          ldx PARAM3
          cpx #HIGHSCORE_NAME_SIZE
          bcs .GetNextChar
          
          ;save text
          sta HIGHSCORE_NAME,y
          iny
          inx
          
.ShowChar           
          stx PARAM3
          sty PARAM4

          ;display high scores
          ;x,y pos of name
          lda #6
          sta PARAM1
          lda #10
          sta PARAM2

          lda #<HIGHSCORE_NAME
          sta ZEROPAGE_POINTER_1
          lda #>HIGHSCORE_NAME
          sta ZEROPAGE_POINTER_1 + 1

          jsr DisplayText

          ldx PARAM3
          ldy PARAM4

          jmp .GetNextChar

.EnterPressed      
          ;fill entry with blanks
          lda #32
          ldx PARAM3
          ldy PARAM4
          
.FillNextChar          
          cpx #HIGHSCORE_NAME_SIZE
          beq .FilledUp
          sta HIGHSCORE_NAME,y
          iny
          inx
          jmp .FillNextChar          

.FilledUp
          jmp TitleScreen


Previous Step Next Step

Attached Files




A C64 game - Step 22

Posted by , 09 September 2011 - - - - - - · 1,182 views

The title screen looks somewhat empty, doesn't it? Well, let's fill it with highscores! Lazy as I am I'm storing the highscore scores and names in two separate texts which are simply displayed with the common display routine.

After Game Over we check for a new spot. If we manage to enter the list all lower entries are moved down. For now that happens automatically and only affects the scores. Name entry follows up next.

Attached Image

First the newly added text fields, note that '-' is used as line break and '*' as end of text marker.
Another note: If you default fill high scores always have an easily beatable top score. People like to be better than someone else.


HIGHSCORE_SCORE
          !text "00050000-"
          !text "00040000-"
          !text "00030000-"
          !text "00020000-"
          !text "00010000-"
          !text "00001000-"
          !text "00000300-"
          !text "00000100*"
          
HIGHSCORE_NAME
          !text "SUPERNATURAL-"
          !text "SUPERNATURAL-"
          !text "SUPERNATURAL-"
          !text "SUPERNATURAL-"
          !text "SUPERNATURAL-"
          !text "SUPERNATURAL-"
          !text "SUPERNATURAL-"
          !text "SUPERNATURAL*"


In the title screen layout code we add the display routines:
          ;display high scores
          ;x,y pos of name
          lda #6
          sta PARAM1
          lda #10
          sta PARAM2

          lda #<HIGHSCORE_NAME
          sta ZEROPAGE_POINTER_1
          lda #>HIGHSCORE_NAME
          sta ZEROPAGE_POINTER_1 + 1

          jsr DisplayText

          ;x,y pos of score          
          lda #25
          sta PARAM1
          lda #10
          sta PARAM2

          lda #<HIGHSCORE_SCORE
          sta ZEROPAGE_POINTER_1
          lda #>HIGHSCORE_SCORE
          sta ZEROPAGE_POINTER_1 + 1

          jsr DisplayText


And last but not least, the actual checking for a highscore. You'll see that the score is not actually kept in a variable. Instead the screen chars at the display are used to compare against the text.

;------------------------------------------------------------
;check if the player got a new highscore entry
;------------------------------------------------------------
!zone CheckForHighscore
CheckForHighscore
          lda #0
          sta PARAM1
          ldy #0
          
.CheckScoreEntry          
          ldx #0
          sty PARAM2
          
.CheckNextDigit          
          lda SCREEN_CHAR + ( 23 * 40 + 8 ),x
          cmp HIGHSCORE_SCORE,y
          bcc .NotHigher
          bne .IsHigher
          
          ;need to check next digit
          iny
          inx
          cpx #HIGHSCORE_SCORE_SIZE
          beq .IsHigher
          jmp .CheckNextDigit
          
.NotHigher
          inc PARAM1
          lda PARAM1
          cmp #HIGHSCORE_ENTRY_COUNT
          beq .NoNewHighscore
          
          ;y points somewhere inside the score, recalc next line pos
          lda PARAM2
          clc
          adc #( HIGHSCORE_SCORE_SIZE + 1 )
          tay
          jmp .CheckScoreEntry

.NoNewHighscore
          jmp TitleScreen
          
.IsHigher
          ;shift older entries down, add new entry
          lda #( HIGHSCORE_ENTRY_COUNT - 1 )
          sta PARAM2
          
          ;y carries the offset in the score text, position at start of second last entry
          ldy #( ( HIGHSCORE_SCORE_SIZE + 1 ) * ( HIGHSCORE_ENTRY_COUNT - 2 ) )
          
.CopyScore          
          lda PARAM2
          cmp PARAM1
          beq .SetNewScore
          
          ;copy score
          ldx #0
          
.CopyNextScoreDigit          
          lda HIGHSCORE_SCORE,y
          sta HIGHSCORE_SCORE + ( HIGHSCORE_SCORE_SIZE + 1 ),y
          
          iny
          inx
          cpx #HIGHSCORE_SCORE_SIZE
          bne .CopyNextScoreDigit
          
          tya
          sec
          sbc #( HIGHSCORE_SCORE_SIZE + HIGHSCORE_SCORE_SIZE + 1 )
          tay
          dec PARAM2
          jmp .CopyScore
          
.SetNewScore
          ;y points at score above the new entry
          tya
          clc
          adc #( HIGHSCORE_SCORE_SIZE + 1 )
          tay
          
          ldx #0
          
.SetNextScoreDigit          
          lda SCREEN_CHAR + ( 23 * 40 + 8 ),x
          sta HIGHSCORE_SCORE,y
          
          iny
          inx
          cpx #HIGHSCORE_SCORE_SIZE
          bne .SetNextScoreDigit
          
          jmp TitleScreen

Previous Step Next Step

Attached Files




A C64 game - Step 21

Posted by , 02 September 2011 - - - - - - · 1,207 views

Now some more tidbits. A game is not complete without a neat title screen:


For now this is nothing more than a almost empty screen and a separate control loop. The loop simply waits for a button press and then jumps to the game main loop. Once the player lost all lives he is returned to the title loop.

Note that for both cases we also check if the button has been released before allowing to go forward. Nothing's more annyoing than accidental commencing.

Attached Image



Rather unspectacular, the title screen code. First the screen is displayed and then the check button loop entered. Once the button has been pressed the game variables are reset and the code runs into the main game loop.


;------------------------------------------------------------
;the title screen game loop
;------------------------------------------------------------
!zone TitleScreen
TitleScreen
          ldx #0
          stx BUTTON_PRESSED
          stx BUTTON_RELEASED
          sta VIC_SPRITE_ENABLE
          
          ;clear screen
          lda #32
          ldy #0
          jsr ClearScreen
          
          ;display title logo
          lda #<TEXT_TITLE
          sta ZEROPAGE_POINTER_1
          lda #>TEXT_TITLE
          sta ZEROPAGE_POINTER_1 + 1
          lda #0
          sta PARAM1
          lda #1
          sta PARAM2
          jsr DisplayText
          
          ;display start text
          lda #<TEXT_FIRE_TO_START
          sta ZEROPAGE_POINTER_1
          lda #>TEXT_FIRE_TO_START
          sta ZEROPAGE_POINTER_1 + 1
          lda #11
          sta PARAM1
          lda #23
          sta PARAM2
          jsr DisplayText
          
          
.TitleLoop
          jsr WaitFrame
          
          lda #$10
          bit $dc00
          bne .ButtonNotPressed
          
          ;button pushed
          lda BUTTON_RELEASED
          bne .Restart
          jmp .TitleLoop
          

.ButtonNotPressed
          lda #1
          sta BUTTON_RELEASED
          jmp .TitleLoop
          
.Restart
          ;game start values
          lda #3
          sta PLAYER_LIVES
          
          ;setup level
          jsr StartLevel
          
          lda #0
          sta LEVEL_NR
          jsr BuildScreen
          
          jsr CopyLevelToBackBuffer
          
;------------------------------------------------------------
;the main game loop
;------------------------------------------------------------




Of course the counter part needs to be added to the DeadControl routine. If the player lost his last life, return to the title screen:

!zone DeadControl
DeadControl
          lda SPRITE_ACTIVE
          beq .PlayerIsDead
          rts
          
.PlayerIsDead
          lda #$10
          bit $dc00
          bne .ButtonNotPressed
          
          ;button pushed
          lda BUTTON_RELEASED
          bne .Restart
          rts
          

.ButtonNotPressed
          lda #1
          sta BUTTON_RELEASED
          rts
          
.Restart
          ;if last live return to title
          lda PLAYER_LIVES
          bne .RestartLevel
          jmp TitleScreen
          
.RestartLevel          


Previous Step Next Step

Attached Files




A C64 game - Step 20

Posted by , 26 August 2011 - - - - - - · 1,094 views

More refinement, this time with more impact on the gameplay. For one, the shotgun needs to be reloaded (stand still) and the collected items now also have an effect. Namely a new bullet slot and invincibility.

Attached Image


Adding the reload part is done with the following code piece. If the joystick is not moved and the player isn't waiting for the recoil to end the variable PLAYER_STAND_STILL_TIME is increased. Once it hits 40 frames one shell is added.


          ;check if player moved
          lda $dc00
          and #$1f
          cmp #$1f
          bne .PlayerMoved
          
          ;do not reload while recoil
          lda PLAYER_SHOT_PAUSE
          bne .PlayerMoved
          
          inc PLAYER_STAND_STILL_TIME
          lda PLAYER_STAND_STILL_TIME
          cmp #40
          bne .HandleFire
          
          ;reload
          lda #1
          sta PLAYER_STAND_STILL_TIME
          
          ;already fully loaded?
          lda PLAYER_SHELLS
          cmp PLAYER_SHELLS_MAX
          beq .HandleFire
          
          inc PLAYER_SHELLS
          
          ;display loaded shells
          ldy PLAYER_SHELLS
          lda #2
          sta SCREEN_COLOR + 23 * 40 + 18,y
          lda #7
          sta SCREEN_COLOR + 24 * 40 + 18,y


The other addition enhances the PickItem routine. Simply check the picked item type, and either add a new bullet slot or increase the player live counter.


!zone PickItem
PickItem
          lda ITEM_ACTIVE,y
          cmp #ITEM_BULLET
          beq .EffectBullet
          cmp #ITEM_HEALTH
          beq .EffectHealth
          
.RemoveItem          
          lda #ITEM_NONE
          sta ITEM_ACTIVE,y
          
          lda #3
          jsr IncreaseScore

          jsr RemoveItemImage
          rts

.EffectBullet
          lda PLAYER_SHELLS_MAX
          cmp #5
          beq .RemoveItem
          
          ldx PLAYER_SHELLS_MAX
          
          lda #224
          sta SCREEN_CHAR + 23 * 40 + 19,x
          lda #225
          sta SCREEN_CHAR + 24 * 40 + 19,x
          lda #6
          sta SCREEN_COLOR + 23 * 40 + 19,x
          sta SCREEN_COLOR + 24 * 40 + 19,x
          
          inc PLAYER_SHELLS_MAX
          jmp .RemoveItem
          
.EffectHealth
          lda PLAYER_LIVES
          cmp #99
          beq .RemoveItem
          
          inc PLAYER_LIVES
          sty PARAM1
          jsr DisplayLiveNumber
          ldy PARAM1
          jmp .RemoveItem



Previous Step Next Step

Attached Files




A C64 game - Step 17

Posted by , 05 August 2011 - - - - - - · 1,745 views

Another rather small step, but visually pleasing. We're enhancing the player sprite with animation and better jump abilities.

All the hard work is added to PlayerControl. On every movement we update the sprite while checking the player states like jumping, recoil, falling, etc. Suddenly things look more interesting ;)

It's basically updating and checking counters during different control parts. SPRITE_ANIM_DELAY is used for controlling animation speed while SPRITE_ANIM_POS is used for the animation frame.

Attached Image

Here are the new parts for walking left:
          ;animate player
          lda SPRITE_FALLING
          bne .NoAnimLNeeded
          lda PLAYER_JUMP_POS
          bne .NoAnimLNeeded
          
          inc SPRITE_ANIM_DELAY
          lda SPRITE_ANIM_DELAY
          cmp #8
          bne .NoAnimLNeeded
          
          lda #0
          sta SPRITE_ANIM_DELAY
          
          inc SPRITE_ANIM_POS
          lda SPRITE_ANIM_POS
          and #$3
          sta SPRITE_ANIM_POS

.NoAnimLNeeded 

The same for right movement:
          ;animate player
          lda SPRITE_FALLING
          bne .NoAnimRNeeded
          lda PLAYER_JUMP_POS
          bne .NoAnimRNeeded
          
          inc SPRITE_ANIM_DELAY
          lda SPRITE_ANIM_DELAY
          cmp #8
          bne .NoAnimRNeeded
          
          lda #0
          sta SPRITE_ANIM_DELAY
          
          inc SPRITE_ANIM_POS
          lda SPRITE_ANIM_POS
          and #$3
          sta SPRITE_ANIM_POS

.NoAnimRNeeded          

And all the missing animation for jumping, falling, recoil and combined states. Note that the sprites are arranged in right/left pairs, so that adding SPRITE_DIRECTION (0 = facing right, 1 = facing left) to the sprite frame results in the proper sprite.
          ;update player animation
          lda SPRITE_FALLING
          bne .AnimFalling

          lda PLAYER_JUMP_POS
          bne .AnimJumping
          
          ;is player shooting?
          lda PLAYER_SHOT_PAUSE
          beq .AnimNoRecoil

          ;recoil anim          
          lda SPRITE_ANIM_POS
          asl
          clc
          adc SPRITE_DIRECTION
          adc #SPRITE_PLAYER_WALK_R_1
          adc #8
          sta SPRITE_POINTER_BASE
          
          rts
          
.AnimNoRecoil          
          
          lda SPRITE_ANIM_POS
          asl
          clc
          adc SPRITE_DIRECTION
          adc #SPRITE_PLAYER_WALK_R_1
          sta SPRITE_POINTER_BASE
          rts
          
.AnimFalling
          lda PLAYER_SHOT_PAUSE
          bne .AnimFallingNoRecoil

          lda #SPRITE_PLAYER_FALL_R
          clc
          adc SPRITE_DIRECTION
          sta SPRITE_POINTER_BASE
          rts
          
.AnimFallingNoRecoil          
          lda #SPRITE_PLAYER_FALL_RECOIL_R
          clc
          adc SPRITE_DIRECTION
          sta SPRITE_POINTER_BASE
          rts
          
.AnimJumping          
          lda PLAYER_SHOT_PAUSE
          bne .AnimJumpingNoRecoil

          lda #SPRITE_PLAYER_JUMP_R
          clc
          adc SPRITE_DIRECTION
          sta SPRITE_POINTER_BASE
          rts
          
.AnimJumpingNoRecoil          
          lda #SPRITE_PLAYER_JUMP_RECOIL_R
          clc
          adc SPRITE_DIRECTION
          sta SPRITE_POINTER_BASE
          rts


Previous Step Next Step

Attached Files








August 2016 »

S M T W T F S
 123456
78910111213
14151617181920
2122 23 24252627
28293031   


PARTNERS