Starfield Simulation

Creating Games? Developing something for fun?

Moderators: FourthWorld, heatherlaine, Klaus, kevinmiller, robinmiller

Post Reply
vbro
Livecode Opensource Backer
Livecode Opensource Backer
Posts: 7
Joined: Tue Mar 26, 2013 7:20 am
Location: Los Angeles, CA
Contact:

Starfield Simulation

Post by vbro » Sat Jun 08, 2013 9:17 am

I'm currently trying to code an animated starfield (with parallax) for a game I'm working on, however, I've run into a bit of difficulty. I started by taking an implementation in Python that I found and 'translating' it to LiveCode. It 'works' so far, except that each repeat of the main loop runs extremely slow, even when I'm running the simulation with as few as 10 or 15 stars.

At the moment, I'm drawing the stars as single-point line graphics, and animating them with the move command. I'm not sure if that's the best approach, or if should be using tiny pixel images instead. Is animating many objects at once smoothly a limitation of LiveCode, or am I doing something completely wrong?

This is the code that I put in the stop/start button of the card:

Code: Select all

local sContinue

on mouseUp
   if label of me is "Start" then
      put true into sContinue
      set title of button "startStop" to "Stop"
      startSim
   else if label of me is "Stop" then
      put false into sContinue
      set title of button "startStop" to "Start"
   end if
end mouseUp

on startSim
   local kMAX_STARS, kSTAR_SPEED
   local tStarsA, tContinue
   
   // Set constants
   put 15 into kMAX_STARS
   put 4 into kSTAR_SPEED
   
   // Create stars array and star objects
   put initStarsArray(kMAX_STARS, kSTAR_SPEED) into tStarsA
   initStarObjects tStarsA
   
   repeat while sContinue is true
      animateStars tStarsA, kMAX_STARS, kSTAR_SPEED
   end repeat
end startSim

function initStarsArray pMaxStars pStarSpeed
   local tStar, tStarsA
   
   // Generate attributes for the star
   repeat with starNum = 1 to pMaxStars
      put random(the width of this card) - 1 into tStar["x"]
      put random(the height of this card) - 1 into tStar["y"]
      put randomInRange(2,pStarSpeed) into tStar["speed"]
      
      // Store the star in the array
      put tStar into tStarsA[starNum]
   end repeat
   
   return tStarsA
end initStarsArray

command initStarObjects pStarsA
   local tStarNum, tStarName, tColor
   put 1 into tStarNum
   
   lock screen
   repeat for each element thisStar in pStarsA
      put "star" & tStarNum into tStarName
      
      // Create the star as a grapic object,
      // and make sure we're not making duplicate objects.
      if exists(graphic tStarName) is false then
         create graphic tStarName
         set the style of graphic tStarName to "polygon"
         set the layerMode of graphic tStarName to "dynamic"
         --set the antialiased of graphic tStarName to false
      end if

      // Set its points and size
      set the points of graphic tStarName to thisStar["x"] & comma & thisStar["y"]
      set the lineSize of graphic tStarName to thisStar["speed"]
      
      # Set the star color acording it's speed.
      # The slower the star, the darker should be its color.
      if thisStar["speed"] is 2 then
         put "#646464" into tColor
      else if thisStar["speed"] is 3 then
         put "#BEBEBE" into tColor
      else if thisStar["speed"] is 4 then
         put "#FFFFFF" into tColor
      end if
      set the foregroundColor of graphic tStarName to tColor
      
      // Reference the next star
      add 1 to tStarNum
   end repeat
   unlock screen
end initStarObjects

