learning about sprites

Advanced game related topics
User avatar
sirrab
Enthusiast
Enthusiast
Posts: 106
Joined: Thu Dec 07, 2006 8:52 am
Location: New Zealand

learning about sprites

Post by sirrab »

I have been playing around with sprites, but when ever i try and move the character sprite it always shows a collision even when its miles from the one sprite im trying to detect. Can anyone tell me what im doing wrong please :)

Code: Select all

UseJPEGImageDecoder()

Structure mapdata
  blocktype.s
  blockx.i
  blocky.i
  blocksprite.i
  
EndStructure

Global thomasx.i,thomasy.i
Global movestep.i = 5
If InitSprite() = 0 Or InitKeyboard() = 0
  MessageRequester("Error", "Sprite system can't be initialized", 0)
  End
EndIf

;
; Now, open a 800*600 - 32 bits screen
;

Enumeration
  #mainwin
  #mazewin
  
  #fwall
  #hwall
  #vwall
  #floor0
  #thomas
EndEnumeration

Procedure drawscreen()
  ClearList(currentmap())
  
  ClearScreen(RGB(255,255,255))
  y = 0
  
  If IsSprite(#thomas)
            DisplaySprite(#thomas,thomasx,thomasy)
  EndIf
  Restore level1
  Repeat
    Read.s mapline$
    If mapline$ <> "end"
    bs = 1
    x = 0
    
    For c = 1 To 40 
      blockcode$ = Mid(mapline$,bs,2)
      bs + 2
      Select blockcode$
                 
        Case "01"
          If IsSprite(#fwall)
            
            DisplaySprite(#fwall,x,y)
           
          EndIf
          
        Case "02"
          If IsSprite(#vwall)
            
            DisplaySprite(#vwall,x,y)
          EndIf
          
        Case "03"
          If IsSprite(#vwall)
           
            DisplaySprite(#vwall,x,y)
          EndIf
          
        Case "04"
          If IsSprite(#floor0)
            ns = CopySprite(#floor0,#PB_Any)
            DisplaySprite(#floor0,x,y)
          EndIf
          
      EndSelect
      
      x + 32
    Next c
    y + 32
  EndIf
  Until mapline$ = "end"
    FlipBuffers()
  
EndProcedure
  
Procedure movementthread(blank.i = 0)
    
  oldposx = thomasx
  oldposy = thomasy
    ExamineKeyboard()
    
    If KeyboardPushed(#PB_Key_Down)                       
            thomasy + movestep           
           
            kp = 1
    EndIf
     
    If KeyboardPushed(#PB_Key_Up)                   
            thomasy - movestep
          
            kp = 1
    EndIf
    
    If KeyboardPushed(#PB_Key_Right)                
           thomasx + movestep
              
           kp = 1
    EndIf
    
    If KeyboardPushed(#PB_Key_Left)               
            thomasx - movestep
          
    EndIf
    
    If kp > 0
      pow  = 0
     pow = SpriteCollision(#thomas,thomasx,thomasy,#fwall,thomasx,thomasy) 
          
          Debug pow 
          
      If pow > 0
        thomasx = oldposx
        thomasy = oldposy
        
      Else
        drawscreen()
      EndIf                             
  EndIf
  
EndProcedure

If OpenWindow(#mainwin,1,1,800,600, "Casper Compile",#PB_Window_TitleBar  |  #PB_Window_Invisible | #PB_Window_ScreenCentered | #PB_Window_MinimizeGadget | #PB_Window_SystemMenu)   
    
   If OpenWindowedScreen(WindowID(#mainwin),0,0,800, 600)

  ; Load our 16 bit sprite (which is a 24 bit picture in fact, as BMP doesn't support 16 bit format)
  
  
  LoadSprite(#fwall,"sprites\fwall.jpg")
  LoadSprite(#hwall,"sprites\hwall.jpg")
  LoadSprite(#vwall,"sprites\vwall.jpg")
  LoadSprite(#floor0,"sprites\floortrip.jpg")
  LoadSprite(#thomas,"sprites\thomas.jpg")
  
  thomasx = 133
  thomasy = 133
  drawscreen()
  
  HideWindow(#mainwin,0)
  
  Repeat ; Start of the event loop    
       WindowID = EventWindow() ; The Window where the event is generated, can be used in the gadget procedures 
       Event = WaitWindowEvent(2) ; This line waits until an event is received from Windows    
       GadgetID = EventGadget() ; Is it a gadget event? 
       EventType = EventType() ; The event type
      
    If  Event = #PB_Event_Gadget 
      
      Select GadgetID 
          
                 
                           
          
      EndSelect
         
    EndIf
    
    
    movementthread()
    
  Until Event = #PB_Event_CloseWindow 
  
  
  
Else
  MessageRequester("Error", "Can't open a 800*600 - 32 bit screen !", 0)
EndIf 
EndIf
  
  
DataSection
  level1:
  Data.s "01010101010101010101010101010101010101010101010101"
  Data.s "01000000000000000000000000000000000000000000000001"
  Data.s "01000000000000000000000000000000000000000000000001"
  Data.s "01000000030000000000000000000000000000000000000001"
  Data.s "01000000000000000000000000000000000000000000000001"
  Data.s "01000000000000000000000000000000000000000000000001"
  Data.s "01000000000000000000000000000000000000000000000001"
  Data.s "01000000000000000000000000000000000000000000000001"
  Data.s "01000000000000000000000000000000000000000000000001"
  Data.s "01000000000000000000000000000000000000000000000001"
  Data.s "01000000000000000000000000000000000000000000000001"
  Data.s "01000000000000000000000000000000000000000000000001"
  Data.s "01000000000000000000000000000000000000000000000001"
  Data.s "01000000000000000000000000000000000000000000000001"
  Data.s "01000000000000000000000000000000000000000000000001"
  Data.s "01000000000000000000000000000000000000000000000001"
  Data.s "01000000000000000000000000000000000000000000000001"
  Data.s "01000000000000000000000000000000000000000000000001"
  Data.s "01010101010101010101010101010101010101010101010101"
  Data.s  "end"
  
EndDataSection
thanks
User avatar
Demivec
Addict
Addict
Posts: 4268
Joined: Mon Jul 25, 2005 3:51 pm
Location: Utah, USA

Re: learning about sprites

Post by Demivec »

sirrab wrote:I have been playing around with sprites, but when ever i try and move the character sprite it always shows a collision even when its miles from the one sprite im trying to detect. Can anyone tell me what im doing wrong please
Your code is incomplete and unrunnable. That is bad for testing and helping.

It is still possible to discern the error though. It is in this line:

Code: Select all

pow = SpriteCollision(#thomas,thomasx,thomasy,#fwall,thomasx,thomasy) 
This tests if sprite #thomas at position (thomasx,thomasy) collides with sprite #fwall when it is positioned at (thomasx, thomasy).


You'll notice the coordinates you gave for both sprites are the same so the natural result is this should always report a collision. What you need is to know the coordinates for the #fwall sprite (there are more than one) that you are testing for a collision with.

I noticed that you don't seem to keep track of these but merely draw them by reinterpreting the level data each time the screen is rendered. You may test for the collision while rendering the screen by comparing each thing (or just #fwall) at its rendered coordinates to the #thomas sprite and its rendered coordinates.

Other ways would work just as well also. There are more than few variations on this theme. Ask if you need to know more.

Just because you drew or rendered something to the screen doesn't mean that particular sprite image has unique coordinates associated with it. As a sprite it can be rendered as many times as desired so you need a custom method for tracking a particular rendering of a sprite at the position it was rendered to make a proper collision detection between it and another sprite (with its respective particulars).
User avatar
sirrab
Enthusiast
Enthusiast
Posts: 106
Joined: Thu Dec 07, 2006 8:52 am
Location: New Zealand

Re: learning about sprites

Post by sirrab »

HI,

At first I kept track of each sprite in a structured list but it was slow going though the list each time, I would be very thankful for some info on the better way of doing it.

Thank You.
User avatar
Demivec
Addict
Addict
Posts: 4268
Joined: Mon Jul 25, 2005 3:51 pm
Location: Utah, USA

Re: learning about sprites

Post by Demivec »

Here is another approach.

It works like this. We load our level into an array where each position of the array keeps track of the sprite that is drawn there (a tile).

We then do collision checking by rounding off the players position to the nearest tile in the level array. We assume the player's sprite is smaller than the tile size and also that the movement rate is also smaller than a tile size. We then check for collision with 1 of four possible tile positions. We do this by looking at the level array to see what sprite is there. If it is one that has collisions we check for overlap of the sprite images.

Code: Select all

+---------+---------+
|.........|.........|      The player sprite is represented by the o's.
|.........|.........|      As can be seen it may overlap up to four tiles,
|.........|.........|      so we check each of four possible tiles for a
|......ooo|oo.......|      collision.  We use the player's position to 
+------ooo+oo-------+      determine which tiles in the layer map
|......ooo|oo.......|      to check for collision.  
|......ooo|oo.......|
|.........|.........|
|.........|.........|
+---------+---------+
Checking for things in this way minimizes the the number of checks we are performing to just four. It is possible to make further improvements but the water starts to get a little muddy (i.e. less clear) the further the method is refined. At this stage it is relatively simple.

If a collision occurs we move the player back to the former position. I've also added an additional level for testing as well as mad the sprite #vwall act as an exit and coded some things specifically to handle that (I partly muddied the water in the process, oops). The additions naturally would be removed to tailor things to your specific game play.

Since you haven't included any of your sprites I created some simple ones to enable a demonstration.

Things can be further enhanced depending on what you are doing.

Code: Select all

UseJPEGImageDecoder()

;   Structure mapdata_block
  ;   type.s
  ;   x.i
  ;   y.i
  ;   sprite.i
;   EndStructure

Enumeration ;windows
  #mainwin
  ;#mazewin
EndEnumeration

Enumeration ;sprites
  #floor0 = 1
  #fwall 
  #hwall
  #vwall
  #thomas
EndEnumeration

#True = 1   ;define basic constants for Macintosh (if they aren't defined already)
#False = 0

#blockSize = 32 ;assumes all tiles are the same size and that they are square

Global thomasx.i,thomasy.i
Global movestep.i = 5


Procedure handleError(value, text.s)
  If value = #False
    MessageRequester("Error", "Sprite system can't be initialized")
    End
  EndIf
EndProcedure

;This may seem a little complicated but it simply takes the pointer to the level data in the data section,
;reads in each line and converting the blocks into sprite#'s and stores them in an array that corresponds to
;tile positions on the screen.  This array of the levelData sprites is used to render the screen and check for
;collisions with the player.
Procedure setupLevel(*levelData, Array levelData(2))
  Protected rowCount, columnCount, x, y, *levelDataPtr = *levelData, mapline$, blockcode$
  
  While PeekS(*levelData) <> "end"
    rowCount + 1
    *levelData + (MemoryStringLength(*levelData) + 1) * SizeOf(Character)
  Wend
  
  If rowCount > 1
    columnCount = MemoryStringLength(*levelDataPtr) / 2 ;each block uses 2 characters
    rowCount - 1: columnCount - 1
    Dim levelData(columnCount, rowCount)
    
    *levelData = *levelDataPtr
    For y = 0 To rowCount
      mapline$ = PeekS(*levelData)
      For x = 0 To columnCount
        
        blockcode$ = Mid(mapline$, x * 2 + 1, 2)
        Select blockcode$
          Case "01"
            levelData(x, y) = #fwall
          Case "02"
            levelData(x, y) = #hwall
          Case "03"
            levelData(x, y) = #vwall
          Case "00"
            levelData(x, y) = #floor0
        EndSelect
        
      Next
      *levelData + ((columnCount + 1) * 2 + 1) * SizeOf(Character) ;add memory length of line onto pointer
    Next
  EndIf
  
EndProcedure


Procedure drawscreen(Array levelData(2))
  Protected i, j, x, y, columnCount = ArraySize(levelData(), 1), rowCount = ArraySize(levelData(), 2)
  
  ClearScreen(RGB(255,255,255))
  
  y = 0
  For j = 0 To rowCount
    x = 0
    For i = 0 To columnCount
      DisplaySprite(levelData(i, j), x, y)
      x + #blockSize
    Next
    y + #blockSize
  Next
  
  DisplaySprite(#thomas, thomasx, thomasy)
  FlipBuffers()
EndProcedure

;If the return value <> 0 then there was a collision with the given spriteID.
Procedure collisionCheck(spriteID, mapX, mapY, Array levelData(2))
  ;Collision checking assumes previous position was a legal position and that movestep < #blockSize.
  ;It compares the tile position that overlaps the position (thomasx, thomasy) with the levelData()
  ;to see what sprite occupies that space.
  
  Protected pow, columnCount = ArraySize(levelData(), 1), rowCount = ArraySize(levelData(), 2)
  
  If mapX >= 0 And mapX <= columnCount And mapY >= 0 And mapY <= rowCount
    ;check four positions for collisions
    If levelData(mapX, mapY) = spriteID
      ;only map is needed for a collision in this position
      pow = %100
    EndIf
    
    If pow = 0 And mapX + 1 <= columnCount And levelData(mapX + 1, mapY) = spriteID
      ;check overlap with tile to the right
      pow = Bool(SpriteCollision(#thomas, thomasx, thomasy, spriteID, (mapX + 1) * #blockSize, mapY * #blockSize) <> 0) * %110
    EndIf
    
    If pow = 0 And mapX + 1 <= columnCount And  mapY + 1 <= rowCount And levelData(mapX + 1, mapY + 1) = spriteID
      ;check overlap with tile to the lower-right
      pow = Bool(SpriteCollision(#thomas, thomasx, thomasy, spriteID, (mapX + 1) * #blockSize, (mapY + 1) * #blockSize) <> 0) * %111
    EndIf
    
    If pow = 0 And mapY + 1 <= rowCount And levelData(mapX, mapY + 1) = spriteID
      ;check overlap with tile below
      pow = Bool(SpriteCollision(#thomas, thomasx, thomasy, spriteID, mapX * #blockSize, (mapY + 1) * #blockSize) <> 0) * %101
    EndIf
    
  Else
    ;player off map, further check required to determine direction
    pow = %1000
  EndIf
  
  ProcedureReturn pow ;return value encodes type of collision and where
EndProcedure


Procedure movementthread(Array levelData(2), blank.i = 0)
  Protected oldposx = thomasx, oldposy = thomasy, collision, redraw
  Protected columnCount = ArraySize(levelData(), 1), rowCount = ArraySize(levelData(), 2)
  
  ExamineKeyboard()
  
  If KeyboardPushed(#PB_Key_Down)                       
    thomasy + movestep           
    kp = 1
  EndIf
  
  If KeyboardPushed(#PB_Key_Up)                   
    thomasy - movestep
    kp = 1
  EndIf
  
  If KeyboardPushed(#PB_Key_Right)                
    thomasx + movestep
    kp = 1
  EndIf
  
  If KeyboardPushed(#PB_Key_Left)               
    thomasx - movestep
    kp = 1
  EndIf
  
  If kp > 0
    ;Collision checking assumes (oldposx, oldposy) is a legal position and that movestep < #blockSize.
    ;It compares the tile position that overlaps the position (thomasx, thomasy) with the levelData()
    ;to see what sprite occupies that space.
    redraw = #True
    
    x = thomasx / #blockSize: y = thomasy / #blockSize
    If collisionCheck(#fwall, x, y, levelData())
      thomasx = oldposx
      thomasy = oldposy
      redraw = #False
    Else
      ;treat collision with #vwall as an exit
      collision = collisionCheck(#vwall, x, y, levelData())
      If collision
        x + Bool(collision & %10 <> 0): y + Bool(collision & %01 <> 0) ;adjust position for the four possible tiles
        thomasx = x * #blockSize + #blockSize / 4;move player inside the tile where the exit 'was'
        thomasy = y * #blockSize + #blockSize / 4
        setupLevel(?level2, levelData()) ;level2 uses #vwall that is placed in a random position as an exit
        Select Random(3, 1)
          Case 1: levelData(columnCount - x, rowCount - y) = #vwall
          Case 2: levelData(columnCount - x, y) = #vwall
          Case 3: levelData(x, rowCount - y) = #vwall
        EndSelect
      EndIf
    EndIf    
    
    If redraw
      drawscreen(levelData())
    EndIf
  EndIf
  
EndProcedure

handleError(InitSprite(), "Sprite system can't be initialized")
handleError(InitKeyboard(), "Sprite system can't be initialized")

;
; Now, open a 800*600 - 32 bits screen
;
handleError(OpenWindow(#mainwin,1,1,800,600, "Casper Compile",
                       #PB_Window_TitleBar  | #PB_Window_Invisible | #PB_Window_ScreenCentered |
                       #PB_Window_MinimizeGadget | #PB_Window_SystemMenu),
            "Unable to open main window")   

handleError(OpenWindowedScreen(WindowID(#mainwin),0,0,800, 600), "Can't open a 800*600 - 32 bit screen !")

; Load our 16 bit sprite (which is a 24 bit picture in fact, as BMP doesn't support 16 bit format)

;handleError(LoadSprite(#fwall,"sprites\fwall.jpg"), "Couldn't load sprite " + "sprites\fwall.jpg")
;handleError(LoadSprite(#hwall,"sprites\hwall.jpg"), "Couldn't load sprite " + "sprites\hwall.jpg")
;handleError(LoadSprite(#vwall,"sprites\vwall.jpg"), "Couldn't load sprite " + "sprites\vwall.jpg")
;handleError(LoadSprite(#floor0,"sprites\floortrip.jpg"), "Couldn't load sprite " + "sprites\floortrip.jpg")
;handleError(LoadSprite(#thomas,"sprites\thomas.jpg"), "Couldn't load sprite " + "sprites\thomas.jpg")

;create a few sprites for testing
CreateSprite(#fwall, 32, 32)
StartDrawing(SpriteOutput(#fwall))
Box(0, 0, #blockSize, #blockSize, RGB(255, 255, 255))
StopDrawing()

CreateSprite(#vwall, 32, 32)
StartDrawing(SpriteOutput(#vwall))
Box(0, 0, #blockSize, #blockSize, RGB(255, 0, 255))
StopDrawing()

CreateSprite(#floor0, 32, 32) ;use default black sprite

CreateSprite(#thomas, 11, 11)
StartDrawing(SpriteOutput(#thomas))
Circle(OutputWidth() / 2,  OutputHeight() / 2, OutputWidth() / 2, RGB(0, 255, 0))
StopDrawing()




Dim levelData(0, 0) ;this will be redimensioned for each level
setupLevel(?level1, levelData())

thomasx = 133
thomasy = 133
drawscreen(levelData())

HideWindow(#mainwin,0)

Define quit = #False

Repeat ; Start of the event loop    
  WindowID = EventWindow() ; The Window where the event is generated, can be used in the gadget procedures 
  Event = WaitWindowEvent(2) ; This line waits until an event is received from Windows    
  GadgetID = EventGadget()   ; Is it a gadget event? 
  EventType = EventType()    ; The event type
  
  Repeat
    event = WindowEvent()
    If event = #PB_Event_CloseWindow
      quit = #True
    EndIf
    
    ;     If  Event = #PB_Event_Gadget 
    ;       
    ;       Select GadgetID 
    ;           
    ;           
    ;           
    ;           
    ;       EndSelect
    ;       
    ;     EndIf
  Until event = 0 Or quit = #True
  
  movementthread(levelData())
  
Until Event = #PB_Event_CloseWindow 



DataSection
  level1:
  Data.s "01010101010101010101010101010101010101010101010101"
  Data.s "01000000000000000000000000000000000000000000000001"
  Data.s "01000000000000000000000000000000000000000000000001"
  Data.s "01000000030000000000000000000000000000000000000001"
  Data.s "01000000000000000000000000000000000000000000000001"
  Data.s "01000000000000000000000000000000000000000000000001"
  Data.s "01000000000000000000000000000000000000000000000001"
  Data.s "01000000000000000000000000000000000000000000000001"
  Data.s "01000000000000000000000000000000000000000000000001"
  Data.s "01000000000000000000000000000000000000000000000001"
  Data.s "01000000000000000000000000000000000000000000000001"
  Data.s "01000000000000000000000000000000000000000000000001"
  Data.s "01000000000000000000000000000000000000000000000001"
  Data.s "01000000000000000000000000000000000000000000000001"
  Data.s "01000000000000000000000000000000000000000000000001"
  Data.s "01000000000000000000000000000000000000000000000001"
  Data.s "01000000000000000000000000000000000000000000000001"
  Data.s "01000000000000000000000000000000000000000000000001"
  Data.s "01010101010101010101010101010101010101010101010101"
  Data.s  "end"
  
  level2:
  Data.s "01010101010101010101010101010101010101010101010101"
  Data.s "01000000000100000001010000000001000000000000000001"
  Data.s "01010001000101010000000001010001000101010101000101"
  Data.s "01000001000100010101010000010101000100000001000001"
  Data.s "01000101010100000000010100010000000100010101010001"
  Data.s "01000000000000010100000100010100010100000000000001"
  Data.s "01010001010101010101000100000000000101010101010001"
  Data.s "01000001000000000100000100010001000000010000000001"
  Data.s "01000101000101000000010101010001010101010101010101"
  Data.s "01000101000000010101000000010001000001000000010001"
  Data.s "01000001010100000000000100000001010001000101010001"
  Data.s "01010101000101010101010101010000000001000000000001"
  Data.s "01000001000000010000000000010001010001010001010101"
  Data.s "01000101010100010001010100010101000000010000000001"
  Data.s "01000000000100010001000100010001000101010101010001"
  Data.s "01000101000100010001000100010001000000010000000001"
  Data.s "01000001010100010001000100010001000100010101010001"
  Data.s "01010000000000000000000100000000000100000000000001"
  Data.s "01010101010101010101010101010101010101010101010101"
  Data.s  "end"
  
EndDataSection
Let me know if this was helpful or if there is anything about the code I can clear up. :)


@Edit: made the player's sprite size a little smaller
Last edited by Demivec on Wed Aug 19, 2015 11:28 am, edited 2 times in total.
User avatar
Vera
Addict
Addict
Posts: 858
Joined: Tue Aug 11, 2009 1:56 pm
Location: Essen (Germany)

Re: learning about sprites

Post by Vera »

Thanks Demivec,
I much appreciate your collision excursion :-) while trying to grasp a bit more from the recent Maze-examples

While adjusting your code for me on Linux I'd like to share the following.
- Defining #True and #False for Linux isn't necessary.
- Neither quit nor CloseWindow worked and I missed ESC. To achive it I changed the following:

Code: Select all

; move 
Define quit = #False
; to the top of the code as: 
Global quit = #False

Code: Select all

; add to Procedure movementthread(...)
  If KeyboardPushed(#PB_Key_Escape)
    quit = #True
  EndIf

Code: Select all

; Repeat
; ...
;   Event = WaitWindowEvent(2)
; ...

; change the loop to:
  Repeat
    Event1 = WindowEvent()                   ; ' Event' was already declared
    If Event1 = #PB_Event_CloseWindow
      quit = #True
    EndIf
    ;     If  Event = #PB_Event_Gadget
    ;       ...
    ;     EndIf
;   Until event = 0 Or quit = #True
  Until Event1 = 0
 
  movementthread(levelData())
 
; Until Event = #PB_Event_CloseWindow
Until Event = #PB_Event_CloseWindow Or quit = #True
Hint: the movestep of 5 is a bit too large as one often can't reach the area where you want to take the turn and get stuck at exactly the boundaries of a branch. A smaller stepsize works out fine and the movement becomes more smooth.

A last thing I couldn't solve yet is that the maze won't show up when the window displays with unhiding. A single click on any arrow-key will bring it up for good ... so it's only a small issue for now.

greets ~ Vera
User avatar
Demivec
Addict
Addict
Posts: 4268
Joined: Mon Jul 25, 2005 3:51 pm
Location: Utah, USA

Re: learning about sprites

Post by Demivec »

Vera wrote:Thanks Demivec,
I much appreciate your collision excursion while trying to grasp a bit more from the recent Maze-examples
@Vera: Your welcome. :)

The code is meant to illustrate a method of collision detection for sirrab. Hopefully it gives him some more ideas for his maze game.

I tried not to deviate very much from the basic structure he started with in his first posting. This is the reason that some of the features you suggested are missing. Simply put they weren't in the original.

I would continue modifying the code until it barely resembled anything sirrab may of had in mind and that would be a bad thing. Best to leave well enough alone and let him take it in the direction he wants. :)


Vera, on a similar topic, I had modified falsam's CrazySnake to include a wrap-around mode. I hesitate to post it because I wanted to also add something else that was even crazier. I have to get the idea programmed out to see if it is actually worthwhile though...it may be awhile. :mrgreen:
User avatar
Vera
Addict
Addict
Posts: 858
Joined: Tue Aug 11, 2009 1:56 pm
Location: Essen (Germany)

Re: learning about sprites

Post by Vera »

I see Demivec.
Now as I'd only posted changes [also having any potential visitors in mind] it'll still support a learning effect when applying it by oneself ... and maybe it too comes in handy for sirrab on the way.

As for the Crazy Snake - I too enhanced it with a random background-image-switcher but also didn't post it fearing it might come across as 'dislike of the original' - still it brings up interesting experiences to various levels.
I'm surely looking forward to further crazy updates ... in a while :wink:
Post Reply