Page 1 of 1
Starfield Simulation
Posted: Sat Jun 08, 2013 9:17 am
by vbro
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
Re: Starfield Simulation
Posted: Sat Jun 08, 2013 10:34 am
by bn
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
Re: Starfield Simulation
Posted: Sat Jun 08, 2013 8:19 pm
by vbro
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
Re: Starfield Simulation
Posted: Sat Jun 08, 2013 9:59 pm
by bn
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
Re: Starfield Simulation
Posted: Sun Jun 09, 2013 10:16 am
by vbro
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.
Re: Starfield Simulation
Posted: Mon Jun 10, 2013 3:48 pm
by bn
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
Re: Starfield Simulation
Posted: Tue Jun 11, 2013 8:59 am
by vbro
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
Re: Starfield Simulation
Posted: Tue Jun 11, 2013 9:49 am
by bn
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
Re: Starfield Simulation
Posted: Wed Jun 12, 2013 5:58 am
by vbro
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