PLAYBASIC SOURCE CODES


Shape Ribbon

Shape Ribbon - PlayBASIC Demo

This demo draws a flowing, snake-like ribbon that appears to move toward the camera, creating a simple 3D illusion using only 2D shapes.

It works by drawing many circular shapes (rings) in a line, stretching off into the distance. Each ring is positioned using sine and cosine waves, which makes the path curve and twist in a smooth, organic way.

To fake depth, the rings are scaled and moved based on their distance from the camera. Rings that are “far away” are smaller and closer together, while rings that are “near” are larger and spread out. This gives the impression of perspective, like looking down a tunnel.

Each ring is also slightly stretched and rotated, which helps turn the stack of circles into something that looks more like a twisting ribbon or snake.

Finally, the colours are adjusted based on distance, fading into the background as they move further away. This adds to the depth effect and helps blend everything together visually.

The result is a continuous stream of shapes that feels like it’s flowing through 3D space, even though it’s all just 2D drawing tricks.






PlayBASIC Code:
; PROJECT : Shape_Ribbon
; AUTHOR  : Kevin Picone
; CREATED : 25/07/2008
; EDITED  : 13/04/2026
; ---------------------------------------------------------------------

    setfps 50

    Colour_Offset =0

    screen=NewFxIMage(GetScreenWidth(),GetScreenHeight())
    
    rings        =1000
    RingSize#=100

    FarDepth#    =30000
    NearDepth#    =100

    me=NewConvexShape(RingSize#,32)

    Edges=GetShapeEdges(me,0)*rings

    ;shapedebug me,true

    ClsColour=rgb(20,30,50)

    mx=getscreenwidth()/2
    my=getscreenheight()/2

        
    Do

        rendertoimage Screen


        CLs ClsColour
    
        ink $ffffff

;        thisrgb=rgb(50,20,210)

        zstep#=(NearDepth#-FarDepth#)/Rings
            
            Scale_Step# += 1.1
            inkmode 1+32

            Colour_Offset =  -int(Scale_Step#)

            projection#=400
        lockbuffer        
            For lp=0 to rings-1
                
                offset =  Colour_Offset +lp ;(rings-lp)
                
                x#=cos(angle#+lp*2)*455
                y#=sin(angle#+lp)*355

                x#+=cos(angle2#+lp*4)*255
                y#+=sin(angle2#+lp*2)*155

                Scale# = lp  + mod(Scale_Step#,1.0)

                z#=FarDepth#+(Scale#*zstep#)

                if z#<NearDepth# then continue

                pbyz# =projection#/z#

                Scale# = (RingSize# *pbyz#) / RingSize#

                rotateshapexy me,Angle2#+(offset/2),Scale#*1.25,Scale#*(3+1*sin(offset))

                sx#=mx+(x#*pbyz#)
                sy#=my+(y#*pbyz#)
                
                ThisRGB = offset * $010203
                ThisRGB = rgbdepthcue(ThisRgb,ClsColour, z#,NearDepth#,FarDepth#)  
            
                ink ThisRGB
                DrawShape me,sx#,sy#,2
            next
        unlockbuffer        

        rendertoscreen
        setcursor 0,0
        ink rgb(255,255,255)
        inkmode 1


        if LeftKey()=false and mousebutton()=0
            angle#=wrapangle(Angle#,1.5)
            angle2#=wrapangle(Angle2#,3)

        endif

        drawimage screen,0,0,false
        print fps()
        print edges
        
        Sync    
    loop



Scancode Selection Tool

Scancode Selection Tool

This is conceptual key selection tool. Ideally, this is type of thing that we could attach/link to an editor, where the editor calls the tool(s) which are passed / export some message data back to the ide.

To use the either tap the key or select it via the mouse. Left click to make selection
#1
ScanCode-Selection-Tool-V001.zip (96.49 KB)

Candy Rope

Candy Rope - Sinus Wave - Gouraud  Strip Demos

  This is update one of the example pack demos.  While it's nicer than the original, it's mainly to stress test the gouraud strip functions.. 


How Does it work ?

  This article goes through the BASIC concepts on show in this demo:

  Building a Procedural Rope Effect in PlayBASIC





Watch Short

PlayBASIC Code:
    ; This is pixel format test version of this demo
    
    ; Function keys f5/f6/f7/f8 to set the display depth    
    ;  Space key to add more sides of objects


    ; open a tall strip like screen for the short
    openscreen 800,1000,32,1

    PositionScreen GetSCreenXpos(),0;    GetScreenYpos()    ;*0.30

    ;Setfps 60
  LoadFont "Courier New",1,48,0,8
  fontdrawmode 1,1

    ; force a known randomize sequence by seeding  the generator
    randomize 8262

  Global RopeEdges=3

    Dim WaveRippleAngle#(10)
    
    ScreenWidth  =GetScreenWidth()
    ScreenHeight  =GetScreenHeight()

    ScreenDepth=32
    Screen=        NewFXImage(ScreenWidth,ScreenHeight)
    
    Type RopeVerts    
        X1#,X2#, rgb1,rgb2
    endtype
    


  
For Tests=0 to 20  


    ; init the palette 
    Dim Palette(RopeEdges+2)
    For lp=0 to RopeEdges-1
        Palette(lp) =NiceRGB(lp,0.25,0.90)
    next
    Palette(lp+1) =Palette(0)


  Dim RopeSegs(Ropeedges) as Ropeverts

    Do
        
            if functionkeys(5) then    ScreenDepth = 15
            if functionkeys(6) then    ScreenDepth = 16
            if functionkeys(7) then    ScreenDepth = 24
            if functionkeys(8) then    ScreenDepth = 32

            if GetIMageDepth(Screen) <> ScreenDepth
                    deleteimage Screen
                    Screen =GetFreeImage()
                    CReateFXImageEX Screen,GetScreenWidth(),GetScreenHeight(),ScreenDepth
              endif
        
            rendertoimage Screen


        // backdrop
        X1=ScreenWidth *0.00
        X2=ScreenWidth *0.25
        X3=SCreenWidth *0.50
        X4=SCreenWidth *0.75
        X5=SCreenWidth *1.00
        
        lockbuffer
            PaletteBandSize=50
            ThisRGB1=Palette(0)
            for ylp=0 to getscreenheight()-1
                
                YOffset = mod(Ylp+ScrollY,PaletteBandSize*(RopeEdges-1)) 

                Index = mod(yoffset/PaletteBandSize,(RopeEdges-1))
                ThisRGB1=Palette(Index)
                ThisRGB2=Palette(Index+2)
                
                //  Scale /blend level between bands 
                Scale# = (Yoffset-(PaletteBandSize*Index)) / float(PaletteBandSize)

                ThisRGB    =  RgbAlphaBlend(ThisRGB1,ThisRGB2, Scale#*80)
                ThisRGB50 = Rgbfade(ThisRGB,50) 
                ThisRGB20 = Rgbfade(ThisRGB,10) 

                GouraudStripH x1,ThisRGB,X2,ThisRGB50,Ylp
                GouraudStripH x2,ThisRGB50,X3,ThisRGB20,Ylp
                GouraudStripH x3,ThisRGB20,X4,ThisRGB50,Ylp
                GouraudStripH x4,ThisRGB50,X5,ThisRGB,Ylp

            next

            DrawRope()

        unlockbuffer
        ScrollY=mod(ScrollY+1,PaletteBandSize*(RopeEdges-1))
        
        renderimage Screen,0,0


        Sync
loop spacekey()

RopeEdges++    
    flushkeys
next

    end
    


Function DrawRope()
    
    Static RadiusAngle
    RadiusAngle=wrapangle(RadiusAngle +1)

  radius=250+cos(RadiusChange)*50
  ScReenHeight=GetScreenHeight()
    basex=GetScreenwidth()/2

    //  step the global angles
        AngleStep=2
        WaveAngle#= WaveRippleAngle#(1)
        WaveAngle2#= WaveRippleAngle#(2)
        WaveAngle3#= WaveRippleAngle#(3)
        WaveAngle4#= WaveRippleAngle#(4)
    
        WaveRippleAngle#(1)=wrapangle(WaveAngle#,0.7*AngleStep)
        WaveRippleAngle#(2)=wrapangle(WaveAngle2#,1.2*AngleStep)
        WaveRippleAngle#(3)=wrapangle(WaveAngle3#,2.1*AngleStep)
        WaveRippleAngle#(4)=wrapangle(WaveAngle4#,-0.4*AngleStep)
    
    EdgeAngleStep#=360.0/(RopeEdges)    

    BaseZ=1000
    
    Segments=0
        static BaseAngle#
    
        ThisRGB1 = Palette(RopeEdges)

        For lp=0 To RopeEdges-1        
            ThisRGB2 = Palette(lp+1)
            
            Angle#=BaseAngle#+(EdgeAngleStep#*lp)
            Angle2#=Angle#+EdgeAngleStep#

            
            X1#=Cosradius(angle#,Radius)
            X2#=CosRadius(angle2#,Radius)
            Z1#=basez+SinRadius(angle#,Radius)
            Z2#=basez+SinRadius(angle2#,Radius)

            Px1#=((X1#*400)/z1#)
            Px2#=((X2#*400)/z2#)

            If pX1#<=pX2#

                R=RgbR(ThisRgb1)
                G=RgbG(ThisRgb1)
                B=RgbB(ThisRGB1)
                
                R1=(R*500)/z1#
                G1=(G*500)/z1#
                B1=(B*500)/z1#
                R=RgbR(ThisRgb2)
                G=RgbG(ThisRgb2)
                B=RgbB(ThisRGB2)

                R2=(R*500)/z2#
                G2=(G*500)/z2#
                B2=(B*500)/z2#

                RopeSegs(Segments).x1=px1#
                RopeSegs(Segments).x2=px2#
                RopeSegs(Segments).rgb1=rgb(r1,g1,b1)
                RopeSegs(Segments).rgb2=rgb(r2,g2,b2)
                inc segments                
            EndIf

            ThisRGB1=ThisRGB2
        Next        

    dec segments

    For ThisScanLine=0 To ScreenHeight-1
        Xoffset=basex+cosradius(wAVEangle#,50)
        Xoffset=Xoffset+cosradius(wAVEangle2#+wAVEangle#,40)    
        Xoffset=Xoffset+cosradius(wAVEangle3#,80)    
        Xoffset=Xoffset+cosradius(wAVEangle4#,30)
        
        For lp=0 to Segments
                GouraudStripH Xoffset+RopeSegs(lp).x1,RopeSegs(lp).rgb1,Xoffset+RopeSegs(lp).x2,RopeSegs(lp).rgb2,ThisScanline                
    ;            GouraudStripV Xoffset+RopeSegs(lp).x1,RopeSegs(lp).rgb1,Xoffset+RopeSegs(lp).x2,RopeSegs(lp).rgb2,ThisScanline                
        next                            
        waveangle#    +=    0.15
        waveangle2#    +=    0.25
        waveangle3#    +=    0.35
        waveangle4#    += 0.33

    Next    

    BaseAngle#++
EndFunction




; ===============================================================
;  Golden Ratio based pleasant random colors
;  - Each call with incrementing index gives nicely spaced hues
; ===============================================================
    constant GoldenRatio# = 0.6180339  ; ˜ (v5 - 1) / 2

Function NiceRGB(Index=0, Saturation#=0.75, Value#=0.88)
    Local Hue#

    ; Start from a random base hue, then walk by golden ratio
    ; (mod 1.0 to wrap around colour wheel)
    Hue# = mod(Rnd#(1) + (Index * GoldenRatio#),1)
    Hue# = Hue# - Floor(Hue#)          ; fractional part only (mod 1)

    ; Optional: You can randomise S/V a little for more variety
    Saturation# = Saturation# + RndRange#(-0.15, 0.15)
    Value#      = Value#      + RndRange#(-0.1,  0.1)
    ; Clamp them
    If Saturation# < 0.4 Then Saturation#=0.4
    If Saturation# > 0.98 Then Saturation#=0.98
    If Value#      < 0.5 Then Value#=0.5
    If Value#      > 0.98 Then Value#=0.98

    #if playbasicversion =>165
        Result = HSV2RGB(Hue#, Saturation#, Value#)
    #else
            Result=rndrgb()
    #Endif    
EndFunction Result





Function RenderImage(ThisImage,xpos,ypos,Mode=false)
    if GetIMageStatus(ThisIMAGE)    

            inkmode 1+2048
            boxc 00,18,275,112,true,$a0a0a0
            inkmode 1


            Xpos+=10
            Ypos+=20
            ink $80fFFFFF
            Text XPos,Ypos        ,"  Fps:"+Str$(fps())
            Text XPos,Ypos+40    ,"Depth:"+Str$(GetIMageDepth(ThisIMage))

            rendertoscreen
            lockbuffer
                drawimage ThisImage,0,0,false
            unlockbuffer

    endif
EndFunction 

Heli Blaster

  Heli Blaster - Play Blaster Update -  Camera Rotation Demo

  This demo take advantage of the latest camera rotation modes in PlayBASIC V1.65.   

  The player controls a helicopter using the arrow keys. The player can rotate and move freely using the turn / forward controls.  Think asteroids, but without the physics.  Now to make this more interesting, the camera is attached to the players rotation. 

  The demo uses CameraDrawMode #1 , which is a Rotated 2D.   







  Scene this before ? 

  Yes, this is indeed a revised edition of the classic PlayBASTER demo from 2005.  That demo focuses upon sprite rotation and pixel perfect collisions.  Both of which are included in this demo too of course.


  Controls:

  Up/Down Arrows:  Move Helicopter Forward / Back

  Left/Right Arrows:  Turn Helicopter

  A / D = Straf Left / Right

  S    = Fire Helicopter machine guns


#1
2026-03-24 - PB_Baster Update.zip (772.84 KB)

City Building Flyover Effect

This demo shows how to create a simple “flying over a city” effect using PlayBASIC.

The scene is made entirely from 2D squares arranged in a grid. Each square represents a building. Multiple copies of this grid are drawn at different depths, and the camera projects them in perspective so they appear smaller in the distance.

To make the scene more visually interesting:

* The buildings use additive blending to create a glowing effect
* The colour fades with distance to simulate depth
* The camera can move and rotate, giving the feeling of flying over the city

Even though it looks like a 3D world, everything is built from simple 2D drawing commands. The illusion of depth comes from layering and the camera system.

The world also wraps around the camera position, which makes it appear endless without needing to generate new geometry.







PlayBASIC Code:
; --------------------------------------------------------
; PlayBASIC Demo: Glowing City Flyover (Rotated Camera)
; --------------------------------------------------------

; Limit the frame rate
SetFPS 60

; Create and configure the camera
Cam = NewCamera()

; Enable rotated / perspective rendering mode
CameraDrawMode Cam, 2

; Disable automatic clearing when drawing the camera
CameraCLS Cam, OFF


; Distance between tiles in the grid
TileSpacing = 512


; --------------------------------------------------------
; Main Loop
; --------------------------------------------------------
Do

    ; Clear the screen (dark blue background)
    CLS $223344

    ; Begin capturing draw commands into the scene buffer
    CaptureToScene

    ; Clear previous frame's scene data
    ClsScene


    ; Enable additive blending for glow effect
    InkMode 1+32


    ; ----------------------------------------------------
    ; Draw multiple grid layers at different depths
    ; ----------------------------------------------------
    For lp = 0 To 100 Step 8

        ; Assign depth (further layers appear smaller)
        CaptureDepth 500 + (lp * 5)

        ; Scale value used for color fading
        Scale# = lp / 100.0

        ; Fade color based on distance
        C = RgbFade($884433, Scale# * 100)

        ; Draw a grid of glowing blocks
        Tile_Layer(TileSpacing, TileSpacing * 0.75, 10, 8, C)

    Next


    ; Render the scene using the camera
    DrawCamera Cam


    ; ----------------------------------------------------
    ; Camera Movement Controls
    ; ----------------------------------------------------
    Speed# = 5

    If LeftKey()  Then MoveCamera Cam, -Speed#, 0
    If RightKey() Then MoveCamera Cam,  Speed#, 0
    If UpKey()    Then MoveCamera Cam, 0, -Speed#
    If DownKey()  Then MoveCamera Cam, 0,  Speed#


    ; Wrap camera position to create an infinite scrolling effect
    x# = WrapValue(GetCameraX(Cam), 0, TileSpacing)
    y# = WrapValue(GetCameraY(Cam), 0, TileSpacing)
    PositionCamera Cam, x#, y#


    ; ----------------------------------------------------
    ; Camera Rotation Controls
    ; ----------------------------------------------------
    TurnAngle# = 0
    RotationSpeed# = 0.25

    If LeftMouseButton() Or KeyState(44)  Then TurnAngle# = -RotationSpeed#
    If RightMouseButton() Or KeyState(45) Then TurnAngle# =  RotationSpeed#

    ; Cycle camera modes when ENTER is pressed
    If EnterKey()
        CameraDrawMode Cam, GetCameraDrawMode(Cam) + 1
        FlushKeys
    EndIf

    ; Apply rotation if needed
    If TurnAngle# <> 0
        Angle# = GetCameraAngle(Cam)
        RotateCamera Cam, Angle# + TurnAngle#
    EndIf


    ; Update display
    Sync

Loop SpaceKey()



; --------------------------------------------------------
; Function: Draw a grid of blocks (buildings)
; --------------------------------------------------------
Function Tile_Layer(TileSpacing, BlockSize, GridWidth, GridHeight, ThisColour)

    ; Center the grid around (0,0)
    BaseX = (TileSpacing * GridWidth)  / -2
    BaseY = (TileSpacing * GridHeight) / -2

    ; Loop through grid positions
    For ylp = 0 To GridHeight
        For xlp = 0 To GridWidth

            ; Calculate block position
            x1 = BaseX + xlp * TileSpacing
            y1 = BaseY + ylp * TileSpacing

            x2 = x1 + BlockSize
            y2 = y1 + BlockSize

            ; Draw filled box (building)
            BoxC x1, y1, x2, y2, TRUE, ThisColour

        Next
    Next

EndFunction






Historical Note

This demo was inspired by classic effects from the early demo scene.

I first remember seeing a similar visual style in the Arte demo by the demogroup Sanity back in 1993 on the Commodore Amiga. The top-down city-like perspective left a lasting impression on me.

Seeing the potential to recreate a similar effect using PlayBASIC’s rotated camera system, I took the opportunity to build a modern take on that idea.

Enjoy!