Help with enemy-tank firing behavior

Hi @Saerothir,

Yes, I remember this project somewhat. :slight_smile:

Going back a few steps, you’re firing “invisible bullets”, I assume for the purpose of tracking the user? Sort of a predictive method whether the user tank is within range? If so, would you be interested in an actual, proven method of mathematically predicting the collision point of a moving object, i.e. fire a bullet from a stationary point (enemy tank) and have it hit the user tank, assuming the user tank is going in a straight line at the same speed?

This is the method commonly used in tower defense games, and the formula is proven many times over. It’s not “perfect”, i.e. it can’t predict if the user tank is going to slow down, speed up, or adjust its path post-fire, but it’s accurate if the target moving object continues on its normal vector path.

If you want that code, I can supply it for you. I didn’t write it myself… instead, credit Corona physics veteran @horacebury (Matt Webster)… but I have the code, and can supply it to you.

Brent

Yes, that’s exactly right. Whether the enemy tank is moving or stationary, it would constantly be firing ( the bullets would react to the physics objects wall, obstacles etc… ) and upon collision with the user it would trigger the enemy to then fire a real bullet.

  • It is the same concept used by the devs of TinyTanks -

I’d be very interested if you think it would be a better solution.
My tank is controlled by a virtual-joystick and the speed is fairly consistent - technically you could slow it down by only slightly moving the stick, but who would want to do that? :wink:

To give you a better idea of the type of game environment, here is a picture of a muckup level I’ve been using to test various things such as animations, effects, mechanics etc… etc…
link

Eventually I’ll be implementing the ‘Perspective’ camera module because I’ll be adding much bigger arenas.

So I was able to incorporate horacebury’s code, but I’m getting a weird response when my user_tank collides with my enemy tank’s invisible sensor (radar).
Here is a short vid of what is happening: video 
* For some reason the video quality on dropbox is terrible, but if you download the video it should be HD.

Here is my code for the enemy tank, fyi all the objects without local preceding them have a forward declaration:
[lua]

local enemyB02 = loader:spriteWithUniqueName(“Ebase2”)
enemyB02.class = “EnemyTank02”    
    

local enemyT02 = loader:spriteWithUniqueName(“Eturret2”)
enemyT02.class = “EnemyTank02”


------------Adding a Radar for Etank02
--------  

ET02radar = display.newCircle(enemyT02.x,enemyT02.y, 10)
ET02radar.isVisible = false      
physics.addBody(ET02radar,“static”,{radius=400,isSensor=true})
        

– detect a target entering the object’s area and fire at it
function ET02radar:collision(e)
     if (e.phase == “began” and e.other.class == “Player”) then
         print(“began”)
          timer.performWithDelay(1, function()
             fire( ET02radar, e.other )
        end, 1)
    end
        return true
end
ET02radar:addEventListener(“collision”, ET02radar)

– fires bullet
function fire( tower, user_tank )
        – solution: “firing solution” - the collision point between the tower’s bullet and the target
        – success: true if the tower can successfully fire on the target, false if the target will escape
        local solution, success = intercept( tower, user_tank, bulletSpeed )
                    –   
                local math_deg = math.deg  – localize ‘math.deg’ for better performance
                local math_atan2 = math.atan2  – localize ‘math.atan2’ for better performance
 
                – Calculates the angle between specified points A & B
                local function angleBetween( enemyB02X, enemyB02Y, user_tankX, user_tankY )
                local angle = ( math_deg( math_atan2( user_tankY-enemyB02Y, user_tankX-enemyB02X ) )+180) --; return angle
                    if ( angle < 0 ) then angle = angle + 360 end ; return angle % 360
                end

                local ang = angleBetween( enemyB02.x, enemyB02.y, user_tank.x, user_tank.y )
                enemyT02.rotation = ang
            –
        
        – only fire if the firing solution is successful
        if (success) then
                – calculate vx and vy
                local angle = angleOf( tower, solution )
                local pt = rotateTo( {x=bulletSpeed, y=0}, angle)
                local pt2 = mathapi.rotateAboutPoint( {x=enemyT02.x,y=enemyT02.y -65}, enemyT02, enemyT02.rotation - 90 )
                
                – create bullet
                Enemybullet = loader:createSpriteFromSHDocument(“Weapon5ex”, “GameFX”, “LevelAssets.pshs”);
                Enemybullet.x, Enemybullet.y, Enemybullet.solution = pt2.x, pt2.y, solution
                --bullet.x = pt2.x; bullet.y = pt2.y;
                physics.addBody(Enemybullet,{friction = 0, bounce = 1.0, density = 1.5, radius=10, isSensor = true})
                Enemybullet:scale(.65,.65)
                Enemybullet.class = “bullet”
                Enemybullet.isBulet = true
                Enemybullet.rotation = enemyT02.rotation + 90
                
                – fire bullet
                --bullet:applyForce( pt2.x - enemyT02.x, pt2.y - enemyT02.y, enemyT02.x, enemyT02.y )
                Enemybullet:setLinearVelocity(pt.x, pt.y)
        end
