0
# Task and Event Management
1
2
Panda3D provides a comprehensive system for frame-based task scheduling and event-driven programming, essential for game logic, animations, and inter-object communication.
3
4
## Capabilities
5
6
### TaskManager - Frame-Based Task Scheduling
7
8
The TaskManager handles frame-based tasks that run continuously or periodically, forming the backbone of game logic execution.
9
10
```python { .api }
11
class TaskManager:
12
def add(self,
13
task: callable,
14
name: str,
15
priority: int = 0,
16
uponDeath: callable = None,
17
appendTask: bool = False,
18
owner: object = None) -> Task:
19
"""
20
Add a task to run every frame.
21
22
Args:
23
task: Function to call each frame, must return task.cont or task.done
24
name: Unique name for the task
25
priority: Task priority (lower numbers run first)
26
uponDeath: Function to call when task completes
27
appendTask: Whether to append Task object as last argument
28
owner: Object that owns this task
29
30
Returns:
31
Task object for management
32
"""
33
34
def remove(self, taskOrName: str | Task) -> bool:
35
"""Remove a task by name or Task object."""
36
37
def removeTasksMatching(self, pattern: str) -> int:
38
"""Remove all tasks with names matching glob pattern."""
39
40
def step(self) -> None:
41
"""Execute one frame of all tasks."""
42
43
def run(self) -> None:
44
"""Run task manager main loop (blocking)."""
45
46
def stop(self) -> None:
47
"""Stop the task manager main loop."""
48
49
def hasTaskNamed(self, name: str) -> bool:
50
"""Check if task with given name exists."""
51
52
def getTasksNamed(self, name: str) -> List[Task]:
53
"""Get all tasks with given name."""
54
55
def getAllTasks(self) -> List[Task]:
56
"""Get list of all active tasks."""
57
58
def getTasks(self) -> List[Task]:
59
"""Get list of all tasks."""
60
61
def getNumTasks(self) -> int:
62
"""Get total number of active tasks."""
63
64
def doMethodLater(self,
65
delayTime: float,
66
task: callable,
67
name: str,
68
extraArgs: List = [],
69
priority: int = 0,
70
uponDeath: callable = None,
71
owner: object = None) -> Task:
72
"""Schedule a task to run after a delay."""
73
74
def doMethodLaterPriority(self,
75
delayTime: float,
76
task: callable,
77
name: str,
78
priority: int,
79
extraArgs: List = []) -> Task:
80
"""Schedule a delayed task with specific priority."""
81
```
82
83
### Task Objects and Control
84
85
Individual task objects provide fine-grained control over task execution and state.
86
87
```python { .api }
88
class Task:
89
# Task return constants
90
cont: int # Continue running task next frame
91
done: int # Task completed, remove from manager
92
again: int # Restart task from beginning
93
94
def __init__(self, function: callable = None, name: str = None) -> None:
95
"""Create new task object."""
96
97
def setFunction(self, function: callable) -> None:
98
"""Set task function."""
99
100
def getFunction(self) -> callable:
101
"""Get task function."""
102
103
def setName(self, name: str) -> None:
104
"""Set task name."""
105
106
def getName(self) -> str:
107
"""Get task name."""
108
109
def setPriority(self, priority: int) -> None:
110
"""Set task priority."""
111
112
def getPriority(self) -> int:
113
"""Get task priority."""
114
115
def setDelay(self, delay: float) -> None:
116
"""Set delay before task starts."""
117
118
def getDelay(self) -> float:
119
"""Get task delay."""
120
121
def setUponDeath(self, function: callable) -> None:
122
"""Set function to call when task completes."""
123
124
def getUponDeath(self) -> callable:
125
"""Get upon-death function."""
126
127
def remove(self) -> None:
128
"""Remove this task from manager."""
129
130
def pause(self) -> None:
131
"""Pause task execution."""
132
133
def resume(self) -> None:
134
"""Resume paused task."""
135
136
def isPaused(self) -> bool:
137
"""Check if task is paused."""
138
139
def getElapsedTime(self) -> float:
140
"""Get time elapsed since task started."""
141
142
def getElapsedFrames(self) -> int:
143
"""Get frames elapsed since task started."""
144
145
def getWakeTime(self) -> float:
146
"""Get time when delayed task will wake up."""
147
148
# Task state properties
149
time: float # Current task time
150
frame: int # Current frame count
151
dt: float # Delta time since last frame
152
id: int # Unique task ID
153
```
154
155
### Messenger - Event System
156
157
The Messenger provides a global publish-subscribe event system for inter-object communication.
158
159
```python { .api }
160
class Messenger:
161
def accept(self,
162
event: str,
163
method: callable,
164
extraArgs: List = [],
165
persistent: int = 1) -> None:
166
"""
167
Accept an event and bind it to a method.
168
169
Args:
170
event: Event name to listen for
171
method: Function to call when event occurs
172
extraArgs: Additional arguments to pass to method
173
persistent: How many times to accept event (0 = forever)
174
"""
175
176
def acceptOnce(self,
177
event: str,
178
method: callable,
179
extraArgs: List = []) -> None:
180
"""Accept an event only once."""
181
182
def ignore(self, event: str) -> None:
183
"""Stop accepting a specific event."""
184
185
def ignoreAll(self) -> None:
186
"""Stop accepting all events."""
187
188
def isAccepting(self, event: str) -> bool:
189
"""Check if currently accepting an event."""
190
191
def getAllAccepting(self) -> List[str]:
192
"""Get list of all accepted events."""
193
194
def send(self, event: str, sentArgs: List = []) -> None:
195
"""Send an event to all listeners."""
196
197
def clear(self) -> None:
198
"""Clear all event handlers."""
199
200
def isEmpty(self) -> bool:
201
"""Check if messenger has no handlers."""
202
203
def replaceMethod(self,
204
oldMethod: callable,
205
newMethod: callable) -> int:
206
"""Replace method in all event handlers."""
207
208
def toggleVerbose(self) -> None:
209
"""Toggle verbose event logging."""
210
211
def setVerbose(self, verbose: bool) -> None:
212
"""Enable or disable verbose event logging."""
213
```
214
215
### DirectObject - Event-Aware Base Class
216
217
DirectObject provides a convenient base class that integrates with the event system.
218
219
```python { .api }
220
class DirectObject:
221
def __init__(self) -> None:
222
"""Initialize DirectObject with event handling."""
223
224
def accept(self,
225
event: str,
226
method: callable,
227
extraArgs: List = []) -> None:
228
"""Accept event (automatically cleaned up on destruction)."""
229
230
def acceptOnce(self,
231
event: str,
232
method: callable,
233
extraArgs: List = []) -> None:
234
"""Accept event once."""
235
236
def ignore(self, event: str) -> None:
237
"""Ignore specific event."""
238
239
def ignoreAll(self) -> None:
240
"""Ignore all events for this object."""
241
242
def isAccepting(self, event: str) -> bool:
243
"""Check if accepting event."""
244
245
def getAllAccepting(self) -> List[str]:
246
"""Get all accepted events."""
247
248
def destroy(self) -> None:
249
"""Clean up object and remove all event handlers."""
250
251
def addTask(self,
252
task: callable,
253
name: str,
254
priority: int = 0,
255
uponDeath: callable = None,
256
appendTask: bool = False) -> Task:
257
"""Add task (automatically cleaned up on destruction)."""
258
259
def doMethodLater(self,
260
delayTime: float,
261
task: callable,
262
name: str,
263
extraArgs: List = []) -> Task:
264
"""Schedule delayed task."""
265
266
def removeTask(self, taskOrName: str | Task) -> None:
267
"""Remove specific task."""
268
269
def removeAllTasks(self) -> None:
270
"""Remove all tasks for this object."""
271
```
272
273
### Common Event Types
274
275
Standard events generated by Panda3D systems.
276
277
```python { .api }
278
# Window events
279
"window-event" # Window state changes
280
"window-close-request" # User requests window close
281
282
# Input events
283
"escape" # Escape key pressed
284
"space" # Space key pressed
285
"enter" # Enter key pressed
286
"mouse1" # Left mouse button pressed
287
"mouse1-up" # Left mouse button released
288
"mouse2" # Middle mouse button pressed
289
"mouse2-up" # Middle mouse button released
290
"mouse3" # Right mouse button pressed
291
"mouse3-up" # Right mouse button released
292
"wheel_up" # Mouse wheel up
293
"wheel_down" # Mouse wheel down
294
295
# Key events (format: key name, key-up)
296
"a", "a-up" # A key pressed/released
297
"shift", "shift-up" # Shift key pressed/released
298
"control", "control-up" # Control key pressed/released
299
"alt", "alt-up" # Alt key pressed/released
300
301
# Arrow keys
302
"arrow_up", "arrow_up-up"
303
"arrow_down", "arrow_down-up"
304
"arrow_left", "arrow_left-up"
305
"arrow_right", "arrow_right-up"
306
307
# Function keys
308
"f1", "f1-up" # F1 through F12
309
# ... f2 through f12
310
311
# Task events
312
"task-added" # Task added to manager
313
"task-removed" # Task removed from manager
314
```
315
316
### Clock and Timing
317
318
Access global timing information for frame-based calculations.
319
320
```python { .api }
321
class ClockObject:
322
def getDt(self) -> float:
323
"""Get delta time since last frame."""
324
325
def getFrameTime(self) -> float:
326
"""Get time of current frame."""
327
328
def getRealTime(self) -> float:
329
"""Get real elapsed time since start."""
330
331
def getFrameCount(self) -> int:
332
"""Get total frame count."""
333
334
def getAverageFrameRate(self) -> float:
335
"""Get average frame rate."""
336
337
def getMaxDt(self) -> float:
338
"""Get maximum allowed delta time."""
339
340
def setMaxDt(self, maxDt: float) -> None:
341
"""Set maximum delta time limit."""
342
343
def tick(self) -> None:
344
"""Advance clock by one frame."""
345
346
# Global clock instance
347
globalClock: ClockObject
348
```
349
350
## Usage Examples
351
352
### Basic Task Management
353
354
```python
355
from direct.showbase.ShowBase import ShowBase
356
from direct.task import Task
357
from panda3d.core import Vec3
358
359
class TaskDemo(ShowBase):
360
def __init__(self):
361
ShowBase.__init__(self)
362
363
# Create object to animate
364
self.cube = self.loader.loadModel("models/cube")
365
self.cube.reparentTo(self.render)
366
self.cube.setPos(0, 10, 0)
367
368
# Add continuous rotation task
369
self.taskMgr.add(self.rotateCube, "rotate-cube")
370
371
# Add periodic task
372
self.taskMgr.doMethodLater(
373
2.0, self.changeColor, "change-color"
374
)
375
376
# Task with completion
377
self.taskMgr.add(self.moveAndStop, "move-cube")
378
379
self.colors = [(1,0,0,1), (0,1,0,1), (0,0,1,1), (1,1,0,1)]
380
self.colorIndex = 0
381
self.startTime = globalClock.getFrameTime()
382
383
def rotateCube(self, task):
384
"""Rotate cube continuously."""
385
dt = globalClock.getDt()
386
387
# Rotate around Y axis
388
currentH = self.cube.getH()
389
self.cube.setH(currentH + 30 * dt) # 30 degrees per second
390
391
return task.cont # Continue running
392
393
def changeColor(self, task):
394
"""Change cube color periodically."""
395
color = self.colors[self.colorIndex]
396
self.cube.setColor(*color)
397
398
self.colorIndex = (self.colorIndex + 1) % len(self.colors)
399
400
# Schedule next color change
401
task.delayTime = 2.0 # Wait 2 seconds
402
return task.again # Restart task with delay
403
404
def moveAndStop(self, task):
405
"""Move cube for 5 seconds then stop."""
406
dt = globalClock.getDt()
407
elapsed = globalClock.getFrameTime() - self.startTime
408
409
if elapsed < 5.0:
410
# Move cube
411
currentPos = self.cube.getPos()
412
self.cube.setPos(currentPos + Vec3(dt, 0, 0))
413
return task.cont
414
else:
415
print("Movement task completed!")
416
return task.done # Task finished
417
418
app = TaskDemo()
419
app.run()
420
```
421
422
### Event-Driven Programming
423
424
```python
425
from direct.showbase.ShowBase import ShowBase
426
from direct.showbase.DirectObject import DirectObject
427
from panda3d.core import Vec3
428
429
class EventDemo(ShowBase):
430
def __init__(self):
431
ShowBase.__init__(self)
432
433
# Create game objects
434
self.player = self.loader.loadModel("models/player")
435
self.player.reparentTo(self.render)
436
self.player.setPos(0, 10, 0)
437
438
# Create game controller
439
self.controller = GameController(self.player)
440
441
# Setup system event handling
442
self.accept("escape", self.exitGame)
443
self.accept("window-close-request", self.exitGame)
444
445
# Custom game events
446
self.accept("player-moved", self.onPlayerMoved)
447
self.accept("game-over", self.onGameOver)
448
449
# Send initial event
450
messenger.send("game-started")
451
452
def onPlayerMoved(self, newPos):
453
"""Handle player movement event."""
454
print(f"Player moved to: {newPos}")
455
456
# Check win condition
457
if newPos.getX() > 10:
458
messenger.send("game-over", ["win"])
459
460
def onGameOver(self, result):
461
"""Handle game over event."""
462
print(f"Game over: {result}")
463
if result == "win":
464
print("Congratulations!")
465
466
# Restart after delay
467
self.taskMgr.doMethodLater(3.0, self.restartGame, "restart")
468
469
def restartGame(self, task):
470
"""Restart the game."""
471
self.player.setPos(0, 10, 0)
472
messenger.send("game-started")
473
return task.done
474
475
def exitGame(self):
476
"""Clean exit."""
477
print("Exiting game...")
478
self.userExit()
479
480
class GameController(DirectObject):
481
def __init__(self, player):
482
DirectObject.__init__(self)
483
self.player = player
484
self.speed = 5.0
485
486
# Accept input events
487
self.accept("arrow_left", self.moveLeft)
488
self.accept("arrow_right", self.moveRight)
489
self.accept("arrow_up", self.moveUp)
490
self.accept("arrow_down", self.moveDown)
491
492
# Accept game events
493
self.accept("game-started", self.onGameStart)
494
495
# Add update task
496
self.addTask(self.update, "controller-update")
497
498
def moveLeft(self):
499
"""Move player left."""
500
pos = self.player.getPos()
501
newPos = pos + Vec3(-1, 0, 0)
502
self.player.setPos(newPos)
503
messenger.send("player-moved", [newPos])
504
505
def moveRight(self):
506
"""Move player right."""
507
pos = self.player.getPos()
508
newPos = pos + Vec3(1, 0, 0)
509
self.player.setPos(newPos)
510
messenger.send("player-moved", [newPos])
511
512
def moveUp(self):
513
"""Move player forward."""
514
pos = self.player.getPos()
515
newPos = pos + Vec3(0, 1, 0)
516
self.player.setPos(newPos)
517
messenger.send("player-moved", [newPos])
518
519
def moveDown(self):
520
"""Move player backward."""
521
pos = self.player.getPos()
522
newPos = pos + Vec3(0, -1, 0)
523
self.player.setPos(newPos)
524
messenger.send("player-moved", [newPos])
525
526
def onGameStart(self):
527
"""Handle game start event."""
528
print("Game started! Use arrow keys to move.")
529
530
def update(self, task):
531
"""Update controller logic."""
532
# Could add continuous input handling here
533
return task.cont
534
535
app = EventDemo()
536
app.run()
537
```
538
539
### Advanced Task Patterns
540
541
```python
542
from direct.showbase.ShowBase import ShowBase
543
from direct.task import Task
544
545
class AdvancedTaskDemo(ShowBase):
546
def __init__(self):
547
ShowBase.__init__(self)
548
549
# Task with state management
550
self.addTaskWithState()
551
552
# Task chains and dependencies
553
self.addTaskChain()
554
555
# Task with cleanup
556
self.addTaskWithCleanup()
557
558
def addTaskWithState(self):
559
"""Task that maintains state across frames."""
560
def countingTask(task):
561
# Initialize state on first run
562
if not hasattr(task, 'counter'):
563
task.counter = 0
564
task.startTime = globalClock.getFrameTime()
565
566
task.counter += 1
567
elapsed = globalClock.getFrameTime() - task.startTime
568
569
print(f"Frame {task.counter}, Time: {elapsed:.2f}")
570
571
# Stop after 10 seconds
572
if elapsed > 10.0:
573
print(f"Counting task completed after {task.counter} frames")
574
return task.done
575
576
return task.cont
577
578
self.taskMgr.add(countingTask, "counting-task")
579
580
def addTaskChain(self):
581
"""Chain of dependent tasks."""
582
def task1(task):
583
"""First task in chain."""
584
if not hasattr(task, 'startTime'):
585
task.startTime = globalClock.getFrameTime()
586
print("Task 1 started")
587
588
elapsed = globalClock.getFrameTime() - task.startTime
589
if elapsed > 2.0:
590
print("Task 1 completed, starting Task 2")
591
# Start next task
592
self.taskMgr.add(self.task2, "task-2")
593
return task.done
594
595
return task.cont
596
597
self.taskMgr.add(task1, "task-1")
598
599
def task2(self, task):
600
"""Second task in chain."""
601
if not hasattr(task, 'startTime'):
602
task.startTime = globalClock.getFrameTime()
603
print("Task 2 started")
604
605
elapsed = globalClock.getFrameTime() - task.startTime
606
if elapsed > 1.5:
607
print("Task 2 completed, starting Task 3")
608
# Start final task
609
self.taskMgr.doMethodLater(0.5, self.task3, "task-3")
610
return task.done
611
612
return task.cont
613
614
def task3(self, task):
615
"""Final task in chain."""
616
print("Task 3 completed - chain finished!")
617
return task.done
618
619
def addTaskWithCleanup(self):
620
"""Task with cleanup function."""
621
def workTask(task):
622
"""Main work task."""
623
if not hasattr(task, 'workDone'):
624
task.workDone = 0
625
print("Work task started")
626
627
task.workDone += 1
628
print(f"Work progress: {task.workDone}/10")
629
630
if task.workDone >= 10:
631
print("Work task completed")
632
return task.done
633
634
return task.cont
635
636
def cleanupTask(task):
637
"""Cleanup after work completes."""
638
print("Performing cleanup...")
639
return task.done
640
641
# Add task with cleanup
642
self.taskMgr.add(
643
workTask,
644
"work-task",
645
uponDeath=cleanupTask
646
)
647
648
app = AdvancedTaskDemo()
649
app.run()
650
```
651
652
## Types
653
654
```python { .api }
655
# Task constants and types
656
Task.cont: int = 0 # Continue task
657
Task.done: int = 1 # Complete task
658
Task.again: int = 2 # Restart task
659
660
# Global instances
661
taskMgr: TaskManager # Global task manager
662
messenger: Messenger # Global messenger
663
globalClock: ClockObject # Global clock
664
665
# Priority constants
666
SORT_VERY_HIGH = -1000
667
SORT_HIGH = -100
668
SORT_DEFAULT = 0
669
SORT_LOW = 100
670
SORT_VERY_LOW = 1000
671
```