Third Tutorial: an exercise.
In the 2 previous tutorials we have looked at some basic concepts of programming such as:
- Syntax: basic syntax of a statement and comments.
- Types: basic types, boolean, int, char, float and also String.
- Variables: declaration, initialisation and assignment, conversion between variables of different types.
- Control flow: Conditional Statements, if(), else() and logical operators ( OR “||“, AND “&&“, NOT “!“). Loops, mainly through the use of the for() loop, and we also mentioned the while() loop.
- Scope: the importance of curly brackets “{” and “}” in the visibility of variables.
- The basic setup in processing, using setup() and draw().
- Functions: how to create your own functions which take 0 or more parameters, and return 0 or 1 values, and the keywords void, and return.
- Arrays: syntax for their declaration and access through the [] operator.
- Classes and objects: basic syntax and what they are.
- References: all things created with the new operator are passed by reference, instead of their values being copied, when assigning and passing to as arguments to functions.
This tutorial consists of a basic exercise, for getting more familiar with these concepts, before we move on to examine self-organisation and other form generating processes in more detail.
The exercise:
Have you ever played the Atari classic game Asteroids? (also one of the examples in Daniel Shiffman’s processing book “the Nature of Code“). The idea of this exercise is not to implement the whole game, but just the movement of the space ship. These are the possible steps you can take:
- Start by having the basic processing setup, with the setup()function and the draw() function. Remember, these are called by processing, setup() once when it starts executing the program, and draw() every time it draws on the screen.
- What are the basic variables you need? of what type? start declaring them. Perhaps this spaceship is not that different from the particle of the previous tutorial…Also, if you have looked at the latest updated version of the flocking algorithm, you may have seen that many vector operations can be done easier using the PVector class, so using it may be an option. For the spaceship you will need most likely need, like in the particle, a position and a way of defining a direction. You will also need to initialise the values. If you get stuck, look at the reference or at examples in the processing site for some inspiration.
- You should draw a spaceship… there are a number of different ways of doing this. If you want to keep things simple draw it as we draw our particle, with a circle and a direction, at least to begin with. A more advance way of doing it is using transformations such as translate() or rotate(). Check the reference and how they work.
- How does the spaceship respond to controls? you should input keystrokes…can you see in the processing reference any way of doing that?
- How do you convert the key input into rotation and speed of the rocket?. First you should perhaps start with the rotation… Remember that you can use print() and println() to see how your variables change.
- Does the screen wrap around? you can see how we did it last time, or perhaps you can find another way of writing it.
I will help you through the class and post a possible implementation here…This process is a bit hard because it implies writing code from scratch, but hopefully, we will get through it and we will have learned something afterwards…Good luck!
“Après-coding”:
And after a morning of hard work, (hopefully mild) headaches, and perhaps some satisfaction of actually coding for the first time something from scratch and making it work (no small feat!) here comes my version of the code, and a recap on a few things I have talked about with you this morning:
//These are the variables, as with the agent code float xpos; float ypos; //velocity float vx; float vy; //...but we add this for the headingAngle float headingAngle; void setup(){ size (500,500); stroke(255); noFill(); //middle of the screen xpos=width/2; ypos=height/2; //velocity=0, that is, still. vx=0; vy=0; } void draw(){ background(0); drawRocket(xpos,ypos,headingAngle); xpos+=vx*0.04; ypos+=vy*0.04; //this is for wrapping around (it could be in its own function...) if(xpos<0){ xpos+=width; } if(ypos<0){ ypos+=height; } if(xpos>width){ xpos-=width; } if(ypos>height){ ypos-=height; } } //function to draw the rocket. void drawRocket(float x, float y, float heading){ pushMatrix(); //this store the current coordinate system translate(x,y); //this moves the origin to x,y rotate(heading); //this rotates the coordinate system, by heading triangle(0,-5,15,0,0,5); //this draws a triangle facing in the (rotated!) x axis popMatrix(); //this resets the coordinate system to one stored with pushMatrix() } void keyPressed() { if (key == CODED) { if (keyCode == UP) { vx+=cos(headingAngle); //use cos to convert an angle to an x component of a vector, and add vy+=sin(headingAngle); //use sin to convert an angle to an y component of a vector, and add } if (keyCode == LEFT) { headingAngle-=0.1; } if (keyCode == RIGHT) { headingAngle+=0.1; } } }
There are a couple of things we have not seen in the previous tutorials (the idea was to train in using the Processing reference to find out new things), and that I have discussed with some of you in today’s class:
Keyboard input:
There are a couple of different ways of doing it. One is to use the keyPressed system variable in the draw() function. keyPressed is a system variable of type boolean, which means that it is a variable defined and set by Processing (not by you) and that it can be true or false. To see how it works, you can write this minimal program:
void setup(){ size(500,100); } void draw(){ if(keyPressed){ background(255); fill(0); text(key+" has been pressed" , 10, 50); } }
the if() statement will be evaluated every time Processing does a draw(). If no key is pressed, keyPressed will be false so it won’t do anything. If a key is pressed, keyPressed will be true, and then it will redraw the background (and erase everything that was there before), and write the key + ” has been pressed” String on the screen, at the (10,50) coordinates (you can see how this works in the text() function.
Instead of writing that text to the screen, you could check what type of key it is and if it is the key you want to process, for example like this:
if(keyPressed){ if (key == CODED) { if (keyCode == UP) { vx+=cos(headingAngle); //use cos to convert an angle to an x component of a vector, and add vy+=sin(headingAngle); //use cos to convert an angle to an y component of a vector, and add } if (keyCode == LEFT) { headingAngle-=0.1; } if (keyCode == RIGHT) { headingAngle+=0.1; } } }
This code would go, as I mentioned, inside you draw() code, or otherwise in a function that you call from draw() -you could for example call it “processInput()”, or any other name you want to give it-.
Another way of getting keyboard input (which is the one I have used in my version) is by using the keyPressed() function. We have explained that if you define a function you have to call it from somewhere within your code, perhaps in setup(), draw(), or in any other function that you may have created. The function you have defined won’t execute by itself. But actually this is not entirely true; the same way both setup() and draw() are automatically called by Processing when starting the program and every time the screen is redrawn, respectively , the keyPressed() pre-defined function will be called automatically every time a key is pressed. It may be a bit confusing that the keyPressed system variable and the keyPressed() function have the same name, but they are two different things (though they are used for similar purposes). With the keyPressed() function, you put there the code you want to be executed every time (and only when) a key is pressed (like you do in draw() or setup()). There are a number of similarly predefined function for mouse input, like mouseClicked() or mouseMoved(), for example.
Drawing transformations:
In the introduction I recommended to use the typical way of drawing the agent or particle we have previously used, with a circle and line. In my implementation of the code I have used instead translate() and rotate(), and its friends pushMatrix() and popMatrix(). The way of using these functions to draw is different to what we have seen until now. If one wants to rotate a triangle or a rectangle, or actually any complicated shape, this is the easiest way to do it… the best parallel to what these functions do is to think of a coordinate system in a CAD program; by default, the origin and direction of the axes of the coordinate system are (0,0), and the X axis horizontal and Y axis vertical. But you can change this, and put your origin and direction of the axis as you want. This is what translate() and rotate() do. They translate and rotate the coordinate system. For example, after doing a translate() to coordinates (a,b), if you draw something like “line(0,0,10,0);” it will draw a line at (a,b) (where we have moved the origin) and, if we have not rotated it, the line will be 10 pixels long in the x direction. We can combine as many translate() and rotate() calls as we want, but the order in which we call them will matter. It is not the same to translate first and rotate afterwards than to rotate first and then translate (you can change the order in the example code and see the effect). These transformations are set for the rest of the drawing process, so if you want to get back to where you where (for example to reset the transformations to do a set of new ones) this would be rather difficult. pushMatrix() and popMatrix() help with that. If we use pushMatrix(), the current coordinate system will be stored, we can then do any transformations we want, and then we can restore the “saved” coordinate system with popMatrix(). An easy way of seen the effects of using pushMatrix() and popMatrix() is to copy the code given in their respective reference, and deleting or commenting out (by writing “//” in the beginning of the line you want to comment out) pushMatrix() and popMatrix(). In the example code, because I am drawing only one rocket, removing pushMatrix() and popMatrix() will not have any effect, but it is good practice to always make your transformations and drawing between push and pops, so you reset any changes you may have done to the coordinate system.
If you want to learn more about transformations, here is a tutorial. Also, and if you want to get a grip on the vector stuff, I remind you of this tutorial, and here is a basic introduction to vectors. We are not going to use vectors in many of the examples, but it is always good to know if you want to compute any geometry. This is applicable not only to Processing, but to many other contexts in scripting or manipulating geometry.
Also, if you want to see another version of the Asteroids Spaceship, you can find it in the Processing applications, under “File->Examples…” and there under “Books->Nature of Code->chp3_oscillation->Exercise_3_05_asteroids”. Lots of other interesting code in the book too!