end

[/lua]

Also, I want the bullets to react the physical environment, but in doing so causes my enemy tank fire bullets towards the player even if there is an obstacle between them.
In adition to what I have already, should I add my original method of firing an invisible bullet and if it collides only with the user then tell it to fire a real bullet Or would that be the wrong to go about things?

Hi @Saerothir,

The main question now is, how are you dealing with the “range” of the enemies vs. the player? Do the enemies fire constantly no matter where the player is? Or do they have a certain radial range that the player must cross before they start firing? If so, how are you sensing this range? As a radial sensor (physics body) that’s larger than the enemy, or do you simply loop and do the distance math for the enemy depending on the location of the player?

Basically, there are a lot of factors involved which determine the best solution for this. The predictive aiming method I mention can work in various ways, but you’ll need to figure out the basic structure before you implement that part.

Brent

I haven’t started working with range yet. :] I’ve been focusing on trying to get the firing behavior working.
Eventually that is probably what I would do though, create an invisible radial sensor. <-- I assume no matter what method I end up using, the radial sensor concept will work? Or is there some math for working with/calculating range in the code from @horacebury?

The basic structure I had in mind was the following:
Firing - Like how I described in my previous posts.
Movement - Some enemies will move along bezier paths while others will move randomly within specified x,y coordinate “zones”.

I know there are quite a few variables to take into account when deciding what method to use, and I trust you to know what method would work best. I’m up for trying anything. :smiley:

 

Hi @Saerothir,

What is the weird response you’re getting? I didn’t see it in the video. Is it an error message?

On another note, when you turned on “hybrid view” in the video, I almost gasped… how many physics bodies/shapes are making up that one scene? It appears you’ve used a third-party tool to trace the objects? The outlying debris and walls look to be made of dozens or even hundreds of overlapping shapes. I’m sure that tool did its tracing very “accurately”, but you should probably build these physics bodies yourself. The lower pile of rocks, for example, could probably be constructed of 4 shapes instead of what appears to be 104. While it wouldn’t be as perfectly accurate, it would be close enough that the user wouldn’t notice it (it’s not like you need a tiny marble to roll across a detailed, jagged piece of terrain). Anyway, food for thought… this kind of over-use of the physics engine could drag your app performance way down on slower devices.

Brent

Oh, now I see your other request at the bottom, below the code. horacebury’s code doesn’t yet factor in obstacles that are sitting in the potential path of the projectile. Fortunately, you can add this yourself, using raycasting.

All you need to do, when the bullet direction is calculated by the predictive aiming code, is to cast a ray from the source (enemy tank) out to an extended distance along the predicted path (it can be some point off the screen; just multiply out the vector length). Then, detect if there’s an obstacle between the source and the player, and void (skip) the bullet firing if true.

See the demo project below about how to implement raycasting. With this, there is definitely not any need for “invisible bullets” or anything like that.

http://www.coronalabs.com/blog/2013/05/07/physics-raycasting-and-reflection/

Brent

haha! I figured you’d make a comment about that.  ;)  Yeah, I don’t know why they show up like that. I only use one physics outline - I trace around the object using SpriteHelper, example: photo
I’m an extreme perfectionist and I didn’t think meticulously tracing around the object would yield more than one physics shape. If you’ll notice in the right-side panel, there is some greyed-out text that says ‘UntitledFixture’, that represents my physics outline. If I wanted I could make many more, but like you said that would most likely cause performance issues.
I’ll email the dev and ask if this is normal.

