| 03-02-2010, 10:26 PM | #1 |
I have been working on this for a little while now and I was wondering if anybody could help me out. I'm trying to optimize the code and do things as quickly as they can be done to maximize the performance that I can get out of it. So far the projectile's collision booleans (for unit-collision and projectile-collision) are set to off, it will not run any mid-flight collisions. The code in the projectile-collision segment is commented away anyways. JASS:library Projectiles requires Vectors optional TimerUtils globals //********************** //* Configuration //* ¯¯¯¯¯¯¯¯¯¯¯¯¯ //* Many default constants. //* //* private constant integer FLY_ABIL_ID = 'Amrf' //* This is medihv's crow-form ability. //* public constant real COLLISION_DEFAULT = 50 //* This is the range at which units/projectiles will "collide". public constant real COLLISION_MAX = 180 //* This is the maximum value for projectile collision. If 'COLLISION_DEFAULT' //* is higher then this it will not over-ride it. //* public constant boolean EXPIRATION_DEFAULT = true //* The projectile's tendency to die once it has "finished". Setting this to false //* should only be done if you are managing the projectiles yourself. //* private constant boolean hitDead = false //* Projectiles will "strike" dead units. private constant boolean hitEnemy = true //* Projectiles will "strike" enemy units. private constant boolean hitAllied = false //* Projectiles will "strike" allied units. private constant boolean hitStructure = false //* Projectiles will "strike" structures. //* //* //***************************************** endglobals interface projectileinterface //************* //* Projectile Interface //* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ //* The following functions can be re-declared in child-structs and gain their logical functionality. For example, declaring //* 'onUnitCollision' will allow you to customize the actions executed when the projectile comes into contact with a unit. //* //* method onLoop takes nothing returns nothing defaults nothing //* Each iteration that updates the projectile. method onFinish takes nothing returns nothing defaults nothing //* Once the projectile reaches the end of its path (unless otherwise specified). method onBirth takes nothing returns nothing defaults nothing //* When the projectile starts its projection path. method onUnitCollision takes unit u returns nothing defaults nothing //* When the projectile collides with a unit (u = collide with -unit-). method onCollision takes projectile p returns nothing defaults nothing //* When the projectile collides with another projectile (p = other projectile). method onGround takes nothing returns nothing defaults nothing //* Projectile has struck the ground. //* //* endinterface struct projectile extends projectileinterface //*************************** //* Projectiles //* ¯¯¯¯¯¯¯¯¯¯¯ //* Data-structure that supports the creation/launch (as well as other functions) of in-game projectiles. The projectiles //* are meant to mimic the basic ones as close as possible. //* //* // Primary handle-base for the projectile. readonly unit toUnit = null //* This unit is "projected" as if it were a projectile. readonly real speed readonly real arc real collision = COLLISION_DEFAULT real timescale = 1.00 //* Time-scale is at 100% by default. private real p_scale = 1.00 //* Size-scale is at 100% by default. method operator scale= takes real s returns nothing set .p_scale=s call SetUnitScale(.toUnit, s, s, s) endmethod method operator scale takes nothing returns real return .p_scale endmethod // Active feature data boolean activeUnitCollision = false //* The projectile can collide with units. boolean activeCollision = false //* The projectile can collide with other projectiles. private static location p_sampleLoc = Location(0, 0) //* Location used often for 'MoveLocatin' and 'GetLocationZ' private static group p_sampleGroup = CreateGroup() //* Group used to enumerate nearby units. private static thistype p_sampleProj //* Used to store 'this' when doing a group enum. // These vectors store information including projectile motion. private s_vector3d p_currentPos private s_vector3d p_currentVelocity private s_vector3d p_currentAcceleration private s_vector3d p_currentTargetPos //* When the target-position vector is changed during flight, it will activate the projectile's //* "homing" capabilities, which will eliminate 'arc' and move directly towards the target. private real p_timeRemaining // -------- // The following method operators allow the user more access to the projectile's vectors, without being able to // do anything "illegal". // // Returns a COPY of the position vector. method operator position takes nothing returns s_vector3d return .p_currentPos.copy() endmethod // Returns a COPY of the velocity vector. method operator velocity takes nothing returns s_vector3d return .p_currentVelocity.copy() endmethod // Returns a COPY of the acceleration vector. method operator acceleration takes nothing returns s_vector3d return .p_currentAcceleration.copy() endmethod // Returns a COPY of the target-position vector. method operator targetposition takes nothing returns s_vector3d return .p_currentTargetPos.copy() endmethod // // Destroy removes projectile from the stack, but it does not kill the unit. method onDestroy takes nothing returns nothing set thistype.p_stackTrack = thistype.p_stackTrack - 1 set thistype.p_stack[this.p_index] = thistype.p_stack[thistype.p_stackTrack] set thistype.p_stack[this.p_index].p_index = this.p_index if (thistype.p_stackTrack==0) then call PauseTimer(thistype.p_loop) //* Pause the timer if it is no longer needed. endif // Don't forget to destroy the vectors! call .p_currentPos.destroy() call .p_currentTargetPos.destroy() call .p_currentVelocity.destroy() call .p_currentAcceleration.destroy() endmethod // We don't want the projectile struct to leak so we need to remove it once its trajectory has completed. method onFinish takes nothing returns nothing call this.destroy() endmethod // In order to keep track of the projectile within a stack of projectiles, it must have necessary indexing mechanisms. When // a projectile is created it is added to this stack, which is looped through on each iteration of the timer. private integer p_index //* Keeps track of 'this' index so that the stack can be maintained. private static thistype array p_stack private static integer p_stackTrack = 0 //* There are no projectiles in the stack to begin with. // In order to update the stack, we must use a timer. If the 'TimerUtils' library is available then it will use 'NewTimer' instead of 'CreateTimer'. private static constant real p_loopPeriod = 0.03 //* Rate that the timer will update the stack. Increasing it will decrease the performance. static if (LIBRARY_TimerUtils) then private static timer p_loop = NewTimer() //* Uses TimerUtils. The timer is never destroyed so its not that important, I just wanted to else //* use the 'optional' keyword and try out static if statements. private static timer p_loop = CreateTimer() //* This does not. endif boolean toDestroy = EXPIRATION_DEFAULT //* If this is 'false' then the projectile will not run .onFinish() and its remaining time //* will not decrease. boolean hitDead = hitDead //* Dead units are valid targets. boolean hitEnemy = hitEnemy //* Enemy units are valid targets. boolean hitAllied = hitAllied boolean hitStructure = hitStructure static method onLoopUnitEnum takes nothing returns boolean local thistype this = .p_sampleProj local unit filt = GetFilterUnit() local integer c = 0 local real x local real y local real z set x = GetUnitX(filt)-this.p_currentPos.x set y = GetUnitY(filt)-this.p_currentPos.y call MoveLocation(.p_sampleLoc, x, y) set z = GetLocationZ(.p_sampleLoc)+GetUnitFlyHeight(filt) - this.p_currentPos.z if not (IsUnitInRange(this.toUnit, filt, this.collision) and (SquareRoot(x*x + y*y + z*z) <= this.collision)) then return false endif // .onUnitCollision is only called if a valid target is struck. if (hitDead==IsUnitType(filt, UNIT_TYPE_DEAD) /* */and GetUnitTypeId(filt)==0) /* */and (hitEnemy==IsUnitEnemy(filt, GetOwningPlayer(this.toUnit))) /* */and (hitAllied==IsUnitAlly(filt, GetOwningPlayer(this.toUnit))) /* */and (hitStructure==IsUnitType(filt, UNIT_TYPE_STRUCTURE)) then call this.onUnitCollision(filt) endif return false endmethod // Updates stack .p_stack on a periodic timer. static method onLoopStatic takes nothing returns nothing local integer i = .p_stackTrack - 1 local integer j local thistype this local s_vector3d vec1 local s_vector3d vec2 loop exitwhen (i < 0) set this = .p_stack[i] if (this != 0) then set vec1 = this.p_currentVelocity set vec2 = this.p_currentAcceleration // Scale the added acceleration based on the time-scale of the projectile. set vec1.x = vec1.x+(vec2.x*this.timescale*this.timescale) set vec1.y = vec1.y+(vec2.y*this.timescale*this.timescale) set vec1.z = vec1.z+(vec2.z*this.timescale*this.timescale) set vec2 = this.p_currentPos // Scale the added velocity (to position) based on time-scale. set vec2.x = vec2.x+(vec1.x*this.timescale) set vec2.y = vec2.y+(vec1.y*this.timescale) set vec2.z = vec2.z+(vec1.z*this.timescale) // Update the projectile. call SetUnitX(this.toUnit, this.p_currentPos.x) call SetUnitY(this.toUnit, this.p_currentPos.y) call MoveLocation(.p_sampleLoc, this.p_currentPos.x, this.p_currentPos.y) call SetUnitFlyHeight(this.toUnit, this.p_currentPos.z-GetLocationZ(.p_sampleLoc), 0) // .onLoop should be executed after the projectile has been updated but before any of the other user methods. call this.onLoop() // Since 'doSync' was just called, .p_sampeLoc will still have the correct coordinates. if (this.p_currentPos.z <= GetLocationZ(.p_sampleLoc)) then // Indicates that the projectile has struck the ground. call this.onGround() endif // Only execute the projectile-collision if it is activated. if (this.activeCollision) then //set j = 0 //loop // exitwhen (j == .p_stackTrack) // if not (.p_stack[j] == this) then // set vec = .p_stack[j].p_currentPos.copy() // call vec.sub(this.p_currentPos) // if (vec.magnitude() < this.collision) then // // Indicates that a projectile has come within collision-range of another projectile. // call this.onCollision(.p_stack[j]) // endif // call vec.destroy() // endif // set j = j + 1 //endloop endif if (this.toDestroy) then set this.p_timeRemaining = this.p_timeRemaining - .p_loopPeriod if (this.p_timeRemaining <= 0.00) then // Indicates the projectile's trajectory is complete. call this.onFinish() endif endif // Only execute unit-collision segment if the projectile unit-collision is activated. if (this.activeUnitCollision) then // Enumerate nearby units so that they can be "detected". Store 'this' in global variable for referencing in enum response. set .p_sampleProj = this call GroupEnumUnitsInRange(.p_sampleGroup, this.p_currentPos.x, this.p_currentPos.y, COLLISION_MAX, Filter(function thistype.onLoopUnitEnum)) call GroupClear(.p_sampleGroup) endif endif set i = i - 1 endloop endmethod // Launches the projectile with provided input. If the projectile cannot be created with specified input then this // method will return false. method doLaunch takes s_vector3d start, s_vector3d target, real speed, real pitchArc returns boolean local real d local real a if (start==0) or (target==0) then return false endif // Initialize vectors and specific info. set .p_currentPos = start.copy() set .p_currentTargetPos = target.copy() set .speed = speed //* Speed/arc are never used internally, but they are there for user reference in any set .arc = pitchArc //* case. // The timer must be started if this is the first projectile in-stack. if (thistype.p_stackTrack==0) then call TimerStart(thistype.p_loop, thistype.p_loopPeriod, true, function thistype.onLoopStatic) endif // Add the projectile to the stack. set .p_index = thistype.p_stackTrack set thistype.p_stack[thistype.p_stackTrack] = this set thistype.p_stackTrack = thistype.p_stackTrack + 1 // set d = SquareRoot((start.x-target.x)*(start.x-target.x) + (start.y-target.y)*(start.y-target.y)) //* This is a 2D distance. set a = Atan2(target.y-start.y, target.x-start.x) if (d==0.0) or (speed==0) then set .p_currentPos.x = .p_currentTargetPos.x set .p_currentPos.y = .p_currentTargetPos.y set .p_currentPos.z = .p_currentTargetPos.z set .p_timeRemaining = 0.0 // This indicates the projectile will be instant. set .p_currentAcceleration = s_vector3d.createEx(0, 0, 0) set .p_currentVelocity = s_vector3d.createEx(0, 0, 0) // The projectile should be destroyed immediately (ignoring the inaccuracy of the timer). else set .p_timeRemaining = d/speed set .p_currentAcceleration = s_vector3d.createEx(0, 0, -8*pitchArc*speed*speed/d) set .p_currentVelocity = s_vector3d.createEx(speed*Cos(a), speed*Sin(a), -.p_currentAcceleration.z * (d/speed)/2 + (target.z-start.z)/(d/speed)) endif // Scale projectile vectors set .p_currentAcceleration.x=.p_currentAcceleration.x*thistype.p_loopPeriod*thistype.p_loopPeriod set .p_currentAcceleration.y=.p_currentAcceleration.y*thistype.p_loopPeriod*thistype.p_loopPeriod set .p_currentAcceleration.z=.p_currentAcceleration.z*thistype.p_loopPeriod*thistype.p_loopPeriod set .p_currentVelocity.x=.p_currentVelocity.x*thistype.p_loopPeriod set .p_currentVelocity.y=.p_currentVelocity.y*thistype.p_loopPeriod set .p_currentVelocity.z=.p_currentVelocity.z*thistype.p_loopPeriod // Sync projectile and call .onBirth user-method. If a projectile is "instant" it will not be destroyed until the first // timer-loop. This is a maximum inaccuracy of the timer's loop timeout. call SetUnitX(.toUnit, .p_currentPos.x) call SetUnitY(.toUnit, .p_currentPos.y) call MoveLocation(thistype.p_sampleLoc, .p_currentPos.x, .p_currentPos.y) call SetUnitFlyHeight(.toUnit, .p_currentPos.z-GetLocationZ(thistype.p_sampleLoc), 0) call .onBirth() return true endmethod // Creates a projectile based on a unit. static method create takes unit u returns thistype local thistype proj = thistype.allocate() set proj.toUnit = u call UnitAddAbility(u, FLY_ABIL_ID) call UnitRemoveAbility(u, FLY_ABIL_ID) return proj endmethod endstruct endlibrary So far this library will mimic a WarCraft III missile, given an arc/speed. If the speed is 0 or the distance between the source/target is 0, then it will treat it as an instant projectile. Other than that, the projectile will fly through the air (it is able to be slowed/sped up by the use of the timescale variable) until reaching its target, at which point it will call the onFinish method. It uses a unit to construct a "projectile" but it keeps all the functionality of the unit intact, theoretically allowing the user to make jump type effects as well. I will also post the code for a structure that extends the projectile, called rangedprojectile. This uses the dummy.mdx model with multiple pitch animations to achieve a proper looking arc. It also allows you to create various different types of projectiles easily. JASS:library Ranged requires Projectiles globals //***************** //* Configuration //* ¯¯¯¯¯¯¯¯¯¯¯¯¯ //* //* //* The unit used to create ranged projectiles. The dummy model I am using is Vexorian's pitch animation model, so I can //* simulate projectile "tilt". public constant integer DUMMY_ID = 'dumy' //* //* //**************************** endglobals struct rangedprojectile extends projectile //******************** //* Ranged Projectile //* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ //* A structure with methods that allows the user to easily create missiles that look/act exactly like WarCraft III missiles. //* //* readonly unit source = null //* Source from which the projectile came. readonly effect model = null //* The effect that represents the projectile's "model". readonly string modelpath private static location p_sampleLoc = Location(0, 0) //* Used to optimize GetLocationZ method onDestroy takes nothing returns nothing call KillUnit(.toUnit) if (.model!=null) then call DestroyEffect(.model) endif endmethod // Each time the projectile is updated on the timer-loop. method onLoop takes nothing returns nothing local s_vector3d vec = .velocity call SetUnitFacingTimed(.toUnit, Atan2(vec.y, vec.x)*bj_RADTODEG, 0) call SetUnitAnimationByIndex(.toUnit, R2I(bj_RADTODEG*Atan2(vec.z, SquareRoot(vec.x*vec.x + vec.y*vec.y)+0.5)+90)) // Since we are only given a copy of the velocity, we must destroy it. call vec.destroy() endmethod // Easy-to-use constructor method. static method create takes unit source, real heightoff, real targx, real targy, real speed, real pitchArc, string modelpath returns thistype local thistype r local unit u local real x local real y local real theta local s_vector3d vec local s_vector3d vecb set x = GetUnitX(source) set y = GetUnitY(source) set theta = Atan2(targy-y, targx-x) set u = CreateUnit(GetOwningPlayer(source), DUMMY_ID, x, y, theta*bj_RADTODEG) set r = thistype.allocate(u) set r.source = source set r.modelpath = modelpath set r.model = AddSpecialEffectTarget(modelpath, u, "origin") call MoveLocation(thistype.p_sampleLoc, x, y) set vec = s_vector3d.createEx(x, y, GetLocationZ(thistype.p_sampleLoc)+GetUnitFlyHeight(source)+heightoff) call MoveLocation(thistype.p_sampleLoc, targx, targy) set vecb = s_vector3d.createEx(targx, targy, GetLocationZ(thistype.p_sampleLoc)) call r.doLaunch(vec, vecb, speed, pitchArc) call vec.destroy() call vecb.destroy() return r endmethod endstruct endlibrary |
| 03-02-2010, 11:12 PM | #2 |
#1: Use a single timer which iterates over every active projectile at once, instead of one timer per projectile. Just build a simple list in a static unit array, and iterate over it in a single timer callback. Much less overhead per projectile that way. #2: onLoop is insanely slow. Virtual methods in vJass are insanely slow, so calling one n-times, many times a second is true madness. #3: Vexorian had some magic script that calculated the collision size of units. You may wanna look in to that. |
| 03-02-2010, 11:40 PM | #3 | ||||
Quote:
I have two versions. One that uses hashtables and multiple timers and another that uses a stack and a single timer. The one with hashtables seems to perform better. Quote:
It really doesn't make much difference in this situation. Its not like I'm running up thousands of projectiles (I wish). Quote:
What do you mean "Virtual Methods"? Quote:
Well if you enumerate units in a radius plus the maximum unit collision then you can use the function IsUnitInRangeXY to factor in collision. Its not magical, nor does it improve the performance of this system. |
| 03-03-2010, 12:24 AM | #4 | |||
Quote:
Quote:
Interface methods (and stub methods) in vJass are virtual. When you instantiate (.create()) a class that extends from an interface, the lowest-order implementation of a virtual method is stored in a variable tied to the instance. (interface A has method Do, B extends A, C extends B. C.create() stores C's version of Do, as opposed to B's.) When the virtual method is invoked, the game has to dynamically invoke the function that was stored at creation using TriggerEvaluate(), which is VERY slow. (Why virtualization is implemented this way in vJass is a topic I'm too lazy to write aboot without being asked.) Quote:
--------------- Other things to note: - Function calls in JASS (and thus vJass) are slow in the first place. Avoid them wherever possible. (JASS Helper inlines a few functions/methods, but it's inlining is very, very limited.) - Using vector structs like that is very nice and clean, and also slower than using x/y/z members directly. It's up to you if the tradeoff in speed is worth making the code more complex. There's probably 10 million more things I could say about writing optimized vJass/JASS code. |
| 03-03-2010, 12:33 AM | #5 | |||
Quote:
Okay, well despite the fact that it could possibly be slow it is the only way of achieving the functionality that I want. Quote:
Yea, that uses IsUnitInRangeXY. I have no real need of using a unit's collision-size anyways. 03 / 02 / 2010 (8:50) It seems that as I create/destroy these projectiles, the memory usage on my computer goes up. I did some FPS tests and it seemed to always shoot back up to 64 after the projectiles had been destroyed, but it varies a lot depending on how many units are visible on the screen and other things. Quote:
Actually, now that I'm testing it again, the single timer seems to be working better. Much better. The hashtable is actually really slow. Weird because I swear when I was testing it before the hashtables were much quicker. I'm getting roughly 200 projectiles on a screen without any real lag issue. I still have to implement unit enumeration and projectile enumeration though, which will weigh a lot in terms of performance. I will update the first post's code with the code used with a single-timer system. |
| 03-03-2010, 03:05 AM | #6 |
It should be the hashtable, which is slower than a stack. I tested running several thousands of timers, which seemed to have almost no effect on performance at all. Also there was an article somewhere around here, which stated that multiple timers are actually better in the long run, because for 1 timer all callbacks concentrate on one interval, while for multiple timers, all callbacks are "smoothly" divided, while the performance loss of running multiple times is very insignificant. For very many instances, one timer causes noticeable lag spikes and becomes unplayable earlier than multiple timers (at least thats what I read). |
| 03-03-2010, 04:09 AM | #7 | |
Quote:
I actually found now that using the stack yields better performance. I'm testing them both at the moment and the stack can handle 80 quite easily, while the hashtables start lagging. I hate how over-all inefficient JASS is... or at least object manipulation. Just tested again (to make sure) and the single timer with a stack obliterates the multiple timers with a hashtable. I originally thought that the stack would be more efficient, so I coded it that way. After realizing that maybe using hashtables was the way to go, I made two versions so that I could compare them easily - I honestly thought hashtables would be much quicker : ( |
| 03-03-2010, 01:28 PM | #8 |
One timer per instance is only sane to do with a method like timerutils... red?... (Initializing timers very early in map init so when you subtract the initial handle index from their GetHandleId() values, they fall below 8190 and thus can be used directly as array indexes, negating the need for hashtables.) That may spread the processing out a little bit better, but really, apples and apples at that point. --------------- Let me stress again how slow onLoop is: It is around 7 times slower than a normal function call. Even if a projectile class does not implement the method, the overhead is _still_ there. Really, really, if you need to be doing something every iteration, it should be part of the core system. If you just need to do something approximately every iteration, why can't it run on a separate timer and avoid the rape of virtualization? (You'd need to run one timer per type of projectile that needs something done per tick, but that shouldn't be too bad.) ----------- JASS:// We don't want the projectile struct to leak so we need to remove it once its trajectory has completed. method onFinish takes nothing returns nothing call this.destroy() endmethod ------------- If you're going to use vector classes, at least put them in to locals to reduce the overhead. (this. is an array access, p_currentPos. is another array access. By making a local "curPos" and placing p_currentPos in to it, you reduce the number of array accesses. Note that the overhead of allocating and initializing a local may dwarf the overhead of a few array accesses, so you have to be using a vector a significant number of times to warrant this.) ------------- I would really, really suggest you remove the code that simulates projectiles from the projectile struct. It makes the code harder to follow by mixing an object's code with the code that deals with said objects. (Not at all an optimization, just a suggestion.) ------------- JASS:method onCollision takes projectileinterface p returns nothing defaults nothing //* When the projectile collides with another projectile (p = other projectile). Why does it take projectileinterface instead of projectile? seems like you intend all concrete types to extend from projectile itself. -------------- JASS:if (hitDead == (IsUnitType(filt, UNIT_TYPE_DEAD) and GetUnitTypeId(filt)==0)) then set c=c+1 endif if (hitEnemy == IsUnitEnemy(filt, GetOwningPlayer(this.toUnit))) then set c=c+1 endif if (hitAllied == IsUnitAlly(filt, GetOwningPlayer(this.toUnit))) then set c=c+1 endif if (hitStructure == IsUnitType(filt, UNIT_TYPE_STRUCTURE)) then set c=c+1 endif if (c==4) then call this.onUnitCollision(filt) endif Heck, even using a boolean and doing b = b and _____ would be faster, as "and" and "or" are short-circuit in JASS. ----------------- As I said... 10 million more things. |
| 03-03-2010, 09:16 PM | #9 | |||||
Quote:
To be honest when I coded the interface right away knowing what functions I wanted (this is a re-write of code) and I must have used projectileinterface thinking that using projectile would have caused an error. After that point I completely overlooked it. Quote:
What exactly is the point of inheritance if it sucks. More coming. Just having a smoke. Quote:
JASS:// Scale the added acceleration based on the time-scale of the projectile. set this.p_currentVelocity.x = this.p_currentVelocity.x+(this.p_currentAcceleration.x*this.timescale*this.timescale) set this.p_currentVelocity.y = this.p_currentVelocity.y+(this.p_currentAcceleration.y*this.timescale*this.timescale) set this.p_currentVelocity.z = this.p_currentVelocity.z+(this.p_currentAcceleration.z*this.timescale*this.timescale) // Scale the added velocity (to position) based on time-scale. set this.p_currentPos.x = this.p_currentPos.x+(this.p_currentVelocity.x*this.timescale) set this.p_currentPos.y = this.p_currentPos.y+(this.p_currentVelocity.y*this.timescale) set this.p_currentPos.z = this.p_currentPos.z+(this.p_currentVelocity.z*this.timescale) So you think I should do something like: JASS:set vec1 = this.p_currentVelocity set vec2 = this.p_currentAcceleration // Scale the added acceleration based on the time-scale of the projectile. set vec1.x = vec1.x+(vec2.x*this.timescale*this.timescale) set vec1.y = vec1.y+(vec2.y*this.timescale*this.timescale) set vec1.z = vec1.z+(vec2.z*this.timescale*this.timescale) set vec2 = this.p_currentPos // Scale the added velocity (to position) based on time-scale. set vec2.x = vec2.x+(vec1.x*this.timescale) set vec2.y = vec2.y+(vec1.y*this.timescale) set vec2.z = vec2.z+(vec1.z*this.timescale) Quote:
What exactly do you mean? Also I suppose if I'm doing that I should remove the onLoop from the interface so that users can only access the projectile methods on certain events (finished, started, etc.). Quote:
This is an idea in development, I'm not really sure where I'm going to go with it. You think I should do this? JASS:// .onUnitCollision is only called if a valid target is struck. if (hitDead==IsUnitType(filt, UNIT_TYPE_DEAD) and GetUnitTypeId(filt)==0) and (hitEnemy==IsUnitEnemy(filt, GetOwningPlayer(this.toUnit))) and (hitAllied==IsUnitAlly(filt, GetOwningPlayer(this.toUnit))) and (hitStructure==IsUnitType(filt, UNIT_TYPE_STRUCTURE)) then call this.onUnitCollision(filt) endif |
| 03-03-2010, 09:26 PM | #10 |
There was some trick using delimited comments to create multi-line oneliners: JASS:
if hitDead == IsUnitType (filt, UNIT_TYPE_DEAD) /*
*/and 0 == GetUnitTypeId(filt) /*
*/and hitEnemy == IsUnitEnemy (filt, GetOwningPlayer(this.toUnit)) /*
*/and hitAllied == IsUnitAlly (filt, GetOwningPlayer(this.toUnit)) /*
*/and hitStructure == IsUnitType (filt, UNIT_TYPE_STRUCTURE) then
call this.onUnitCollision(filt)
endif
Still pretty ugly, but as fast as one line and at least looks a little better (since it gets compiled to one line I assume) € just tested and seems to compile :) €2 :-* @ rep, sorry but I have no idea how to manipulate tesh, never tried that |
| 03-03-2010, 09:57 PM | #11 |
I'm going to rep you for that. That makes it so much neater. By the way, is there any way of changing the color of delimited comments with TESH (the recent one, with cJass implemented). Right now I use a darker background and the green comments just looks U-G-L-Y. Lol, ftw! JASS:if (hitDead==IsUnitType(filt, UNIT_TYPE_DEAD) /* */and GetUnitTypeId(filt)==0) /* */and (hitEnemy==IsUnitEnemy(filt, GetOwningPlayer(this.toUnit))) /* */and (hitAllied==IsUnitAlly(filt, GetOwningPlayer(this.toUnit))) /* */and (hitStructure==IsUnitType(filt, UNIT_TYPE_STRUCTURE)) then call this.onUnitCollision(filt) endif This is one of the best uses of delimiting comments I've found yet (of course sometimes I avoid their ugly hulkish charm). |
| 03-03-2010, 10:49 PM | #12 | ||||
Quote:
Quote:
Yes. You may also wanna put timescale in a local considering how many times it's referenced. Mind you, it's still possible that the overhead of the locals is more than the overhead of the array accesses . compiles to. I doubt it in this case, though. Quote:
Right now, all of your code is in one struct. What I suggest is splitting the code in two: Put the static timer loop callback as a function, and split out some other code (e.g., the stack of projectiles in to globals instead of static members) to help separate the what (a projectile) from the how (move it like a projectile should move). That should help in maintenance and in other people reading it. Just my opinion, of course. Quote:
|
| 03-04-2010, 01:50 AM | #13 | |
Okay. Now, well at the moment I'm content with the amount of projectiles that this can handle at a time (around 200 on a single screen, I'm sure if they were separated over map it would increase a little). They are capable of being efficiently created/destroying (lots of short life-spans) but they currently lack the functionality of .onCollision and .onUnitCollision. Quote:
I will try to split it up a little better (this is just the second re-write, the first version was lame) in my next version. I really would like some tips on doing the nearby unit/projectile enumeration though. |
| 03-04-2010, 02:09 AM | #14 |
It would be good if your projectiles could destroy trees in a efficient way. |
| 03-04-2010, 02:30 AM | #15 |
They have to be enumerated separately, so I suppose they'd be methods: JASS:method onDestructableCollision takes destructable d returns nothing method onItemCollision takes item i returns nothing If I'm going to add destructables, I might as well include all of the widgets. Though, these features require lots of enumerations which can lag the game. Doesn't the SEE engine have collision detection? |
