Optimizing my game

BlitzMax Forums/BlitzMax Beginners Area/Optimizing my game

Drakim(Posted 2007) [#1]
So, I have a main loop that is pretty heavy. In it, we have movement, collision and magnetism for each object, and my game can get some hundred objects on the screen at the same time.

It doesn't produce much slowdown really, but I think lower end systems wouldn't be able to run this so well. So, at the very least, I'm optimizing the loop a lot.

Now, I have some optimizing questions:

Question 1:
Methods and functions can be very clean and nice, but do they use extra resources complared to just having the code directly in the loop?

For example, to do collisions between objects, the distance between objects are constantly checked. I have a function that returns the distance between two objects, and it gets used several hundred times a second at it's worst. Would putting the distance code directly into the loop help optimize some?

Question 2:
When I refer to objects, does ever step within it cause a slight extra resource use?
For example, let's say that I do this:
Object1.XPosition = Self.ParentUniverse.Player.Location1X
Object1.YPosition = Self.ParentUniverse.Player.Location1Y
Object2.XPosition = Self.ParentUniverse.Player.Location1X
Object2.YPosition = Self.ParentUniverse.Player.Location1Y

would this be faster or slower?:
Local temp1 = Self.ParentUniverse.Player.Location1X
Local temp2 = Self.ParentUniverse.Player.Location1X
Object1.XPosition = temp1
Object1.YPosition = temp2
Object2.XPosition = temp1
Object2.YPosition = temp2


Since, although we use two extra local variables, we avoid accessing values though so many objects.

Question3:
If a draw command is called with an X and Y position that is outside the screen (like -500,-200), will it be done or ignored?


xlsior(Posted 2007) [#2]
You could easily benchmark it --

look at millisecs(), loop through a million iterations of each snippet, and look at millisecs() again.


tonyg(Posted 2007) [#3]
It's not going to make that much difference. There are probably other optimisations you can do which make much more of a difference. Without knowing how you've coded it you might want a custom collision routine, shifted grid checking, array vs list vs custom list check etc etc.


Drakim(Posted 2007) [#4]
xlsior:
Ah, thanks. This will be very useful.

tonyg:
I suspected that much, but, I was thinking that even small things can become much when done several hundred times a second.

But, anyways, is there a list somewhere over common traps for ineffective code? (such as deciding between an array or list)


Brucey(Posted 2007) [#5]
If you aren't already, use Strict or SuperStrict. Apart from doing some extra compile-time error checking, it results in slightly more efficient code.

1) Yes, there is some overhead, but the cost is minimal.

3) It will certainly get as far as asking the driver to perform the task. You might find it's better to do a bounds check first, if you know that the resulting draw won't be visible.


big10p(Posted 2007) [#6]
In your distance checking code, use squared distances instead of actual distances, if you can. This'll cut out the need to use Sqr().


tonyg(Posted 2007) [#7]
Try not to constantly create and remove objects as it increases the number of times GC is run. Use arrays or reuse existing objects (do a search for 'Object pooling') Try not to use strings for the same reason.


Drakim(Posted 2007) [#8]
big10p:
Squared distances? Sorry, not quite following you, what is a squared distance?

tonyg:
Unfortunately, I have to create and destroy a lot of objects. (I'll try to reuse) However, could I run the Garbage collector manually to make sure it doesn't run too often?


tonyg(Posted 2007) [#9]
Yep you can run manual GC. You probably *don't* have to destroy the objects though. If they're bullets have a pre-built list/array of bullets with a pointer to the next free one. Once 'dead' mark it is 'available' and you won't put it into GC. There is a good post on Object pools (you did read it... didn't you?) which explains the benefits.


big10p(Posted 2007) [#10]
Squared distances? Sorry, not quite following you, what is a squared distance?
Say your sprites have a collision radius: instead of calculating the actual distance between two sprites (which requires a call to Sqr), you can work with the squared distance between the two sprites, instead. e.g. (pseudo code)

dx# = s1.x - s2.x
dy# = s1.y - s2.y

dist_sqr# = dx * dx + dy * dy

if dist_sqr < (s1.rad * s1.rad + s2.rad * s2.rad) then sprites have collided!

This assumes that sprite x/y refers to the centre of the sprites, BTW.


QuietBloke(Posted 2007) [#11]
of course if you go one step further and have a field radsquared in each sprite which contains rad*rad.
That way you can say :

if dist_sqr < ( s1.radSquared + s2.radSquared ) then collided.


Drakim(Posted 2007) [#12]
tonyg:
Ah, I see. Yes, I have a machine gun kinda thing, so this could be very useful. Thanks!

big10p and QuietBloke:
Ah, thanks, but it won't do. Physics is very important in my game, and this won't give me the possibilities I want. If you bounce into another object, there is a very nice collision system which bounces and does damage both.

HOWEVER, It could be an idea to apply both. First have a square collision system that saves a lot of resources with it's easy calculating, and then, when we are sure they are close enough, then I could apply my own advanced collision.

I'm not sure how much more effective it would be, since it does add a new calculation for ever bounce. I guess it depends on how often objects bounce and how large the playing area is. I shall do both and test them.


big10p(Posted 2007) [#13]
Yes, you can use it for an initial check before proceeding with a more involved collision check. If you then need the actual distance between the two objects, you can, of course, just do dist# = Sqr(dist_sqr).


tonyg(Posted 2007) [#14]
If you have lots of objects which might collide there is a good post on 'shifted grid collisions'. You did search for it... didn't you?


big10p(Posted 2007) [#15]
You mean this demo I did?
http://www.blitzbasic.com/codearcs/codearcs.php?code=1065

Or, is there another one done in bmax?


Beaker(Posted 2007) [#16]
Do optimise, but if it means your code is less readable, as it would if you were to unroll loops or move code out of methods, then I would avoid it.


ImaginaryHuman(Posted 2007) [#17]
I would say use as many Local variables as you can, inline small pieces of code rather than functionalizing or OOPing everything, find better algorithms, and find ways to split up your problem into smaller groups so you don't have to compare every object against every other object.


big10p(Posted 2007) [#18]
Does bmax support inline functions? I wish Blitz3D did.

I agree with Beaker, although there's always occasions where you want to squeeze the last bit of speed out of some code. While the overhead of a function call is usually negligible, it can become significant if the function is called many, many times per loop - especially if said function takes a bucket load of params, as they'll have to be pushed on the stack every call.


Czar Flavius(Posted 2007) [#19]
Not sure how useful it would be to your own program, but you could consider creating a lookup table of precalculated or reused results.

For example, it seems you are making some kind of space game. If the stars do not move during a period of time then instead of calculating their distance every time you need a calculation, store the distances in a table.

Each star might have to have its own table with calculated distances to the other stars. This will use up more memory, but looking up a value in a table is much quicker than calculating it. If the stars move, simply update the value in the tables and use that from now on.

Be warned, if you are using a large number of stars or other objects the memory usage can become prohibitively large as each new object requires a new entry in every other objects' table.


Drakim(Posted 2007) [#20]
hmmh, this shifted grid collision system seems like perfect for me. hmmmmmmh.

[reads up on it to understand how it works]


MGE(Posted 2007) [#21]
In my limited blitzmax experience the bottleneck, if there is one, is usally GPU related not CPU related.


Drakim(Posted 2007) [#22]
Oh? To me, I can't see how simply drawing 100 sprites on the screen can ever compare to having a full 3D world with anti-aliasing and light that is reflected of metal, and all that stuff.

Or am I completely in the wrong here?


tonyg(Posted 2007) [#23]
Or am I completely in the wrong here?


You are not wrong but you seem to be asking the wrong question again.

simply drawing 100 sprites on the screen

It is no problem to draw >1000 sprites on the screen so its odd you are having problems with 100. This goes back to the earlier suggestion that something else could be optimised rather than the overhead in calling methods.


Drakim(Posted 2007) [#24]
tonyg, you got it all wrong >>

The reason my game runs slow is because of all the math, collisions and magnetism and such. I never really talked about any slowdown because of sprites.

My post, when talking about 100 sprites, was a reply to the post above, were JGOware said that the bottleneck could be GPU instead of CPU related.


tonyg(Posted 2007) [#25]
... and, I believe, JGOware was stating that you're more likely to get a performance issue transferring data to the GPU than using the CPU.
With that in mind :
Have you timed your code without drawing anything?
What is the difference between the maths+draw vs the draw? Simply time your update logic and not your draw logic.
.
Another point, you haven't given much to go on so people are guessing things that might help.
Your original question concerned the overhead for calling functions/methods (even 1000's of times). A quick test on my machine suggested using methods adds 1ms per 10000 calls.
I understand the argument that every ms counts but I doubt that will make a slow framerate into an acceptable framerate.


Derron(Posted 2007) [#26]
Think you will have to post (partially) the code of your collision-checks...

a) to see which objects are checked against (and how many)
b) how you check.

Checking 500 Objects for collision against each other (unregular) can be a frame-cutter. May be you have an unrecognized bottleneck there.


bye
MB


Drakim(Posted 2007) [#27]
Oh, I think we have a pretty big missunderstanding here. When I coded the game, I pretty much saw what was slowing down the most. So, I already knew this.

However, I wasn't sure on optimizing much, so I made this topic to clear up a few questions I had. I've gotten good responses, and I'm now in the proccess of unlagging my game based on this. There is no need to guess on what things might help, since I've already gotten help.

But, since you asked, I'll supply my collision code:
  Method ObjectsCollide(Object1:TObject, Object2:TObject)
    Local CollisionDistance:Float = (((Object1.XSize + Object1.YSize) / 2) / 2) + (((Object2.XSize + Object2.YSize) / 2) / 2)
    Local CurrentDistance:Float = DistanceBetweenPoints(Object1.XPosition,Object1.YPosition,Object2.XPosition,Object2.YPosition)
      If CollisionDistance > CurrentDistance
        Local CollisionAngle:Float = ATan2(Object2.YPosition-Object1.YPosition, Object2.XPosition-Object1.XPosition)
        Local moveDist1:Float = (CollisionDistance-CurrentDistance)*(Object2.Weight/Float((Object1.Weight+Object2.Weight)))
        Local moveDist2:Float = (CollisionDistance-CurrentDistance)*(Object1.Weight/Float((Object1.Weight+Object2.Weight)))
        Object1.XPosition = Object1.XPosition + moveDist1*Cos(CollisionAngle+180)
        Object1.YPosition = Object1.YPosition + moveDist1*Sin(CollisionAngle+180)
        Object2.XPosition = Object2.XPosition + moveDist2*Cos(CollisionAngle)
        Object2.YPosition = Object2.YPosition + moveDist2*Sin(CollisionAngle)
        Local nX:Float = Cos(CollisionAngle)
        Local nY:Float = Sin(CollisionAngle)
        Local a1:Float = Object1.XSpeed*nX + Object1.YSpeed*nY
        Local a2:Float = Object2.XSpeed*nX + Object2.YSpeed*nY
        Local optimisedP:Float = (2.0 * (a1-a2)) / (Object1.Weight + Object2.Weight)
        Object1.XSpeed = Object1.XSpeed - (optimisedP*Object2.Weight*nX)
        Object1.YSpeed = Object1.YSpeed - (optimisedP*Object2.Weight*nY)
        Object2.XSpeed = Object2.XSpeed + (optimisedP*Object1.Weight*nX)
        Object2.YSpeed = Object2.YSpeed + (optimisedP*Object1.Weight*nY)
    End If
  End Method


notes: this is only part of the code. I've not included the code for the functions like DistanceBetweenPoints() since I think you guys can figure out what that means ;)


Brucey(Posted 2007) [#28]
Busy in there, isn't it?

If you are using Sqr() in DistanceBetweenPoints() that'll slow it down a bit.
There's a couple of unnecessary calls to Cos()/Sin() too.

You may (or may not) find the code less busy if you were to change things like :
Object1.XSpeed = Object1.XSpeed -

to
Object1.XSpeed :-

Not an optimisation.. but it makes it easier to read.

How many object crashing into each other before you notice it slowing down?


Vilu(Posted 2007) [#29]
One way to optimize trigonometry is to create pre-calculated arrays of Sin()/Cos() functions instead of calling Sin()/Cos() every time.

For example, create a float array called CosValues[360] and prepopulate it with Cosine values for degrees 1 - 360 for quick lookup. The same for Sin(). The drawback of course is the inaccuracy caused by the 1 degree granularity, but the performance is surely worth it.


big10p(Posted 2007) [#30]
Arghh, not the lookup table debate again?! This could muddy the water. :P

Personally, I don't bother with them these days. Modern processors seem to have pretty much made their use redundant, IMO.


Vilu(Posted 2007) [#31]
Hmm... I've been using lookup tables for Sin, Cos and Tan for a long time now, and the last time I did a performance test indicated that lookup tables were, in fact, a whole lot faster than calling the functions directly.

When I get home, I'll run another test on my old laptop and post the results.


Derron(Posted 2007) [#32]
Another thing to sum up the code:

        Local a1:Float = Object1.XSpeed*nX + Object1.YSpeed*nY
        Local a2:Float = Object2.XSpeed*nX + Object2.YSpeed*nY
        Local optimisedP:Float = (2.0 * (a1-a2)) / (Object1.Weight + Object2.Weight)


could be written as:
        Local optimisedP:Float = (2.0 * ((Object1.XSpeed-Object2.XSpeed)*nX  + (Object1.YSpeed - Object2.YSpeed)*nY)) / (Object1.Weight + Object2.Weight)


Would be shorter and uses less variables (locals).

For better readability use bruceys suggestion (x :+ y)
and be sure to use "short names" to identify easy things like positions:

Object1.X (PositionX could also be posX)
Object1.vX (velocityX)
Object1.mass (Weight is using Gravity, its no fixed value I think)
Object1.dX (delta - speed)

Although its nice to have variables really really declaring their usage itself, some variables have nearly standard name schemes. ... So "current" would become "curr" and "distance" becomes "dist" and so on.

This makes code-lines way shorter and takes away the frighting of "i better make locals to shorten one line of calculation to have a better overview".


Another thing... are your objects stored as TObject? I see no cast to your object-type: TMyRock(object1)


bye
mB


Drakim(Posted 2007) [#33]
Brucey:
Yeah, I've already noticed a few small things I could improve, but generally I find this collision code very fast. I can have several hundred objects collide often without any problems. (but, not combined with everything else in the game, such as magnetism, then it starts to lag. But, I'm making a grid collision system now, like the one big10p had)

Vilu:
Thanks, I shall test if it causes any trouble, and it not, use it.


Drakim(Posted 2007) [#34]
MichaelB:
Yeah, you are right, I could shorten a lot of stuff. Would make my life easier too. ^^ (if only there was a replace function over several files at once)

As for the TObject, it's not here, since I really don't need to show it in addition to the collision code. TObject is simply an abstract type for all my space objects.


Derron(Posted 2007) [#35]
So whats the count ? How many objects may be on screen without delay ... and which count makes it lag (low fps or whatever).

The function you showed was the one used for ONE collision-check.

With the grid collision system you still may run into problems (imagine a big rock exploding into 100 rocks - they surely share the same grid for a short while ... making a huge comparison-round needed).

If all of them have magnetic abilities... you may think about cutting down redundant checks... if two rocks wont influence through magnetics, they mostly (if you think of magnetics as gravity instead of + and - ) wont collide in this moment.
If using + and - and they don't attract, you also wont have to check for collision between both objects.

So you may search some ways to avoid additional checks (in addition to the grid which separates all objects into a kind of groups).


bye
MB


big10p(Posted 2007) [#36]
With the grid collision system you still may run into problems (imagine a big rock exploding into 100 rocks - they surely share the same grid for a short while ... making a huge comparison-round needed).
Sure, but it should still be faster than checking every object against every other object. The secret to getting the most out of grid collisions is in tweaking the grid sector size to fit the actual game.


Drakim(Posted 2007) [#37]
MichaelB:
there generally aren't THAT many objects on the screen at the same time. Because of the scrolling, I have a "object is only updated if it is within 1500 pixels of the player", thus, I am pretty sure I will never have above 250 objects at the same time, and that will only be the very extreme cases. It's on the tip of lagging right now with 250 objects on a relative medicore computer, but I'm sure that with grind collisions and other methods presented to me here, I can reduce the lag away.

As for magnetism, it can't be put into the grind system. you see, magnetism can have really really long range, so it would cross several grinds. Making the grinds bigger to make up for this would make the optimizing weaker to the point that grinds doesn't help so much anymore.

Instead, I'm going to make a much simpler and more optimized magnetic formula, that "caches" how it attracts objects, and if their position and weight haven't changed too much, use the cache instead of calculating everything over and over.

Oh, and no, they don't all have magnetic abilities. Magnets are very rare, usually only one or two at the screen at the same time, so they aren't that big of a problem.


Derron(Posted 2007) [#38]
@big10p:

wasn't meant as negative aspect of grid systems... you just may have to glue it to other things to make things like exploding rocks work (small pieces excluded from normal grid-comparison ... or so).

In a short test I compared a list of 150 objects straight against 150 other objects (circle-collision). It made my old 1,7GHz Athlon laggy.
So 250 objects... may be a bit more work ;D.

Hope the grid helps.


bye
MB


Vilu(Posted 2007) [#39]
Just did the Sin() lookup table performance comparison by generating 10 million random values:

SuperStrict

' Create and populate the sin array
Local SinValues:Float[360] 
For Local i:Int = 0 To 359
	SinValues[i] = Sin(i) 
Next

' Lookup table performance
Local start:Int = MilliSecs() 
For Local i:Int = 1 To 10000000
	Local Sin:Float = SinValues[Rnd(0, 359)] 
Next
Print "Lookup table time: " + (MilliSecs() - start) 

' Sin call performance
start:Int = MilliSecs() 
For Local i:Int = 1 To 10000000
	Local Sin:Float = Sin(Rnd(0, 359)) 
Next
Print "Sin() call time: " + (MilliSecs() - start) 


I got 4600ms for lookup table time and 5140ms for Sin() call time using a 1.5GHz ThinkPad. The difference is quite small but is it negligible?

@Drakim:
A bit off-topic but anyway: As you can see above, array referencing works with floats and doubles as well (Rnd() returns a double), but it rounds it up to the next largest integer. That is, arrayname[342.11] and arrayname[342.99] are both equivalent to arrayname[343]. So you'll lose precision here as well unless you do a proper rounding of the lookup value.


Derron(Posted 2007) [#40]
SuperStrict

' Create and populate the sin array
Global SinValues:Float[360] 
For Local i:Int = 0 To 359
	SinValues[i] = Sin(i) 
Next

Local degree:Int =0
' Lookup table performance
Global start:Int 
start = MilliSecs() 
For Local i:Int = 1 To 10000000
	Local Sin:Float = SinValues[degree] 
	degree:+1
	If degree > 360 Then degree = 0
Next
Print "Lookup table time: " + (MilliSecs() - start) 

' Sin call performance
degree=0
start:Int = MilliSecs() 
For Local i:Int = 1 To 10000000
'	Local Sin:Float = Sin(Rnd(0, 10)) 
	Local Sin:Float = Sin(degree) 
	degree:+1
	If degree > 360 Then degree = 0
Next
Print "Sin() call time: " + (MilliSecs() - start) 


'rnd call performance
start:Int = MilliSecs() 
For Local i:Int = 1 To 10000000
	Local MyRND:Float = Rnd(0, 359) 
Next
Print "rnd() call time: " + (MilliSecs() - start) 


using another method than rnd (or just checking for a fixed value) speeds up the lookup to be a 10th of the time a sin-calculation needs.


bye
MB


Vilu(Posted 2007) [#41]
I'm using Rnd() in both tests so the difference must come from the Sin() usage.


Derron(Posted 2007) [#42]
Edited my post... I know that rnd makes unordered access, you can emulate this by preparing a "randomized" list of 360 numbers from 1 to 360... shuffling them after a certain step.

May some with knowhow say if it has to do something with stacks or other caches...

btw the code above gives:
Lookup table time: 43
Sin() call time: 921
rnd() call time: 1993


bye
MB


Czar Flavius(Posted 2007) [#43]
I'd be interested to know how you calculate magnetism in your game. Perhaps this could be causing slowdown?


Drakim(Posted 2007) [#44]
meh, sure, but as I said earlier, I am going to remake this to have a cache system, so this is outdated. But hints and tips are still wanted :)

  Method ObjectsMagnet(Object1:TObject,Object2:TObject)
    Local Distance:Float = DistanceBetweenPoints(Object1.XPosition, Object1.YPosition, Object2.XPosition, Object2.YPosition)
    If Distance < Object2.MagnetRange
      Local CollisionDistance:Float = (((Object1.XSize + Object1.YSize) / 2) / 2) + (((Object2.XSize + Object2.YSize) / 2) / 2)
      If Distance > CollisionDistance * 1.1
        Local PercentPowerModifier:Float = ((Distance / Object2.MagnetRange) - 1) * (-1)
        If PercentPowerModifier > 0.5 Then PercentPowerModifier = 0.5
        Local AngleNegative:Int = 0
        Local ModifiedPower:Float = Object2.MagnetPower 
        If Object2.MagnetPower < 0
          ModifiedPower :* -1
          AngleNegative = 180
        End If
        Local ModifiedMagnetPowerObject1:Float = PercentPowerModifier * PreventNegative(ModifiedPower - Object1.Weight)
        Local ModifiedMagnetPowerObject2:Float = PercentPowerModifier * PreventNegative(ModifiedPower - Object2.Weight)
        Local Angle:Float = DegreesBetweenPoints(Object1.XPosition, Object1.YPosition, Object2.XPosition, Object2.YPosition)      
        Object1.XSpeed :+ Sin(Angle + AngleNegative) * ModifiedMagnetPowerObject1
        Object1.YSpeed :+ Cos(Angle + AngleNegative) * ModifiedMagnetPowerObject1
        Object2.XSpeed :+ Sin(Angle+180 - AngleNegative) * ModifiedMagnetPowerObject2
        Object2.YSpeed :+ Cos(Angle+180 - AngleNegative) * ModifiedMagnetPowerObject2
      End If
    End If
  End Method


notes: this is still an effect in development. You'll see strange things like the line:
If Distance > CollisionDistance * 1.1

which doesn't make sense just yet.


big10p(Posted 2007) [#45]
I'm not sure those lookup table tests are particularly valid as they dont represent real world useage of sin/cos. i.e. making a whole bunch of sin calls/lookups inside a loop.

I'm sketchy of cache functioning in modern CPU's but I think when the first lookup is made, a whole block of data is transferred to the cache (i.e. the entire array), and so, from then on it's simply doing a lookup from cache mem which is bound to be faster.


Derron(Posted 2007) [#46]
Although it's no speed-killer you should avoid things like:
      Local CollisionDistance:Float = (((Object1.XSize + Object1.YSize) / 2) / 2) + (((Object2.XSize + Object2.YSize) / 2) / 2)


you could shorten it (and save about 33% of needed calculations):
      Local CollisionDistance:Float = (Object1.XSize + Object1.YSize + Object2.XSize + Object2.YSize) / 4


I know that it may just be not optimized for the sake of "not final and just to see if the effect is reached".

But when you sum up such small improvements, the speed will increase a bit too (especially when the bottleneck seems to be the cpu).

After you included your grid-methods, try to narrow the lag down using some Profiler-Functions (search the codebase), this will provide some information which may be needful (don't worry about a possible mem-leak, coz every call is logged, the mem usage increases slightly).


bye
MB


Czar Flavius(Posted 2007) [#47]
What I mean is, do all magnetic objects update their magnetism to each other every cycle? If you have a lot of magnetic objects, that must be many calls.


Drakim(Posted 2007) [#48]
update their magnetism for each other every cycle? I'm not sure what you mean but, this is how it works:

Every frame, the magnets applies a X speed on all metal based objects in it's magnetic range Y in the direction of the magnet.

Magnets however isn't such a big problem, mostly because I already had a few tricks of optimizing ready, and that there is almost never more than 3 magnets alive at the same time.


Drakim(Posted 2007) [#49]
There, here is my new collide code:

  Method ObjectsCollide(Object1:TObject, Object2:TObject)
    Local CollisionDistance:Float = (Object1.XSize + Object1.YSize + Object2.XSize + Object2.YSize) / 4
    Local CurrentDistance:Float = DistanceBetweenPoints(Object1.XPos,Object1.YPos,Object2.XPos,Object2.YPos)
      If CollisionDistance > CurrentDistance
        Local CollisionAngle:Float = ATan2(Object2.YPos-Object1.YPos, Object2.XPos-Object1.XPos)
        Local moveDist1:Float = (CollisionDistance-CurrentDistance)*(Object2.Weight/Float(Object1.Weight+Object2.Weight))
        Local moveDist2:Float = (CollisionDistance-CurrentDistance)*(Object1.Weight/Float(Object1.Weight+Object2.Weight))
        Object1.XPos = Object1.XPos + moveDist1*CacheTable.GetCos(PreventInvalidAngle(CollisionAngle+180))
        Object1.YPos = Object1.YPos + moveDist1*CacheTable.GetSin(PreventInvalidAngle(CollisionAngle+180))
        Object2.XPos = Object2.XPos + moveDist2*CacheTable.GetCos(PreventInvalidAngle(CollisionAngle))
        Object2.YPos = Object2.YPos + moveDist2*CacheTable.GetSin(PreventInvalidAngle(CollisionAngle))
        Local nX:Float = CacheTable.GetCos(PreventInvalidAngle(CollisionAngle))
        Local nY:Float = CacheTable.GetSin(PreventInvalidAngle(CollisionAngle))
        Local OptimisedP:Float = (2.0*((Object1.XSpeed*nX+Object1.YSpeed*nY)-(Object2.XSpeed*nX+Object2.YSpeed*nY)))/(Object1.Weight+Object2.Weight)
        Object1.XSpeed :- (OptimisedP*Object2.Weight*nX)
        Object1.YSpeed :- (OptimisedP*Object2.Weight*nY)
        Object2.XSpeed :+ (OptimisedP*Object1.Weight*nX)
        Object2.YSpeed :+ (OptimisedP*Object1.Weight*nY)
    End If
  End Method


It runs quite nicely, and I've been able to have up to 600 objects and still have about 60 fps (about 1.35 delta), which isn't a problem at all. I think especially the lookup tables did the trick.

But, a quick question. How am I to apply damage to the objects that collide? I can't for my life find out how to extract the force out of this formula. Any help?


LarsG(Posted 2007) [#50]
One way of doing it it is to add together the velocity of the two objects which collided.
The total combined velocity should give you some idea of what damage it would make.


Drakim(Posted 2007) [#51]
ah, but how do I get the velocity?

The movement of the objects are determined by an XSpeed and YSpeed, which can be both negative and positive (depending on what way you go). So how would I get the velocity out of these two variables?

For example, if an object moves up and right, it might have the speed:
XSpeed = 9
YSpeed = -4

How would I get the real velocity out of this?


LarsG(Posted 2007) [#52]
You can just use that speed variable as your velocity.

I'm sure there are a few guys here who might have a nice math formula to do this.
If not, I think you might have to do some "clever" checking to see what damage might be, depending on if they travel in the "same" direction, or if they are colliding head to head..


Drakim(Posted 2007) [#53]
I can't make this "clever" math ><, I tried three different ways, but they all had major gaps and faults. Could anyone help me?

Oh, another optimizing question (which is kinda the real topic at hand)

My objects are now starting to get really really many fields, around 20 or so. All fields vary for the different objects upon creation, thus, my creation function takes almost 20 parameters now. It's a messy thing.

However, I've also heard that parameters is a bit of a slowdown. Is this true? And if so, is there a cleaner better way to do things?

In JavaScript, I could create objects in a way that allowed me to only specify the parameters I wanted, and the unspecified would get their default value. Is this possible in BlitzMax?


tonyg(Posted 2007) [#54]
What is xspeed measured in? Pixels per sec? Pixels per cycle? It is better to do your direction stuff in vectors . There are some good vector math libs in the Code Archives. I found this useful in Blitzbasic days.
As for objects, I try and limit them to about 7-10 attributes. After that I create sub-types.
I haven't noticed any slowdown but I am not doing intensive collision tests. From what I have seen in your project I would first make working code that is readable (at the moment it is like having a pencil jabbed in my eye) and then optimise later. Having said that some people swear by the 'optimise from the start' approach.
You can set default values in Bmax either by
a) setting it in the field statement :
field name:string="Bob"
or
b) within the function calls.
function create(name:string="Bob")


Drakim(Posted 2007) [#55]
tonyg:
I thought vectors was a lot heavier to calculate, and because my game will have lots of objects with lots of constant movement, I thought of it as a straight ahead dead end. Am I wrong?

When it comes to the objects, I really need that many attributes for various things in my game. I can't divide into sub-types anymore because they are already divided into so many subtypes.
(unless I start making types like "rock1", "rock2" and "rock3" etc, instead of having a size field from 1 to 20. But I'm not willing to destroy my remaining humanity in order to avoid some parameters.)

As for optimizing, I must optimize from the start. It's just how I work, sorry. ^^, But I won't do the insane optimizing such as not using functions to avoid calls. After some testing I found that it's not near worth it in any way.

And I can't agree with you that the code hurts my eyes. It may be bias from writing it, but it's clear and nice for me :D

Lastly, the parameters. Even if I put default values in the fields, I still have to supply all the parameters right? Thus, if I want the possibility to use all parameters, I have to use it every time after all. I JavaScript, you could skip parameters in order to let the default apply instead.


tonyg(Posted 2007) [#56]
Vectors should be OK as I am sure many people have used them for fast-paced games. There is the source for a 'grid-wars' game which might show how they implemented it. Do a search on these forums/
.
If you post the object and its fields it will be interesting to get feedback on how others might do the same.
Maybe we have misunderstood each other.
Your TRock object might have :
field image
field xpos
field ypos
field alpha
field blend

which can all be replaced by
field sprite:TSprite

or a field which points to a 'TRock_Attrib' object. This sub-object contains size, damage, TColor etc.
.
I understand the optimise first approach but, personally, find myself testing more and more bizarre solutions which complicate matters.
.
It is difficult to comment on the readability of other people's code. e.g. I would have
        Object1.xpos=etcetcetc 

replaced by
        Object1.updatepos() 

but that's probably a personal thing.
.
If you put params in the fields you can leave the function call blank :
type TRock
   field size:int
   field color_red:int
   function create(red:int,size:int=4)
   end function
end type
local myrock:trock=trock.create(255)
print myrock.size



Drakim(Posted 2007) [#57]
But, let's say you want to specify what size that TRock is going to have, but I want to leave the color_red field to it's default, is that possible?

Because, you simply skipped the last parameter, since you have a default for that. However, how do you skip the parameters that aren't last?


tonyg(Posted 2007) [#58]
Type TRock
   Field size:Int
   Field color_red:Int
   Field color_green:Int
   Field color_blue:Int=10
   Function Create:trock(red:Int,green:Int=255,size:Int=4)
		Local temp:trock=New trock
		temp.size=size
		temp.color_red=red
		temp.color_green=green
		Return temp
   End Function
End Type
Local myrock:trock=trock.Create(255,,3)
Print myrock.size
Print myrock.color_red
Print myrock.color_green
Print myrock.color_blue

I don't think you can skip the first parm


LarsG(Posted 2007) [#59]
You can't, as far as I know..


Brucey(Posted 2007) [#60]
I don't think you can skip the first parm

Of course you can, as long as you give it a default :-)

Function abc(a:Int = 5, b:Int = 1, c:Int = 2)
Print a
Print b
Print c
End Function

abc(,2)
abc(,6,3)
abc()




MGE(Posted 2007) [#61]
Optimizing code is always good, but I've coded demos with hundreds of sprites moving around, and the frame rate stays at the 50-100 fps mark, even on lower end machines. And my code is extremly high level, hardly any optimizing on the code end. But I did focus on techniques to improve graphic drawing. Some obvious considerations:

16bit is still faster than 32bit in every test I've performed.
Use lower res (640x480, 800x600) for fast action games. I still see BMax coders making their games run in high resolutions and they stutter on older machines.
Instead of drawing lots of smaller tiles (16x16,32x32, etc) draw 128x128 or larger tiles. The less you draw every loop the faster it will be.


tonyg(Posted 2007) [#62]
Brucey, yes. you are right. I can only believe I hit a different problem when I tried it and assumed it was the missing parm.


Derron(Posted 2007) [#63]
To the "how much damage" thing:

To calculate the "velocity" you need to 4 Points (Point = x,y)
Obj1.OldPosition, Obj1.NewPosition
Obj2.OldPosition, Obj2.NewPosition

OldPosition = X,Y
NewPosition = X+dx, Y+dy

Having 4 Points you can calculate the angle between the 2 Lines:
Line1(Obj1.OldPosition, Obj1.NewPosition)
Line2(Obj2.OldPosition, Obj2.NewPosition)

The one with "Mass*LineLength" bigger than the other object is the "winner" of a collision (more force, cinectic energy mass/2 * speed^2)

Now you have a energy (cinectic energy) connected to a direction (Line1 and Line2).

The bigger "energyline" (linelength * mass) will be the hypothenuse the other is mostly the ankathede (dunno if its the same in english).


cos(energylineSmall / energylineBig) = angle of power the objects hit together with.

The smaller the angle the bigger the impact (like a train colliding into another train driving into the straight opposite direction, compared to a car crashing into another on the crossroad).

If the Angle is near 180° then Object1 moves faster than Object2 (on the same movement-path) and hitting it will do a ForceDamage of Force1-Force2).

Assume Object1 has very less Force than Object2, then damage done to Object1 is (Force2 - Force1). Object2 would have a damage done of Force1.


With the collision angle and the damage (forces) you can additionally calculate the new direction the objects take.



Before I write too much without bringing some effort into it (for you).
This was just a bit of brain-smashing to give some ideas.


To add some kind of summary:
Damage done is force*angle of collision.
Force is mass/2 * speed^2 (E_cinetic).
New direction of movement is angle of collision (0-100%) * PathObject1 + (100%-angle of collision)*PathObject2).


bye
MB


Drakim(Posted 2007) [#64]
MichaelB:
This is a bit over my head, so I need to read over it carefully and test it, but some quick questions for it:

If object1 travels to the north at 90 speed, and object2, which is behind object1, travels at 95 speed, it should eventinally catch up.

However, due to the way they travel (the same way and almost the same speed), there should really be almost no damage, despite that object2 holds the high speed of 95, simply because object1 holds almost the same speed, "canceling out" 90 of the 95 speed, thus only making the impact one of 5 speed (really simple terms).

Any system I've made does not take this into account. for me, as object2 reaches object1, it would deal massive damage at it, despite that it would be a lot more realistic if almost no damage was dealt.

How will your system react to that situation?


Derron(Posted 2007) [#65]
the damage dealt in this situation is:
(check internet for "impulse" or cinetic energy)

Object1.Energy = Object1.Mass / 2 * Object1.speed^2
Object2.Energy = Object2.Mass / 2 * Object2.speed^2

To simplify the damage calculation you would say that the difference of the objects energies is the damage done.

If you are not satisfied with this you will have to take the angle of collision and then multiply the difference of energies with a factor from 0 to 1.0 (depending on angle).

To make it a bit more realistic you could decrease the energy of the objects about the difference:

local backupenergy:int=Object1.energy
Object1.energy = Object1.energy - Object2.energy
Object2.energy = Object2.energy - backupenergy

If one of the objects has its energy below 0, its destroyed
The survivor of the crash has eventually lost a bit of its mass (size) and/or speed (remember mass/2 * speed^2 = energy).


The result of this is, if a really huuuge, heavy and/or fast object crashs into a smaller/slower one, the last is destroyed and "big boss" is still alive (but he wont care for the loss of a bit energy).


This brings another idea in the game: they can "melt" together... loosing a bit of energy while crashing (they loose some mass) but afterwards they got heavy and strong.


Hope this helps,

bye
MB