Matematika, náhoda a kolize
Trojice modulů, o kterou se opírá každá hra na PicoPadu při pohybu, spawnování a detekci zásahů. Jsou drobné, šetří alokacemi a stačí je naimportovat (žádné nastavování enginu). Holé signatury najdeš v /cs/reference/; tahle stránka je o tom proč a kdy.
picogame_math
Sekce “picogame_math”Co to je: malí číselní pomocníci, po kterých saháš v každém frame - clamp/lerp/approach/wrap, trigonometrie po otáčkách (úhly jako 0..1 otáčky místo radiánů) a 2D vektorová matematika (zabalená sem ze starého picogame_vec). Naimportuj ho jednou jako m a používej na vyhlazování, míření, screen-wrap a vzdálenosti.
Kdy ho použít: kdykoli bys jinak ručně psal clamp, plynulé sledování nebo atan2. Po trigonometrii s _t sáhni, když chceš úhly, které čistě wrapují v [0,1) (stav rotace, míření), místo radiánů.
API:
clamp(v, lo, hi)- vrátívpřipnuté do[lo, hi]. Každodenní pomocník na udržení pozice na obrazovce nebo HP v rozsahu.mid(a, b, c)- medián tří hodnot; chová se jako clamp, když je prostřední argument hodnota.lerp(a, b, t)- lineární prolnutía + (b-a)*t. S malýmtv každém frame dostaneš plynulé sledování/ease.inv_lerp(a, b, v)- inverzelerp: kde ležívv[a,b]jako0..1. Vrátí0.0, pokuda == b.remap(v, a, b, c, d)- namapujevz rozsahu[a,b]na[c,d]. Bezpečné, kdyža == b(namapuje nac).sgn(x)- znaménko jako-1,0nebo1.approach(v, target, step)- posunevsměrem ktargetnejvýš ostep, nikdy nepřestřelí. Skvělé na tření/akceleraci k nějaké hodnotě.wrap(v, lo, hi)- zabalívdo polootevřeného rozsahu[lo, hi). Degenerované nebo obrácené rozsahy vrátílo(žádné dělení nulou, nikdy mimo rozsah).sin_t(turns)/cos_t(turns)- sinus/kosinus úhlu v OTÁČKÁCH (1.0= celý kruh). Kladná hodnota je po směru hodinových ručiček na obrazovce s osou y dolů.atan2_t(dy, dx)- úhel vektoru(dx, dy)v otáčkách, normalizovaný do0..1. Použij na míření.length(dx, dy)- velikost vektoru.distance(x1, y1, x2, y2)- vzdálenost mezi dvěma body.normalize(dx, dy)- jednotkový vektor; pro vstup nulové délky vrátí(0.0, 0.0).angle_rad(dx, dy)- úhel v RADIÁNECH (surovýatan2);from_angle_rad(a, mag=1.0)- vektor délkymagpod radiánovým úhlema.TAU- konstanta2*pi, kterou interně používá trigonometrie s_t.
Příklad (rotace lodi a tah po její špičce, jako v asteroids):
import picogame_math as m
ang = 0.0 # heading, in turns 0..1TURN = 0.01ang = (ang + TURN) % 1.0 # rotate rightdx, dy = m.sin_t(ang), -m.cos_t(ang) # nose-up directionvx += dx * 0.25vy += dy * 0.25sp = m.length(vx, vy) # current speedx = m.clamp(x, 8, W - 8) # keep on screenZáludnosti:
- Trigonometrie s
_tje po směru hodinových ručiček na obrazovce s osou y dolů a “nahoru” je-cos_t- obrať znaménko y složky, jako u lodi v asteroids výše, jinak se ti sprite otáčí špatným směrem. wrapje polootevřený:wrap(hi, lo, hi)vrátílo, nehi. Pro obrácený nebo nulový rozsah prostě vrátílo, místo aby vyhodil chybu.- Otáčky (
sin_t/cos_t/atan2_t) a radiány (angle_rad/from_angle_rad) používej konzistentně; pro stejný úhel je nemíchej dohromady.
picogame_rand
Sekce “picogame_rand”Co to je: drobný, seedovatelný xorshift32 RNG plus pomocníci, které malé hry doopravdy potřebují (vážené výběry, in-place zamíchání, zamíchávací “bag”). Použij ho místo random ze stdlib, když chceš reprodukovatelné běhy - replaye, ghosty, seedy denních výzev nebo deterministické rozložení levelů.
Kdy ho použít: vytvoř jeden Rand na hru (seedni ho kvůli reprodukovatelnosti, nebo seed vynech pro běh seedovaný časem) a veškerou náhodu ber z něj. Po Bag sáhni, když chceš férovost bez šňůr (spawny, díly ve stylu Tetrisu).
Rand(seed=None):
Rand(1234)seedne z intu (reprodukovatelné);Rand()seedne z hodin.seed=0se interně přemapuje (xorshift nemůže začít na nule).seed(s)- znovu seedne existující generátor.below(n)- celé číslo v0 .. n-1. Vrátí0, pokudn <= 0.randint(a, b)- celé číslo va .. bvčetně. VyhodíValueError, pokudb < a.random()- float v[0.0, 1.0).chance(p)-Trues pravděpodobnostíp(kdepje0..1).choice(seq)- jeden prvek zeseq. VyhodíValueErroru prázdné sekvence.shuffle(lst)- Fisher-Yates zamíchánílst, in place (vracíNone).weighted(weights)- vrátí index0..len-1vybraný úměrně kweights. VyhodíValueError, pokud je součet<= 0. Bez řízení šňůr (nezávislé tahy).
Bag(items, rng):
- Zamíchávací bag / “7-bag”: vydá každou položku jednou za cyklus v zamíchaném pořadí, takže žádné dlouhé šňůry ani sucha. Férovější než nezávislé výběry pro spawny a díly.
next()- vrátí další položku, na začátku každého cyklu automaticky zamíchá. VyhodíValueErrorpři konstrukci, pokud jeitemsprázdné.
Příklad (jeden seedovaný RNG pro celou hru plus spawnovací bag proti šňůrám):
import picogame_rand
rng = picogame_rand.Rand(0x1234) # seeded -> reproduciblex = rng.randint(40, W - 40) # 40 .. W-40 inclusiveif rng.chance(0.25): spawn_powerup(x)kind = rng.weighted([5, 3, 1]) # index 0 most likelybag = picogame_rand.Bag([0, 1, 2, 3, 4, 5, 6], rng)piece = bag.next() # every value once per cycleZáludnosti:
shufflemutuje seznam in place a vracíNone- nepišlst = rng.shuffle(lst).Bagsi bere do vlastnictví kopiiitems;next()přemíchává tento vnitřní seznam, takže pořadí, které jsi předal, se nezachová.weightedvrací index, ne hodnotu - indexuj jím do svého vlastního seznamu.- Stejný seed plus stejná sekvence volání rovná se stejné výsledky: o to právě jde, ale taky to znamená, že omylem sdílený
rngpropojí náhodu dvou systémů.
picogame_collide
Sekce “picogame_collide”Co to je: kolizní testy bez alokace, které čtou pozice a velikosti přímo ze spritů, takže přestaneš v každé entitní smyčce ručně psát abs(a.x - b.x) < w and .... Sahají jen na celočíselné gettery (sprite.x, sprite.y, bitmap.width, bitmap.height) a nikdy ne na sprite.anchor (jehož getter alokuje), takže je bezpečné je volat mnohokrát za frame. Per-frame rozpočet, který tohle chrání, najdeš v /cs/hardware/.
Kdy ho použít: uvnitř svých kolizních smyček. Použij hit na překryv boxů, hit_point na test holé souřadnice proti spritu a is_within na levné kruhové testy dosahu (výbuchy, magnetické sběry, blízkost).
API:
hit(a, b, hw=None, hh=None)- překryv os zarovnaných boxů (AABB) dvou spritů.hw/hhjsou poloviční rozměry kombinovaného boxu; výchozí je polovina součtu velikostí obou bitmap (test překryvu středů). Vrací bool.hit_point(a, px, py, hw=None, hh=None)- je bod(px, py)v rámci polovičních rozměrů spritu od jeho pozice? Výchozí poloviční rozměry jsou polovina velikosti bitmapy spritu.is_within(a, b, r)- kruhový test: jsou pozice obou spritů v poloměrur? Používá kvadrát vzdálenosti, takže žádnýsqrt.
Pozice se berou jako referenční body spritů: dokud oba sprity sdílejí stejný anchor (oba střed, nebo oba levý horní roh), offset anchoru se vyruší a tyhle fungují bez ohledu na to, který anchor jsi zvolil - viz /cs/scene-format/ ohledně anchorů.
Příklad (střely vs kameny kruhově, hráč vs nepřítel boxově - upraveno z asteroids a plošinovky):
import picogame_collide as collide
for b in bullets: for r in rocks: if b.visible and collide.is_within(b, r, 18): # circular, no sqrt kill(b, r)
if collide.hit(player, enemy, 12, 16): # explicit half-extents take_damage()Záludnosti:
- Tyhle čtou živé pozice spritů. Pokud sleduješ pohyb ve floatech (
data["x"]), zavolej před testemsprite.move(int(x), int(y)), jinak kolize zaostává o frame za viditelným spritem. hitje test boxů, ne pixel-perfect - dolaďhw/hh(často menší než grafika), aby rohy nezpůsobovaly falešné zásahy.- Výchozí rozměry se berou z
bitmap.width/height, takže sprite musí mít nastavenoubitmap, než zavolášhit/hit_points výchozími rozměry; jinak předej explicitníhw/hh. - Hlídej si sám
if sprite.visible(a jakýkoli příznak “naživu”) - pomocníci testují jen geometrii, ne to, jestli je entita aktivní. Viz /cs/memory/ o poolování uvolněných entit.