Objects
The goal of the lab today is learning about objects. By the end of the lab, you should have a program that stores and draws multiple objects, resulting in an image similar to this:
Learning Objectives
- Create objects using {} notation.
- Access fields of objects using
object.fieldname
notation. - Study an example method (a function that is associated with an object), in this case draw methods.
- Learn how different types of objects can be drawn using a single method call, such as
object.draw()
- Practice using functions to separate out details from the “main” program.
Get Started
To get started, create a sketch by copying the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
// Lab 7 template var things = []; var NAMES = ["Alex", "Riley", "Chris", "Dana", "Geri", "Jan", "Kim", "Pat"]; var HATCOLORS = ["brown", "seagreen", "blue", "tan", "tomato", "dimgrey"]; function setup() { createCanvas(400, 300); for (var i = 0; i < 8; i++) { if (random(1.0) < 0.5) { things.push(makeBird(i * 40 + random(20, 60), 50 + random(50), random(15, 30))); } // Later, we'll use this to make "people" to push onto things[] array. // makePerson(50 + i * 40, 200, random(5, 60)); } noLoop(); } function draw() { background("lightcyan"); noStroke(); fill("yellowgreen"); rect(0, 180, width, height - 180); for (var i = 0; i < things.length; i++) { things[i].draw(); } } function personDraw() { var w = this.ph * 0.25; noStroke(); fill(0); ellipseMode(CORNER); // the head ellipse(this.px, this.py - this.ph, w, w); // the body ellipse(this.px, (this.py - this.ph) + w, w, this.ph - w); ellipseMode(CENTER); // restore default setting } function birdDraw() { noFill(); stroke(0); strokeWeight(2); push(); translate(this.bx, this.by); scale(this.bsz / 50); ellipseMode(CENTER); // just to be safe arc(35, 35, 100, 100, PI * 1.25, PI * 1.5); arc(-35, 35, 100, 100, PI * 1.5, PI * 1.75); fill(0); ellipse(0, 0, 5, 5); pop(); } function pickHatColor() { return HATCOLORS[int(random(HATCOLORS.length))]; } function ageToHeight(age) { return 30 * (sqrt(age) - age / 10 + age * age / 3300) - 20; } function makePerson(x, y, age, name) { var person = {"px": x, "py": y, "ph": ageToHeight(age) }; person.draw = personDraw; return person; } function makeBird(x, y, size) { var bird = {"bx": x, "by": y, "bsz": size}; bird.draw = birdDraw; return bird; } |
Code Walk-Through
Read the code with your TA. Here are some highlights:
- In
setup
, bird objects are created and “pushed” onto the end of thethings
array. - The expression
random(1) < 0.5
is true half the time, so only some iterations of the loop create a new bird. - The function
makeBird()
accepts parameters and uses the values of those parameters to initialize fields in objects. The “bird” object is returned bymakeBird()
to the caller (setup()
). - The
draw()
function draws all of the objects inthings[]
by invoking thethings[i].draw()
method. This retrieves an object fromthings
, finds thedraw
field or property, and calls it as a function. The particular function for bird objects isbirdDraw()
, so it’sbirdDraw()
that is actually called. - Within
birdDraw()
, we can refer to the bird object being drawn usingthis
, sothis.bx
is the “bx” property or field of the bird object.
Step 1
Define the function coinToss()
. It should return true
half the time, and false
half the time. Use print(coinToss());
in setup()
to test your function (or devise another test).
Then, instead of using if (random(1) < 0.5) {
in setup()
, use if (coinToss()) {
instead. This is your first use of a function to simplify and clarify the main program. Again, test your program.
Step 2
Uncomment the line with makePerson()
. Following the example of makeBird()
, use coinToss()
to make people only on some iterations, and when you conditionally make a person, “push” the person object onto the things
array so that the person will be drawn by things[i].draw()
.
Note that makePerson()
and personDraw
are already defined, so now when you run your program, it should draw some “bowling pin” people.
Notice how makePerson()
calls "ph": ageToHeight(age),
to convert the age parameter (that is randomly chosen from 5 to 60) to a height parameter (in pixels).
Step 3 – Names
Give people names. The array NAMES
is already defined, so pass another parameter, NAMES[i]
to makePerson()
(we’ll just use the i-th name in NAMES
for convenience so that each “person” gets a different name).
You will need to add another formal parameter to the definition of makePerson()
so that the caller’s actual parameter count and the callee’s formal parameter counts will match. You can name the formal parameter name
if you like.
You will also need to add name
into the object by creating a new field or property. You can name the property "name"
or "pname"
as you wish.
Finally, in personDraw()
, you will need to draw the name. Here’s some code:
1 2 3 4 |
// draw the name fill(0); textFont("fantasy"); text(??, this.px, this.py - this.ph - w); |
In place of ??
, you should access the “name” field/property from the person object. Notice that this property has the type of String.
Step 4 – Hats
Now, we’re going to make hats. Hats will depend on color, and we’ll add a hat color property to person objects. We have already provided the function pickHatColor()
. Instead of passing in hat color as yet another parameter to makePerson()
, we will just pick the color and set a property within makePerson()
. The code to initialize the hat color should be ..., "phat": pickHatColor(), ...
inside the {...}
notation that builds the person objects. Notice that pickHatColor()
is called to pick a random color each time a person object is constructed, so different “people” will have different colored hats.
You might like to see how hat colors are randomly selected. Ask your TA if you do not understand the implementation of pickHatColor()
.
To draw hats, add the following lines to personDraw()
. (You should be able to write this — it has been and will be on the exam, but we’ll save you the time now to mess with coordinates and get things looking right.)
1 2 3 4 |
// the hat fill(this.phat); rect(this.px, this.py - this.ph - w / 4, w, w / 2); rect(this.px - w / 2, this.py - this.ph + w / 4 - 1, w * 2, 3); |
Notice that the phat
field or property is of type Color (which is actually a specially constructed integer.) Test your code!
Step 5 – Dresses
Finally, we’ll add a field or property of type Boolean — a true
or false
value — to determine whether or not to draw a “dress.” (Please forgive any implied gender stereotyping – we picked gender-neutral names, avoided any “male/female”-“he/she” terminology, and only wish to create clear visual indicators of the otherwise abstract machinations of the code.)
Add yet another property/field to person objects in makePerson()
: "dress" : coinToss()
. This re-uses our friend coinToss()
to make a random choice of true
or false
as the value of “dress”.
Next, add this code to draw dress
1 2 3 4 5 6 7 8 9 10 |
// dress? if (??) { var dressX = this.px + w / 2; var dressY = this.py - this.ph * 0.6; var dressW = w * 1.3; var dressH = w * 1.5; triangle(dressX, dressY, dressX - dressW, dressY + dressH, dressX + dressW, dressY + dressH); } |
In place of ??
you should write a (very short) expression for the Boolean value of the dress
property of the person.