Really?! Yay! I was ecstatic when they announced raycasting (as I can see it being a hugely efficient solution to many gaming scenarios ) but I thought it was only available to ‘PRO’ users. lol this is very welcome news.  :D 
I’ll get to work on it right away!

Oh yes, this meticulous tracing will result in a ton of physics shapes. :slight_smile:  For a few reasons:

  1. No physics shape can have more than 8 sides (except a circle, which clearly has more than 8 “sides” as seen in the hybrid view; but that’s a special case).

  2. No physics shape can have concave angles… so, when you try to trace a complex shape, it will need to accommodate all of those concave angles by making additional shapes. Sometimes it can’t be avoided, but certainly you should simplify your traced objects to under 10 shapes if possible (ideally more like 5 or 6).

As for raycasting, you’re right, it’s currently only for Pro users. However, the next public release is coming quite soon, and when it’s released you’ll have access to raycasting as a Starter.

Brent

lol okay, that makes sense. I’ll work on reducing them. B)
Would changing their shape from concave to convex make a difference?

Alright. So at the moment I can work with it, but until the next pubRelease I can’t actually build with it?

BTW - lol the “weird response” that I was referring to was the enemy-tank firing many bullets towards the user rather than just one, like in horacebury’s included physics example.
To my knowledge, I haven’t changed any of the original firing settings from horacebury’s code… could it be related to my physics outline? Like the rocks, in that it is showing multiple physics shapes and each one colliding with the radar is causing multiple bullets to be fired?

Once I get the bullet’s collision working properly, I’ll be adding a cap to the number of bullets that can be present in the display at any given time.
For example: say the brown tanks can fire five bullets; once all five have been fired, the Etank would not be able to fire again until at least one is removed, making the overall count 4. Same with the user.

When I run your demo project in the simulator and I click within the display, I get an error -
"Line: 34

Attempt to call field ‘rayCast’ (a nil value)"

Hi @Saerothir,

For the raycasting API (and the demo project), you’ll have to wait until the next public build, but as I mentioned, that’s coming very soon.

As for a ton of bullets being fired at once, I think I know what’s happening. :slight_smile: When I watch the video, it appears that your tank is also composed of many shapes. If that’s true, you’re actually generating a collision on every instance of a shape colliding with the firing radius. So, you’ll get a collision report when the tank’s “right tread” collides, when it’s “left tread” collides, when it’s “body front” collides, when it’s “read exhaust pipe” collides, and any other instance where ANY of it’s individual body shapes collides.

The solution to this is generally to create a “counter” property on the tank like “collisionCount” and increase/decrease that count on each collision “began”/“ended” event respectively. So, when any shape that makes up the tank body collides with the firing radius (began phase), you add 1 to the count, and when a shape exits the firing radius (ended phase), you subtract 1. Then, you only queue firing of a bullet if the count is 1… but not greater than 1. This means that when the tank first crosses the firing radius line, a bullet will be fired, but no other bullets will be fired until the tank fully exits the field and then re-enters it.

Of course, ideally, you’ll want to set up a system where the enemy continues to fire on some kind of repeating timer while the tank remains in the field… but that’s a whole different topic and will require a modified approach.

Brent

Oh okay. I’ll be anxiously and impatiently waiting! :lol:

Well actually, my user_tank is only comprised of one outline. With SH you can create multiple physics-bodies on one object, identified by a custom tag e.g. the greyed-out-text I pointed out before, for example I could outline the front and back wheel of a bicycle and give them custom tags/identifiers if I wanted to program unique collisions for each portion of the same object.

In my case, I just have one physics-shape which is an outline of the entire tank… and SH seems to like to chop it up into multiple physics-shapes <_<

Yes, and that is precisely what I’m going to do. :smiley:
I would probably have something random, like as long as the user_tank is within the radar shoot x number of bullets, randomizing each shot from between say 1 and 2 second delays.
would it make sense to wrap all of the code inside the entire enemy fire function in an if else statement?
[lua]
function Fire()
local EtankBullets = 0

    if EtankBullets < 5 then
       EtankBullets = EtankBullets +1
    —
    —
    – FireBullet code –
    —
    —
  else if EtankBullets == 5 then
           — stop function
    end
