Skip to content

Commit b4202aa

Browse files
committed
Sprite effects
1 parent b446c83 commit b4202aa

File tree

3 files changed

+60
-21
lines changed

3 files changed

+60
-21
lines changed

src/engine/Component/Sprite.jl

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,10 @@ module SpriteModule
4545
# effects support
4646
effects::Vector{Any} # Will hold Effect objects
4747
effectTexture::Union{Ptr{Nothing}, Ptr{SDL2.LibSDL2.SDL_Texture}}
48+
effectSize::Math.Vector2 # Size of effect texture (may be larger due to glow padding)
4849
needsEffectUpdate::Bool
50+
useEffectTexture::Bool # Toggle to enable/disable effect texture rendering
51+
interactionScale::Float64 # Scale factor for hover/click hitbox (1.0 = full size, <1.0 = smaller)
4952

5053
function InternalSprite(
5154
parent::JulGame.IEntity,
@@ -89,7 +92,10 @@ module SpriteModule
8992
# Initialize effects
9093
this.effects = Any[]
9194
this.effectTexture = C_NULL
95+
this.effectSize = Math.Vector2(0, 0)
9296
this.needsEffectUpdate = false
97+
this.useEffectTexture = true # Default to showing effects when applied
98+
this.interactionScale = 1.0 # Default to full-size hitbox
9399

94100
# Early returns
95101
if isCreatedInEditor
@@ -120,8 +126,8 @@ module SpriteModule
120126
update_effects(this)
121127
end
122128

123-
# Use effect texture if available, otherwise use regular texture
124-
texture_to_render = if !isempty(this.effects) && this.effectTexture != C_NULL
129+
# Use effect texture if available and enabled, otherwise use regular texture
130+
texture_to_render = if this.useEffectTexture && !isempty(this.effects) && this.effectTexture != C_NULL
125131
this.effectTexture
126132
else
127133
# Create texture if it doesn't exist
@@ -132,15 +138,14 @@ module SpriteModule
132138
this.texture
133139
end
134140

135-
# Check and set color if necessary (only for regular texture, not effect texture)
136-
if texture_to_render == this.texture
137-
colorRefs = (Ref(UInt8(0)), Ref(UInt8(0)), Ref(UInt8(0)))
138-
alphaRef = Ref(UInt8(0))
139-
SDL2.SDL_GetTextureColorMod(this.texture, colorRefs...)
140-
SDL2.SDL_GetTextureAlphaMod(this.texture, alphaRef)
141-
if colorRefs[1] != this.color[1] || colorRefs[2] != this.color[2] || colorRefs[3] != this.color[3] || this.color[4] != alphaRef
142-
Component.set_color(this)
143-
end
141+
# Check and set color if necessary (for both regular and effect textures)
142+
colorRefs = (Ref(UInt8(0)), Ref(UInt8(0)), Ref(UInt8(0)))
143+
alphaRef = Ref(UInt8(0))
144+
SDL2.SDL_GetTextureColorMod(texture_to_render, colorRefs...)
145+
SDL2.SDL_GetTextureAlphaMod(texture_to_render, alphaRef)
146+
if colorRefs[1][] != this.color[1] || colorRefs[2][] != this.color[2] || colorRefs[3][] != this.color[3] || this.color[4] != alphaRef[]
147+
SDL2.SDL_SetTextureColorMod(texture_to_render, UInt8(clamp(this.color[1], 0, 255)), UInt8(clamp(this.color[2], 0, 255)), UInt8(clamp(this.color[3], 0, 255)))
148+
SDL2.SDL_SetTextureAlphaMod(texture_to_render, UInt8(clamp(this.color[4], 0, 255)))
144149
end
145150

146151
# Calculate camera difference
@@ -157,7 +162,10 @@ module SpriteModule
157162
# Calculate pixels per unit
158163
ppu = this.pixelsPerUnit > 0 ? this.pixelsPerUnit : JulGame.PIXELS_PER_UNIT
159164

160-
# Precompute values to avoid redundant calculations
165+
# Check if using effect texture
166+
usingEffectTex = texture_to_render == this.effectTexture && this.effectSize != Math.Vector2(0, 0)
167+
168+
# Always use original sprite size for positioning calculations
161169
cropWidth = srcRect == C_NULL ? this.size.x : this.crop.z
162170
cropHeight = srcRect == C_NULL ? this.size.y : this.crop.t
163171
scaleX = this.parent.transform.scale.x
@@ -179,7 +187,7 @@ module SpriteModule
179187
scaledHeight = cropHeight * scaleFactor * scaleY
180188
end
181189

182-
# Compute position based on anchor
190+
# Compute position based on anchor (using original sprite dimensions)
183191
centeredX = adjustedX
184192
centeredY = adjustedY
185193

@@ -218,6 +226,19 @@ module SpriteModule
218226
centeredX -= (scaledWidth - SCALE_UNITS * scaleX)
219227
centeredY -= (scaledHeight - SCALE_UNITS * scaleY)
220228
end
229+
230+
# AFTER anchor positioning: expand render size for effect texture and offset to center it
231+
if usingEffectTex
232+
scaleFactor = this.pixelsPerUnit == 0 ? (SCALE_UNITS/64.0) : (SCALE_UNITS / ppu)
233+
effectScaledWidth = this.effectSize.x * scaleFactor * scaleX
234+
effectScaledHeight = this.effectSize.y * scaleFactor * scaleY
235+
# Offset to center the larger effect texture over the original sprite position
236+
centeredX -= (effectScaledWidth - scaledWidth) / 2
237+
centeredY -= (effectScaledHeight - scaledHeight) / 2
238+
# Use effect dimensions for rendering
239+
scaledWidth = effectScaledWidth
240+
scaledHeight = effectScaledHeight
241+
end
221242

222243
# Select float or integer precision
223244
if this.isFloatPrecision
@@ -268,7 +289,7 @@ module SpriteModule
268289

269290
# effects API
270291
function Component.apply_effects!(this::InternalSprite, effects::Vector)
271-
this.effects = effects
292+
this.effects = Any[effect for effect in effects] # Convert to Vector{Any}
272293
this.needsEffectUpdate = true
273294
update_effects(this)
274295
return this
@@ -291,6 +312,13 @@ module SpriteModule
291312
result = JG.EffectRendererModule.apply_effects!(target, this.effects)
292313
if result isa JG.EffectsModule.SpriteTarget
293314
# Effect texture should be updated by the renderer
315+
# Query the effect texture size and store it
316+
if this.effectTexture != C_NULL
317+
w = Ref{Cint}(0); h = Ref{Cint}(0)
318+
fmt = Ref{UInt32}(0); access = Ref{Cint}(0)
319+
SDL2.SDL_QueryTexture(this.effectTexture, fmt, access, w, h)
320+
this.effectSize = Math.Vector2(w[], h[])
321+
end
294322
this.needsEffectUpdate = false
295323
end
296324
catch e
@@ -405,6 +433,7 @@ module SpriteModule
405433

406434
function Component.duplicate(this::InternalSprite, parent::Any)
407435
newSprite = InternalSprite(parent, this.imagePath, this.crop, this.isFlipped, this.color, false; pixelsPerUnit=this.pixelsPerUnit, position=this.position, rotation=this.rotation, layer=this.layer, center=this.center, anchor=this.anchor, offset=this.offset, isStatic=this.isStatic)
436+
newSprite.interactionScale = this.interactionScale
408437
Component.initialize(newSprite)
409438
return newSprite
410439
end

src/engine/Effects/effect_renderer.jl

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -164,12 +164,11 @@ module EffectRendererModule
164164
texture = SDL2.SDL_CreateTextureFromSurface(JulGame.Renderer, surface)
165165
return EffectsModule.TextureTarget(texture)
166166
elseif target isa EffectsModule.SpriteTarget
167-
# Update sprite's surface and regenerate texture
168-
target.sprite.image = surface
169-
if target.sprite.texture != C_NULL
170-
SDL2.SDL_DestroyTexture(target.sprite.texture)
167+
# Update sprite's effect texture (not base texture)
168+
if target.sprite.effectTexture != C_NULL
169+
SDL2.SDL_DestroyTexture(target.sprite.effectTexture)
171170
end
172-
target.sprite.texture = SDL2.SDL_CreateTextureFromSurface(JulGame.Renderer, surface)
171+
target.sprite.effectTexture = SDL2.SDL_CreateTextureFromSurface(JulGame.Renderer, surface)
173172
return target
174173
elseif target isa EffectsModule.RectangleTarget
175174
# Update rectangle's effect texture

src/engine/Input/Input.jl

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,15 @@ module InputModule
479479
if element.sprite === nothing || element.sprite === C_NULL
480480
return Math.Vector2(0, 0)
481481
end
482-
return element.sprite.lastRenderedScreenPosition === nothing ? Math.Vector2(0, 0) : element.sprite.lastRenderedScreenPosition
482+
basePosition = element.sprite.lastRenderedScreenPosition === nothing ? Math.Vector2(0, 0) : element.sprite.lastRenderedScreenPosition
483+
baseSize = element.sprite.lastRenderedScreenSize === nothing ? Math.Vector2(0, 0) : element.sprite.lastRenderedScreenSize
484+
# Center the scaled hitbox over the original sprite position
485+
interactionScale = try element.sprite.interactionScale catch; 1.0 end
486+
if interactionScale < 1.0
487+
sizeDiff = Math.Vector2(baseSize.x * (1.0 - interactionScale), baseSize.y * (1.0 - interactionScale))
488+
return Math.Vector2(basePosition.x + sizeDiff.x / 2, basePosition.y + sizeDiff.y / 2)
489+
end
490+
return basePosition
483491
end
484492

485493
function get_element_size(element::JulGame.IUIElement)
@@ -490,7 +498,10 @@ module InputModule
490498
if element.sprite === nothing || element.sprite === C_NULL
491499
return Math.Vector2(0, 0)
492500
end
493-
return element.sprite.lastRenderedScreenSize === nothing ? Math.Vector2(0, 0) : element.sprite.lastRenderedScreenSize
501+
baseSize = element.sprite.lastRenderedScreenSize === nothing ? Math.Vector2(0, 0) : element.sprite.lastRenderedScreenSize
502+
# Apply interaction scale to shrink/grow hitbox independently of visual size
503+
interactionScale = try element.sprite.interactionScale catch; 1.0 end
504+
return Math.Vector2(baseSize.x * interactionScale, baseSize.y * interactionScale)
494505
end
495506

496507
function check_scan_code(this::Input, keyboardState, keyState, scanCodes)

0 commit comments

Comments
 (0)