Optimizing my game
BlitzMax Forums/BlitzMax Beginners Area/Optimizing my game
| ||
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? |
| ||
You could easily benchmark it -- look at millisecs(), loop through a million iterations of each snippet, and look at millisecs() again. |
| ||
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. |
| ||
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) |
| ||
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. |
| ||
In your distance checking code, use squared distances instead of actual distances, if you can. This'll cut out the need to use Sqr(). |
| ||
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. |
| ||
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? |
| ||
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. |
| ||
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. |
| ||
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. |
| ||
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. |
| ||
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). |
| ||
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? |
| ||
You mean this demo I did? http://www.blitzbasic.com/codearcs/codearcs.php?code=1065 Or, is there another one done in bmax? |
| ||
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. |
| ||
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. |
| ||
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. |
| ||
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. |
| ||
hmmh, this shifted grid collision system seems like perfect for me. hmmmmmmh. [reads up on it to understand how it works] |
| ||
In my limited blitzmax experience the bottleneck, if there is one, is usally GPU related not CPU related. |
| ||
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? |
| ||
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. |
| ||
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. |
| ||
... 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. |
| ||
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 |
| ||
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 ;) |
| ||
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? |
| ||
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. |
| ||
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. |
| ||
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. |
| ||
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 |
| ||
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. |
| ||
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. |
| ||
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 |
| ||
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. |
| ||
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. |
| ||
@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 |
| ||
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. |
| ||
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 |
| ||
I'm using Rnd() in both tests so the difference must come from the Sin() usage. |
| ||
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 |
| ||
I'd be interested to know how you calculate magnetism in your game. Perhaps this could be causing slowdown? |
| ||
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. |
| ||
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. |
| ||
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 |
| ||
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. |
| ||
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. |
| ||
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? |
| ||
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. |
| ||
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? |
| ||
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.. |
| ||
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? |
| ||
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") |
| ||
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. |
| ||
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 |
| ||
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? |
| ||
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 |
| ||
You can't, as far as I know.. |
| ||
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() |
| ||
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. |
| ||
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. |
| ||
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 |
| ||
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? |
| ||
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 |