command animateStars @pStarsA pMaxStars pStarSpeed 
   local tStarName, tColor
   
   lock screen
   --set the lockMoves to true
   repeat with thisStar = 1 to pMaxStars
      put "star" & thisStar into tStarName
      
      // 'Move' the star's position in the array.
      subtract pStarsA[thisStar]["speed"] from pStarsA[thisStar]["y"]
      
      // Update stars array.
      // If the star hits the top of the screen
      // then move it to the top of the screen with a random X coordinate.
      if pStarsA[thisStar]["y"] < -5 then
         put the height of this card into pStarsA[thisStar]["y"]
         put random(the width of this card - 1) into pStarsA[thisStar]["x"]
         put randomInRange(1,pStarSpeed) into pStarsA[thisStar]["speed"]
         
         # Adjust the star color acording to it's speed.
         # The slower the star, the darker should be its color.
         if pStarsA[thisStar]["speed"] is 2 then
            put "#646464" into tColor
         else if pStarsA[thisStar]["speed"] is 3 then
            put "#BEBEBE" into tColor
         else if pStarsA[thisStar]["speed"] is 4 then
            put "#FFFFFF" into tColor
         end if
         set the foregroundColor of graphic tStarName to tColor
         set the lineSize of graphic tStarName to pStarsA[thisStar]["speed"]
      end if
      
      // Move the star object.
      move graphic tStarName to pStarsA[thisStar]["x"] & comma & pStarsA[thisStar]["y"] in 0 ticks
   end repeat
   --set the lockMoves to false
   unlock screen
end animateStars

command deleteStars
   local tStarName
   lock screen
   repeat with thisStar = 1 to the number of graphics of this card
      put "star" & thisStar into tStarName
      delete graphic tStarName
   end repeat
   unlock screen
end deleteStars

function randomInRange lowerLimit upperLimit
   return random(upperLimit - lowerLimit + 1) + lowerLimit - 1
end randomInRange

bn
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 4027
Joined: Sun Jan 07, 2007 9:12 pm
Location: Bochum, Germany

Re: Starfield Simulation

Post by bn » Sat Jun 08, 2013 10:34 am

Hi vbro,

complex animations is not the strength of Livecode. But what you are aiming at is feasable.

I took your code and changed it slightly to speed things up.
The main change is that I don't use a repeat loop but use

send animateStars in xxx milliseconds to me

The advantage being that repeat is blocking whereas send in time is not blocking.

and I dont use the move command but set the location of the graphics directly.(a lot faster for many objects)

I increased the number of objects to 150 and kSTAR_SPEED to 6 (did not adjust the coloring)

Code: Select all

local sContinue

on mouseUp
   if label of me is "Start" then
      put true into sContinue
      set title of button "startStop" to "Stop"
      startSim
   else if label of me is "Stop" then
      put false into sContinue
      set title of button "startStop" to "Start"
   end if
end mouseUp

on startSim
   local kMAX_STARS, kSTAR_SPEED
   local tStarsA, tContinue
   
   // Set constants
   put 150 into kMAX_STARS
   put 6 into kSTAR_SPEED
   
   // Create stars array and star objects
   put initStarsArray(kMAX_STARS, kSTAR_SPEED) into tStarsA
   initStarObjects tStarsA
   
   -- repeat while sContinue is true
   --   animateStars tStarsA, kMAX_STARS, kSTAR_SPEED
   -- end repeat
   
   send "animateStars tStarsA, kMAX_STARS, kSTAR_SPEED" to me in 0 milliseconds
end startSim

function initStarsArray pMaxStars pStarSpeed
   local tStar, tStarsA
   
   // Generate attributes for the star
   repeat with starNum = 1 to pMaxStars
      put random(the width of this card) - 1 into tStar["x"]
      put random(the height of this card) - 1 into tStar["y"]
      put randomInRange(2,pStarSpeed) into tStar["speed"]
      
      // Store the star in the array
      put tStar into tStarsA[starNum]
   end repeat
   
   return tStarsA
end initStarsArray

command initStarObjects pStarsA
   local tStarNum, tStarName, tColor
   put 1 into tStarNum
   
   lock screen
   repeat for each element thisStar in pStarsA
      put "star" & tStarNum into tStarName
      
      // Create the star as a grapic object,
      // and make sure we're not making duplicate objects.
      if exists(graphic tStarName) is false then
         create graphic tStarName
         set the style of graphic tStarName to "polygon"
         set the layerMode of graphic tStarName to "dynamic"
         --set the antialiased of graphic tStarName to false
      end if
      
      // Set its points and size
      set the points of graphic tStarName to thisStar["x"] & comma & thisStar["y"]
      set the lineSize of graphic tStarName to thisStar["speed"]
      
      # Set the star color acording it's speed.
      # The slower the star, the darker should be its color.
      if thisStar["speed"] is 2 then
         put "#646464" into tColor
      else if thisStar["speed"] is 3 then
         put "#BEBEBE" into tColor
      else if thisStar["speed"] is 4 then
         put "#FFFFFF" into tColor
      end if
      set the foregroundColor of graphic tStarName to tColor
      
      // Reference the next star
      add 1 to tStarNum
   end repeat
   unlock screen
