VPW Example Table Walkthrough¶
The VPW Example Table is the canonical reference implementation showing VPW build conventions. It's a bag of tricks, not a framework - designed for copying features into your own projects.
Purpose & Philosophy¶
What it is: - Demonstration of standard VPW features in one place - Copy-and-paste reference for specific techniques - Best practices implementation examples
What it's NOT: - Not a game framework (like Orbital or JPSalas) - Not meant to be used as complete starting point for game logic - Not a substitute for understanding the underlying systems
Core Features (v1.0+)¶
Physics & Flippers¶
- nFozzy flippers and physics corrections
- Slingshot angle correction (4-7 degree range recommended)
- Kicker randomization
- TargetBouncer for realistic target hits
- Rubberizer for post/sleeve dampening
Lighting System¶
- Lampz light fading
- 3D inserts (2-prim version recommended)
- Flupper dome flashers with bloom
- GI lamp control architecture
- Modulated signal support (PWM-style)
Audio¶
- Fleep sound package integration
- Ramp rolling SFX (ZRRL system)
- Ball rolling sounds with volume parameter (no _amp9 suffix)
- Mechanical tilt implementation
Visual Effects¶
- Dynamic ball shadows with Z-scaling on ramps
- RTX shadow primitives with soft edges
- Rendered playfield shadows
- Roth drop targets with shadows
VR Support¶
- VR cabinet and room
- FlexDMD scoreboard with intro
Original Table Features (v1.4+)¶
- Queue system for multiball/jackpot/bonus sequences
- FlexDMD scene initialization and management
- Physical trough (balls created once, never destroyed)
Key Implementation Details¶
2-Prim vs 3-Prim Inserts¶
Why 2-prim is default: - 3-prim inserts extremely difficult to tune correctly - Requires precise material opacity adjustment (can even be reversed between On/Off) - Depth bias complications - 2-prim version tunable with any material/depth combination
Lampz Control Light Method (VPX 10.7+)¶
For VPX built-in LightSequencer compatibility:
- Add invisible "control light" as first element in MassAssign arrays
- Control all lights through these control lights
- All other objects in array fade properly
- Invisible lights must be positioned within playfield area
- Requires GetInPlayStateBool property (10.7+)
Lampz Timer Configuration¶
Critical: Use fixed interval, NOT frame rate (-1) - Frame rate linking causes different fading speeds per player (165Hz vs 30fps) - On some GPUs causes FPS cycling (30-160fps with 1-second intervals) - Fixed interval resolves both consistency and performance issues
GI Architecture¶
Incorrect approach: Changing lamp states for entire GI collection directly - Breaks Lampz fading logic
Correct approach: Single control lamp controls collection via Lampz - Preserves fading behavior - Maintains proper state management
Physical Trough Best Practice¶
Balls created once at initialization, never destroyed:
- Forces realistic table behavior
- Makes ball tracking much easier
- Shadow ramp-type tracking requires this approach
- SolRelease kicks next ball, chain reaction prepares for next
Note: ROM compatibility requires control logic outside SolRelease sub
gBOT (Global Ball-On-Table) vs GetBalls¶
Use gBOT array instead of GetBalls calls:
- Physical trough creates balls once at init
- More realistic behavior
- Required for shadow tracking
- If using traditional scripting (destroying balls), replace gBOT with BOT and use GetBalls
Dynamic Ball Shadow Implementation¶
Primitive shadows recommended (not flashers): - Primitives can be scaled on-fly - Flashers cannot scale dynamically - Z-scaling on ramps:
objBallShadow(s).size_x = 5 * ((gBOT(s).Z+BallSize)/80)
objBallShadow(s).size_y = 4.5 * ((gBOT(s).Z+BallSize)/80)
UpdateMaterial objBallShadow(s).material,1,0,0,0,0,0,AmbientBSFactor*(30/(gBOT(s).Z)),RGB(0,0,0),0,0,False,True,0,0,0,0
Optimization: Don't write size/UpdateMaterial every loop iteration - Detect when ball returns to playfield - Only write original values once - Leave only position updates in main loop
Flupper Dome Flasher Setup¶
Auto-placement code:
If objbase(nr).roty > 135 then
objflasher(nr).y = objbase(nr).y + 50
objflasher(nr).height = objbase(nr).z + 20
Else
objflasher(nr).y = objbase(nr).y + 20
objflasher(nr).height = objbase(nr).z + 50
End If
objflasher(nr).x = objbase(nr).x
Light object placement:
objlight(nr).x = objbase(nr).x
objlight(nr).y = objbase(nr).y
objlight(nr).bulbhaloheight = objbase(nr).z - 10
Flasher Bloom: - FlasherBloom placed at table glass height/angle - Tied to Flupper dome code - Position with additive blend OFF, then re-enable - Used in Bad Cats, Austin Powers, NBAF, Radical
Ball Rolling Sounds (v1.14+)¶
Use loudest sound effect with volume parameter:
- No multiple pre-recorded amplitude levels
- _amp9 suffix removed from all sound names
- Simplifies sound management
- Volume controlled programmatically
Export multiple sounds from VPX: Select all desired sounds, click export - exports all selected
Slingshot Angle Correction¶
Standard for VPW tables:
- 4-7 degree range is correct (original 10 degrees too high)
- Based on video research
- Don't forget velocity correction calls from _slingshot subs
- Requires dsin/dcos functions
Kicker Implementation¶
Always add small randomization: - Not needed if Game Difficulty > 20 - Caution with Game Difficulty variable - found in unexpected VPX source locations
TargetBouncer¶
Critical: Requires idx parameter:
' Wrong:
Sub TargetBounce_Hit
TargetBouncer activeball, 2
End Sub
' Correct:
Sub TargetBounce_Hit(idx)
TargetBouncer activeball, 2
End Sub
Rubber Dampener Scope¶
RubbersD applies to posts and sleeves only, NOT rubber bands:
- Sleeves use different profile than posts
- nFozzy/rothbauerw design
ROM Integration¶
SoundCommandListener: Intercept ROM sound commands for custom handling
SetLamp for SolCallback:
Bridges SolCallback array with Lampz fading system
Solenoid subs architecture: - Fire kicker regardless of ball presence - Only contain enabled/disabled parameter logic - No game control logic inside - Control logic determining when to call should exist outside - For original tables: add BIPL check BEFORE calling SolRelease, not inside
Original Table Features (v1.4+)¶
Queue System¶
Examples implemented: - Multiball launch with FlexDMD animation - Jackpot on ramp shot - Bonus X on bumper inlanes - Drop target bonus on saucer - Queue-controlled saucer ejection with flasher warning
FlexDMD scenes: Initialized into array at Flex_Init using ShowScene wrapper
- Prevents errors from re-creating scenes each time shown
- Animated GIFs do NOT restart from beginning each show
Note: Not ROM-compatible - ROM tables have queuing in ROM logic
Version History & Migration¶
VPX 10.6 to 10.7.2 Migration¶
Decision factors: - 10.7.2 superior for players (better layers, webp image savings) - 10.7.x editor lost authoring features (camera/debugger preview, multi-select viewing, object list refresh, dragging) - More layers for organizing techniques - Example table is copy-paste reference, not interactive debugging tool - Caveat: Once saved in 10.7, cannot revert to 10.6
Major Version Updates¶
- v1.12: Dynamic shadows converted to arrays (rothbauerw implementation from Tee'd Off)
- v1.14: Ball rolling sounds updated,
_amp9suffix removed - v1.21-1.24: Slingshot angle correction added
- v1.26: ObjTargetLevel for flasher domes, modulated signal support
- v1.32: Ball shadow Z-scaling on ramps
- v1.4: Queue system for original tables
- v1.5.3: Rendered playfield shadows, updated RTX shadow image
Common Issues & Fixes¶
FlexDMD VR Exit Error¶
Prevents error from VR terminating FlexDMD before VPX cleanupDTArrayID/STArrayID Bug¶
Exit Function must be INSIDE the If block:
Function DTArrayID(switch)
Dim i
For i = 0 To UBound(DTArray)
If DTArray(i)(3) = switch Then
DTArrayID = i
Exit Function ' Must be inside If
End If
Next
End Function
Removing Bumpers¶
- Comment out initialization lines
- Delete table objects
- Update
For ind = 1 to Xloop count
VPX File Corruption from Full SSD¶
- VPX creates 12KB dummy file when drive full
- Reports success but doesn't save
- Keep adequate free space
- Verify file size after saving critical work
- VPX autosave unreliable (saves twice then stops)
Fleep Sound Dependencies¶
Error "Variable is undefined: 'min'" when calling RandomSoundBallBouncePlayfieldSoft/Hard: - Missing utility functions from ZMAT section - Functions may show purple but still need copying
Best Practices¶
Code Formatting (VPW Recommendation)¶
- Use tabs (not spaces) for indentation
- Dim and assign on separate lines (enables VPX hover comments)
- Section headers with unique 4-letter search codes
- Table of Contents at top of script
- Comment headers between major libraries
- Commented code: one tab after apostrophe
Version Control¶
- Always verify VPX version before saving shared tables
- VPX doesn't warn about version changes
- Check save format matches project standard
Saving & Verification¶
- Always verify file size after save
- Full SSD can cause silent corruption
- Keep backups of working versions
- Use WinMerge (https://winmerge.org) for comparing script changes
Tools & Comparison¶
WinMerge for script comparison: - Compare folders and files - Visual diff presentation - Detects undocumented changes - Essential for multi-author tables