infinitim Posted December 15, 2016 Share Posted December 15, 2016 Hi all... This is my first post on this forum, so tell me if I'm breaking any rules . I've just been getting into html5 canvas game programming, and I'm just making a simple shooter game (NOT space invaders style). Basically, my green square guy (controlled by arrow keys) gets chased by the red square guys. I want to be able to have him shoot them (spacebar) with a little blue square, which I want to fly at them, no aiming required, and then disappear on contact, but none of the code I've tried has worked yet. Just want some pointers on how to implement that. Thanks. Included JS file. game.js run.html Quote Link to comment Share on other sites More sharing options...
mattstyles Posted December 16, 2016 Share Posted December 16, 2016 Does your blue square shoot in a straight line? Or is it a heat seeking missile that can turn? Either way you'll need your maths skills to be up to scratch, specifically, vector maths skills are useful here. You need a couple of things to start with, namely the position of your origin (which would be your current, or, in this case, your green dude) and a position of your target. If you have multiple targets you'll need to select one, this is your auto-aim (there are lots of ways to do that, simplest to start with is to probably iterate over all your enemies and do a simple distance check on each one, whilst you might do some square rooting to get an accurate distance you actually don't need to which can save you some calculations, you only need the closest and you don't need accurate distances for that, so long as they are consistent distances you're golden, but maybe I've shouldn't have mentioned optimisations this early!). Now you want an angle between the two points, lots of ways to get that, but lets assume you now have it. Depending on how you've done this you'll either get back a vector, or you'll want to convert your angle into a vector. A Euclidean Vector is a simple structure describing magnitude and direction, in this case you only really need direction. Now create you bullet at your turret position (green dude) and give it the vector you just calculated so that it has a direction. Now it gets fun. Each game tick you simply want to use the direction vector to update the position of the projectile (blue squares). Again, there are a few ways you can do this. The best way is to apply a force, in the given direction, to your object. Then calculate the outcome of that force upon your projectile, which gives you a velocity, and you then apply that velocity to your projectile's position. This can be as simple as adding the force vector to the current velocity vector and then applying the velocity vector to your current position (which can, and probably should be a vector too, then you can create functions or even classes to represent vectors and simplify your code a whole load). If your projectile just shoots in a straight line then when you create it give it a velocity vector, based on the angle between origin and target, and then each tick you just apply the velocity vector to your positional vector to get your new position. This way you assume the velocity vector is a constant. Your projectile will stop either when you hit something, just destroy it in your collision detection algorithm, or you might want to add something like a life variable, update that each tick and when its hits 0 (or if you're counting up, some number) kill it (you'll still want to be running collision detection against this entity if you want it to have any impact in the world, quite literally). If this sounds like a lot of work to move an entity in a straight line, well, its just different work not necessarily more or harder, but, it sets us up perfectly for the next challenge, which is heat-seeking, or, pursuit mechanism. If you want to implement pursuit then each tick you need to recalculate the angle between the projectile and the target. Let's assume that you're applying a constant force to the projectile each tick, we've been doing this by locking velocity. We actually want the magnitude of the velocity vector (how big it is) to be constant, but, we can change the direction. So, calculate the angle between origin and target and then calculate the difference between current angle and this desired angle, it will either be positive, negative or equal. If its equal, happy days, we're heading bang in the right direction. If it's positive or negative we'll need to rotate our velocity vector (usually positive is clockwise), again, this is fairly trivial to do with vector maths. Now apply the rotated velocity vector to your positional vector to get your new position. This method of constant velocity vector application will make your projectile ridiculously responsive, if your target was able to teleport (or jump, or phase-shift, or whatever) from infront of your projectile to behind it, your projectile has no modelled momentum so it would turn immediately, a handy attribute for a missile but probably not very realistic. If you wanted to implement more realistic movement you'll need to model momentum, this can be done fairly simply by using the velocity vector and actually applying a constant thrust to the velocity, which probably means you want to model some sort of drag or something to slow the projectile otherwise it'll continually accelerate (maybe you want that). As your desired direction changes you change the direction of your force vector to match it, this way as you turn you will still have some of your residual force (momentum) applied, you'll need to dampen your velocity vector so that the old direction fades away leaving you travelling wholly in your new direction, this is actual simply, just apply a slight negative scalar to the vector (say, multiply by 0.9 each tick, although you can be smarter if you like, remember to use a shield pattern to reduce velocity to 0 if it under a certain amount otherwise just applying a negative scalar will never get you to a total stop). If you wanted to implement avoidance rather than pursuit then you just make sure your rotation is away from the angle between two entities, not towards it. If you wanted to implement intercept behaviour then it gets trickier as you'll need to project your direction and current velocity vectors and calculate an intercept of rays, but, its not that much of a jump from what you already have going. If you've never done any of this before then its worth doing all this by hand yourself, however, most physics libraries (I believe Phaser has 2 or 3 it can use) handle all this stuff for you, particularly regarding applying force vectors and attributing drag and mass etc etc the better the engine the better the simulation, but, the more horsepower it takes to run it, a lot of times you can cheat this stuff to get a good-enough approximation (only calc what you need to). In any case you'll need to know what is going on, even if you're not totally 100% on how the engine/library/simulation is making all this modelling happen. Quote Link to comment Share on other sites More sharing options...
infinitim Posted December 16, 2016 Author Share Posted December 16, 2016 Thanks for the reply. Just for clarification, I want my shot to hit every time, no aiming required (homing, but no special stuff yet betond bare minimum). I tried using the atan2 function to get the angle you were talking about, but I don't know if I did it right, as my code teleports the rocket to the green guy whenever I shoot. xxxx is the angle I want to capture, so I did atan2 of xxxx's tangent ratio ((target.pos.y - rocket.pos.y) (adj), (target.pos.x - rocket.pos.x) (opp)). According to W3schools you're supposed to put the y in before the x in the function, so that's why the (target.pos.y - rocket.pos.y) came first. Rocket __ __ __ __ __ __ __ __ | xxxx \ | \ | \ | \ | Target Now the next step to do is to, on a keydown, call a function that will basically do this on the y and the x: rocket.pos.x += xxxx * 100 * dt (the time since the update function was last called). For some reason, this isn't working. I'm not quite sure why. I think the best idea right now would just be to have someone look at the code, find a solution and explain it to me, as this is the first game I have ever programmed, JS or otherwise, and I've been trying to solve it for a while now. Thanks again for taking the time to write that detailed reply! Will upload the (updated, but not in a helpful way) JS file with this post. Don't bother replying until I've done that as I don't have time right now. Quote Link to comment Share on other sites More sharing options...
mattstyles Posted December 17, 2016 Share Posted December 17, 2016 Math.atan2 requires two arguments, y and x (in that order) and returns the angle of the vector in radians, with 0 pointing positively along the x axis. You're doing the maths absolutely correct to find the angle between two points by subtracting the target from the origin and using atan2. However, you actually don't need the angle explicitly, all you need is a vector. Note that I'm referring to a vector as a Euclidean Vector, there are many disambiguations for representing a vector, but here our vectors are length-2 vectors represented as arrays, basically just [x, y]. In this case direction and magnitude are combined to create the array, an alternative representation would be to use an array to represent direction (a unit vector) and a float to represent magnitude. I'll touch on that later but it's up to you how you want to do it, pros/cons to each. rocketDirection = [target.x - origin.x, target.y - origin.y] If you added some helper functions to a Vector class this would be a simple subtraction task: // Where vectors/points are of the form [x, y] function subtract (origin, target) { return [target[0] - origin[0], target[1] - origin[0]] } Either way you're basically doing the same thing. Either of these functions returns a new vector representing the direction of your rocket, but we can actually get a little smarter with our representation by converting that direction to a unit vector (a unit vector is simply a vector normalised to have a length of 1, this will become useful later when we want to apply some force/thrust to the rocket). // Calculates the length of the vector // I'm sure you know where this comes from! function length (v) { return Math.sqrt(Math.pow(v[0], 2) + Math.pow(v[1], 2)) } // Not that the OR 0 accounts for dividing by 0 errors function divide (a, b) { return [a[0] / b[0] || 0, a[1] / b[1] || 0] } function unit (vector) { let l = length(vector) return divide(vector, [l, l]) } (if we had defined our vectors as a direction and a magnitude earlier then we could have foregone this conversion from combined vector into a vector of parts, but, whatever, we have options). Now we have a unit vector representing the direction that we want to make the rocket travel in. Let's assume we'll use a value of 1 to represent the force we'll apply to the rocket. We can assume that each of our entities have a position and a force to apply each tick, which we have just calculated for the rocket, I'll leave it out for the origin and the target and just use their positions in this example. var origin = { position: [10, 0] } var target = { position: [15, 0] } // Create our new rocket var rocket = { position: origin.position, force: { direction: unit(subtract(target.position, origin.position)), magnitude: 1 } } // In this case our rocket position should equal [10, 0] as it starts at the origin, its direction should become [1, 0] with a magnitude of 1, this represents the velocity. // You can see that the target and origin and aligned on the x axis so a direction of [1, 0] means we are facing along the x axis, perfecto. // Now on each update simply apply your velocity to the position function add (a, b) { return [a[0] + b[0], a[1] + b[1]] } function multiply (a, b) { return [a[0] * b[0], a[1] * b[1]] } function update () { // Calculate the amount of actual force to apply by converting rocket.force into a vector var force = multiply(rocket.force.direction, rocket.force.magnitude) rocket.position = add(rocket.position, force) } Calling the update function each tick should calculate the force to be applied, in this case a magnitude of 1 simply means our direction vector will stay as [1, 0] so by applying that each tick our position will update from starting at [10, 0], through [11, 0], [12,0], [13,0] etc etc. All we've done is create a few helper functions for working out the properties of 2 dimensional arrays and then modelled our entities by a position and a force, there are lots of different ways of doing this but the theory is always the same, each update you calculate the sum of all the forces acting on your model and apply those forces to its position to get a new position. Hopefully you can also see that to implement a crude pursuit mechanism all we'd have to do is recalculate the direction vector (just a subtract and a unit based on current position and target position) each tick, to account for the fact that your target is likely moving, and you're done, simple pursuit. As we haven't modelled any momentum or residual force your rocket will be ludicrously responsive and so long as it is faster than your target it will always hit it (unless it is so fast it jumps past it during a tick, but thats a separate issue). From there you can get smarter by trying to implement prediction so that your rocket aims at where it thinks the target will be at the time it takes to reach it. Thats a fun bit of maths! Also its a fairly easy step to model momentum by keeping track of the current momentum and applying your forces to it, before applying the momentum to the position during an update. This will give you far more realistic movement patterns and it actually isn't too difficult. From there you could start implementing mass and drag. All of these helper functions could be added to a vector class if thats more your thing. If we fleshed out the entity functions a little more we'd have implemented our own crude physics modelling system in about 100 lines of code or less. For many types of game this would be enough and its fairly cheap computationally too. Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.