My middle-schooler had been playing some sort of arrow-shooting game, and woke up this morning with a question: ignoring air resistance, would a projectile fired at 15° from horizontal travel the same distance as one fired at 75°? He had reason to suspect so, but wasn't sure.
I told him I wasn't sure either; to find out we would have to either use calculus, or code. And I couldn't remember the calculus! So we fired up Mini Micro to find out.
Yesterday in our getting-started series we learned about the pixel graphics display. Today we'll use that again, but with the setPixel
method to draw the projectiles. Most of the code (though it's not very much) will be about simulating the physics of projectiles in motion. Load up your own Mini Micro and follow along — it's surprisingly easy!
The first thing we want to do is make a little class that represents an object flying through the air. A class in MiniScript is really just a map. So this defines an empty class called Object:
Object = {}
But then we want to give this object some default properties that represent its state. An object in motion has a position (in X and Y) and a velocity (also in X and Y). So now add:
Object.x = 0 // pixels
Object.y = 0
Object.vx = 0 // pixels/sec
Object.vy = 0
(The //
double-slash thing starts a comment, which is some helpful text left in the code for human readers; it's entirely ignored by the computer. We add comments just to make things clearer or remind us later of what's going on.)
So now our Object class has these position and velocity properties; next it needs some functions (sometimes called "methods" when they're on a class) for initializing these properties, and then updating them on each step of the simulation. We're going to let our objects always start at x=0 and y=0 (which as you learned yesterday, is the lower-left corner of the screen). But we need to set the velocity according to the launch angle and speed. That's easy enough with the sin
and cos
functions.
Object.launch = function(degrees, speed)
radians = degrees * pi / 180
self.vy = speed * sin(radians)
self.vx = speed * cos(radians)
end function
(If you've forgotten — or not yet learned — enough trigonometry to understand the above, don't be embarrassed! Most people don't use it every day. Ask below and I'll be happy to explain.)
Also, notice the use of self here. When you have a function on a class, self
is a special variable that automatically refers to the object that function was called on. So in this case self.vx
is the vx of the object that we're launching. This will all become clear by the time we're done — hang on! 🙂
Next, the function to update the position and velocity on each time step. We'll need to include gravity here, or our projectiles would just travel forever in a straight line. So somewhere at the top of your code, insert:
gravity = -10 // pixels/sec^2
And then after the Object.launch function, let's add an Object.update function, that takes a time step (dt) in seconds to scale everything by:
Object.update = function(dt)
self.vy = self.vy + gravity * dt
self.x = self.x + self.vx*dt
self.y = self.y + self.vy*dt
end function
Hey hey, we just implemented Newton's laws! The velocity is updated by gravity, and then the position is updated by velocity. Easy peasy.
And that completes our Object class. Now we need to create and launch a few objects:
objA = new Object
objA.launch 45, 100
objB = new Object
objB.launch 15, 100
objC = new Object
objC.launch 75, 100
We create each object using new, and then call the launch
method we defined above, each one with a different angle (but the same speed).
Now to make it easier to update all three objects, let's put them into a list called, I dunno, how about stuff
:
stuff = [objA, objB, objC]
Almost done! We have a list of stuff (objects) we want to update and plot. All that's left is the main loop to do exactly that.
clear
while true
for obj in stuff
obj.update 0.1
gfx.setPixel obj.x, obj.y, color.yellow
end for
end while
This clears the screen, and then loops forever (while true
). On each iteration of the main loop, we also loop over the stuff
list (for obj in stuff
). We call each object's update
method that we defined above, passing in 0.1 (seconds) as the time step. And finally, we then plot the position of the object using gfx.setPixel
.
The result? After putting all of the above together and fixing any typos, when you run
, you should get this:
Neat, huh? So my middle-schooler was right!
This shows the power of programming. When you have a question, you can get an answer, and make a fun little simulation in the process. This same code is also the basis for a lot of classic games, e.g. Worms, which you could totally make in Mini Micro now!
And once you have this, it's easy to take it further... such as: what happens if we do add a bit of air resistance? A simple way to do that is to just multiply self.vx and self.vy by 0.99 in the update function. Try it!
Click here for Day 7!