From 4e3abaa3816c92440c465e4c08a039318c2ee578 Mon Sep 17 00:00:00 2001 From: shweet Date: Fri, 13 Nov 2026 01:02:25 -0500 Subject: [PATCH] Draw the touch ring in engine instead of a particle --- MODDING.md | 6 ++-- resources/common/character.json | 15 ++++------ resources/common/gfx/circle.anm2 | 48 ------------------------------- resources/common/gfx/circle.png | Bin 2603 -> 0 bytes src/character.cpp | 10 ++----- src/character.h | 2 -- src/effects.cpp | 9 ++++++ src/effects.h | 6 ++-- src/interact.cpp | 2 +- src/items.cpp | 5 ++-- src/ui.cpp | 8 ++++++ 11 files changed, 33 insertions(+), 78 deletions(-) delete mode 100644 resources/common/gfx/circle.anm2 delete mode 100644 resources/common/gfx/circle.png diff --git a/MODDING.md b/MODDING.md index 0dec4ba..c0ab01f 100644 --- a/MODDING.md +++ b/MODDING.md @@ -174,7 +174,6 @@ The first expand area is treated as the belly: it breathes, jiggles when smacked | `scaleCycles` | float | How many oscillations the scale effect performs. Defaults to 1. | | `scaleTime` | float, seconds | How long the scale effect lasts. Defaults to a third of a second. | | `sounds` | paths | Sounds played when interacting. | -| `particles` | strings | Particles (see `particles`) spawned when interacting. | ### sounds | Key | Meaning | @@ -186,12 +185,11 @@ The first expand area is treated as the belly: it breathes, jiggles when smacked How items move in the background (`gfx/area.anm2`): `gravity` (applied to items' velocities per tick), `friction` (applied when items are grounded or hit walls) and `airResistance`. ### cursor -The cursor's sounds and particles. Its look is `gfx/cursor.anm2` (see "Fixed files and names"). +The cursor's sounds. Its look is `gfx/cursor.anm2` (see "Fixed files and names"). | Key | Meaning | |---|---| | `sounds` | `grab` (grabbing an item), `release` (letting go) and `throw` (letting go while dragging quickly). | -| `particles` | `grab`: particles spawned when grabbing an item. | ### menu The UI's sounds. @@ -201,7 +199,7 @@ The UI's sounds. | `sounds` | `open` and `close` (the taskbar or one of its windows), `hover` and `select` (a widget), and `cheats` (after entering a special code to activate cheats). | ### particles -An object of particles by name, each with the `anm2` and `animation` it plays once where spawned. The `Circle` particle is also spawned where an item lands hard. +An object of particles by name, each with the `anm2` and `animation` it plays once where spawned. (The ring where something is touched, grabbed or lands hard is drawn by the game, not a particle.) # items.json diff --git a/resources/common/character.json b/resources/common/character.json index 91906c5..8aa98aa 100644 --- a/resources/common/character.json +++ b/resources/common/character.json @@ -20,8 +20,7 @@ "isHold": true, "cursorHover": "Hover", "cursorActive": "Rub", - "sounds": ["sfx/rub.ogg"], - "particles": ["Circle"] + "sounds": ["sfx/rub.ogg"] }, "Smack": { "icon": "smack", @@ -30,21 +29,18 @@ "dialogueCooldown": 0.6667, "scaleAmplitude": 1, "scaleCycles": 3, - "sounds": "sfx/smack*.ogg", - "particles": ["Circle"] + "sounds": "sfx/smack*.ogg" }, "Kiss": { "icon": "kiss", "cursorHover": "KissHover", "cursorActive": "Kiss", - "sounds": "sfx/kiss*.ogg", - "particles": ["Circle"] + "sounds": "sfx/kiss*.ogg" }, - "Turn Around": {"icon": "turn", "sounds": ["sfx/turn.ogg"], "particles": ["Circle"]} + "Turn Around": {"icon": "turn", "sounds": ["sfx/turn.ogg"]} }, "cursor": { - "sounds": {"grab": ["sfx/grab.ogg"], "release": ["sfx/release.ogg"], "throw": "sfx/throw*.ogg"}, - "particles": {"grab": ["Circle"]} + "sounds": {"grab": ["sfx/grab.ogg"], "release": ["sfx/release.ogg"], "throw": "sfx/throw*.ogg"} }, "menu": { "sounds": { @@ -56,7 +52,6 @@ } }, "particles": { - "Circle": {"anm2": "gfx/circle.anm2", "animation": "Main"}, "Puff": {"anm2": "gfx/puff.anm2", "animation": "Main"} }, "area": {"gravity": 0.95, "friction": 0.75, "airResistance": 0.975} diff --git a/resources/common/gfx/circle.anm2 b/resources/common/gfx/circle.anm2 deleted file mode 100644 index 5191e85..0000000 --- a/resources/common/gfx/circle.anm2 +++ /dev/null @@ -1,48 +0,0 @@ -{ - "anm2ed": 1, - "info": {}, - "spritesheets": [ - { - "path": "circle.png", - "regions": [ - { - "name": "Main", - "size": [128, 128], - "pivot": [64, 64], - "origin": "center" - } - ] - } - ], - "layers": [ - {"name": "Main"} - ], - "defaultAnimation": "Spawn", - "animations": { - "Spawn": { - "length": 11, - "loop": false, - "root": [ - {"delay": 11} - ], - "layers": [ - { - "layer": 0, - "frames": [ - { - "region": 0, - "scale": [10, 10], - "delay": 10, - "interpolation": "linear" - }, - { - "region": 0, - "tint": [255, 255, 255, 0], - "interpolation": "linear" - } - ] - } - ] - } - } -} diff --git a/resources/common/gfx/circle.png b/resources/common/gfx/circle.png deleted file mode 100644 index 7cd3f72d60273995218bbd03f388d229e9496526..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2603 zcmV+`3e@$9P)z>=-AP12RCwC$T}wz*dl+}d2Qoh5ipu!F%{7pqNDFD>y(&~2ixw`e zMIpFz8|1=G;I@UEpv@FS(yBI%i)tCONJ$7Ou8Xkjp+#BY?cv<-qi?)6e&3lhGv}Q7 z|Id6M{1A?3{*UkXJOB5iqC!ngt$Zy~4w+1+(=qtd31|m21DXI0fLcH`z4jfj1=s|< z2do2L0ahcC$X6AB9YiCUhX8$m!+-;T9zeXvpkD))0Z##ofX9F*AdK%SfYK5kC9+Qf zh5^R_v4En-QsEKcJ|Kyr06}c40Q@Jt3jPcMMggZi_`1C^EG6y$ZUY{G7=EY#yd@rs z@(X}5gGhH0!wtYS5W=zwpm5SF;m;|+Bw(My*#sMF5IOq*v z3eaL<9$P5wZ*t`EBl#jyF64Irk~y5R2AHH_T-);-qr9x;$TRw+Bkbgl05%+)unD*X zs0(4<>nLxV4*5sljD(T+O2ABxF97S!wlXLI+bFM2HbY&|P-J)?VBR6K{{V){upKl+ zd6x`*)@XYsxyd|tvi&E-L_@u2;i-d zp%kD?Y2V|sF7l1k9qzLwBZCetU2Oc+D&3;*Y8@Jg=+i;dGtlQi{&d+4b`(EIHZAz@ zld&ZM)%o10*EV26smGo>87kh*%>+V`h-AaxN( zf=*0_Fsql+e!lPH1Sw$_u_H(&CWYZ$9pUre|6RhcaWasSmsYQoz8ETR5lGeVxZe@C z3(L4>m6pIoFe@xCYxgtKIbl$uPALgo0QJIpf?Iw?I4l%9LrO=s>`&od_ANp-t%BA8@&dREkA5&Za!upl9(E;mj9F=0W@nPLKczyvs@ z?lx8gQw(1Z3Jt7B*rnT|t~OQxEyAwk9`5~I{lp8*v$;ql@=jfCrV%%DF1d76QkBrE z98fnKUkVHey|&7H0y!@9D|MlV`2vUv<2J|hsRBlsXE&jUNvn&EuLkhI%9}1Z6&1u4 z|Gny7Yf6D$p&E#~`SDTVG%hXv?GZpkIKAwsn=0TW^X#4$|5jB4$kn}E+)4uA=91^t zt=8GROKyKFI&kLi3sZEosz0r(23i>&zc|hi(!TyZ6AJ21Yem3KAn(0PmR> z|4{!~Zybm3IRUIQFX}!(`;A-9x6TRR74u@J`pgohlG{B+haM1NfAR0?fC%ZwdPI!<0&sOi3|j@zAwzeVe~qeZtuc--`@5PZ6iC!IRt^oYMI+h~R%cIRBKbXs-cSWez$D2cbu-C^^r0sx@Or*0) zN#FvQWq3et+Wn4rTqt%%l$OBA9}zeh4qMU($i7<`MH zme_7#LBh6H2L%`V+romJKTS<^pD;6(rbdkR4&1f&r3T+8<2UF)0tX#LkkE00oc;WS z1Nnd3l94{4I>-zb>4@Q8pLGd?Jegv@&$bM83$s<3VPl;;_}^C}!tidU!PjkT#^OR3 zac340r!~u%_ZPOwD8=-F-(NuCW#}}|h8y#XH$Zw4DvX&#y z$-t*{OFY;yE-Ww0?*2^KxFMA`YEC(Hhj%aI8ZXO1GFoGJE1lc6sNNj#e7Uo?`H4~* z6T&XtJqtK}U&CjK5Qc?)2Wer)%VjzTCt^rx)=OzxahZoj-zEMlk-a6j8wz(v6RJb(TikOC|KmMVUojQz6E8_=mM zh7B`3U;z(a#)+8z0@H;ncdqX~s8I!w+xe#exD|b{h@iez{JI4lZ_&n!l<_WIyiOI* z%+Fk^h*zs&j{`>~@s6EVA|7u{sVO4vhkz@9yM>CdYz2@d2K)jq0B~`M=|iJ<=?0#! zbPY&fHaVfzbvvk3z{V4vgLm2>F(B!9w&HETeG|X-PzWHq(FlKViu5Fj;#fs+7hPiY zfro12*}F-=W7mf;ssMa#JRTxZ90nYq##`KjuN#YUylDe3Tfp=7pP1B`Qvo;&BSwum z9B^p{GyxhaeqEuSIY9%-#OqY?Moqj_5%1N*tJPLB7c8l%si}p_{sW>=1;c#)W|sf} N002ovPDHLkV1hLb%<%vK diff --git a/src/character.cpp b/src/character.cpp index a2ddae9..19ec67b 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -118,7 +118,7 @@ static void interact_parse(object source, InteractArea& area, Character& charact json_keys_check(source, {"type", "null", "layer", "icon", "isHold", "animation", "idleOnEnd", "cursorHover", "cursorActive", "dialoguePool", "dialogueCooldown", "digestionClick", "digestionHover", - "scaleAmplitude", "scaleCycles", "scaleTime", "sounds", "particles"}, + "scaleAmplitude", "scaleCycles", "scaleTime", "sounds"}, "an interact area"); area.animation = json_string(source, "animation", area.animation); area.cursorActive = json_string(source, "cursorActive", area.cursorActive); @@ -137,8 +137,6 @@ static void interact_parse(object source, InteractArea& area, Character& charact area.layer = character.anm2 ? character.anm2->layer_get(json_string(source, "layer")) : -1; if (!source["null"].error()) area.null = character.anm2 ? character.anm2->null_get(json_string(source, "null")) : -1; - if (!source["particles"].error()) - area.particles = json_strings(source, "particles"); if (!source["sounds"].error()) area.sounds = audio.sounds_get(character.roots, json_strings(source, "sounds")); } @@ -259,11 +257,7 @@ static void character_parse(object root, Character& character, Renderer& rendere float_read(area, "friction", character.area.friction); float_read(area, "airResistance", character.area.airResistance); - object cursor = json_object(root, "cursor"); - object cursorParticles = json_object(cursor, "particles"); - json_keys_check(cursor, {"sounds", "particles"}, "cursor"); - if (!cursorParticles["grab"].error()) - character.cursor.grabParticles = json_strings(cursorParticles, "grab"); + json_keys_check(json_object(root, "cursor"), {"sounds"}, "cursor"); json_keys_check(json_object(root, "menu"), {"sounds"}, "menu"); for (auto [name, value] : json_object(root, "particles")) diff --git a/src/character.h b/src/character.h index 5379caf..0c6f5a6 100644 --- a/src/character.h +++ b/src/character.h @@ -84,7 +84,6 @@ struct InteractArea std::string icon; std::string idleOnEnd; std::string name; - std::vector particles; Sounds sounds; float digestionClick = 0; float digestionHover = 0; @@ -283,7 +282,6 @@ struct Swap struct Cursor { - std::vector grabParticles; Anm2* anm2 = nullptr; }; diff --git a/src/effects.cpp b/src/effects.cpp index e7651d6..9f647e6 100644 --- a/src/effects.cpp +++ b/src/effects.cpp @@ -1,5 +1,6 @@ #include "effects.h" +constexpr Spark RING_SPARK = {WHITE, {}, {}, {}, 0, 64, 20, 0, SPARK_RING}; constexpr float FLASH_FADE = 0.04f; constexpr float FLOATER_RISE = 0.8f; constexpr float SPARK_DRAG = 0.95f; @@ -36,6 +37,14 @@ void Effects::spark_add(Spark spark) sparks.push_back(spark); } +// A white ring growing and fading where something was touched or hit. +void Effects::ring_add(Vec2 position) +{ + Spark spark = RING_SPARK; + spark.position = position; + spark_add(spark); +} + // Throws count copies of a spark out in every direction, at up to speed. void Effects::burst_add(Spark spark, int count, float speed) { diff --git a/src/effects.h b/src/effects.h index a9d4208..6ec484e 100644 --- a/src/effects.h +++ b/src/effects.h @@ -15,6 +15,7 @@ enum SparkShape { SPARK_CIRCLE, SPARK_LINE, + SPARK_RING, SPARK_STAR }; @@ -27,8 +28,8 @@ struct Floater FloaterType type = FLOATER_CALORIES; }; -// A glint, puff or line (from its position to its end) that drifts, falls (or rises) by its gravity, and fades over its -// life. +// A glint, puff, line (from its position to its end) or ring (growing to its size) that drifts, falls (or rises) by its +// gravity, and fades over its life. struct Spark { Color color; @@ -54,4 +55,5 @@ public: void floater_add(Vec2 position, float value, FloaterType type); void spark_add(Spark spark); void burst_add(Spark spark, int count, float speed); + void ring_add(Vec2 position); }; diff --git a/src/interact.cpp b/src/interact.cpp index b67209f..e884d91 100644 --- a/src/interact.cpp +++ b/src/interact.cpp @@ -62,7 +62,7 @@ void Interactions::update(Play& play, Pointer pointer) } play.audio->play(entry.sounds); body.idle_override_set(entry.idleOnEnd); - play.particles_spawn(entry.particles, pointer.position); + play.effects.ring_add(pointer.position); if (entry.digestionClick > 0 && body.stats.calories > 0 && !body.stats.isDigesting) body.stats.digestionProgress += entry.digestionClick; body.actor.pulse_add(entry.layer, entry.scaleAmplitude, entry.scaleCycles, entry.scaleTime); diff --git a/src/items.cpp b/src/items.cpp index 345ed65..3029523 100644 --- a/src/items.cpp +++ b/src/items.cpp @@ -10,7 +10,6 @@ constexpr float CRUMB_SPEED_Y_MIN = 2; constexpr float CRUMB_SPIN = 15; constexpr Color GOLD = {1, 0.85f, 0.3f, 1}; constexpr Color TRAIL_COLOR = {1, 1, 1, 0.35f}; -constexpr const char* IMPACT_PARTICLE = "Circle"; constexpr float BITE_JIGGLE = 4; constexpr float IMPACT_SPEED = 12; constexpr float FANFARE_MIN = 0.75f; @@ -360,7 +359,7 @@ void Items::update(Play& play, Pointer pointer) if (pointer.isClicked) { audio.play(character.sounds[SOUND_GRAB]); - play.particles_spawn(character.cursor.grabParticles, pointer.position); + play.effects.ring_add(pointer.position); isJustHeld = true; } if (pointer.isDown) @@ -394,7 +393,7 @@ void Items::update(Play& play, Pointer pointer) if (impact > 0) audio.play(character.sounds[SOUND_BOUNCE]); if (impact >= IMPACT_SPEED) - play.particles_spawn({IMPACT_PARTICLE}, position); + play.effects.ring_add(position); entity.isThrown &= vec2_length(entity.velocity) >= TRAIL_SPEED; if (entity.isThrown) { diff --git a/src/ui.cpp b/src/ui.cpp index c931949..98126b2 100644 --- a/src/ui.cpp +++ b/src/ui.cpp @@ -31,6 +31,8 @@ constexpr float BAR_SHAKE_SPEED = 40; constexpr float FLOATER_SHADOW = 3; constexpr float OVERSTUFFED_PULSE = 0.35f; constexpr float OVERSTUFFED_SPEED = 6; +constexpr float RING_START = 0.1f; +constexpr float RING_THICKNESS = 3; constexpr float STAR_WAIST = 0.3f; constexpr float BAR_WIDTH_MIN = 120; constexpr float BUTTON_SIZE_MAX = 96; @@ -741,6 +743,12 @@ void Ui::effects_draw(Play& play, ImVec2 screen) drawList->AddCircleFilled({at.x, at.y}, size, color); continue; } + if (spark.shape == SPARK_RING) + { + float radius = spark.size * view.a * (RING_START + (1 - RING_START) * (1 - fade)); + drawList->AddCircle({at.x, at.y}, radius, color, 0, RING_THICKNESS * view.a); + continue; + } if (spark.shape == SPARK_LINE) { Vec2 end = view * spark.end;