Placeholder Systeem
Tijdelijke graphics voor development en asset ticket workflow.
Overzicht
┌─────────────────────────────────────────────────────────────────┐
│ Placeholder Workflow │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Development Fase: │
│ 1. Placeholder sprite maken (snel, herkenbaar) │
│ 2. Implementeren in game │
│ 3. Asset ticket aanmaken in SpacetimeDB │
│ 4. Kunstenaar claimt ticket │
│ 5. Finale asset uploaden naar R2 │
│ 6. Game download automatisch nieuwe asset │
│ │
│ Voordelen: │
│ • Parallelle ontwikkeling (code + art) │
│ • Gestandaardiseerde asset specs │
│ • Versie beheer van assets │
│ • Hot-reload zonder rebuild │
│ │
└─────────────────────────────────────────────────────────────────┘
Placeholder Conventies
Kleur Codes
┌─────────────────────────────────────────────────────────────────┐
│ Placeholder Kleuren │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Magenta (#FF00FF) - Algemeen placeholder │
│ Cyan (#00FFFF) - UI elementen │
│ Lime (#00FF00) - Collision shapes │
│ Yellow (#FFFF00) - Interactie zones │
│ Orange (#FF8800) - NPC/karakters │
│ Blue (#0088FF) - Items/objecten │
│ │
└─────────────────────────────────────────────────────────────────┘
Naamgeving
placeholder_[category]_[name]_[size].png
Voorbeelden:
- placeholder_character_milena_256x256.png
- placeholder_item_apple_64x64.png
- placeholder_ui_button_large_200x50.png
- placeholder_bg_treehouse_1920x1080.png
Placeholder Generator
GDScript Tool
# placeholder_generator.gd
@tool
extends EditorScript
const COLORS = {
"character": Color("#FF8800"),
"item": Color("#0088FF"),
"ui": Color("#00FFFF"),
"background": Color("#FF00FF"),
"prop": Color("#00FF00")
}
func _run() -> void:
generate_placeholder("character", "milena", Vector2(256, 256))
generate_placeholder("item", "apple", Vector2(64, 64))
generate_placeholder("ui", "button_large", Vector2(200, 50))
func generate_placeholder(category: String, name: String, size: Vector2) -> void:
var image = Image.create(int(size.x), int(size.y), false, Image.FORMAT_RGBA8)
var color = COLORS.get(category, Color.MAGENTA)
# Fill with color
image.fill(color)
# Add border
_draw_border(image, Color.WHITE, 2)
# Add diagonal lines (placeholder pattern)
_draw_diagonal_lines(image, Color.BLACK.lerp(color, 0.5))
# Add text label
_draw_text(image, name)
# Save
var path = "res://assets/placeholders/placeholder_%s_%s_%dx%d.png" % [
category, name, int(size.x), int(size.y)
]
image.save_png(path)
print("Created: ", path)
func _draw_border(image: Image, color: Color, width: int) -> void:
for x in range(image.get_width()):
for y in range(width):
image.set_pixel(x, y, color)
image.set_pixel(x, image.get_height() - 1 - y, color)
for y in range(image.get_height()):
for x in range(width):
image.set_pixel(x, y, color)
image.set_pixel(image.get_width() - 1 - x, y, color)
func _draw_diagonal_lines(image: Image, color: Color) -> void:
var step = 16
for i in range(-image.get_height(), image.get_width(), step):
for j in range(image.get_height()):
var x = i + j
if x >= 0 and x < image.get_width():
image.set_pixel(x, j, color)
Asset Ticket Systeem
Ticket Aanmaken
# asset_ticket_manager.gd
extends Node
func create_asset_ticket(
asset_type: String,
asset_name: String,
specs: Dictionary,
priority: int = 2
) -> void:
SpacetimeDB.call_reducer("create_asset_ticket", [
asset_type, # "sprite", "animation", "audio", etc.
asset_name, # Unieke identifier
JSON.stringify(specs),
priority, # 1=high, 2=medium, 3=low
"" # reference_image_url (optioneel)
])
# Voorbeeld gebruik:
func request_character_sprite() -> void:
create_asset_ticket("sprite", "character_milena_idle", {
"width": 256,
"height": 256,
"format": "PNG",
"transparency": true,
"style": "cute_cartoon",
"description": "Milena's idle pose, facing right",
"reference": "See character sheet doc"
}, 1)
Ticket Status
enum TicketStatus {
OPEN, # Wacht op kunstenaar
CLAIMED, # In progress
REVIEW, # Wacht op review
APPROVED, # Asset goedgekeurd
NEEDS_REVISION, # Wijzigingen nodig
COMPLETED # Asset live in game
}
func _on_ticket_status_changed(old: AssetTicket, new: AssetTicket) -> void:
if new.status == TicketStatus.COMPLETED:
# Download en activeer nieuwe asset
_fetch_and_activate_asset(new.asset_url, new.asset_name)
Asset Loading
Dynamic Asset Loader
# asset_loader.gd
extends Node
const ASSETS_BASE_URL = "https://assets.milenas-treehouse.com"
const LOCAL_CACHE_PATH = "user://asset_cache/"
var _cached_assets: Dictionary = {} # path -> Texture2D/Resource
func load_asset(asset_name: String, fallback: Texture2D = null) -> Texture2D:
# 1. Check memory cache
if asset_name in _cached_assets:
return _cached_assets[asset_name]
# 2. Check local cache
var local_path = LOCAL_CACHE_PATH + asset_name
if FileAccess.file_exists(local_path):
var texture = _load_texture_from_file(local_path)
if texture:
_cached_assets[asset_name] = texture
return texture
# 3. Check if remote asset exists
var asset_info = AssetDatabase.get_asset(asset_name)
if asset_info and asset_info.status == "completed":
# Async download
_download_asset(asset_name, asset_info.url)
# 4. Return fallback/placeholder
if fallback:
return fallback
return _get_placeholder(asset_name)
func _download_asset(asset_name: String, url: String) -> void:
var http = HTTPRequest.new()
add_child(http)
http.request_completed.connect(
_on_download_complete.bind(asset_name, http)
)
http.request(url)
func _on_download_complete(
result: int,
response_code: int,
headers: PackedStringArray,
body: PackedByteArray,
asset_name: String,
http: HTTPRequest
) -> void:
http.queue_free()
if result != HTTPRequest.RESULT_SUCCESS:
push_error("Failed to download: " + asset_name)
return
# Save to local cache
var local_path = LOCAL_CACHE_PATH + asset_name
DirAccess.make_dir_recursive_absolute(local_path.get_base_dir())
var file = FileAccess.open(local_path, FileAccess.WRITE)
file.store_buffer(body)
file.close()
# Load into memory
var texture = _load_texture_from_file(local_path)
if texture:
_cached_assets[asset_name] = texture
# Notify listeners
asset_loaded.emit(asset_name, texture)
Hot Reload
signal asset_loaded(asset_name: String, texture: Texture2D)
# Components kunnen subscriben voor updates
# character_sprite.gd
func _ready() -> void:
AssetLoader.asset_loaded.connect(_on_asset_loaded)
# Load initial (may be placeholder)
sprite.texture = AssetLoader.load_asset("character_milena_idle")
func _on_asset_loaded(asset_name: String, texture: Texture2D) -> void:
if asset_name == "character_milena_idle":
sprite.texture = texture
_play_update_effect()
func _play_update_effect() -> void:
# Subtle flash to show asset updated
var tween = create_tween()
tween.tween_property(sprite, "modulate", Color(1.5, 1.5, 1.5), 0.1)
tween.tween_property(sprite, "modulate", Color.WHITE, 0.2)
Placeholder Sprites
Karakter Placeholder
func create_character_placeholder(name: String, size: Vector2) -> Texture2D:
var image = Image.create(int(size.x), int(size.y), false, Image.FORMAT_RGBA8)
# Orange background
image.fill(Color("#FF8800"))
# Simple body shape
_draw_circle(image, Vector2(size.x/2, size.y/3), size.x/4, Color.WHITE) # Head
_draw_rect(image, Rect2(size.x/4, size.y/3, size.x/2, size.y/2), Color.WHITE) # Body
# Name label
_draw_text_centered(image, name.substr(0, 3).to_upper())
return ImageTexture.create_from_image(image)
Item Placeholder
func create_item_placeholder(name: String, size: Vector2 = Vector2(64, 64)) -> Texture2D:
var image = Image.create(int(size.x), int(size.y), false, Image.FORMAT_RGBA8)
# Blue background
image.fill(Color("#0088FF"))
# Item icon (simple shape based on category)
var category = _guess_category(name)
match category:
"food":
_draw_circle(image, size/2, size.x/3, Color.GREEN)
"tool":
_draw_rect(image, Rect2(size.x/4, size.y/4, size.x/2, size.y/2), Color.GRAY)
_:
_draw_diamond(image, size/2, size.x/3, Color.WHITE)
# Border
_draw_border(image, Color.BLACK, 1)
return ImageTexture.create_from_image(image)
Asset Specs Template
{
"asset_id": "character_milena_idle",
"type": "sprite",
"specs": {
"dimensions": {
"width": 256,
"height": 256
},
"format": "PNG",
"transparency": true,
"color_palette": ["#FFE4C4", "#8B4513", "#FFD700"],
"style_guide": "Cute cartoon, soft edges, warm colors",
"requirements": [
"Character facing right",
"Neutral expression",
"Arms at sides",
"Consistent with character sheet"
],
"reference_urls": [
"https://docs.milenas-treehouse.com/art/milena-sheet"
]
},
"priority": 1,
"deadline": "2025-02-01"
}