end initStarObjects

command animateStars pStarsA pMaxStars pStarSpeed
   if not sContinue then exit animateStars
   local tStarName, tColor
   
   lock screen
   --set the lockMoves to true
   repeat with thisStar = 1 to pMaxStars
      put "star" & thisStar into tStarName
      
      // 'Move' the star's position in the array.
      subtract pStarsA[thisStar]["speed"] from pStarsA[thisStar]["y"]
      
      // Update stars array.
      // If the star hits the top of the screen
      // then move it to the top of the screen with a random X coordinate.
      if pStarsA[thisStar]["y"] < -5 then
         put the height of this card into pStarsA[thisStar]["y"]
         put random(the width of this card - 1) into pStarsA[thisStar]["x"]
         put randomInRange(1,pStarSpeed) into pStarsA[thisStar]["speed"]
         
         # Adjust the star color acording to it's speed.
         # The slower the star, the darker should be its color.
         if pStarsA[thisStar]["speed"] is 2 then
            put "#646464" into tColor
         else if pStarsA[thisStar]["speed"] is 3 then
            put "#BEBEBE" into tColor
         else if pStarsA[thisStar]["speed"] is 4 then
            put "#FFFFFF" into tColor
         end if
         set the foregroundColor of graphic tStarName to tColor
         set the lineSize of graphic tStarName to pStarsA[thisStar]["speed"]
      end if
      
      // Move the star object.
      -- move graphic tStarName to pStarsA[thisStar]["x"] & comma & pStarsA[thisStar]["y"] in 0 ticks
      set the loc of  graphic tStarName to pStarsA[thisStar]["x"] & comma & pStarsA[thisStar]["y"]
   end repeat
   unlock screen
   if  not ("animateStars" is in  the pendingMessages)  then
      send "animateStars pStarsA, pMaxStars, pStarSpeed" to me in 10 milliseconds 
   end if
   
end animateStars

command deleteStars
   local tStarName
   lock screen
   repeat with thisStar = 1 to the number of graphics of this card
      put "star" & thisStar into tStarName
      delete graphic tStarName
   end repeat
   unlock screen
end deleteStars

function randomInRange lowerLimit upperLimit
   return random(upperLimit - lowerLimit + 1) + lowerLimit - 1
end randomInRange


for a related task have a look at:
http://forums.runrev.com/phpBB2/viewtop ... 10&t=11726
it uses just one graphic instead of many.
But I think you are ok with your approach. If however you want to use this on a mobile device you might have to tweak this considerably.
BTW I like your coding style

Kind regards
Bernd

vbro
Livecode Opensource Backer
Livecode Opensource Backer
Posts: 7
Joined: Tue Mar 26, 2013 7:20 am
Location: Los Angeles, CA
Contact:

Re: Starfield Simulation

Post by vbro » Sat Jun 08, 2013 8:19 pm

That is perfect, Bernd!

I'm still new to LiveCode so I'm not exactly clear on the term 'blocking'. However, I can see how you set up the animateStars to automatically loop itself with the conditional statements at the beginning and end of the handler. I had no idea that setting the location of the graphics directly would be dramatically faster than using the move command (in 0 ticks). I'd be curious to know the reason behind that since I'll be doing more animation in my project, though definitely with fewer objects at once than in this case.

