Timer-Based Movement - Frame Rate Independent Motion Example

September 26, 2024

 

Timer-Based Movement - Frame Rate Independent Motion Example

When developing video games, one of the key challenges is ensuring smooth and consistent movement across different devices and frame rates. A game running on a high-end computer might process frames much faster than one on an older machine. If movement is tied directly to the frame rate, objects will move faster on some systems and slower on others, leading to inconsistent gameplay. To avoid this, we use timer-based movement, also known as frame rate independent motion.

This PlayBASIC code snippet shows how to make moving objects (balls) in a game move independently of the frame rate. This means the movement remains smooth and consistent regardless of how fast or slow the game runs. This is achieved by using timers to calculate the position of each ball based on elapsed time rather than relying on the game’s frame rate.

How It Works

1. Setting Frame Rate Goals

The program sets a target frame rate of 50 frames per second (FPS) and calculates how many milliseconds each frame should last:

1000 / 50 = 20ms per frame

This helps standardize movement calculations.

2. Ball Setup

The balls are defined using a custom type (`tBALL`), which includes:

  • Position (`X`, `Y`) – The current coordinates of the ball.
  • Size – The radius of the ball.
  • Color – A randomly assigned color.
  • Speed – The movement speed of the ball.
  • Direction – The angle at which the ball moves.
  • Start Time – The timestamp when the ball was created.
  • 3. Adding Balls

    New balls are randomly generated within the display area. Every 50 milliseconds, multiple new balls are added with:

  • Random positions within the screen’s width and height.
  • Random sizes and colors for variety.
  • Random movement speeds and directions to create dynamic movement.
  • 4. Moving the Balls

    Each ball's movement is updated based on the elapsed time since its creation:

    1. 1. Calculate elapsed time – Determine how long the ball has existed.
    2. 2. Compute the distance traveled – Using the formula:
    3.    Distance = (Speed / Frame Rate Milliseconds) * Elapsed Time
    1. 3. Update position – The ball moves based on its speed, direction, and the elapsed time using trigonometric calculations:
    2. X = OriginX + cos(Direction) * Distance
      Y = OriginY + sin(Direction) * Distance

    This movement is frame rate independent, meaning the speed is consistent even if the frame rate fluctuates.

    5. Rendering and Removal

  • Each ball is drawn at its updated position.
  • If a ball moves outside the screen, it is removed from the list.
  • 6. Display Information

    The program also provides real-time feedback by displaying:

  • Total number of active balls
  • Current frames per second (FPS)
  • Why Timer-Based Movement Matters

    Many games rely on frame-dependent movement, where objects move a fixed amount per frame. This can cause issues:

  • If the frame rate drops, movement appears slower.
  • If the frame rate increases, movement appears faster.
  • By using time-based calculations, the movement remains consistent, making the game perform smoothly across different hardware and performance conditions.

    This technique is essential for modern game development, ensuring a better player experience by maintaining consistent motion no matter the frame rate.



    Source Code Example:


    // Set our users ideal frames per second rate
    Frame_RATE# = 50
    
    // Ticks per frame
    Frame_RATE_MILLISECOND_PER_FRAME# = 1000.0/Frame_RATE#
    
    
    Type tBALL
    		X#
    		Y#
    		Size
    		Colour
    		Speed#
    		Direction#
    		StartTime
    EndType
    
    Dim Ball as TBall List
    
    CurrentTime = timer() and $7fffffff
    
    
    SurfaceWidth = GetSurfaceWidth()
    SurfaceHeight = GetSurfaceHeight()
    
    Do
    
    	Cls
    
    	CurrentTime= Timer() and $7fffffff
    
    	//  Randomly Add more balls to the scene
    	if Add_Balls < CurrentTime
    
    		for lp =1 to rndrange(100,500)
    			Ball        = new tBall
    			Ball.x      = rndrange(100,SurfaceWidth-100)
    			Ball.y      = rndrange(100,SurfaceHeight-100)
    			Ball.size   = rndrange(10,20)
    			Ball.Colour = rndrgb()
    			Ball.Speed  = rndrange#(1, 5)
    			Ball.Direction= rnd#(360)
    			Ball.StartTime= CurrentTime
    		next
    		Add_Balls=CurrentTime+50
    	endif
    
    	//  Draw Balls
    	lockbuffer
    	For each Ball()
    
    		// How long as this ball been alive
    		// and move in this direction?
    		ElapsedTime = CurrentTime-Ball.StartTime
    
    		// compute how far this ball have
    		//  moved since creation
    		Dist# = (Ball.Speed /Frame_RATE_MILLISECOND_PER_FRAME#)
    		Dist#*= ElapsedTime
    
    		//  Compute the moved position from
    		// it's origin point
    		x#=Ball.x+cos(ball.direction)*Dist#
    		y#=Ball.y+sin(ball.direction)*Dist#
    
    
    		//  Get size of this ball
    		Size=ball.size
    
    		// check if the ball is on screen or not ?
    		// if not; delete it from the list
    		if x#<(-size) or x#>(SurfaceWidth+size)_
    			or y#<(-ball.size) or y#>(SurfaceHeight+size)
    				Ball=null
    				continue
    		endif
    
    		// draw the ball since it's still visible
    		circlec x#,Y#,size,true,ball.colour
    
    	Next
    	unlockbuffer
    
    	text 10,10,"Balls #"+str$(GetListSize(Ball()))
    	text 10,20,"  Fps #" +str$(fps())
    
    	sync
    
    Loop spacekey()





    By implementing these principles in your PlayBASIC projects, you'll create games that feel polished and professional, no matter the hardware they're running on!

    Improving the Performance of the Classic Bubble Sort Algorithm

    May 16, 2022

     

    Sorting algorithms are a crucial part of programming, and choosing the right one for your data is essential for optimal performance. However, even simple algorithms like Bubble Sort can be improved to handle larger datasets more efficiently. In this post, we’ll explore a few ways to optimize the classic Bubble Sort algorithm, using a PlayBASIC example to demonstrate the improvements.

    Understanding Bubble Sort

    Bubble Sort is one of the most commonly taught sorting algorithms in programming. It’s simple to understand but can be slow for large datasets. The concept is straightforward: you iterate through the data, comparing adjacent elements, and swap them if they are in the wrong order. The process repeats until no swaps are necessary, meaning the array is sorted.

    The key flaw of Bubble Sort is that it’s an "n-squared" algorithm, meaning its performance degrades rapidly as the number of elements in the array increases. Despite this, there are still a few optimizations we can apply to make it faster in certain situations.

    Optimizing Bubble Sort

    While Bubble Sort will never be the fastest sorting algorithm, there are ways to make it more efficient for specific datasets. Below are a couple of key improvements that can help speed up the process.

    1. Reduce the Set Size After Each Pass

    One improvement involves reducing the size of the array that’s being processed after each pass. As each pass moves the largest remaining element to the end of the array, you don’t need to check it again in subsequent passes. By decreasing the range of elements to check after each pass, you can reduce unnecessary comparisons and speed up the sorting process.

    2. Bi-directional Bubble Sort

    Instead of only iterating left to right, the Bi-directional Bubble Sort (also known as Cocktail Shaker Sort) goes through the array in both directions. The first pass moves the largest element to the end of the array (just like the classic version), but the next pass moves the smallest element to the beginning of the array. By alternating directions, this approach can reduce the number of passes needed to sort the data.

    Example Code in PlayBASIC

    Here’s an example implementation of these optimizations in PlayBASIC, which demonstrates the classic Bubble Sort alongside the faster variants:


    loadfont "Courier New", 1, 24
    
    MaxItems = 500
    DIM Table(MaxItems)
    DIM Stats#(10, 5)
    
    DO
        Cls
    
        inc frames
        Seed = Timer()
    
        Test = 1
    
        SeedTable(Seed, MaxItems)
        StartInterval(0)
        ClassicBubbleSort(MaxItems)
        tt1 +  = EndInterval(0)
        test = Results("Classic Bubble Sort:", Test, MaxItems, Tt1, Frames)
    
    
    
        SeedTable(Seed, MaxItems)
        StartInterval(0)
        ClassicBubbleSortFaster(MaxItems)
        tt2 +  = EndInterval(0)
        test = Results("Classic Bubble Sort Faster:", Test, MaxItems, TT2, Frames)
    
    
        SeedTable(Seed, MaxItems)
        StartInterval(0)
        BiDirectionalBubbleSort(MaxItems)
        tt3 +  = EndInterval(0)
        test = Results("BiDirectional Bubble Sort:", Test, MaxItems, Tt3, Frames)
    
    
        Sync
    
        REPEAT
        UNTIL enterkey() = 0
    
    LOOP
    
    
    FUNCTION ShowTable(items)
    
        t$ = ""
        n = 0
        FOR lp = 0 to items
            T$ = t$ + str$(table(lp)) + ", "
            inc n
            IF n > 10
                t$ = Left$(t$, Len(t$) - 1)
                print t$
                t$ = ""
                n = 0
            ENDIF
    
        NEXT lp
    
        IF t$ <  > "" THEN print Left$(t$, Len(t$) - 1)
    
    ENDFUNCTION
    
    
    FUNCTION SeedTable(Seed, Items)
        Randomize seed
        FOR lp = 0 to Items
            Table(lp) = Rnd(32000)
        NEXT lp
    ENDFUNCTION
    
    
    FUNCTION ValidateTable(Items)
        result = 0
        FOR lp = 0 to items - 1
            IF Table(lp) > Table(lp + 1)
                result = 1
                exit
            ENDIF
        NEXT lp
    ENDFUNCTION Result
    
    
    
    FUNCTION Results(Name$, index, Items, Time, Frames)
        ` Total Time
        Time = Time / 1000
        Stats#(index, 1) = Stats#(index, 1) + time
    
        print "Sort Type:" + name$
        print "Total Time:" + str$(Stats#(index, 1))
        print "Average Time:" + str$(Stats#(index, 1) / frames)
    
        IF ValidateTable(Items) = 0
            Print "Array Sorted"
            ELSE
            print "NOT SORTED - ERROR"
        ENDIF
        print ""
    
        inc index
    
    ENDFUNCTION index
    
    
    
    
    FUNCTION ClassicBubbleSort(Items)
        Flag = 0
        REPEAT
            Done = 0
            FOR lp = 0 to items - 1
                IF Table(lp) > Table(lp + 1)
                    done = 1
                    t = Table(lp)
                    Table(lp) = Table(lp + 1)
                    Table(lp + 1) = t
                ENDIF
            NEXT lp
        UNTIL done = 0
    ENDFUNCTION
    
    
    
    FUNCTION ClassicBubbleSortFaster(Items)
        Flag = 0
        REPEAT
            Done = 0
            dec items
            FOR lp = 0 to items
                IF Table(lp) > Table(lp + 1)
                    done = 1
                    t = Table(lp)
                    Table(lp) = Table(lp + 1)
                    Table(lp + 1) = t
                ENDIF
            NEXT lp
        UNTIL done = 0
    ENDFUNCTION
    
    
    FUNCTION BiDirectionalBubbleSort(Items)
        First = 0
        Last = Items
    
        REPEAT
            Done = 0
            dec Last
            FOR lp = First to Last
                V = Table(lp + 1)
                IF Table(lp) > V
                    done = 1
                    Table(lp + 1) = Table(lp)
                    Table(lp) = v
                ENDIF
            NEXT lp
    
            IF Done = 1
                Done = 0
                inc First
                FOR lp = Last to First step - 1
                    V = Table(lp - 1)
                    IF V > Table(lp)
                        Done = 1
                        Table(lp - 1) = Table(lp)
                        Table(lp) = v
                    ENDIF
                NEXT lp
            ENDIF
        UNTIL Done = 0
    ENDFUNCTION

    Explanation of the Code

  • Table Initialization: We start by defining an array (`Table`) and filling it with random numbers using the `SeedTable` function.
  • Sorting Functions: Three sorting functions are defined:
  • - `ClassicBubbleSort`: The traditional Bubble Sort that compares adjacent elements and swaps them.

    - `ClassicBubbleSortFaster`: This is an optimized version of the classic algorithm where we reduce the set size after each pass.

    - `BiDirectionalBubbleSort`: This method sorts the array by alternating the direction of passes, improving performance.

  • Performance Tracking: The sorting times are tracked using `StartInterval` and `EndInterval`, allowing us to compare the performance of each sorting method.
  • Results and Performance

    After running the sorting methods, we display the results, including the total time taken and the average time per frame. We also validate that the array is correctly sorted at the end of each method.

    The results can vary depending on the size of the dataset, but in most cases, the optimized versions of Bubble Sort will show significant performance improvements compared to the classic method.

    Final Thoughts

    While Bubble Sort is not the most efficient sorting algorithm, these optimizations provide a good demonstration of how you can improve its performance in certain scenarios. Reducing the size of the set and implementing bi-directional sorting can make the classic Bubble Sort more practical for moderate-sized datasets.

    However, if you’re dealing with larger datasets, it’s often better to use more advanced sorting algorithms like Merge Sort or Quick Sort, which offer much better performance.

    As always, the key takeaway is that sorting is situational, and selecting the right algorithm for your data is essential. These optimizations are not a silver bullet but can provide useful improvements in the right circumstances.

    Have Fun with Sorting!

    Sorting is a fundamental concept in computer science, and experimenting with different algorithms and optimizations can help you understand how they work. Feel free to try out these optimizations in your own projects and see how they perform with your data!

    Links:

  • PlayBASIC,com


  • Syntax Highlighting PlayBASIC Code Example

    August 03, 2012

     

    Vector Library

    This is simple library of 2d vector functions. Bellow is the example.

    	//  Use a typed array to hold all the vectors we'll be using
    	Dim vectors(100) as vector2d
    	for lp =0 to getArrayElements(vectors())
    		vectors(lp)= new vector2D
    	next
    
    
    	//  declare some pointers of type vector
    	// for use in the examples
    	Dim N as vector2d pointer
    	Dim V1 as vector2d pointer
    	Dim V2 as vector2d pointer
    	Dim OldPoint as vector2D pointer
    
    	//  Get pointers to some pre--alloated
    	//  vectors in our cache array
    	n = Vectors(0).vector2d
    	V1 = Vectors(1).vector2d
    	V2 = Vectors(2).vector2d
    
    
    	Dim CurrentPoint as vector2D pointer
    	CurrentPOint = Vectors(3).vector2d
    
    	Dim OldPoint as vector2D pointer
    	OldPOint = Vectors(4).vector2d
    
    	Dim Lerp1 as vector2D pointer
    	Lerp1 = Vectors(10).vector2d
    
    	Dim Lerp2 as vector2D pointer
    	Lerp2 = Vectors(11).vector2d
    
    
    	SetVector2d lerp1, 500,100
    	SetPolar2d lerp2, Rnd(36),rndrange(10,100)
    	Addvectors2d Lerp2, Lerp2, Lerp1
    
    
    
    	// Set up this vector
    	Setvector2d OldPOint,Mousex(),MouseY()
    	Flushmouse
    
    	// -----------------------------------------
    	Do
    	// -----------------------------------------
    
    		CLS
    
    
    		// Set up this vecor
    		Setvector2d CurrentPoint,Mousex(),MouseY()
    
    
    		circlec OldPoint.X,OldPoint.Y, 50,true, $00ff00
    
    		circle CurrentPoint.X,CurrentPOint.Y, 50,true
    
    
    		// Subtract target from current  N = Old - Current
    		Subvectors2d(N , oldPoint,CurrentPoint)
    
    		ypos= 50
    
    		text 10,Ypos, "Delta:"+StrVector2d$(N)
    
    		//  Get the length of this delta vector
    		Dist#=GetVectorLenght2D(N)
    
    		// normalize N = and store it back in N
    		GetVectorNormal2D(N , N)
    
    		text 10,Ypos+30, "Normalized:"+StrVector2d$(N)
    
    
    		//  Scale Normal by Dist#
    		Multvector2d(N , N, Dist#)
    
    		text 10,Ypos+60, "Scaled Normalized:"+StrVector2d$(N)
    
    		//  Add origin (current point) back on.
    		Addvectors2d(N , N, CurrentPoint)
    
    		text 10,Ypos+90, "Result:"+StrVector2d$(N)
    
    
    		//  Scale it  so we can draw line between them
    		line CurrentPOint.x,CurrentPOint.y,n.x,n.y
    
    		if Mousebutton()=1
    				CopyVector2d OldPoint,CurrentPOint
    		endif
    
    
    		//  test lerp
    		Linec Lerp1.x,Lerp1.y,Lerp2.x,Lerp2.y, $ff00ff
    
    		//
    		LerpFrame		=mod(LerpFrame+1,100)
    		LerpScale#		=LerpFrame/100.0
    
    		LerpVector2d N,Lerp1,Lerp2,LerpScale#
    
    		Circlec n.x , n.y, 5, true ,$ff00ff
    
    
    		//  Curve Lerp vector to the one between
    		LerpVector2d V1,Lerp1,Lerp2,LerpScale#
    		LerpVector2d V2,CurrentPoint,OldPoint,LerpScale#
    		LerpVector2d V1,V2,V1,LerpScale#
    		Circlec v1.x , v1.y, 5, true ,$ff00ff
    
    		LerpVector2d V1,Lerp1,Oldpoint,LerpScale#
    		LerpVector2d V2,lerp2,CurrentPoint,LerpScale#
    		LerpVector2d V1,V2,V1,LerpScale#
    		Circlec v1.x , v1.y, 5, true ,$ff00ff
    
    		Sync
    
    loop  Spacekey()