第十五章:精灵与纹理
"精灵是2D游戏的视觉基石,纹理则是精灵的皮肤。"
精灵(Sprite)是2D游戏中最基本的可视元素。本章将全面讲解Godot中的精灵系统、纹理管理、图集优化等核心内容。
15.1 Sprite2D基础
15.1.1 创建精灵
# 编辑器中创建
# 1. 添加Sprite2D节点
# 2. 在检查器中设置Texture属性
# 3. 拖拽图片文件到属性槽
# 代码创建
func create_sprite():
var sprite = Sprite2D.new()
sprite.texture = preload("res://assets/player.png")
sprite.position = Vector2(100, 100)
add_child(sprite)
15.1.2 核心属性
extends Sprite2D
func _ready():
# 纹理
texture = preload("res://icon.png")
# 位置与变换
position = Vector2(100, 200)
rotation = PI / 4 # 45度
scale = Vector2(2, 2)
# 中心点偏移
centered = true # 默认居中
offset = Vector2(0, -16) # 自定义偏移
# 翻转
flip_h = false # 水平翻转
flip_v = false # 垂直翻转
# 区域裁剪
region_enabled = true
region_rect = Rect2(0, 0, 32, 32) # 只显示部分纹理
# 帧动画(单张精灵表)
hframes = 4 # 水平帧数
vframes = 2 # 垂直帧数
frame = 0 # 当前帧
frame_coords = Vector2i(0, 0) # 帧坐标
15.1.3 颜色与混合
extends Sprite2D
func _ready():
# 颜色调制
modulate = Color(1, 0.5, 0.5, 1) # 红色调
self_modulate = Color.WHITE # 不影响子节点
# 可见性
visible = true
modulate.a = 0.5 # 半透明
# 混合模式(通过材质设置)
var material = CanvasItemMaterial.new()
material.blend_mode = CanvasItemMaterial.BLEND_MODE_ADD # 加法混合
# BLEND_MODE_MIX - 正常
# BLEND_MODE_ADD - 加法(发光效果)
# BLEND_MODE_SUB - 减法
# BLEND_MODE_MUL - 乘法
self.material = material
15.2 纹理类型
15.2.1 Texture2D
# 基本纹理加载
var tex: Texture2D = preload("res://icon.png")
# 从文件加载
var image = Image.load_from_file("user://screenshot.png")
var tex2 = ImageTexture.create_from_image(image)
# 纹理信息
func get_texture_info(tex: Texture2D):
print("宽度:", tex.get_width())
print("高度:", tex.get_height())
print("大小:", tex.get_size())
15.2.2 AtlasTexture(图集纹理)
# 从大图中截取部分作为纹理
func create_atlas_texture():
var atlas = AtlasTexture.new()
atlas.atlas = preload("res://spritesheet.png")
atlas.region = Rect2(0, 0, 64, 64) # 截取区域
atlas.margin = Rect2(0, 0, 0, 0) # 边距
atlas.filter_clip = true # 防止边缘渗透
$Sprite2D.texture = atlas
15.2.3 AnimatedTexture(动画纹理)
# 自动播放帧动画的纹理
func create_animated_texture():
var anim_tex = AnimatedTexture.new()
anim_tex.frames = 4
for i in range(4):
var frame_tex = load("res://frames/frame_%d.png" % i)
anim_tex.set_frame_texture(i, frame_tex)
anim_tex.set_frame_duration(i, 0.1) # 每帧0.1秒
anim_tex.one_shot = false # 循环播放
anim_tex.pause = false
$Sprite2D.texture = anim_tex
15.2.4 GradientTexture(渐变纹理)
# 程序生成的渐变纹理
func create_gradient():
var gradient = Gradient.new()
gradient.set_color(0, Color.RED)
gradient.set_color(1, Color.BLUE)
gradient.add_point(0.5, Color.WHITE)
# 1D渐变
var tex1d = GradientTexture1D.new()
tex1d.gradient = gradient
tex1d.width = 256
# 2D渐变
var tex2d = GradientTexture2D.new()
tex2d.gradient = gradient
tex2d.fill = GradientTexture2D.FILL_RADIAL # 径向渐变
tex2d.width = 256
tex2d.height = 256
15.2.5 NoiseTexture(噪声纹理)
# 程序生成的噪声纹理
func create_noise_texture():
var noise = FastNoiseLite.new()
noise.noise_type = FastNoiseLite.TYPE_PERLIN
noise.frequency = 0.05
noise.fractal_octaves = 4
var tex = NoiseTexture2D.new()
tex.noise = noise
tex.width = 256
tex.height = 256
tex.seamless = true # 无缝平铺
tex.as_normal_map = false # 生成法线贴图
# 等待生成完成
await tex.changed
$Sprite2D.texture = tex
15.3 精灵动画
15.3.1 帧动画
extends Sprite2D
@export var animation_speed: float = 10.0
var frame_time: float = 0.0
func _ready():
texture = preload("res://spritesheet.png")
hframes = 8 # 8帧
vframes = 1
func _process(delta):
frame_time += delta * animation_speed
frame = int(frame_time) % hframes
15.3.2 AnimatedSprite2D
extends AnimatedSprite2D
func _ready():
# 播放动画
play("idle")
# 动画控制
animation = "run"
speed_scale = 1.5 # 播放速度
flip_h = true # 水平翻转
# 信号连接
animation_finished.connect(_on_animation_finished)
frame_changed.connect(_on_frame_changed)
func _on_animation_finished():
if animation == "attack":
play("idle")
func _on_frame_changed():
# 在特定帧触发效果
if animation == "attack" and frame == 3:
emit_attack_hitbox()
# SpriteFrames资源设置
func setup_sprite_frames():
var frames = SpriteFrames.new()
# 添加动画
frames.add_animation("walk")
frames.set_animation_speed("walk", 10)
frames.set_animation_loop("walk", true)
# 添加帧
for i in range(4):
var tex = load("res://walk_%d.png" % i)
frames.add_frame("walk", tex)
sprite_frames = frames
15.3.3 动画状态机
extends AnimatedSprite2D
enum State { IDLE, RUN, JUMP, FALL, ATTACK }
var current_state: State = State.IDLE
func update_animation(velocity: Vector2, is_on_floor: bool, is_attacking: bool):
var new_state = current_state
if is_attacking:
new_state = State.ATTACK
elif not is_on_floor:
new_state = State.JUMP if velocity.y < 0 else State.FALL
elif abs(velocity.x) > 10:
new_state = State.RUN
else:
new_state = State.IDLE
if new_state != current_state:
current_state = new_state
_play_state_animation()
func _play_state_animation():
match current_state:
State.IDLE:
play("idle")
State.RUN:
play("run")
State.JUMP:
play("jump")
State.FALL:
play("fall")
State.ATTACK:
play("attack")
15.4 纹理导入设置
15.4.1 导入预设
在Godot中,选择图片文件后可在导入面板设置:
常用导入设置:
├── 2D像素艺术
│ ├── Filter: Nearest(最近邻,保持像素清晰)
│ ├── Repeat: Disabled
│ └── Mipmaps: false
├── 2D高清艺术
│ ├── Filter: Linear(线性插值,平滑缩放)
│ ├── Repeat: Disabled
│ └── Mipmaps: true
└── 平铺纹理
├── Filter: Linear
├── Repeat: Enabled
└── Mipmaps: true
15.4.2 代码设置过滤
# 对单个纹理设置
func setup_texture_filter():
# 方法1:通过CanvasItem
texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
# TEXTURE_FILTER_PARENT_NODE - 继承父节点
# TEXTURE_FILTER_NEAREST - 最近邻(像素风格)
# TEXTURE_FILTER_LINEAR - 线性插值
# TEXTURE_FILTER_NEAREST_WITH_MIPMAPS
# TEXTURE_FILTER_LINEAR_WITH_MIPMAPS
# 项目全局设置
# 项目设置 → Rendering → Textures → Canvas Textures → Default Texture Filter
15.4.3 压缩格式
纹理压缩选项:
├── Lossless(无损)- PNG原始质量
├── Lossy(有损)- WebP压缩
├── VRAM Compressed - GPU压缩(推荐移动端)
│ ├── S3TC(桌面)
│ ├── ETC2(移动端)
│ └── ASTC(现代移动端)
└── VRAM Uncompressed - GPU未压缩(最高质量)
15.5 图集优化
15.5.1 为什么使用图集
图集的优势:
1. 减少Draw Call - 多个精灵共用一个纹理
2. 减少内存碎片 - 纹理尺寸优化
3. 加快加载速度 - 一次加载多个图像
4. 减少纹理切换 - GPU性能提升
15.5.2 创建图集
# 方法1:使用外部工具(TexturePacker, FreeTexturePacker等)
# 导出为精灵表 + JSON数据
# 方法2:使用AtlasTexture资源
# 在编辑器中创建AtlasTexture,设置atlas和region
# 方法3:使用region_rect
extends Sprite2D
func use_sprite_region():
texture = preload("res://spritesheet.png")
region_enabled = true
region_rect = Rect2(0, 0, 32, 32) # 第一个图块
15.5.3 精灵表管理器
# spritesheet_manager.gd
class_name SpritesheetManager
extends RefCounted
var _atlas: Texture2D
var _frame_size: Vector2i
var _columns: int
var _rows: int
var _regions: Dictionary = {}
func _init(atlas_path: String, frame_width: int, frame_height: int):
_atlas = load(atlas_path)
_frame_size = Vector2i(frame_width, frame_height)
_columns = _atlas.get_width() / frame_width
_rows = _atlas.get_height() / frame_height
_generate_regions()
func _generate_regions():
for y in range(_rows):
for x in range(_columns):
var index = y * _columns + x
_regions[index] = Rect2(
x * _frame_size.x,
y * _frame_size.y,
_frame_size.x,
_frame_size.y
)
func get_texture(frame_index: int) -> AtlasTexture:
var tex = AtlasTexture.new()
tex.atlas = _atlas
tex.region = _regions.get(frame_index, Rect2())
return tex
func get_texture_by_coord(x: int, y: int) -> AtlasTexture:
var index = y * _columns + x
return get_texture(index)
# 使用
var sheet = SpritesheetManager.new("res://tiles.png", 16, 16)
$Sprite2D.texture = sheet.get_texture(5)
15.6 高级精灵技术
15.6.1 精灵着色器
# 闪白效果(受伤)
# flash.gdshader
shader_type canvas_item;
uniform float flash_intensity : hint_range(0.0, 1.0) = 0.0;
uniform vec4 flash_color : source_color = vec4(1.0, 1.0, 1.0, 1.0);
void fragment() {
vec4 tex_color = texture(TEXTURE, UV);
COLOR = mix(tex_color, flash_color, flash_intensity);
COLOR.a = tex_color.a;
}
# 使用
extends Sprite2D
var flash_tween: Tween
func flash():
if flash_tween:
flash_tween.kill()
flash_tween = create_tween()
flash_tween.tween_property(material, "shader_parameter/flash_intensity", 1.0, 0.05)
flash_tween.tween_property(material, "shader_parameter/flash_intensity", 0.0, 0.15)
15.6.2 精灵轮廓
# outline.gdshader
shader_type canvas_item;
uniform vec4 outline_color : source_color = vec4(0.0, 0.0, 0.0, 1.0);
uniform float outline_width : hint_range(0.0, 10.0) = 1.0;
void fragment() {
vec2 size = TEXTURE_PIXEL_SIZE * outline_width;
float outline = texture(TEXTURE, UV + vec2(-size.x, 0)).a;
outline += texture(TEXTURE, UV + vec2(size.x, 0)).a;
outline += texture(TEXTURE, UV + vec2(0, -size.y)).a;
outline += texture(TEXTURE, UV + vec2(0, size.y)).a;
outline = min(outline, 1.0);
vec4 tex_color = texture(TEXTURE, UV);
if (tex_color.a < 0.1 && outline > 0.0) {
COLOR = outline_color;
} else {
COLOR = tex_color;
}
}
15.6.3 精灵溶解效果
# dissolve.gdshader
shader_type canvas_item;
uniform float dissolve_amount : hint_range(0.0, 1.0) = 0.0;
uniform sampler2D noise_texture;
uniform vec4 edge_color : source_color = vec4(1.0, 0.5, 0.0, 1.0);
uniform float edge_width : hint_range(0.0, 0.2) = 0.05;
void fragment() {
vec4 tex_color = texture(TEXTURE, UV);
float noise = texture(noise_texture, UV).r;
float edge = smoothstep(dissolve_amount, dissolve_amount + edge_width, noise);
if (noise < dissolve_amount) {
discard;
}
vec4 final_color = mix(edge_color, tex_color, edge);
COLOR = final_color;
}
# 使用
func dissolve():
var tween = create_tween()
tween.tween_property(material, "shader_parameter/dissolve_amount", 1.0, 1.0)
await tween.finished
queue_free()
15.7 性能优化
15.7.1 纹理尺寸规范
推荐尺寸(2的幂次方):
├── 16x16 - 小图标
├── 32x32 - 角色/物品
├── 64x64 - 大角色
├── 128x128 - Boss/UI元素
├── 256x256 - 背景元素
├── 512x512 - 大型精灵表
├── 1024x1024 - UI图集
└── 2048x2048 - 背景图集
注意:移动端最大建议2048x2048
15.7.2 减少Draw Call
# 检查Draw Call
# 在调试面板查看:调试器 → 监视器 → 2D
# 优化策略
# 1. 使用图集
# 2. 合并相同纹理的精灵
# 3. 使用CanvasGroup批处理
# CanvasGroup使用
# 将多个精灵放在CanvasGroup下
# CanvasGroup
# ├── Sprite1
# ├── Sprite2
# └── Sprite3
15.7.3 内存管理
# 及时释放不用的纹理
var cached_textures: Dictionary = {}
func load_texture(path: String) -> Texture2D:
if not cached_textures.has(path):
cached_textures[path] = load(path)
return cached_textures[path]
func unload_texture(path: String):
if cached_textures.has(path):
cached_textures.erase(path)
func clear_cache():
cached_textures.clear()
# 场景切换时清理
func _on_scene_changed():
clear_cache()
15.8 实际案例
15.8.1 完整的角色精灵类
# character_sprite.gd
class_name CharacterSprite
extends AnimatedSprite2D
signal animation_event(event_name: String)
@export var default_animation: String = "idle"
var _flash_material: ShaderMaterial
var _original_material: Material
func _ready():
_setup_flash_material()
play(default_animation)
func _setup_flash_material():
var shader = preload("res://shaders/flash.gdshader")
_flash_material = ShaderMaterial.new()
_flash_material.shader = shader
_original_material = material
func flash_white(duration: float = 0.1):
material = _flash_material
_flash_material.set_shader_parameter("flash_intensity", 1.0)
var tween = create_tween()
tween.tween_property(_flash_material, "shader_parameter/flash_intensity", 0.0, duration)
tween.tween_callback(func(): material = _original_material)
func set_facing(direction: float):
flip_h = direction < 0
func play_animation(anim_name: String, force: bool = false):
if animation != anim_name or force:
play(anim_name)
func play_one_shot(anim_name: String, return_to: String = "idle"):
play(anim_name)
await animation_finished
play(return_to)
15.8.2 动态精灵加载器
# sprite_loader.gd
class_name SpriteLoader
extends Node
const PLACEHOLDER_PATH = "res://assets/placeholder.png"
var _loading_queue: Array = []
var _is_loading: bool = false
func load_sprite_async(sprite: Sprite2D, path: String):
_loading_queue.append({"sprite": sprite, "path": path})
# 设置占位图
sprite.texture = preload(PLACEHOLDER_PATH)
if not _is_loading:
_process_queue()
func _process_queue():
_is_loading = true
while _loading_queue.size() > 0:
var item = _loading_queue.pop_front()
var sprite = item.sprite
var path = item.path
if not is_instance_valid(sprite):
continue
ResourceLoader.load_threaded_request(path)
while true:
var status = ResourceLoader.load_threaded_get_status(path)
if status == ResourceLoader.THREAD_LOAD_LOADED:
var tex = ResourceLoader.load_threaded_get(path)
if is_instance_valid(sprite):
sprite.texture = tex
break
elif status == ResourceLoader.THREAD_LOAD_FAILED:
push_error("加载失败:" + path)
break
await get_tree().process_frame
_is_loading = false
本章小结
本章全面学习了Godot的精灵与纹理系统:
- Sprite2D基础:创建、属性、颜色混合
- 纹理类型:Texture2D、AtlasTexture、AnimatedTexture等
- 精灵动画:帧动画、AnimatedSprite2D、状态机
- 导入设置:过滤模式、压缩格式
- 图集优化:创建图集、精灵表管理
- 高级技术:着色器效果(闪白、轮廓、溶解)
- 性能优化:纹理尺寸、Draw Call、内存管理
- 实际案例:角色精灵类、异步加载器
下一章将学习2D变换与坐标系统。
上一章:节点树架构
下一章:2D变换与坐标系