UNWRITTEN: complete short-form RPG at games/unwritten/
A 30-45 minute turn-based RPG built overnight as an engine showcase: six recruitable characters (party of three), four recurring NPCs, choice-driven dialogue with visible-but-locked skill checks, shops, loot, levels, two bosses with in-battle Talk options, the reverted grey-town beat, and an epilogue assembled from the player's actual choices. 33 dialogue scenes / 171 nodes. Terrain is ColorLayers only; kenney_tinydungeon supplies characters, items, and props. Creative direction, story, dialogue, maps, and systems design by Fable; implementation by four Opus subagents (framework, battle, overworld, integration). Verified by tests/playthrough.py: a scripted headless run of all three acts, 20/20 beats passing, plus per-system unit tests (script integrity, UI kit, 225-battle balance sim, overworld). Run: cd build && ./mcrogueface --exec ../games/unwritten/main.py Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014vxWJ6GY384SmZiMVXgrF4
This commit is contained in:
parent
d94a25706a
commit
80908f771c
31 changed files with 8847 additions and 0 deletions
100
games/unwritten/core/tween.py
Normal file
100
games/unwritten/core/tween.py
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
"""UNWRITTEN - motion helpers. Owner: Agent A.
|
||||
|
||||
Thin wrappers over obj.animate(...) so call sites read as intentions, not
|
||||
easing bookkeeping. Nothing here teleports; every transition is a tween
|
||||
(BIBLE section 6: Motion).
|
||||
"""
|
||||
import mcrfpy
|
||||
from core import palette
|
||||
|
||||
_uid = [0]
|
||||
|
||||
|
||||
def _next_id():
|
||||
_uid[0] += 1
|
||||
return _uid[0]
|
||||
|
||||
|
||||
def fade_scene(scene, on_done=None, dur=0.25):
|
||||
"""Cover the scene with an INK frame fading 0 -> 1 over dur, then call
|
||||
on_done (which typically activates/builds the next scene). Returns the
|
||||
cover frame so the caller can fade it back out on the new scene."""
|
||||
cover = mcrfpy.Frame(pos=(0, 0), size=(1024, 768),
|
||||
fill_color=palette.C(palette.INK_T, 0))
|
||||
cover.z_index = 100000
|
||||
scene.children.append(cover)
|
||||
|
||||
def done(*_a):
|
||||
if on_done:
|
||||
on_done()
|
||||
cover.animate("opacity", 1.0, dur, mcrfpy.Easing.EASE_IN_OUT, callback=done)
|
||||
return cover
|
||||
|
||||
|
||||
def uncover(scene, cover, dur=0.25):
|
||||
"""Fade an INK cover frame back out and remove it from the scene."""
|
||||
def done(*_a):
|
||||
try:
|
||||
scene.children.remove(cover)
|
||||
except Exception:
|
||||
pass
|
||||
cover.animate("opacity", 0.0, dur, mcrfpy.Easing.EASE_IN_OUT, callback=done)
|
||||
|
||||
|
||||
def float_text(parent, text, pos, color, rise=42, dur=0.85, size=20):
|
||||
"""Rising, fading caption. `parent` is a UICollection (scene.children or
|
||||
frame.children). Used for damage numbers, +1 Story Point, loot notes."""
|
||||
cap = mcrfpy.Caption(pos=(pos[0], pos[1]), text=str(text), font_size=size,
|
||||
fill_color=palette.to_color(color))
|
||||
cap.outline = 1
|
||||
cap.outline_color = palette.INK
|
||||
cap.z_index = 5000
|
||||
parent.append(cap)
|
||||
cap.animate("y", pos[1] - rise, dur, mcrfpy.Easing.EASE_OUT)
|
||||
|
||||
def done(*_a):
|
||||
try:
|
||||
parent.remove(cap)
|
||||
except Exception:
|
||||
pass
|
||||
cap.animate("opacity", 0.0, dur, mcrfpy.Easing.EASE_OUT, callback=done)
|
||||
return cap
|
||||
|
||||
|
||||
def shake(drawable, mag=6, dur=0.3):
|
||||
"""Small horizontal shake that returns to the original x."""
|
||||
ox = drawable.x
|
||||
seq = [ox + mag, ox - mag, ox + mag * 0.5, ox]
|
||||
step_dur = max(0.02, dur / len(seq))
|
||||
|
||||
def go(i):
|
||||
if i >= len(seq):
|
||||
return
|
||||
drawable.animate("x", seq[i], step_dur,
|
||||
callback=lambda *_a: go(i + 1))
|
||||
go(0)
|
||||
|
||||
|
||||
def pulse(drawable, prop, hi, dur=1.0, loop=True, easing=None):
|
||||
"""Animate a property toward hi (looping by default) for idle throbs."""
|
||||
drawable.animate(prop, hi, dur,
|
||||
easing if easing is not None else mcrfpy.Easing.EASE_IN_OUT,
|
||||
loop=loop)
|
||||
|
||||
|
||||
def gold_ring(parent, center, max_r=70, dur=0.5):
|
||||
"""A single gold circle that rings outward and fades. The Book hum."""
|
||||
circ = mcrfpy.Circle(radius=2, center=(center[0], center[1]),
|
||||
fill_color=palette.C(palette.GOLD_T, 0),
|
||||
outline=2, outline_color=palette.C(palette.GOLD_T, 200))
|
||||
circ.z_index = 4000
|
||||
parent.append(circ)
|
||||
circ.animate("radius", max_r, dur, mcrfpy.Easing.EASE_OUT)
|
||||
|
||||
def done(*_a):
|
||||
try:
|
||||
parent.remove(circ)
|
||||
except Exception:
|
||||
pass
|
||||
circ.animate("opacity", 0.0, dur, mcrfpy.Easing.EASE_OUT, callback=done)
|
||||
return circ
|
||||
Loading…
Add table
Add a link
Reference in a new issue