A few changes I've made to the demo:

  • I got rid of the stars array and let the createStars and animateStars handlers calculate and modify the properties of the star graphics (loc and lineSize) directly, without referencing a separate source.
  • I changed the deleteStars handler so that it only deletes any excess stars over kMax_Stars at the beginning of the createStars handler, rather than deleting all stars every time the stack closes. I did this because the demo was creating and deleting hundreds of stars from scratch stars every run! (I've gone from a total number of ~1000 objects created in LiveCode to ~10,000 in two days!)

Code: Select all

local sContinue

on mouseUp
   if label of me is "Start" then
      put true into sContinue
      set title of button "startStop" to "Stop"
      startSim
   else if label of me is "Stop" then
      put false into sContinue
      set title of button "startStop" to "Start"
   end if
end mouseUp

on startSim
   local kMAX_STARS, kSTAR_SPEED
   local tContinue
   
   // Set constants
   put 150 into kMAX_STARS
   put 4 into kSTAR_SPEED
   
   // Create the star objects
   createStars kMAX_STARS, kSTAR_SPEED
   --repeat while sContinue is true
      --animateStars tStarsA, kMAX_STARS, kSTAR_SPEED
   --end repeat
   send "animateStars kMAX_STARS, kSTAR_SPEED" to me in 0 milliseconds
end startSim

command createStars pMaxStars pStarSpeed
   local tStarName, tColor
   local tX, tY
   
   lock screen
   repeat with starNum = 1 to pMaxStars
      put "star" & starNum into tStarName
      
      // Delete any excess star objects from previous runs.
      deleteStars pMaxStars
      
      // Create the star as a grapic object,
      // and make sure we're not making duplicately-named objects.
      if exists(graphic tStarName) is false then
         create graphic tStarName
         set the style of graphic tStarName to "polygon"
         set the layerMode of graphic tStarName to "dynamic"
         --set the antialiased of graphic tStarName to false
      end if
      
      // Set its points and size
      put random(the width of this card) -1 into tX
      put random(the height of this card) - 1 into tY
      set the points of graphic tStarName to tX & comma & tY
      set the lineSize of graphic tStarName to randomInRange(2,pStarSpeed)
      
      # Set the star color acording it's speed/size.
      # The slower/smaller the star, the darker should be its color.
      if the lineSize of graphic tStarName is 2 then
         put "#646464" into tColor
      else if the lineSize of graphic tStarName is 3 then
         put "#BEBEBE" into tColor
      else if the lineSize of graphic tStarName is 4 then
         put "#FFFFFF" into tColor
      end if
      set the foregroundColor of graphic tStarName to tColor
   end repeat
   unlock screen
end createStars

command animateStars pMaxStars pStarSpeed
   if not sContinue then exit animateStars
   local tStarName, tColor, tStarCoordX, tStarCoordY
   
   lock screen
   --set the lockMoves to true
   repeat with starNum = 1 to pMaxStars
      put "star" & starNum into tStarName
      
      // If the star hits the top of the screen
      // then move it to the top of the screen with a random X coordinate.
      if item 2 of the loc of graphic tStarName < -5 then
         put the height of this card into tStarCoordY
         put random(the width of this card - 1) into tStarCoordX
         set the lineSize of graphic tStarName to randomInRange(2,pStarSpeed)
         
         # Adjust the star color acording to it's speed.
         # The slower the star, the darker should be its color.
         if the lineSize of graphic tStarName is 2 then
            put "#646464" into tColor
         else if the lineSize of graphic tStarName is 3 then
            put "#BEBEBE" into tColor
         else if the lineSize of graphic tStarName is 4 then
            put "#FFFFFF" into tColor
         end if
         set the foregroundColor of graphic tStarName to tColor
         
         // Move the star object.
         --move graphic tStarName to tStarCoordX & comma & tStarCoordY in 0 ticks
         set the loc of  graphic tStarName to tStarCoordX & comma & tStarCoordY
      else
         // Get the star's coordinates
         put item 1 of the loc of graphic tStarName into tStarCoordX
         put item 2 of the loc of graphic tStarName into tStarCoordY
         
         // Get the star's projected y-axis movement
         subtract the lineSize of graphic tStarName from tStarCoordY
         
         // Move the star object.
         --move graphic tStarName to tStarCoordX & comma & tStarCoordY in 0 ticks
         set the loc of  graphic tStarName to tStarCoordX & comma & tStarCoordY
      end if
   end repeat
   --set the lockMoves to false
   unlock screen
   if  not ("animateStars" is in  the pendingMessages)  then
      send "animateStars pMaxStars, pStarSpeed" to me in 10 milliseconds 
   end if
end animateStars

command deleteStars pMaxStars
   local tStarsOnCard, tStarName
   
   put the number of graphics of this card into tStarsOnCard
   
   if pMaxStars is not empty then
      if tStarsOnCard > pMaxStars then
         lock screen
         repeat with thisStar = (pMaxStars + 1) to the number of graphics of this card
            put "star" & thisStar into tStarName
            delete graphic tStarName
         end repeat
         unlock screen
      end if
   else
      if tStarsOnCard > 0 then
         lock screen
         repeat with thisStar = 1 to the number of graphics of this card
            put "star" & thisStar into tStarName
            delete graphic tStarName
         end repeat
         unlock screen
      end if
   end if
end deleteStars

function randomInRange lowerLimit upperLimit
   return random(upperLimit - lowerLimit + 1) + lowerLimit - 1
end randomInRange

Thanks again,
Vasco

bn
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 4027
Joined: Sun Jan 07, 2007 9:12 pm
Location: Bochum, Germany

Re: Starfield Simulation

Post by bn » Sat Jun 08, 2013 9:59 pm

Vasco,
I had no idea that setting the location of the graphics directly would be dramatically faster than using the move command (in 0 ticks)
I only use the move command when I want to move a limited number of objects (up to 15 maybe) in a way that is difficult/tedious to calculate. As to move them all at once from different places all over the card to the center. Or to move an object along a path. Why exactly move is slower than setting the loc directly probably has to do with the power of the move command. There is a lot more overhead to it.
I'm not exactly clear on the term 'blocking'
The way you set it up is you start the animation in a repeat loop in the startSim handler. That repeat loop calls the animateStars handler until you press the button. All the time the engine has no time to do its housekeeping like releasing memory and stuff like that. That is why you see intermittent stops in your original animation. In very tight repeat loops it can happen that the whole system becomes unresponsive. That is what I meant by blocking. Have a look at the Resource Center in the Help Menu. There you find in Applications (on the left side) "Displaying a count down (blocking)" and "Displaying a count cown (non-blocking)" those two explain pretty well the difference.
I got rid of the stars array and let the createStars and animateStars
too bad, I like arrays :) but I admit it is easier to read without an array. If you are doing animations you want to be as fast as possible. At times arrays are faster. The overhead is in creating the array. If you don't use arrays mind you accessing a property is a tad slower than accessing a variable/script local variable. So if you access the linesize within one handler multiple times just access it once and and put it into a local variable and use that. Likewise, if your reference the width of a card etc.things that never change get the properties early (mouseDown or mouseUp) and store them in a script local variable. All this adds up to shave a couple millisecondsd off your handlers.