end
[/lua]
 

Hi Saer,

If SH insists on chopping up your tank trace into many shapes, you’ll need to make it into one shape manually (not using SH), or else implement the counting system I talked about.

For the repetitive firing, I suppose one method would be to start a repeating timer while the tank is inside the range, and cancel the timer when the tank exits the range. This wouldn’t give your a random increment time between bullets however. For that you’d need to run a timer (just once), fire the bullet, start a new timer of a different random duration, fire a bullet, and keep repeating that process.

Brent

Yeah, I was able to re-trace the user_tank and get it down to just one shape. Obviously the turret is independent from the tank-body so I had to trace that also, and now I have only two bullets being fired when the tank collides with the radial sensor.
I’ll either add a collision counter or some type of collision filter to fix it.

Okay, that sounds like a good idea. I’ll work on that while I wait for the pubRelease. B)

btw, I was able to re-trace many of my objects and here is the result: photo
Definitely better than it was. :smiley:

Any thoughts?
Can the code you mentioned be found in the Code-Exchange? I searched “horacebury” but i didn’t find anything relating to what we’re discussing…

Hi @Saerothir,

I have an update for you! I asked Matt to post these functions on the Code Exchange, and he did, just yesterday:

http://developer.coronalabs.com/code/predictive-aiming-tower-defense

As I mentioned, I’ve briefly tested these and they work great. But you’ll have to work them into your code, and figure out the various settings and parameters that make it function as you wish.

Brent

Awesome!! Thanks for asking him Brent! :smiley:

The included example looks to be just what I need.
Adapting the code to my project is turning out to be a bit tricky, but I’ll get it.

Thanks for all your help and advice.

Hi Saer,

The new public release just went live. :slight_smile:

http://www.coronalabs.com/blog/2013/06/04/new-public-release/

This will allow you to use raycasting, which I know you’re eager to test out.

Have fun!

Brent

Hey Brent,
Yeah I started working on it right when I found out. :smiley:
Thanks again for all your help, I really appreciate it. :smiley:

I had fun messing around with your demo project.
Implementing rayCasting wasn’t too difficult.

So far I’ve got a ray being cast at the user_tank when it collides with the radar, and when the user_tank leaves the radar or the enemy casting the ray is destroyed, the ray is removed - really easy stuff - the only thing I’m having trouble with is figuring out how to tell the ray to stay on the user_tank as long as it is within the radar bounds.
(Then I would have a timer, like you suggested, for shooting behavior while the user_tank is in ‘range’ )

I have the code from rayCast API docs that prints information in the terminal etc… and I’m hoping I implemented this correctly.
At the beginning of my fire function I have all the code that deals with making the ray:
[lua]
function fire( tower, user_tank )
    E1Dray = physics.rayCast( enemyB02.x, enemyB02.y, user_tank.x, user_tank.y, “closest” )
        
    local hitFirst = E1Dray[1]
    local hitX, hitY = hitFirst.position.x, hitFirst.position.y
        
    DRline = display.newLine( enemyB02.x, enemyB02.y, user_tank.x, user_tank.y )
    DRline.width = 10
    DRline:setColor( 255,50,40,100 )
    DRline.blendMode = “add”
    level1group:insert( DRline )

if E1Dray then
    – There’s at least one hit.
    print( "Hit count: " … tostring( #E1Dray ) )

    – Output all the results.
    for i,v in ipairs(E1Dray)  do
        print( "Hit: ", i, object_name, " Position: ", v.position.x, v.position.y, " Surface normal: ", v.normal.x, v.normal.y )
   end
    print( "The first object hit is: ", E1Dray[1].object, " at position: ", E1Dray[1].position.x, E1Dray[1].position.y, "
    where the surface normal is: ", E1Dray[1].normal.x, E1Dray[1].normal.y )

    else
    – There’s no hit.

   end
if E1Dray[1] == “Player” then

— enemy firing code

[/lua]

Also, would it make sense to have some type of Runtime listener that would constantly be checking if there are obstacles in the way and if not then check if the user_tank is within the radar bounds and if that is true then execute the enemy fire function?