<The_Big_Picture />
The Ghost class is where the magic happens. It brings everything together!
It doesn't just look pretty; it moves. The Ghost class demonstrates Composition by "owning" a Head (Disk) and two Eyes (Eyeballs). It also manages complex State (position, velocity) and Behavior (bouncing off walls, coordinating eye movement).
When the Ghost moves, it's responsible for moving its head and eyes along with it. This is a key principle of encapsulation—the outside world just says "ghost.update()", and the Ghost handles all the internal details.
1. The Constructor: Assembling the Spirit
Unlike the simpler classes, the Ghost takes a lot of parameters. Notice how we pass in an existing head object (Disk). We also calculate random velocities if none are provided.
8 class Ghost: 22 def __init__(self, head, ht, num_feet, eye_color, eye_base_colors, eye_span_factor=0.85, 23 start_pos=None, velocity=None, canvas_width=800, canvas_height=600, feet_round=True): 24 36 self.head = head 37 self.b_color = self.head.bg_color # Body matches head color 38 self.ht = self.head.radius + ht # Total height 39 self.wd = 2 * head.radius 40 self.num_feet = num_feet 49 50 # Initialize position (use head center as default) 51 if start_pos is None: 52 self.position = list(head.center) 53 else: 54 self.position = list(start_pos) 55 56 # Initialize velocity (random if not specified) 57 if velocity is None: 58 # Random velocity between 1.0 and 8.0 59 self.velocity = [random(1.0, 8.0), random(1.0, 8.0)] 60 else: 61 self.velocity = list(velocity) 62 63 self.create_eyes() # Helper method to make Eyeball objects 64 #end init
2. The Update Loop: It's Alive!
This method is called every frame of the animation loop. It embodies the logic of the ghost.
- Move: Add velocity to current position.
- Bounce: Check if we hit a wall (
check_bounds). - Sync Parts: Crucial step! The head and eyes must move to the new position.
- Animate Eyes: The
Eyeball.look_in_direction()method is called so the ghost looks where it's going!
91 def update(self): 97 # Update position based on velocity 98 self.position[0] += self.velocity[0] 99 self.position[1] += self.velocity[1] 100 101 # Check boundaries and bounce 102 self.check_bounds() 103 104 # Update head center to match current position 105 self.head.set_center((self.position[0], self.position[1])) 106 107 # Update eye positions to match new head position 108 self.update_eye_positions() 109 110 # Make eyes look in direction of movement 111 self.left_eye.look_in_direction(self.velocity[0], self.velocity[1]) 112 self.right_eye.look_in_direction(self.velocity[0], self.velocity[1]) 113 #end update
3. Rendering: Drawing the Nightmare
Finally, the ghost draws itself. It delegates the complex drawing of the head and eyes to their respective classes, focusing only on drawing its own body and feet.
149 def render(self): 154 # Draw the head (a circular Disk) 155 self.head.render() 156 157 # Draw the body (rectangular section below head) 158 fill(self.b_color) 159 noStroke() 160 rect(self.head.center[0] - self.head.radius, self.head.center[1], self.wd, self.ht) 161 162 # ... (Feet drawing logic omitted for brevity) ... 188 189 # Draw the eyes last so they appear on top 190 self.left_eye.render() 191 self.right_eye.render() 192 #end render
What's Next?
Now that we have all our classes, how do we run them? Check out the Driver Code sketch!
View Driver Code