It's a bit complicated, but I'll give my best shot to describe what I've done...
In my game I'm using mouse controls to allow the player to aim attacks via a given angle based on the relation from the center of the screen to the mouse position.
For example, if I have a mob at position 1,1 and my mouse is at 5,5 then the aiming angle is 45 degrees. This is how pretty much how the player points their attacks, of course, the system I'm using is much more accurate, taking the position based in pixels, rather than tiles.
So, how do we get a beam to work? Well, visually, I did what I suggested in another thread: I used a simple small icon, then I applied color to it, then scaled it based on how far I wanted the beam to go. I also had to translate it by half the scaled distance, since scales happen from the center out. Finally I angled it to match the angle of attack. I displayed it via an image and all is well visually.
Now the tough part: How to make it hit a target?
Let's say we have two mobs, blue as the source, and red as the enemy. The target angle is represented by green.
Now, I could just try to match the angle to the target angle, and if it's the same, it's a hit, but because of how accurate the game is, that doesn't exactly help, and would require the player to be incredibly accurate.
I could also check to see if the target is within a certain range of an angle from the target angle. Let's say 5 degrees within the target angle.
The problem with this solution is that it makes a cone, causing targets closer to miss while further targets are hit more easily. It would be much easier to code and use, but I want accuracy!
What I want to do, is something like this:
How do we do this? Well, it all comes down to one question: Is the target between the dotted lines?
To find this out, I found a helpful procedure online, which uses some relatively simple math to find out of a point is to the left or right of a line.
IsLeft(ax, ay, bx, by, cx, cy)
var/value=(bx - ax)*(cy - ay) - (by - ay)*(cx - ax)
if(value>0) return 1
else return 0
Now, with the help of this procedure, I just need to get two lines, and check to see if the position of the target is to the right of one line, and to the left of the other. If this is the case, then the target is within both lines, and within the beam.
In the example, I've plotted out two points as the origins of our new lines. The way I get these points is by taking the target angle, modifying it by a right angle, and then adding along the new angle that's created to get at an offset position. Here's an example of what that looks like (assuming variables have been initialized already):
mod_x=10
mod_y=0
l_off_x1=position_x+cos(angle-90)*mod_x
l_off_y1=position_y-sin(angle-90)*mod_y
Since we didn't need to adjust the y position relative to the angle give, we only modify the x value along the new angle. The modified x value tells us how far the edge of the beam is away from the center. As a result, this allows us to control the width of the beam, the larger the x offset, the larger the beam.
We now have a position defined as left_offset_x,left_offset_y. This exist as a spot that is left of the angle given. This position is denoted in yellow.
Now, we do the same modification we did to the original line, and apply it to the new position, using the original angle, to give us a line that is parallel to the original angle when we plug the variables into the IsLeft() proc.
l_off_x2=l_off_x1+cos(target_angle)*2
l_off_y2=l_off_y1-sin(target_angle)*2
Here I used only a small adjustment of 2, as the angle will be the same regardless of the position of the second point on the line.
Now, we plug everything into IsLeft(). The target.cy and cy variables are obtained by getting a list of mobs within range of the beam, then checking their precise position to see whether or not they'll be hit.
IsLeft(l_off_x1, l_off_y1, l_off_x2, l_off_y2, target.cx, target.cy)
If this returns true (1), this means that the target is to the left of the left edge of the beam, and should not be hit. If it returns false (0), then it is to the right of the line, and has potential to be within the beam.
We then perform the same check with another set of points, based on the right side of the beam. For this we use target_angle+90 instead of -90, to show that it's to the right of the original position. The offset for x should be kept the same to make sure that the beam is not lopsided.
One everything is said and done, we should be checking to make sure the target is both to the right of the left edge, and to the left of the right edge, if it is, it is within the beam, and affects such as damage can be applied.
Only one step left. The mathematical beam that we've created stretches across the entire map, technically, and enemies at the exact opposite of where you're aiming will end up getting included in the beam as well.
In order to keep from hitting them, we'll do one final IsLeft() check, using the position of the source(cx,cy), the position of the left offset(l_off_x1,l_off_y1), and finally of the target(target.cx,target.cy).
IsLeft(cx,cy,left_off_x1,left_off_y1,target.cx,target.cy)
This line runs perpendicular to the original angle, and since the beam is traveling outward from the center, it is to the right of the line. So, if the target is to the right of the new line we're checking with IsLeft, then it is also in the path of the beam, while also not being on the exact opposite side of the source
In the end, I know that this code is nowhere near efficient, but it is a way to do it viably, and from my testing, it works pretty well.
Full album of images: http://imgur.com/a/vGMV5
Again, I know it's not the best, but it took me some time to figure this all out and I wanted to share for anyone trying to do something similar in the future. Perhaps we can all help each other find a much more efficient and well designed method.
You can see it here
I was intending to make it into a lib but my own fear of judgement prevents me from actually doing so.
Perhaps we should however get something on github and work on improving it together.
I just think it's a cool little thing that can be handy.