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.
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
By: Kevin Picone | Added: April 10th, 2026
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:
; 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 shortopenscreen800,1000,32,1PositionScreenGetSCreenXpos(),0; GetScreenYpos() ;*0.30;Setfps 60LoadFont"Courier New",1,48,0,8fontdrawmode1,1; force a known randomize sequence by seeding the generatorrandomize8262Global RopeEdges=3Dim WaveRippleAngle#(10)
ScreenWidth =GetScreenWidth()
ScreenHeight =GetScreenHeight()
ScreenDepth=32
Screen=NewFXImage(ScreenWidth,ScreenHeight)Type RopeVerts
X1#,X2#, rgb1,rgb2
endtypeFor Tests=0to20; init the palette Dim Palette(RopeEdges+2)For lp=0to RopeEdges-1
Palette(lp)=NiceRGB(lp,0.25,0.90)next
Palette(lp+1)=Palette(0)Dim RopeSegs(Ropeedges)as Ropeverts
Doiffunctionkeys(5)then ScreenDepth =15iffunctionkeys(6)then ScreenDepth =16iffunctionkeys(7)then ScreenDepth =24iffunctionkeys(8)then ScreenDepth =32ifGetIMageDepth(Screen)<> ScreenDepth
deleteimage Screen
Screen =GetFreeImage()CReateFXImageEX Screen,GetScreenWidth(),GetScreenHeight(),ScreenDepth
endifrendertoimage Screen
// backdrop
X1=ScreenWidth *0.00
X2=ScreenWidth *0.25
X3=SCreenWidth *0.50
X4=SCreenWidth *0.75
X5=SCreenWidth *1.00lockbuffer
PaletteBandSize=50
ThisRGB1=Palette(0)for ylp=0togetscreenheight()-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,0Syncloopspacekey()
RopeEdges++flushkeysnextendFunction 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=0static BaseAngle#
ThisRGB1 = Palette(RopeEdges)For lp=0To 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
Nextdec segments
For ThisScanLine=0To 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=0to 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.33Next
BaseAngle#++EndFunction; ===============================================================; Golden Ratio based pleasant random colors; - Each call with incrementing index gives nicely spaced hues; ===============================================================constant GoldenRatio# =0.6180339; ˜ (v5 - 1) / 2Function 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 themIf Saturation# <0.4Then Saturation#=0.4If Saturation# >0.98Then Saturation#=0.98If Value# <0.5Then Value#=0.5If Value# >0.98Then Value#=0.98#ifplaybasicversion=>165
Result = HSV2RGB(Hue#, Saturation#, Value#)#else
Result=rndrgb()#EndifEndFunction Result
Function RenderImage(ThisImage,xpos,ypos,Mode=false)ifGetIMageStatus(ThisIMAGE)inkmode1+2048boxc00,18,275,112,true,$a0a0a0inkmode1
Xpos+=10
Ypos+=20ink$80fFFFFFText XPos,Ypos ," Fps:"+Str$(fps())Text XPos,Ypos+40 ,"Depth:"+Str$(GetIMageDepth(ThisIMage))rendertoscreenlockbufferdrawimage ThisImage,0,0,falseunlockbufferendifEndFunction
Heli Blaster
By: Kevin Picone | Added: April 10th, 2026
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
By: Kevin Picone | Added: March 26th, 2026
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 rateSetFPS60; Create and configure the camera
Cam =NewCamera(); Enable rotated / perspective rendering mode
CameraDrawMode Cam, 2; Disable automatic clearing when drawing the cameraCameraCLS 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 bufferCaptureToScene; Clear previous frame's scene dataClsScene; Enable additive blending for glow effectInkMode1+32; ----------------------------------------------------; Draw multiple grid layers at different depths; ----------------------------------------------------For lp =0To100Step8; Assign depth (further layers appear smaller)CaptureDepth500+(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 cameraDrawCamera Cam
; ----------------------------------------------------; Camera Movement Controls; ----------------------------------------------------
Speed# =5IfLeftKey()ThenMoveCamera Cam, -Speed#, 0IfRightKey()ThenMoveCamera Cam, Speed#, 0IfUpKey()ThenMoveCamera Cam, 0, -Speed#
IfDownKey()ThenMoveCamera 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.25IfLeftMouseButton()OrKeyState(44)Then TurnAngle# =-RotationSpeed#
IfRightMouseButton()OrKeyState(45)Then TurnAngle# = RotationSpeed#
; Cycle camera modes when ENTER is pressedIfEnterKey()
CameraDrawMode Cam, GetCameraDrawMode(Cam)+1FlushKeysEndIf; Apply rotation if neededIf TurnAngle# <>0
Angle# = GetCameraAngle(Cam)
RotateCamera Cam, Angle# + TurnAngle#
EndIf; Update displaySyncLoopSpaceKey(); --------------------------------------------------------; 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 positionsFor ylp =0To GridHeight
For xlp =0To 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
NextNextEndFunction
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.