The Eyeball Class
Composition: Building Complex Objects
While the Disk class taught us about creating simple objects, the Eyeball class introduces a powerful OOP concept called Composition.
Instead of being just one shape, an Eyeball is composed of multiple other objects. An Eyeball HAS-A base (a Disk), HAS-A pupil (another Disk), and even HAS catch-lights (tiny Disks)! This allows us to build complex things by assembling simpler building blocks.
👀 Eyeball Class Overview
The Eyeball class manages the base, pupil, and their interactions (like looking at things). It handles the logic for keeping the pupil inside the eye!
Attributes:
| Attribute | Type | Description |
|---|---|---|
base |
Disk | The white part of the eye |
pupil |
Disk | The colorful center part |
direction |
float | Angle the eye is looking (radians) |
pupil_radial_location |
float | Distance of pupil from the center |
is_locked |
boolean | If true, the pupil stops moving |
Key Methods:
look_at(target_x, target_y)- Rotates pupil to face a pointcalculate_pupil_center()- Math magic to position the pupil based on angle/distancerender()- Draws the whole assembly (base first, then pupil)handle_click()- Detects clicks to lock/unlock the eye
📜 Complete Eyeball Class Code
Notice line 1: `from Disk3 import *`. This allows us to use the `Disk` class we defined earlier. In `__init__`, we don't just store numbers; we create new `Disk` objects and store them in `self.base` and `self.pupil`.
1from Disk3 import * 2import math 3 4class Eyeball: 5 """ 6 Represents an eyeball with a circular base and a movable pupil. 7 Uses HSB color mode for all colors. 8 Stage 3: Enhanced to support looking in a direction 9 Attributes: 10 base: a Disk for the whole eye 11 pupil: a Disk for the pupil 12 light: a Disk for a catch-light 13 ... 19 """ 20 21 def __init__(self, center, radius, b_border, b_colors, p_percent, p_border, p_colors): 22 print("...init Eyeball...") 23 self.base = Disk(center, radius, b_border, b_colors) 24 self.pupil = Disk(center, p_percent*radius, p_border, p_colors) 25 26 # Creating catch-lights (reflections) 27 light_radius1 = pow(p_percent, 2) * radius; 28 light_center1 = (center[0] + 1.2*self.pupil.radius * cos(.75*math.pi), center[1] - .6*self.pupil.radius * sin(.75*math.pi)) 29 self.light1 = Disk(light_center1, light_radius1, b_border, b_colors) 36 self.pupil_radial_location = 0 # initially at center of eye 37 self.direction = 0 38 self.is_locked = False 45 self.allow_wonky = True 46 #end init
The `Eyeball` class handles complex math (trigonometry!) to ensure the pupil stays inside the eye. By hiding this complexity inside methods like `calculate_pupil_center` and `set_pupil_radial_location`, other parts of our program don't need to know trigonometry to move an eye. We just tell it where to look, and the class handles how.
Note the `constrain()` function: It's a handy Processing tool to keep a number between a minimum and maximum value.
59 def set_pupil_radial_location(self, distance): 66 # Calculate maximum distance pupil can move while staying inside 67 max_distance = self.base.radius - self.pupil.radius - self.pupil.thickness/2.0 68 69 # Constrain the distance 70 # eureka! constrain is a great built-in function 71 self.pupil_radial_location = constrain(distance, 0, max_distance) 72 self.pupil.set_center(self.calculate_pupil_center()) 73 #end set_pupil_distance 74 75 def calculate_pupil_center(self): 81 # Convert polar coordinates to Cartesian 82 pupil_x = self.base.center[0] + self.pupil_radial_location * cos(self.direction) 83 pupil_y = self.base.center[1] - self.pupil_radial_location * sin(self.direction) 84 85 # Adjust catch lights... 90 91 return (pupil_x, pupil_y) #a tuple 92 #end get_pupil_center
These methods give the eyeball personality! `look_at` uses `atan2` (arctangent) to find the angle between the eye and a target (like the mouse). `look_in_direction` is new for Stage 3 - it aligns the eyes with the velocity vector so ghosts watch where they are going!
127 def look_at(self, target_x, target_y): 135 # Don't move if locked... 136 if not self.allow_wonky: 137 if self.is_locked or not self.is_point_in_eye(mouseX, mouseY): 138 return 144 145 dx = target_x - self.base.center[0] 146 dy = self.base.center[1] - target_y 147 148 angle = atan2(dy, dx) # Returns angle in radians 149 self.set_direction(angle) 150 151 # Calculate distance to target 152 distance = dist(self.base.center[0], self.base.center[1], target_x, target_y) 153 self.set_pupil_radial_location(distance) 154 #end look_at 156 def look_in_direction(self, velocity_x, velocity_y): 166 if self.is_locked: 167 return 168 169 # Calculate angle from velocity components 170 angle = atan2(-velocity_y, velocity_x) 171 self.set_direction(angle) 175 # Set pupil distance to a fixed amount (about 60% of max) 176 max_distance = self.base.radius - self.pupil.radius - self.pupil.thickness/2.0 177 self.set_pupil_radial_location(0.6 * max_distance) 178 #end look_in_direction
When we call `Eyeball.render()`, it doesn't draw ellipses directly. It asks its components (`base.render()`, `pupil.render()`) to draw themselves. This is the power of composition: delegating tasks to the sub-objects.
180 def render(self): 185 # Draw eyeball base 186 self.base.render() 191 # If locked, slightly darken the pupil as a visual indicator 192 if self.is_locked: 193 # use locked color 194 self.pupil.set_stroke_color(self.pupil_lock_color) 195 else: 196 self.pupil.set_stroke_color(self.pupil_orig_color) 197 198 self.pupil.render() 199 self.light1.render() 200 self.light2.render() 201 202 #end display
Creating an Eyeball object requires passing parameters for both the base and the pupil:
# Setup colors white = color(0, 0, 100) black = color(0, 0, 0) blue = color(240, 80, 80) # Parameters: Center, radius, border_thickness, (bg, stroke), # pupil_percent_size, pupil_border, (pupil_bg, pupil_stroke) eye = Eyeball( (250, 250), # center (x,y) 40, # radius 2, # border width (white, black),# base colors 0.5, # pupil is 50% of the eye size 1, # pupil border width (black, blue) # pupil colors ) # In the draw loop: eye.look_at(mouseX, mouseY) eye.render()
- Create a "Cyclops" sketch with one giant eyeball in the center of the screen properly tracking the mouse.
- Modify the `is_locked` logic so the eye turns red when locked.
- Add a "blink" method. (Hint: You might need to change the base `Disk` height to 0 temporarily, or draw a "lid" shape over it).
- Add functionality for "dilating" the pupil (changing `p_percent`) based on how close the mouse is.
Explore More Classes
Continue learning about OOP with our other classes: