Citrus Engine: Shooting Arrow

Just simple tutorial for this time. I’ll show how to make a character that can aim with mouse and shoot projectiles. And I choose arrow instead of straight bullet because arrow is a little bit more advanced than bullet. But if you want your character to shoot bullet, rocket, or grenade, you can simply modify the implementation. Should be not quite much difference.

orc_archers_squad_by_daroz-d5oi7u9

Start with creating the CE project. Create MainClass and the GameState. Create floor & walls. Not much to explain here, as all of them are just default CE objects.

Open the GameState class, then inside the initialize() method, before the box2D initialization code, add this lines:

PhysicsCollisionCategories.Add("hero");    
PhysicsCollisionCategories.Add("bullet");

Since the bullet is not a sensor, we need to define a collision group for both Hero and Arrow object, so the arrow will not collide with the hero. As you can see, we create two categories, “hero” and “bullet”.

Create a new class extend CE box2D Hero class. Give it name CustomHero. After that override its defineFixture() method.

override protected function defineFixture():void  
{     
     super.defineFixture();     
            
     _fixtureDef.filter.categoryBits = PhysicsCollisionCategories.Get("hero");     
     _fixtureDef.filter.maskBits = PhysicsCollisionCategories.GetAllExcept("bullet");     
}    

We put it to ‘'hero” categories. Then maskBits is to select which object categories that can collide with this. In this case, we make the hero can collide with all objects, except any objects in the “bullet” category.

Create a new class extends CE box2D Crate class. Name it Arrow. Why extend crate? Well, I’m just lazy to create a new object from Box2DPhysicsObject class. But the arrow has quite same characteristic with crate, so why not?

public function Arrow(name:String, params:Object = null)    
{     
     super(name, params);     
            
     _beginContactCallEnabled = true;     
     updateCallEnabled = true;     
}     
        
override protected function defineFixture():void     
{     
     super.defineFixture();     
            
     _fixtureDef.density = .1;     
            
     _fixtureDef.filter.categoryBits = PhysicsCollisionCategories.Get("bullet");     
     _fixtureDef.filter.maskBits = PhysicsCollisionCategories.GetAllExcept("hero", "bullet");     
}

Inside the constructor, set both _beginContactCallEnabled & updateCallEnabled property to true. It will make the object respond when on contact. And it will enable its update() method. We also override the defineFixture() method to define its collision category things.

Shooting
Ok, move back to GameState class and now you can initialize the hero. Also make the stage respond touch input:

var hero:CustomHero = new CustomHero("hero", {width: 50, height: 80});    
hero.x = 100;     
hero.y = 100;     
add(hero);

             
stage.addEventListener(TouchEvent.TOUCH, mouseClick);

And this is what happen when you touch the screen:

private function mouseClick(event:TouchEvent):void    
{     
     var touchVec:Vector.<Touch> = event.getTouches(stage, TouchPhase.ENDED);     
            
     if (touchVec.length > 0)     
     {     
         var posX:int = event.getTouch(event.currentTarget as DisplayObject).globalX;     
         var posY:int = event.getTouch(event.currentTarget as DisplayObject).globalY;  
                 
         var worldPos:Point = ((view as StarlingView).viewRoot as Sprite).globalToLocal(new Point(posX, posY));     
                
         var hero:CustomHero = getObjectByName("hero") as CustomHero;     
                
         var angle:Number = Basic.getPointAngle(hero.x, hero.y, worldPos.x, worldPos.y);     
                
         var distX:Number = Math.abs(hero.x - worldPos.x);            
         var distY:Number = Math.abs(hero.y - worldPos.y);            
         var powerX:Number = distX / 400;     
         var powerY:Number = distY / 200;     
                
         var arrow:Arrow = new Arrow("bullet", {width: 40, height: 10});     
         arrow.x = hero.x;     
         arrow.y = hero.y;     
         arrow.rotation = Basic.radianToDegree(angle);     
         add(arrow);                
                
         var xDir:Number = Math.cos(angle) * powerX;     
         var yDir:Number = Math.sin(angle) * powerY;     
                
         arrow.body.ApplyImpulse(new b2Vec2(xDir, yDir), arrow.body.GetWorldCenter());     
     }     
}

First things first, to detect clicking or tapping, make sure there are touches that ‘ended’ already. If it’s exists then we can execute the shooting action.

We need to know the coordinates where the click happen with posX & posY variables. And then convert them to game state coordinates, worldPos Point variable.

Then get the angle between the click and you’re hero. I’m using my utility class here to do that. If you want to know how it works, there are a lot of reference about that on the web. It just calculate the x & y distance between the objects, then using atan2() to get the angle. Quite simple, but I’m too lazy to remember & rewrite it. Smile with tongue out

I designed the power of launched arrow based on the distance between the click to the hero. That’s what distX, distY, powerX, and powerY variables mean. The value of 400 & 500 on powerX and powerY are just the hardcoded one to get my desired power result.

Then we can create the arrow. Just initialize it like the other CE objects. But we need to specify its rotation using the angle we’ve got before. Convert it to degree value first. Yet again, I’m using my utility class.
Additional note: Replace the arrow object with built-in CE Missile object if you want to create a hero that shoot a straight bullet. And ignore the remaining code below.

The arrow won’t do anything for now. So let’s apply some impulse on it. Before that, define the x and y direction of the impulse, just see the xDir and yDir variables. Then using them as a vector parameter on applyImpulse() method.

Almost done. Open the Arrow class. Override the update() method:

override public function update(timeDelta:Number):void   
{    
     super.update(timeDelta);    
            
     var flyingAngle:Number=Math.atan2(body.GetLinearVelocity().y, body.GetLinearVelocity().x);    
     body.SetAngle(flyingAngle);    
}

We need to define the flyingAngle, so we’ll get a realistic flying arrow effect. Using atan2() and the arrow velocity as parameter to get the current angle.
Additional note: Remove that line, then you’ll have a stone or grenade throwing effect. Probably add a constant small rotation to get better effect.

Sticking Arrow
To get a even more realistic effect, then we can make the arrow that will stick on the target. To do that, it just using a weld joint.
Open the Arrow class, and override the handleBeginContact() method:

override public function handleBeginContact(contact:b2Contact):void   
{    
    var collider:Box2DPhysicsObject = Box2DUtils.CollisionGetOther(this, contact) as Box2DPhysicsObject;    
            
    var jointDef:b2WeldJointDef = new b2WeldJointDef();    
    jointDef.Initialize(body, collider.body, body.GetWorldCenter());    
            
    var joint:b2WeldJoint = b2WeldJoint(_box2D.world.CreateJoint(jointDef));    
            
    updateCallEnabled = false;    
            
    setTimeout(function()    
    {    
       kill = true    
    }, 2000);    
}

Take a reference to the collider, then create a weld joint between it to the arrow. Also set the updateCallEnabled to false. So it not being rotated anymore. And kill the arrow after two second hit the target using setTimeout() function.

Done
Alright, it’s done. You can see the result and download the source code below:


download

Freelance 2D game artist, occassional game developers, lazy blogger, and professional procrasctinator

2 comments:

  1. Very thanks, I was looking for this everywhere.

    ReplyDelete
  2. thank you!
    wow. I am time traveler am I? :-)

    ReplyDelete