think of animations as a movie. If you want a fluent animation you want to have a framerate of about 25. That equals to 40 milliseconds for one round. The screen update takes anything from 3 to 10 milliseconds on the desktop (on mobile it is more) so you want to be ready with your scripts as fast as possible. Sometimes it is good to time different approaches or benchmark them. It is not always obvious which way is in a particular situation the fastest one.

and anyways there is always the forum.

Kind regards
Bernd

vbro
Livecode Opensource Backer
Livecode Opensource Backer
Posts: 7
Joined: Tue Mar 26, 2013 7:20 am
Location: Los Angeles, CA
Contact:

Re: Starfield Simulation

Post by vbro » Sun Jun 09, 2013 10:16 am

That is what I meant by blocking. Have a look at the Resource Center in the Help Menu.
Thanks I'll take a look at that.
too bad, I like arrays but I admit it is easier to read without an array. If you are doing animations you want to be as fast as possible. At times arrays are faster.
Oh, so do I. Since reading a tip somewhere that accessing a variable is faster than a object property, I've been trying to apply that to my code. However, my thinking here was (and bear with a beginner), since the animateStars handler has to...
  • 1. Look up the 'position' of the star in the array
    2. Calculate/store the projected move position in a temporary variable
    3. Save the new position to the star's location property
    4. Save the calculated position back to the array
