0
# Asset Loading
1
2
Panda3D provides a comprehensive asset loading system that handles 3D models, textures, animations, audio files, and other resources with support for various file formats and flexible loading strategies.
3
4
## Capabilities
5
6
### Loader - Main Asset Loading Interface
7
8
The Loader class provides the primary interface for loading game assets with caching, format conversion, and error handling.
9
10
```python { .api }
11
class Loader:
12
def loadModel(self,
13
modelPath: str,
14
noCache: bool = False,
15
allowInstanceFromCache: bool = False,
16
callback: callable = None,
17
extraArgs: list = []) -> NodePath:
18
"""
19
Load 3D model from file.
20
21
Args:
22
modelPath: Path to model file
23
noCache: Skip caching system
24
allowInstanceFromCache: Allow instancing from cache
25
callback: Async loading callback
26
extraArgs: Extra arguments for callback
27
28
Returns:
29
NodePath containing loaded model
30
"""
31
32
def loadModelOnce(self, modelPath: str) -> NodePath:
33
"""Load model with guaranteed caching."""
34
35
def loadModelCopy(self, modelPath: str) -> NodePath:
36
"""Load unique copy of model (no instancing)."""
37
38
def unloadModel(self, model: NodePath) -> None:
39
"""Unload model and free resources."""
40
41
def loadTexture(self,
42
texturePath: str,
43
alphaPath: str = None,
44
readMipmaps: bool = False,
45
okMissing: bool = False,
46
minfilter: int = None,
47
magfilter: int = None) -> Texture:
48
"""
49
Load texture from file.
50
51
Args:
52
texturePath: Path to texture file
53
alphaPath: Optional separate alpha channel file
54
readMipmaps: Read mipmaps if available
55
okMissing: Don't error if file missing
56
minfilter: Minification filter mode
57
magfilter: Magnification filter mode
58
59
Returns:
60
Texture object
61
"""
62
63
def loadCubeMap(self, texturePath: str) -> Texture:
64
"""Load cube map texture for environment mapping."""
65
66
def load3DTexture(self, texturePath: str) -> Texture:
67
"""Load 3D volume texture."""
68
69
def loadFont(self, fontPath: str) -> TextFont:
70
"""Load font for text rendering."""
71
72
def loadSfx(self, soundPath: str) -> AudioSound:
73
"""Load sound effect file."""
74
75
def loadMusic(self, musicPath: str) -> AudioSound:
76
"""Load music file."""
77
78
def loadShader(self,
79
vertexPath: str = None,
80
fragmentPath: str = None,
81
geometryPath: str = None) -> Shader:
82
"""Load shader program from source files."""
83
```
84
85
### Supported File Formats
86
87
Panda3D supports a wide variety of asset file formats through built-in and plugin loaders.
88
89
```python { .api }
90
# 3D Model Formats
91
MODEL_FORMATS = [
92
".egg", # Panda3D native format
93
".bam", # Panda3D binary format (fastest loading)
94
".x", # DirectX format
95
".dae", # COLLADA format
96
".obj", # Wavefront OBJ
97
".ply", # Stanford PLY
98
".lwo", # LightWave Object
99
".3ds", # 3D Studio format
100
".gltf", # glTF 2.0 (if plugin available)
101
".fbx" # FBX (if plugin available)
102
]
103
104
# Texture Formats
105
TEXTURE_FORMATS = [
106
".png", # PNG (recommended)
107
".jpg", ".jpeg", # JPEG
108
".tga", # Targa
109
".bmp", # Bitmap
110
".pnm", # Portable Network Graphics
111
".sgi", # Silicon Graphics Image
112
".rgb", # RGB format
113
".tiff", ".tif", # TIFF
114
".exr", # OpenEXR (HDR)
115
".hdr", # Radiance HDR
116
".dds" # DirectDraw Surface
117
]
118
119
# Audio Formats
120
AUDIO_FORMATS = [
121
".wav", # WAV (uncompressed)
122
".ogg", # Ogg Vorbis
123
".mp3", # MP3 (if available)
124
".flac" # FLAC (if available)
125
]
126
127
# Animation Formats
128
ANIMATION_FORMATS = [
129
".egg", # Panda3D animation
130
".bam", # Panda3D binary animation
131
]
132
```
133
134
### Asset Path Resolution
135
136
Configure and manage asset loading paths and search directories.
137
138
```python { .api }
139
from panda3d.core import getModelPath, Filename, DSearchPath
140
141
# Get current model search path
142
def getAssetPath() -> DSearchPath:
143
"""Get current asset search path."""
144
return getModelPath()
145
146
# Add search directories
147
def addAssetPath(path: str) -> None:
148
"""Add directory to asset search path."""
149
getModelPath().appendDirectory(Filename(path))
150
151
def prependAssetPath(path: str) -> None:
152
"""Add directory to front of search path."""
153
getModelPath().prependDirectory(Filename(path))
154
155
# Search for assets
156
def findAsset(filename: str) -> str:
157
"""Find asset file in search path."""
158
path = getModelPath().findFile(filename)
159
return str(path) if path else None
160
```
161
162
### Asynchronous Loading
163
164
Load assets asynchronously to avoid blocking the main thread.
165
166
```python { .api }
167
class Loader:
168
def loadModelAsync(self,
169
modelPath: str,
170
callback: callable,
171
extraArgs: list = []) -> None:
172
"""Load model asynchronously with callback."""
173
174
def loadTextureAsync(self,
175
texturePath: str,
176
callback: callable,
177
extraArgs: list = []) -> None:
178
"""Load texture asynchronously with callback."""
179
180
def cancelAsyncRequest(self, request) -> None:
181
"""Cancel pending async loading request."""
182
```
183
184
### Resource Management and Caching
185
186
Manage loaded assets and control caching behavior.
187
188
```python { .api }
189
class Loader:
190
def flushCache(self) -> None:
191
"""Clear all cached assets."""
192
193
def clearCache(self, filename: str) -> None:
194
"""Clear specific file from cache."""
195
196
def setCacheLimit(self, limit: int) -> None:
197
"""Set maximum cache size in bytes."""
198
199
def getCacheSize(self) -> int:
200
"""Get current cache size in bytes."""
201
202
def listCachedFiles(self) -> list:
203
"""List all files currently in cache."""
204
```
205
206
### Virtual File System Integration
207
208
Load assets from virtual file systems, compressed archives, and network sources.
209
210
```python { .api }
211
from panda3d.core import VirtualFileSystem, Multifile
212
213
# Virtual file system access
214
vfs = VirtualFileSystem.getGlobalPtr()
215
216
# Mount archive files
217
def mountArchive(archive_path: str, mount_point: str = "/") -> bool:
218
"""Mount archive file as virtual directory."""
219
multifile = Multifile()
220
if multifile.openReadWrite(archive_path):
221
vfs.mount(multifile, mount_point, 0)
222
return True
223
return False
224
225
# Load from VFS
226
def loadFromVFS(vfs_path: str) -> NodePath:
227
"""Load model from virtual file system path."""
228
return loader.loadModel(vfs_path)
229
```
230
231
## Usage Examples
232
233
### Basic Asset Loading
234
235
```python
236
from direct.showbase.ShowBase import ShowBase
237
from panda3d.core import *
238
239
class AssetDemo(ShowBase):
240
def __init__(self):
241
ShowBase.__init__(self)
242
243
# Configure asset paths
244
self.setupAssetPaths()
245
246
# Load various assets
247
self.loadGameAssets()
248
249
# Setup scene
250
self.setupScene()
251
252
def setupAssetPaths(self):
253
"""Configure asset search paths."""
254
# Add custom asset directories
255
getModelPath().appendDirectory("assets/models")
256
getModelPath().appendDirectory("assets/textures")
257
getModelPath().appendDirectory("assets/sounds")
258
259
# Add archive files
260
multifile = Multifile()
261
if multifile.openRead("game_assets.mf"):
262
vfs = VirtualFileSystem.getGlobalPtr()
263
vfs.mount(multifile, "/game", 0)
264
print("Mounted game_assets.mf")
265
266
def loadGameAssets(self):
267
"""Load all game assets."""
268
# Load 3D models
269
self.environment = self.loader.loadModel("environment.egg")
270
self.player_model = self.loader.loadModel("characters/player.egg")
271
self.enemy_model = self.loader.loadModel("characters/enemy.egg")
272
273
# Load textures
274
self.grass_texture = self.loader.loadTexture("grass.png")
275
self.stone_texture = self.loader.loadTexture("stone.jpg")
276
self.metal_texture = self.loader.loadTexture("metal.tga")
277
278
# Load sounds
279
self.footstep_sound = self.loader.loadSfx("footstep.wav")
280
self.background_music = self.loader.loadMusic("background.ogg")
281
282
# Load fonts
283
self.ui_font = self.loader.loadFont("fonts/arial.ttf")
284
285
print("All assets loaded successfully")
286
287
def setupScene(self):
288
"""Setup the 3D scene with loaded assets."""
289
# Setup environment
290
if self.environment:
291
self.environment.reparentTo(self.render)
292
self.environment.setTexture(self.grass_texture)
293
294
# Setup player
295
if self.player_model:
296
self.player_model.reparentTo(self.render)
297
self.player_model.setPos(0, 10, 0)
298
299
# Setup camera
300
self.camera.setPos(0, -10, 5)
301
self.camera.lookAt(self.player_model)
302
303
app = AssetDemo()
304
app.run()
305
```
306
307
### Asynchronous Asset Loading
308
309
```python
310
from direct.showbase.ShowBase import ShowBase
311
from direct.gui.DirectGui import *
312
313
class AsyncLoadingDemo(ShowBase):
314
def __init__(self):
315
ShowBase.__init__(self)
316
317
# Create loading screen
318
self.createLoadingScreen()
319
320
# Asset loading queue
321
self.assets_to_load = [
322
("models/level1.egg", "level"),
323
("models/player.egg", "player"),
324
("models/enemies.egg", "enemies"),
325
("textures/environment.png", "env_texture"),
326
("sounds/music.ogg", "music")
327
]
328
329
self.loaded_assets = {}
330
self.loading_progress = 0
331
332
# Start async loading
333
self.startAsyncLoading()
334
335
def createLoadingScreen(self):
336
"""Create loading screen UI."""
337
# Background
338
self.loading_bg = DirectFrame(
339
frameColor=(0, 0, 0, 1),
340
frameSize=(-2, 2, -1, 1)
341
)
342
343
# Loading text
344
self.loading_text = OnscreenText(
345
text="Loading...",
346
pos=(0, 0.2),
347
scale=0.1,
348
fg=(1, 1, 1, 1)
349
)
350
351
# Progress bar background
352
self.progress_bg = DirectFrame(
353
frameColor=(0.3, 0.3, 0.3, 1),
354
frameSize=(-0.8, 0.8, -0.05, 0.05),
355
pos=(0, 0, -0.2)
356
)
357
358
# Progress bar
359
self.progress_bar = DirectFrame(
360
frameColor=(0, 1, 0, 1),
361
frameSize=(0, 0, -0.05, 0.05),
362
pos=(-0.8, 0, -0.2)
363
)
364
365
def startAsyncLoading(self):
366
"""Start asynchronous asset loading."""
367
if self.assets_to_load:
368
asset_path, asset_name = self.assets_to_load.pop(0)
369
370
# Determine asset type and load accordingly
371
if asset_path.endswith(('.egg', '.bam', '.x')):
372
self.loader.loadModel(
373
asset_path,
374
callback=self.onAssetLoaded,
375
extraArgs=[asset_name]
376
)
377
elif asset_path.endswith(('.png', '.jpg', '.tga')):
378
self.loader.loadTexture(
379
asset_path,
380
callback=self.onAssetLoaded,
381
extraArgs=[asset_name]
382
)
383
elif asset_path.endswith(('.wav', '.ogg', '.mp3')):
384
sound = self.loader.loadSfx(asset_path)
385
self.onAssetLoaded(sound, asset_name)
386
387
def onAssetLoaded(self, asset, asset_name):
388
"""Handle completed asset loading."""
389
self.loaded_assets[asset_name] = asset
390
self.loading_progress += 1
391
392
# Update progress
393
total_assets = len(self.assets_to_load) + self.loading_progress
394
progress = self.loading_progress / total_assets
395
396
# Update progress bar
397
bar_width = 1.6 * progress
398
self.progress_bar['frameSize'] = (0, bar_width, -0.05, 0.05)
399
400
# Update loading text
401
self.loading_text.setText(f"Loading... {int(progress * 100)}%")
402
403
# Continue loading or finish
404
if self.assets_to_load:
405
self.startAsyncLoading()
406
else:
407
self.onLoadingComplete()
408
409
def onLoadingComplete(self):
410
"""Handle loading completion."""
411
print("All assets loaded!")
412
413
# Hide loading screen
414
self.loading_bg.destroy()
415
self.loading_text.destroy()
416
self.progress_bg.destroy()
417
self.progress_bar.destroy()
418
419
# Setup game with loaded assets
420
self.setupGame()
421
422
def setupGame(self):
423
"""Setup game with loaded assets."""
424
# Use loaded assets
425
if "level" in self.loaded_assets:
426
level = self.loaded_assets["level"]
427
level.reparentTo(self.render)
428
429
if "player" in self.loaded_assets:
430
player = self.loaded_assets["player"]
431
player.reparentTo(self.render)
432
player.setPos(0, 10, 0)
433
434
print("Game setup complete!")
435
436
app = AsyncLoadingDemo()
437
app.run()
438
```
439
440
### Asset Management System
441
442
```python
443
from direct.showbase.DirectObject import DirectObject
444
import os
445
446
class AssetManager(DirectObject):
447
"""Centralized asset management system."""
448
449
def __init__(self):
450
DirectObject.__init__(self)
451
452
# Asset registries
453
self.models = {}
454
self.textures = {}
455
self.sounds = {}
456
self.fonts = {}
457
458
# Loading statistics
459
self.load_count = 0
460
self.cache_hits = 0
461
462
# Setup asset paths
463
self.setupPaths()
464
465
def setupPaths(self):
466
"""Setup asset search paths."""
467
paths = [
468
"assets/models",
469
"assets/textures",
470
"assets/sounds",
471
"assets/fonts",
472
"data"
473
]
474
475
for path in paths:
476
if os.path.exists(path):
477
getModelPath().appendDirectory(path)
478
479
def loadModel(self, name: str, path: str = None) -> NodePath:
480
"""Load and cache model."""
481
if name in self.models:
482
self.cache_hits += 1
483
return self.models[name]
484
485
model_path = path if path else name
486
model = loader.loadModel(model_path)
487
488
if model:
489
self.models[name] = model
490
self.load_count += 1
491
print(f"Loaded model: {name}")
492
else:
493
print(f"Failed to load model: {model_path}")
494
495
return model
496
497
def loadTexture(self, name: str, path: str = None) -> Texture:
498
"""Load and cache texture."""
499
if name in self.textures:
500
self.cache_hits += 1
501
return self.textures[name]
502
503
texture_path = path if path else name
504
texture = loader.loadTexture(texture_path)
505
506
if texture:
507
self.textures[name] = texture
508
self.load_count += 1
509
print(f"Loaded texture: {name}")
510
else:
511
print(f"Failed to load texture: {texture_path}")
512
513
return texture
514
515
def loadSound(self, name: str, path: str = None) -> AudioSound:
516
"""Load and cache sound."""
517
if name in self.sounds:
518
self.cache_hits += 1
519
return self.sounds[name]
520
521
sound_path = path if path else name
522
sound = loader.loadSfx(sound_path)
523
524
if sound:
525
self.sounds[name] = sound
526
self.load_count += 1
527
print(f"Loaded sound: {name}")
528
else:
529
print(f"Failed to load sound: {sound_path}")
530
531
return sound
532
533
def preloadAssets(self, asset_list: list):
534
"""Preload list of assets."""
535
for asset_type, name, path in asset_list:
536
if asset_type == "model":
537
self.loadModel(name, path)
538
elif asset_type == "texture":
539
self.loadTexture(name, path)
540
elif asset_type == "sound":
541
self.loadSound(name, path)
542
543
def getStats(self) -> dict:
544
"""Get loading statistics."""
545
return {
546
"models_loaded": len(self.models),
547
"textures_loaded": len(self.textures),
548
"sounds_loaded": len(self.sounds),
549
"total_loads": self.load_count,
550
"cache_hits": self.cache_hits
551
}
552
553
def cleanup(self):
554
"""Clean up all assets."""
555
# Models cleanup (NodePaths)
556
for model in self.models.values():
557
if model:
558
model.removeNode()
559
560
# Clear registries
561
self.models.clear()
562
self.textures.clear()
563
self.sounds.clear()
564
self.fonts.clear()
565
566
print("Asset manager cleaned up")
567
568
# Global asset manager instance
569
asset_manager = AssetManager()
570
```
571
572
## Types
573
574
```python { .api }
575
from panda3d.core import Texture, TextFont, AudioSound, Shader, NodePath
576
577
# Asset loading return types
578
NodePath # 3D models and scene graphs
579
Texture # Image textures and materials
580
AudioSound # Sound effects and music
581
TextFont # Fonts for text rendering
582
Shader # Vertex/fragment shader programs
583
584
# File format constants
585
class Texture:
586
# Texture formats
587
F_rgb = 1 # RGB format
588
F_rgba = 2 # RGBA format
589
F_alpha = 3 # Alpha only
590
F_luminance = 4 # Grayscale
591
592
# Filter modes
593
FT_nearest = 1 # Nearest neighbor
594
FT_linear = 2 # Linear filtering
595
FT_nearest_mipmap_nearest = 3
596
FT_linear_mipmap_nearest = 4
597
FT_nearest_mipmap_linear = 5
598
FT_linear_mipmap_linear = 6
599
```