How to display Text and Sprites

In this section you are going to learn how to reserve your own VIC Bank memory, activate interrupts, clear the C64 screen, write a display text routine, initialize game sprites, calculate sprite delta values, use the CBM Prg Studio Sprite Editor, use the CBM Prg Studio Character Editor, learn about Siggy’s Trump Jump game, create sprite animation, and use a joystick for your game.

F

or the second video series, we introduced a new guest that had contacted me before the stream through Facebook and said he wanted to join. His name was Siggy Hewett and he is from Australia.  For a while he didn’t have a microphone hooked up so we continued talking among ourselves while waiting for him to be live.

Our new friend was generous enough to give us a code sample to work with. This example filled the C64 screen background with a brick tile. There were two sprites occupying the screen. The first one was a white robot (man?) character that used animation to imitate a wave. The second sprite dominated the bottom part of the screen running back and forth with animated frames. Each were set in standard colors.

Sprites on C64 screenSiggy also incorporated a text subroutine that he developed to print text quickly to the screen. So the top left of the screen displayed the words “GREETINGS PROGRAMS!”. At the bottom of the screen was so statistics for the players movement at the X and Y location. The first part showed “XPOS = $05”. Next to that was “YPOS= $B3”. So each of these were detecting the horizontal and vertical movement whenever the robot was being moved with the gamepad (as it was programmed to read the up/down,left, and right movement.  The numbering was in hexadecimal. He also programmed a raster line at the bottom of the screen to keep track of how much memory had already been consumed by the written lines of the program (size in total).

Siggy had left the C64 scene for a good number of years and eventually returned to his roots writing assembly language routines again. Stated he played with both the Commodore 64 and the Amiga for many years, programming both machines in assembly language.

In this session both Bo Goren and Darren had returned once again.  Bo mentioned that he started with Basic, but had always wanted to learn assembly language. Darren was new to assembly language as well and looked forward to what would be learned in this stream.

Machine Language Project for C64At this time we were still trying to decide on a project to work on. I had previously ruled at a game, but hinted that a demo could evolve into one. However, the code here was written to accept expanding for a possible future game. As we started unraveling Siggy’s work, we discovered how he had laid the foundation for some awesome core functionality that would be useful later.

To fill in some allotted time while waiting for Siggy to get a microphone working, I started reviewing his code and began my own analysis on basic core routines that were present. So I identified the labels he used for sprites and the initialization routines he use to activate the sprites. The next part of the program setup the VIC Bank to reserve memory for the sprites and the graphics.  The VIC example is listed below for your convenience.

  • = $0810
  • PRG_START
  • lda #0 ; Turn off sprites
  • sta VIC_SPRITE_ENABLE
  • lda VIC_BANK ; Fetch the status of CIA 2 ($DD00)
  • and #%11111100 ; mask for bits 2-8
  • ora #%00000010 ; the first 2 bits are your desired VIC bank value
  • sta VIC_BANK

The next section of code identified what memory the screen and character set memory utilized. The memory location responsible for this is $D018 (53272). For beginners here, the character set represents any letters you can type on the Commodore 64’s keyboard, including the shift, and control characters. The code again is listed here.

  • lda #%00000010 ; bits 1-3 (001) = character memory 2: $0800 – $0FFF
  • sta VIC_MEMORY_CONTROL

CBM Prg Studio Font SizeA bug was identified in this session with CBM Prg Studio. Apparently if you try to zoom in on the text and click on another file you will get an infamous red X that covers the whole screen and won’t be able to reload until you shut down the application. Darren stated that to get around this he just sets his fonts bigger. This is done by clicking on the Tools menu and Options (or using the icon at the upper right). Then click on the Fonts tab. In the section for Memory/Preview/Disassembler Font, the size can be adjusted by typing in the box and clicking the Change button.

The code was commented very well and made it easier to follow the logic in various parts.  So for this bank the sprite pointers started at $4000 + $3f8. The sprite data shapes began at $500.

Enable the Interrupts

Below this was the area with the label System_Setup. As his code explains, this is where he copied his character set and sprite data under the IO ROM ($d000-$dfff) by turning off the C64 interrupts.  This is how the RAM underneath is accessed.  Siggy added that this routine “it stops all interrupts so you can manipulate the system. Then you can reset them with the cli (clear interrupts) or it will crash.”

  • System_Setup
  • sei
  • cli

Bo wanted to know where he could find further documentation on the CBM Prg Studio command called IncAsm. This was listed earlier in Siggy’s program.  The line was being used to read in an asm (assembly) file as seen here.

  • IncAsm “VICII.asm”

CBM Prg Studio IncAsmI pulled up the Help document for CBM Prg Studio and began searching for IncAsm to see how it is used. Siggy pointed out that it could be found by clicking on the Contents tab, opening up the document called Using the Assembler, and then clicking on Directives. So the help file states it is used to append another asm file to the project as I speculated. Reading the chat message, Siggy stated “That is how you can jump to different labels really fast in multiple files.”

Setup the C64 screen

So in the next section of code (which he labeled “Screen_Setup), he set contant colors for the border and the background and finally cleared the screen with a built in I/O routine in ROM.

  • Screen_Setup
  • lda #COLOR_BLACK
  • sta VIC_BORDER_COLOR
  • sta VIC_BACKGROUND_COLOR
  • lda #$40
  • ldy #COLOR_RED
  • jsr ClearScreen

While experimenting with the Screen_Setup routine, we wanted to see if we could change the background color of the bricks that surrounded the playfield. So it first loads the value of $40 (64) and the y register with the red color. To see what these parameters are used for we will now examine the ClearScreen subroutine.

  • ClearScreen
  • ldx #$00 ; Clear X register
  • ClearLoop
  • sta SCREEN_MEM, x ; Write the character (in A) at screen location
  • sta SCREEN_MEM + 250,x ; at SCREEN_MEM + 250 + x
  • sta SCREEN_MEM + 500,x ; at SCREEN_MEM + 500 + x
  • sta SCREEN_MEM + 750,x  ; at SCREEN_MEM + 750 + x
  • inx
  • cpx #250 ; is X > 250?
  • bne ClearLoop  ; if not – continue clearing
  • tya ; transfer Y (color) to A – example COLOR_RED
  • ldx #$00 ; reset x to 0
  • ColorLoop
  • sta COLOR_MEM,x ; Do the same for color ram
  • sta COLOR_MEM + 250,x
  • sta COLOR_MEM + 500,x
  • sta COLOR_MEM + 750,x
  • inx
  • cpx #250
  • bne ColorLoop
  • rts

 CBM Prg Studio Change Background ColorUpon observation we discovered that the $40 represented the background character for the brick shape. Then all we had to do was simply modify the parameter for COLOR_RED and use a different constant to get a new brick color. Searching the code, we found the constant color values in the asm file called VICII.asm, which ironically was the file being accessed with IncAsm earlier. Likewise we could also swap out the $40 with a different value to point to new character set data. I decided to try changing the brick image and the color to see the new result.

Understanding the DisplayTest subroutine

Next was code for the DisplayTest subroutine, which is used to write messages to the screen. This is accomplished by geting a pointer to TEST_TEXT, which loads the low byte into the zero page pointer and also the high byte for TEST_TEXT into the zero page pointer high byte. Essentially this completes the one word address. The PARAM1 and PARAM2 variables hold the X and Y screen character coordinates where text is written to. Finally, PARAM3 is a color place holder. Darren later said that the DisplayText here is showing the message “GREETINGS PROGRAMS!” on the screen.

  • lda #<TEST_TEXT
  • sta ZEROPAGE_POINTER_1
  • lda #>TEST_TEXT
  • sta ZEROPAGE_POINTER_1 + 1
  • lda #1
  • sta PARAM1
  • sta PARAM2
  • lda #COLOR_WHITE
  • sta PARAM3
  • jsr DisplayText

Looking down at line 537 the message is discovered. It is essentially referencing the variable TEST_TEXT and reading the high and low bytes of screen memory to write it to the screen. The text example is here.

  • TEST_TEXT byte ‘greetings programs!@’

Now to understand where the ‘PARAM’ values are being used at we switched over to look at the file called core_routines.asm. His comments showed the ‘@’ represents the end of a text character and ‘/’ is for a line break.

The variable ZEROPAGE_POINTER_1 is a pointer to text data. PARAM1 is for the X value. PARAM2 is for the Y value. PARAM3 is the color. Siggy also alerted here that ‘all text should be in lower case’.

  • DisplayText
  • ldx PARAM2
  • lda SCREEN_LINE_OFFSET_TABLE_LO,x
  • sta ZEROPAGE_POINTER_2
  • sta ZEROPAGE_POINTER_3
  • lda SCREEN_LINE_OFFSET_TABLE_HI,x
  • sta ZEROPAGE_POINTER_2 + 1
  • clc
  • adc #>COLOR_DIFF
  • sta ZEROPAGE_POINTER_3 + 1
  • lda ZEROPAGE_POINTER_2
  • clc
  • adc PARAM1
  • sta ZEROPAGE_POINTER_2
  • lda ZEROPAGE_POINTER_2 + 1
  • adc #0
  • sta ZEROPAGE_POINTER_2 + 1
  • lda ZEROPAGE_POINTER_3
  • clc
  • adc PARAM1
  • sta ZEROPAGE_POINTER_3
  • lda ZEROPAGE_POINTER_3 + 1
  • adc #0
  • sta ZEROPAGE_POINTER_3 + 1
  • ldy #0
  • @inlineLoop
  • lda (ZEROPAGE_POINTER_1),y ; test for end of line
  • cmp #$00
  • beq @endMarkerReached 
  • cmp #$2F
  • beq @lineBreak ;test for line break
  • sta (ZEROPAGE_POINTER_2),y
  • lda PARAM3
  • sta (ZEROPAGE_POINTER_3),y
  • iny
  • jmp @inLineLoop
  • @lineBreak
  • iny
  • tya
  • clc
  • adc ZEROPAGE_POINTER_1
  • sta ZEROPAGE_POINTER_1
  • lda #0
  • adc ZEROPAGE_POINTER_1 + 1
  • sta ZEROPAGE_POINTER_1 + 1
  • inc PARAM2
  • jmp DisplayText

Some code from the SCREEN_LINE_OFFSET_TABLE_LO is listed below to make sense of the values that are being passed to the zero page pointers in DisplayText

  • SCREEN_LINE_OFFSET_TABLE_LO
  • byte >SCREEN_MEM + 0
  • byte >SCREEN_MEM + 40
  • byte >SCREEN_MEM + 80
  • byte >SCREEN_MEM + 120
  • byte >SCREEN_MEM + 160
  • byte >SCREEN_MEM + 200
  • byte >SCREEN_MEM + 240
  • byte >SCREEN_MEM + 280
  • byte >SCREEN_MEM + 320
  • (and so on)

CBM Prg Studio Line BreakSo these byte states represent screen memory areas to store the values to. This is done by setting a table to point to the correct screen Y address for that individual line. This can be used to write complete paragraphs anywhere on the C64 screen. We changed some of the code in the message and added a slash ‘/’ to see the line break in action as seen here.

Initialize the sprites

Sprite RobotAfter this  was the sprite initialization below. The code sets the multicolor ($d01c – 53276) for the sprites,  sprite X extended (most significant bit) at $d010 (53264), sprite positions ($d000- $d00f – 53248 – 53263) on the screen, sprite colors found in registers $d027 – $d02e (53287 – 53294), and sprite screen shape data that exists in memory locations $7f8 – $7ff (2040-2047). Finally some pointers are saved for the sprite images (used for the animation).

  • lda #0 
  • sta VIC_SPRITE_MULTICOLOR ; set all sprites to single color for now
  • sta VIC_SPRITE_X_EXTEND ; clear extended X bits – don’t need them yet
  • lda #100
  • sta VIC_SPRITE_X_POS ; display at 100,100 sprite coords for now
  • sta VIC_SPRITE_Y_POS  
  • sta SPRITE_POS_X
  • sta SPRITE_POS_Y
  •  ; Store X/Y coords of Sprite 1 at 100,200
  • sta VIC_SPRITE_X_POS + 2 ; Add 2 to arrive at $D002 – Sprite 1 X
  • sta SPRITE_POS_X + 1 ; Store it in our X pos variable
  • lda #200
  • sta VIC_SPRITE_Y_POS + 2 ; Store 200 in the Y variable and the VIC
  • sta SPRITE_POS_Y + 1
  • lda #COLOR_WHITE ; Set sprite 0 to white
  • sta VIC_SPRITE_COLOR
  • lda #COLOR_CYAN ; Set sprite 1 to cyan
  • sta VIC_SPRITE_COLOR + 1 ; Sprite color registers run concurrently 0-8
  • lda #SPRITE_BASE ; Take our first sprite image (Robot dude)
  • sta SPRITE_0_PTR ; Store it in the pointer for sprite 0
  • lda #SPRITE_BASE + 4 ; Take sprite image 5 (Running dude)
  • sta SPRITE_1_PTR

Sprite RunnerThe code below has been divided up for easier clarity. A common goal when learning assembly language is to break everything down into ‘chunks’. It is easier to digest that way. So below we are setting a flag to track which direction the running sprite starts (either running left to right or right to left). The next flag sets the starting animation image for the runner. Then we definitely need to activate the sprites (VIC_SPRITE_ENABLE – ($d015 – 53296) so we can see them on the screen. The binary digits ‘00000011’ activate sprites 1, and 0. If you count the values from right to left you will start at 0 and end at 7). You may also find it easier to see it as 76543210 = 00000011. 0 Finally, we update the joystick by jumping to the subroutine called DisplayInfoNow.

  • lda #$01
  • sta SPRITE_DIRECTION ; start running dude moving right
  • lda #$04
  • sta ANIM_FRAME ; starting sprite image for runner (4)
  • lda #%00000011
  • sta VIC_SPRITE_ENABLE
  • jsr DisplayInfoNow

Within a little over an hour into our session, Siggy made his entrance with an enchanting laugh. He wasn’t aware that the microphone suddenly kicked in, but it was great to finally have him help us untangle the rest of the code. He mentioned that he derived a lot of his code routines from Endurion (to give credit where it is due).

C64 GameAfter Siggy discussed some of the techniques he uses with his code such as passing lda #0 into a lot of values for reset purposes to save cycles, he wanted to demonstrate how his game utilizes delta values. They change after the moving sprite passes the 256 pixel location. It was pretty impressive to see this. He calls it ‘Trump Jump’. The goal is to accelerate a bulldozer up to a desirable speed in order to accurately factor out the jump it requires to clear a takeoff ramp with Trump ranting underneath. You can watch the mini demonstrate in the video above by going to marker 1:19:29.

It utilizes a parallax a scroller. The top and bottom parts are moving at different speeds to simulate a real life situation of a rotating background.

Calculating sprite delta values

He mentioned that the delta values cycle as the bulldozer moves across the screen. They travel through 8 different positions. As the delta values update they were keeping an eye on the sprite bulldozer and how it aligns to character borders. Siggy stated “it is a nice way of checking the background against things”.  Therefore using delta calculation checks is more accurate than trying to use the sprite to sprite collision registers built into the hardware.

The delta values can be used to pull values off the screen to determine if a sprite collided with an object. For example a sprite colliding with a platform or something. Siggy says “it’s just a nice way to deal with it”. He stated that he “initially starts his sprite at a character border so it’s where it has to be at a zero value”. Then whenever he calls his sprite movement to move the sprite around by pixels that is when he updates his deltas. This is because you are always going to be moving the sprite along one pixel at a time. It tracks it for you. You don’t have to recalculate it. Only has to be calculated once at the beginning when you place the sprite. Then it tracks it for you every point after that automatically whenever you move the sprite.

That’s why he has the extended x bit in his code. Siggy says its easier to call the movement routine twice than it is to move the sprite two pixels and then recalculate that. Bo agreed replying “Right. Just have it move by one pixel and then call the routine. As an example if you need to move two pixels you just do the routine twice or  three times, four times, etc”.

When he implements delta he just incorporates that into the existing movement routine and it tracks the delta for you. So it will always be there. Always be correct. The only thing you have to do probably is to modify an offset for the sprite image you are using. This is because you won’t use the whole sprite, border to border. If you have the whole sprite hitting it, it is gonna look a bit funny. So there will be an offset there to bring that scope in. So it will be like the sprite position, plus an offset in either direction.

Bo then asked “So as the screen scrolls right, does that automatically keep reducing the delta?” Siggy stated that is something he included in the scroll routine. It was a pain to keep track of that because his deltas were being thrown off and trying to fix it would throw the sprite off.

How to view the sprites in CBM Prg Studio

CBM Prg Studio Sprite EditorThe sprites can be created in the built in Sprite Editor in CBM Prg Studio.  There is an icon that can be clicked on the right or you can click on the tools menu and then select Sprite Editor. You can use the editor to sketch a sprite or load them into memory. To load one from your computer, click on the File menu in the Sprite Editor. Then  select Import – From Binary File. Locate the bin file and open it. The dialogue box will show the path to the file you selected. Then press the OK button and the Sprite editor will emerge to life with the new data it has received.

Once the sprite is loaded, there are various controls you have at your disposal. You can set any of the colors for the sprite using the controls at the bottom left under Colour. At the top right you will see Current Sprite. Pressing the arrows left or right will cycle through the sprite for that frame. The Description and Linked Sprites were not used for this demo. Below this is multiple buttons that say New, Delete, Clear, Copy, Paste, Multicolor (checkbox), Flip L/R, Shift Up, Shift Left, Flip U/D, Shift Down, Shift Right, and Invert. These can be used to modify the sprite by deleting it, clearing the editor, copying the sprite, set multicolor mode on, flipping the sprite left-right, up to down, shifting the pixels in the sprite up, down, left, right, and finally reversing the colors of the sprite (invert). Below this is a preview of a smaller sprite. Here you can expand it horizontally or vertically. Finally, at the bottom right you will see the Animation panel. Here you can set the starting and ending frames of your sprite and watch the live animation in the preview window.

I asked Siggy how he drew the character sets for the game. He introduced me to the Character Editor. This can be launched with a button at the top right of CBM Prg Studio or selecting the tools menu and choosing Character Editor.

Using the Character Editor for CBM Prg Studio

This utility has a lot of cool features that are similar to the Sprite Editor. You can clean, copy, invert, and even revert the character images. You can also flip them left,right, up or down. Likewise shifting up, down, left, and right is identical to the Sprite Editor. The colors are managed at the bottom left and there is an option to switch on the Multicolor here. In the right panel you will see a list of all the characters in the C64’s Pet Ascii chart. You can switch the character that is shown in the view to the left by clicking on a particular row. You can also manually edit the character in the graph window. The data can be saved with the Character Set menu and selecting Save. There is even an option for exporting (save) and importing a character set on your computer.

Graphics from Trump Jump game

C64 Studio SpritesI was curious about how the sprites were generated with the Trump Jump game Siggy presented. Since it was developed in C64 Studio, he shared his screen and showed me a screenshot of the sprites he designed.

C64 Studio SpritesC64 Studio is an older, often unsupport assembly language editor, but you can also write pretty sophisticated programs with it. Unlike the sprite editor in CBM Prg Studio however, C64 Studio’s editor is quite unique since it lays them out in a preview state. There are a couple of screenshots here. For example, there must have been a title screen that was missed in the live stream earlier because here it is in C64 Studio.

C64 Studio Sprite EditorHe also shared the graphics he made for what he called the “character screen”. This was the smooth scrolling background that moved when the bulldozer was driving to the right to jump off the ramp. He mentioned earlier in the video that he managed to bypass more colors than possible using some tricks. Anyway I thought I’d include all the images as this is quite a revelation of what this guy is capable of. Pretty impressive if you ask me.

The Trump graphic seen under the ramp was actually a character set. Who would have thought? Anyway, Siggy seems to have a strong grasp on programming and graphics in general. This just shows what is possible with the Commodore 64. You can overlay character graphics side by side (using 4×4 tiling) to create some pretty awesome views. He stated that the character set seen here was done in CharPad and imported into C64 Studio. He stated it “has a nice scratch pad to see if all the characters work together”.

How the Animation works

For a good while we all got off on a tangent talking about others things, but eventually Darren was interested in animation and asked the question “How does it work?” (regarding the sprites). Siggy volunteered screen sharing once more as he did when showing Trump Jump and centered on the AnimTest subroutine in main.asm

CBM Prg Studio Project Information

CBM Prg Studio Project InformationNow with CBM Prg Studio there is also an easier way to locate your subroutines and variables. If you peer off to the right side of the screen in a window there you will see a section that says ‘Project Information’. Underneath that are tab categories for Labels and Variables. Keep Labels open or click on it and you will see all the Global variables in the project. You can click the plus signs to up this tree directory. Then if you double click on a variable you will be taken immediately to that part of the project. Eureka!

Animation for the Robot

Siggy’s explains the code below. He states this is the cycling frame code for the robot waving. First we load the SLOW_TIMER. There are two timers. There is a timer that is updated every frame. It increments from 0-255 and flips back around. Every 16 frames it updates the SLOW_TIMER.

C64 Robot AnimationThe and pulls off the first bit. Checks to see if it is a 1 or a 0. If it’s a 1 then we go to ‘.frame1’. If it’s 0, we look up the SPRITE_BASE (image #1) and load it into SPRITE_0_PTR (sprite 0’s pointer).  If it’s frame1, it goes to load SPRITE_BASE + 1 (image #2). It essentially flips the images back and fourth.

  • AnimTest
  • lda SLOW_TIMER ; Take the value of slow timer
  • and #$01 ; check the value of the first bit
  • beq .frame1 ; if it’s 0, use the first frame
  • lda #SPRITE_BASE ; Take our first sprite image
  • sta SPRITE_0_PTR ; store it in the pointer for sprite 0
  • rts
  • .frame1
  • lda #SPRITE_BASE + 1 ; if it’s 1, use the second frame
  • sta SPRITE_0_PTR ; store it in the pointer for sprite 0
  • rts

Animation for the Running Man

Since the animation for AnimTest was too slow for the running sprite, Siggy implemented a second subroutine to govern it. Obviously he named this AnimTest2. He is using the a faster timer here. Once every frame is way too fast. With TIMER we need to send a pulse to tell it to change frames. Everytime AnimTest2 is executed it will test the timer and increment by one. The ‘and’ determines how many frames it is updating by. In the example below it is updating every 3 frames. In the code comment he lists that the TIMER generates a ‘pulse’ (it stays on then off). Then a check is made against both bits 1 and 2 (and #3) to achieve a quick regular pulse every couple of frames.

  • ; User TIMER to update the animation
  • lda TIMER
  • and #$03 ; %0000-0011
  • beq @updateAnimation
  • rts
  • @updateAnimation
  • inc ANIM_FRAME ; move to the next anim image
  • lda SPRITE_DIRECTION
  • bmi @movingLeft
  • clc ; Sprite moving right – use frames 3-7
  • lda ANIM_FRAME 
  • cmp #8 ; frame for running in opposite direction
  • bcc @updateAnim
  • lda #4 ; reset back to start frame
  • sta ANIM_FRAME
  • jmp @updateAnim
  • @movingLeft
  • lda ANIM_FRAME ; Sprite moving left – use frames 9-12
  • cmp #12
  • bne @updateAnim
  • lda #8
  • sta ANIM_FRAME
  • jmp @updateAnim
  • @updateAnim
  • clc
  • adc #SPRITE_BASE ; pointer – SPRITE_BASE + FRAME #
  • sta SPRITE _1_PTR ; store new image pointer in sprite pointer 1
  • rts
  • ANIM_FRAME byte $00

How the joystick routine works

I was anxious to create some single frame animations when the player moved a character around on the screen. Siggy suggested we should try to emulate an example using the AnimTest2 code for the running man sprite. He directed me to check out the UpdateSprites code to learn how it works and to append to it. You will need to get a good controller here such as the Xbox Wireless Controller. By clicking on the link you understand that I get a small commission and its appreciated.

Siggy’s code explains it like this. The first part locks onto Sprite 0’s X position. Then it fetches the joystick’s X position and compare it to 0, which is equal to no input (no movement of the joystick – idle).  If there is no input on the X axis then it checks the down axis.  The goal here was to arrest control of the running man so we could move him with the joystick and discard the automatic animation.

  • UpdateSprites
  • ldx #$01 ; Sprite 0 in X
  • lda JOY_X ; fetch Joystick X position
  • cmp #0 ; 0 = no input in X axis
  • beq @testUpDown
  • bmi @moveLeft ; Our joystick reader treats this as a signed bytes
  • jsr MoveSpriteRight ; Joystick X positive – move right
  • jmp @testUpDown
  • @moveLeft
  • jsr MoveSpriteLeft ; Joystick X negative – move left
  • @testUpDown
  • ldx #$01
  • clc
  • lda SPRITE_POS_Y + $01 ; Exactly the same thing for Y axis on the joystick
  • adc JOY_Y
  • sta SPRITE_POS_Y + $01
  • sta VIC_SPRITE_Y_POS + $02
  • jsr AnimTest ; UPDATE SIMPLE ANIMATION
  • rts
Please follow and like us: