animation loop parameter
This commit is contained in:
parent
550201d365
commit
29fe135161
12 changed files with 563 additions and 54 deletions
203
tests/unit/ping_pong_easing_test.py
Normal file
203
tests/unit/ping_pong_easing_test.py
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
"""Test ping-pong easing functions.
|
||||
|
||||
Verifies that ping-pong easings oscillate (0 -> 1 -> 0) and that
|
||||
complete()/stop() on ping-pong animations returns to the start value.
|
||||
"""
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
PASS = True
|
||||
def check(name, condition):
|
||||
global PASS
|
||||
if not condition:
|
||||
print(f"FAIL: {name}")
|
||||
PASS = False
|
||||
else:
|
||||
print(f" ok: {name}")
|
||||
|
||||
|
||||
# --- Setup ---
|
||||
scene = mcrfpy.Scene("test")
|
||||
mcrfpy.current_scene = scene
|
||||
|
||||
|
||||
# --- Test 1: Ping-pong easing enum members exist ---
|
||||
check("PING_PONG exists", hasattr(mcrfpy.Easing, "PING_PONG"))
|
||||
check("PING_PONG_SMOOTH exists", hasattr(mcrfpy.Easing, "PING_PONG_SMOOTH"))
|
||||
check("PING_PONG_EASE_IN exists", hasattr(mcrfpy.Easing, "PING_PONG_EASE_IN"))
|
||||
check("PING_PONG_EASE_OUT exists", hasattr(mcrfpy.Easing, "PING_PONG_EASE_OUT"))
|
||||
check("PING_PONG_EASE_IN_OUT exists", hasattr(mcrfpy.Easing, "PING_PONG_EASE_IN_OUT"))
|
||||
|
||||
# Check enum values are sequential from 31
|
||||
check("PING_PONG value is 31", int(mcrfpy.Easing.PING_PONG) == 31)
|
||||
check("PING_PONG_EASE_IN_OUT value is 35", int(mcrfpy.Easing.PING_PONG_EASE_IN_OUT) == 35)
|
||||
|
||||
|
||||
# --- Test 2: Ping-pong animation reaches midpoint then returns ---
|
||||
sprite = mcrfpy.Sprite(pos=(0, 0))
|
||||
scene.children.append(sprite)
|
||||
anim = sprite.animate("x", 100.0, 1.0, mcrfpy.Easing.PING_PONG)
|
||||
check("ping-pong anim created", anim is not None)
|
||||
|
||||
# Step to midpoint (t=0.5)
|
||||
for _ in range(5):
|
||||
mcrfpy.step(0.1)
|
||||
|
||||
midpoint_x = sprite.x
|
||||
check(f"at midpoint x ({midpoint_x:.1f}) is near 100", midpoint_x > 80.0)
|
||||
|
||||
# Step to end (t=1.0)
|
||||
for _ in range(5):
|
||||
mcrfpy.step(0.1)
|
||||
|
||||
final_x = sprite.x
|
||||
check(f"at end x ({final_x:.1f}) returns near 0", final_x < 20.0)
|
||||
check("ping-pong anim completes", anim.is_complete == True)
|
||||
|
||||
|
||||
# --- Test 3: Ping-pong smooth animation oscillates ---
|
||||
sprite2 = mcrfpy.Sprite(pos=(0, 0))
|
||||
scene.children.append(sprite2)
|
||||
anim2 = sprite2.animate("x", 200.0, 1.0, mcrfpy.Easing.PING_PONG_SMOOTH)
|
||||
|
||||
# Step to midpoint
|
||||
for _ in range(5):
|
||||
mcrfpy.step(0.1)
|
||||
mid_x2 = sprite2.x
|
||||
check(f"smooth midpoint x ({mid_x2:.1f}) is near 200", mid_x2 > 150.0)
|
||||
|
||||
# Step to end
|
||||
for _ in range(5):
|
||||
mcrfpy.step(0.1)
|
||||
final_x2 = sprite2.x
|
||||
check(f"smooth end x ({final_x2:.1f}) returns near 0", final_x2 < 20.0)
|
||||
|
||||
|
||||
# --- Test 4: Ping-pong with loop=True cycles continuously ---
|
||||
sprite3 = mcrfpy.Sprite(pos=(0, 0))
|
||||
scene.children.append(sprite3)
|
||||
anim3 = sprite3.animate("x", 100.0, 0.5, mcrfpy.Easing.PING_PONG, loop=True)
|
||||
check("ping-pong loop is_looping", anim3.is_looping == True)
|
||||
|
||||
# Step through 2 full cycles
|
||||
for _ in range(20):
|
||||
mcrfpy.step(0.1)
|
||||
|
||||
check("ping-pong loop doesn't complete", anim3.is_complete == False)
|
||||
check("ping-pong loop has valid target", anim3.hasValidTarget() == True)
|
||||
|
||||
|
||||
# --- Test 5: complete() on ping-pong returns to start value ---
|
||||
sprite4 = mcrfpy.Sprite(pos=(50, 0))
|
||||
scene.children.append(sprite4)
|
||||
anim4 = sprite4.animate("x", 200.0, 1.0, mcrfpy.Easing.PING_PONG)
|
||||
|
||||
# Step partway through
|
||||
for _ in range(3):
|
||||
mcrfpy.step(0.1)
|
||||
|
||||
# Now complete - should return to start (50), not go to target (200)
|
||||
anim4.complete()
|
||||
completed_x = sprite4.x
|
||||
check(f"complete() returns to start ({completed_x:.1f})", abs(completed_x - 50.0) < 5.0)
|
||||
|
||||
|
||||
# --- Test 6: stop() on ping-pong freezes at current value (no jump) ---
|
||||
sprite5 = mcrfpy.Sprite(pos=(0, 0))
|
||||
scene.children.append(sprite5)
|
||||
anim5 = sprite5.animate("x", 100.0, 1.0, mcrfpy.Easing.PING_PONG_SMOOTH)
|
||||
|
||||
# Step to ~midpoint
|
||||
for _ in range(5):
|
||||
mcrfpy.step(0.1)
|
||||
|
||||
pre_stop_x = sprite5.x
|
||||
anim5.stop()
|
||||
|
||||
# Step more - value shouldn't change
|
||||
for _ in range(5):
|
||||
mcrfpy.step(0.1)
|
||||
|
||||
post_stop_x = sprite5.x
|
||||
check(f"stop() freezes value ({pre_stop_x:.1f} == {post_stop_x:.1f})",
|
||||
abs(pre_stop_x - post_stop_x) < 1.0)
|
||||
|
||||
|
||||
# --- Test 7: Callback receives start value for ping-pong ---
|
||||
callback_values = []
|
||||
def on_complete(target, prop, value):
|
||||
callback_values.append(value)
|
||||
|
||||
sprite6 = mcrfpy.Sprite(pos=(10, 0))
|
||||
scene.children.append(sprite6)
|
||||
anim6 = sprite6.animate("x", 300.0, 0.5, mcrfpy.Easing.PING_PONG, callback=on_complete)
|
||||
|
||||
# Step to completion
|
||||
for _ in range(10):
|
||||
mcrfpy.step(0.1)
|
||||
|
||||
check("callback was triggered", len(callback_values) == 1)
|
||||
if callback_values:
|
||||
# Callback value should be near start value (10), not target (300)
|
||||
check(f"callback value ({callback_values[0]:.1f}) is near start (10)",
|
||||
abs(callback_values[0] - 10.0) < 5.0)
|
||||
|
||||
|
||||
# --- Test 8: EaseInOut ping-pong (sin^2) is smooth at boundaries ---
|
||||
sprite7 = mcrfpy.Sprite(pos=(0, 0))
|
||||
scene.children.append(sprite7)
|
||||
anim7 = sprite7.animate("x", 100.0, 1.0, mcrfpy.Easing.PING_PONG_EASE_IN_OUT)
|
||||
|
||||
# Capture values at several timesteps
|
||||
values = []
|
||||
for _ in range(10):
|
||||
mcrfpy.step(0.1)
|
||||
values.append(sprite7.x)
|
||||
|
||||
# First value should be small (accelerating from 0)
|
||||
check(f"easeInOut starts slow ({values[0]:.1f} < 30)", values[0] < 30.0)
|
||||
# Middle values should be larger
|
||||
check(f"easeInOut peaks in middle ({max(values):.1f} > 70)", max(values) > 70.0)
|
||||
# Last value should be near 0 again
|
||||
check(f"easeInOut returns to start ({values[-1]:.1f} < 10)", values[-1] < 10.0)
|
||||
|
||||
|
||||
# --- Test 9: Legacy string names work for ping-pong ---
|
||||
sprite8 = mcrfpy.Sprite(pos=(0, 0))
|
||||
scene.children.append(sprite8)
|
||||
anim8 = sprite8.animate("x", 100.0, 0.5, "pingPong")
|
||||
check("legacy string 'pingPong' works", anim8 is not None)
|
||||
|
||||
for _ in range(10):
|
||||
mcrfpy.step(0.1)
|
||||
check("legacy string anim completes", anim8.is_complete == True)
|
||||
check(f"legacy string returns to start ({sprite8.x:.1f})", abs(sprite8.x) < 5.0)
|
||||
|
||||
|
||||
# --- Test 10: Standard easing complete() is unaffected (regression) ---
|
||||
sprite9 = mcrfpy.Sprite(pos=(0, 0))
|
||||
scene.children.append(sprite9)
|
||||
anim9 = sprite9.animate("x", 500.0, 1.0, mcrfpy.Easing.EASE_IN_OUT)
|
||||
|
||||
# Step partway
|
||||
for _ in range(3):
|
||||
mcrfpy.step(0.1)
|
||||
|
||||
anim9.complete()
|
||||
check(f"standard easing complete() goes to target ({sprite9.x:.1f})",
|
||||
abs(sprite9.x - 500.0) < 5.0)
|
||||
|
||||
|
||||
# --- Test 11: Animation constructor with ping-pong easing ---
|
||||
anim10 = mcrfpy.Animation("x", 100.0, 1.0, mcrfpy.Easing.PING_PONG, loop=True)
|
||||
check("Animation constructor with PING_PONG", anim10 is not None)
|
||||
check("Animation constructor loop=True", anim10.is_looping == True)
|
||||
|
||||
|
||||
# --- Summary ---
|
||||
if PASS:
|
||||
print("PASS")
|
||||
sys.exit(0)
|
||||
else:
|
||||
print("FAIL")
|
||||
sys.exit(1)
|
||||
Loading…
Add table
Add a link
Reference in a new issue