... I could look up the star's position in it's location property instead (step 1), thus eliminating the need for step 4. It could be that the cost in speed of doing the loc property lookup outweighs the benefit of eliminating the store-to-array line, but I suppose that, as you say, only benchmarking will show.
Likewise, if your reference the width of a card etc.things that never change get the properties early (mouseDown or mouseUp) and store them in a script local variable. All this adds up to shave a couple millisecondsd off your handlers.
Absolutely. I didn't catch that at all.
Sometimes it is good to time different approaches or benchmark them. It is not always obvious which way is in a particular situation the fastest one.
I've kept a copy of both this and the array-based implementation, and I'll definitely be doing to benchmarks to see how they compare. I've also modified the createStars handler to create the stars in group so I don't inadvertently delete any other object on my cards!
BTW I like your coding style
Still very much the beginner, though! I've dabble a bit with Java and Python, without much to show for it. However, the lower learning curve and friendly, visual-based IDE of LiveCode has given a non-programmer like me the confidence to attempt a full end-to-end project. That's definitely a tribute to everyone at RunRev.

Thanks again for all your help and advice.

bn
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 4027
Joined: Sun Jan 07, 2007 9:12 pm
Location: Bochum, Germany

Re: Starfield Simulation

Post by bn » Mon Jun 10, 2013 3:48 pm

Hi Vasco,

I made a version of the star animation that adds an outerglow to the brightest stars. The outerglow changes in time to simulate a twinkle. It could be improved but shows a way to do it.

I preferred the array version and changed it a bit. Turned out to be faster. Code is commented and benchmarked. Note also the code of the delete button, it only deletes stars without needing a group.

Have a look.

Kind regards
Bernd
Attachments
AnimateStars_BN_0_1.livecode.zip
(3.29 KiB) Downloaded 357 times

vbro
Livecode Opensource Backer
Livecode Opensource Backer
Posts: 7
Joined: Tue Mar 26, 2013 7:20 am
Location: Los Angeles, CA
Contact:

Re: Starfield Simulation

Post by vbro » Tue Jun 11, 2013 8:59 am

Wow. That's great stuff, Bernd!

I'm actually surprised that the array implementation was faster since it had one more step in the animateStars loop than the non-array implementation did. I'm guessing it was looking up the star's position in the graphic's loc property rather than the star array that made the difference. Definitely a testament to the speed of arrays vs object properties.

I love that twinkle effect! Also, aligning the stars array keys with the object names was great idea as well. I have a lot to play with tomorrow when I have the day off from work. I think my next tasks will be to:
  • Modify things so the ratios of large to medium to small stars is 1:2:3.
  • Allow for distant/small star formations (clusters, galaxies, etc).
Thanks again,
Vasco

bn
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 4027
Joined: Sun Jan 07, 2007 9:12 pm
Location: Bochum, Germany

Re: Starfield Simulation

Post by bn » Tue Jun 11, 2013 9:49 am

Vasco,

when talking about the different speeds keep in mind that each method is pretty fast if only done once in a while. It is only in repeat loops where it really matters and you want the fastest version. And Animation or access to imageData is time sensitive and so it is worth to experiment with different approaches to find the fastest one.

To get an idea what the difference is between different methods here is a small stack that tests them.

Also keep in mind when comparing small differences in performance to repeat the test. LiveCode is depending in its speed on what else is going on on the operating system. So small variations are normal and could make comparisons difficult.

What I am saying in the spirit of Richard Gaskin do some benchmarking.

Kind regards
Bernd
Attachments
benchmarkingArrayETC2.livecode.zip
(1.44 KiB) Downloaded 333 times

vbro
Livecode Opensource Backer
Livecode Opensource Backer
Posts: 7
Joined: Tue Mar 26, 2013 7:20 am
Location: Los Angeles, CA
Contact:

Re: Starfield Simulation

Post by vbro » Wed Jun 12, 2013 5:58 am

I played around with that tool a bit, and from what I could tell, multi-dimensional arrays are only a little bit faster than object properties, but single-dimension arrays are usually about twice as fast! I also didn't realize that accessing items in lists was so very slow, but I guess that makes sense.

Thanks again,
Vasco

Post Reply

Return to “Games”