bringing over articles
parent
7db62ebedf
commit
2dd285a089
@ -0,0 +1,23 @@
|
|||||||
|
<!--201217,200903-->
|
||||||
|
<h1>what is blessfrey? </h1>
|
||||||
|
august 6, 2020<br>
|
||||||
|
#game <br>
|
||||||
|
<br>
|
||||||
|
<b>Blessfrey</b> is a 2D action RPG developed for PC by Chimchooree. <br>
|
||||||
|
<br>
|
||||||
|
<a target="_blank" href="/static/img/ent/screenshot_June292019.png">
|
||||||
|
<img src="/static/img/ent/screenshot_June292019.png" alt="(image: Lots of Angels and other characters at a shopping center)" width="500" height="278.66">
|
||||||
|
</a><br>
|
||||||
|
<br>
|
||||||
|
The game is designed to pit your skill and creativity against a series of combat and puzzle challenges while exploring the depths of the downtown dungeon. <br>
|
||||||
|
<br>
|
||||||
|
Class progression is free-form, and virtually no decision is permanent. At character creation, you will choose a permanent First Class, but you can unlock new classes for multiclassing during gameplay. Swap out Second Classes to find a combination to overcome challenges and express your playstyle. <br>
|
||||||
|
<br>
|
||||||
|
Each class has its own style of skills associated with it. Skills are individual powers gained through gameplay which give specific effects. Your skillbar only has 8 skill slots and can only be edited in safe areas. The challenge comes from discovering effective strategies and synergies against the next area. <br>
|
||||||
|
<br>
|
||||||
|
Skills are gained during exploration. As you find new areas, encounter enemies, and interact with your surroundings, you will internalize those experiences as new skills. There are multiple paths to learning, so you are free to focus on your favorite parts of the game. <br>
|
||||||
|
<br>
|
||||||
|
Blessfrey has been lots of fun to work on. I hope you enjoy it once a demo and eventually a game drops. <br>
|
||||||
|
<br>
|
||||||
|
Last updated June 8, 2021 <br>
|
||||||
|
<br>
|
@ -0,0 +1,70 @@
|
|||||||
|
<!--200806,201126-->
|
||||||
|
<h1>my first game jam - #weeklygamejam </h1>
|
||||||
|
september 3, 2020<br>
|
||||||
|
#gamejam<br>
|
||||||
|
<br>
|
||||||
|
<b><a href="https://weeklygamejam.itch.io/">WeeklyGameJam</a></b> is a weekly theme-based game jam hosted through itch.io. It's fairly laid-back for a jam, giving you a full week's time, allowing for premade/stock assets and code, and being understanding towards late submissions. Most people make videogames, but any kind of game is allowed. At the end of the week, streamers will play and critique the submissions while the developers hang out in their chatrooms. <br>
|
||||||
|
<br>
|
||||||
|
<center><img src="/static/img/ent/SmallThingThatMakesThings.png" alt="(image: Key art of Elwell and Small Thing, buried in dogs)" width="500" height="223.77"></center> <br>
|
||||||
|
<br>
|
||||||
|
<h2>small thing that makes things</h2>
|
||||||
|
<br>
|
||||||
|
I participated in Week 85 under the theme Offspring, submitting my game on February 27, 2019. My game was Small Thing That Makes Things, an adventure platformer. You can play it on <a href="https://chimchooree.itch.io/small-thing-that-makes-things">itch.io</a>. <br>
|
||||||
|
<br>
|
||||||
|
You play as Hamish T. Elwell, the hero accountant of an overcrowded animal shelter, investigating the recent explosion of the local stray population. You can walk, jump on platforms, collect items, and chat with NPCs. There's multiple endings, depending on your choices. <br>
|
||||||
|
<br>
|
||||||
|
<h2>positives </h2>
|
||||||
|
<br>
|
||||||
|
For a week-made game, I think STTMT is pretty cute, and I'm pretty happy with it.<img src="/static/img/emo/star.gif" alt=":)"> <br>
|
||||||
|
<br>
|
||||||
|
I actually finished a game, and someone actually finished playing it on a stream. That's really cool, even if STTMT isn't all that good. The deadline forced me to make final decisions, complete features, and move on, and there's a lot of value in that. <br>
|
||||||
|
<br>
|
||||||
|
The short time-frame forced me to get around to every aspect of game development, many of which I had never done before. I had to learn how to export a Godot project, upload an HTML5 game to itch, and make sure the exported game was complete and playable. Lots of the features were first-times for me, too. I've never written code for platformer movements or moving cameras. This also was the first time I've really gotten branching and conditional dialog working in Godot, which was a skill I immediately applied to Blessfrey. <br>
|
||||||
|
<br>
|
||||||
|
<h2>mistakes</h2>
|
||||||
|
<br>
|
||||||
|
I recolored OPP's pixel art and lost the high contrast for the rock ledges. They blend into the rocky background, so you can't tell you can jump on them. I didn't even notice until <a href="https://www.twitch.tv/mrjoshuamclean">Joshua McLean</a> pointed this out during his stream. Having more eyes on your game is so important. <br>
|
||||||
|
<br>
|
||||||
|
Also the level design is just not interesting. I spent lots of time on the choices and adventure game aspects, while the platforming as an extreme afterthought. It's a game jam, though, what do you expect?<br>
|
||||||
|
<br>
|
||||||
|
I took the easy way out with animation. I might have learned more working from scratch, but modifying OPP's sprites to suit my character designs was way faster when I was already struggling to finish on time. <br>
|
||||||
|
<br>
|
||||||
|
<h2>screenshots + progression </h2>
|
||||||
|
<center>
|
||||||
|
<a target="_blank" href="/static/img/ent/SmallThingThatMakesThings_flatland.png">
|
||||||
|
<img src="/static/img/ent/SmallThingThatMakesThings_flatland.png" alt="(image: Elwell and Small Thing in a jumble of dogs)" width="500" height="300.92">
|
||||||
|
</a><br>
|
||||||
|
Experimenting with Small Thing's summoning mechanic. In this version, the characters are just blocks with collision that can move around the flat, empty world with WASD. <br>
|
||||||
|
<br><br>
|
||||||
|
<a target="_blank" href="/static/img/ent/chimchooree_weekly_game_jam.gif">
|
||||||
|
<img src="/static/img/ent/chimchooree_weekly_game_jam.gif" alt="(gif: Elwell rides a broken-physics dog across the valley)" width="500" height="215.77">
|
||||||
|
</a><br>
|
||||||
|
The dog spawning physics was (and still is) really broken, so dogs go flying pretty frequently. It was reliable enough in this version to be used as a moving platform. <br>
|
||||||
|
<br><br>
|
||||||
|
<a target="_blank" href="/static/img/ent/SmallThingThatMakesThings_rainingdogs.png">
|
||||||
|
<img src="/static/img/ent/SmallThingThatMakesThings_rainingdogs.png" alt="(image: Elwell watches the rainfall of dogs from his window)" width="500" height="282">
|
||||||
|
</a><br>
|
||||||
|
Dogs rain down upon Elwell's animal shelter in the finished version. <br>
|
||||||
|
<br><br>
|
||||||
|
<a target="_blank" href="/static/img/ent/SmallThingThatMakesThings_rainingdogs.png">
|
||||||
|
<img src="/static/img/ent/SmallThingThatMakesThings_dialog.png" alt="(image: Dialog)" width="500" height="265.12">
|
||||||
|
</a><br>
|
||||||
|
Dialog between Elwell and his boss. <br>
|
||||||
|
</center>
|
||||||
|
<br>
|
||||||
|
<h2>conclusion</h2>
|
||||||
|
<br>
|
||||||
|
If you're looking for a jam to jump into, WeeklyGameJam is a cute one to try. You aren't too rushed, you get to watch people play your game, you get useful criticism, and the community is pretty chill. The themes are always inspiring, too.<img src="/static/img/emo/heart.gif" alt="<3"> <br>
|
||||||
|
<br>
|
||||||
|
<h2>credits</h2>
|
||||||
|
<ul>
|
||||||
|
<li>coding and art by chimchooree</li>
|
||||||
|
<li>Open Pixel Project (OPP) (animations & tiles, edited to fit my characters & palette) @ <a href="http://www.openpixelproject.com/">http://www.openpixelproject.com/</a></li>
|
||||||
|
<li>Music track "forest" by syncopika under CC-BY 3.0 @ <a href="https://opengameart.org/content/forest">https://opengameart.org/content/forest</a> & <a href="https://greenbearmusic.bandcamp.com/album/bgm-fun-vol-5">https://greenbearmusic.bandcamp.com/album/bgm-fun-vol-5</a></li>
|
||||||
|
<li>Bad ending image from Wikimedia, credit to Jon Sullivan @ <a href="https://tinyurl.com/y6oswx8v">https://tinyurl.com/y6oswx8v</a> (URL contains spoilers)</li>
|
||||||
|
<li>SimpleJPC-16 Palette by Adigun Polack @ <a href="https://lospec.com/palette-list/simplejpc-16">https://lospec.com/palette-list/simplejpc-16</a></li>
|
||||||
|
<li>pixel joy font by chimchooree @ <a href="https://fontstruct.com/fontstructions/show/1596262/pixel-joy">https://fontstruct.com/fontstructions/show/1596262/pixel-joy</a></li>
|
||||||
|
<li>Tools: <a href="https://godotengine.org/">Godot Engine 3.0.6</a>, <a href="https://graphicsgale.com/us/">GraphicsGale</a></li>
|
||||||
|
<li>Weekly Game Jam 85, "Offspring" theme @ <a href="https://itch.io/jam/weekly-game-jam-85">https://itch.io/jam/weekly-game-jam-85</a></li>
|
||||||
|
</ul>
|
||||||
|
<br>
|
@ -0,0 +1,73 @@
|
|||||||
|
<!--201001,201112-->
|
||||||
|
<h1>coroutines in godot engine </h1>
|
||||||
|
september 17, 2020<br>
|
||||||
|
#coroutines #godot #programming<br>
|
||||||
|
<br>
|
||||||
|
<b>Coroutines</b> are functions that, instead of running to completion, can yield until certain criteria are met. Godot Engine supports coroutines through <a href="https://docs.godotengine.org/en/stable/classes/class_@gdscript.html#class-gdscript-method-yield"><b>yield</b> ( Object object=null, String signal="")</a>, <a href="https://docs.godotengine.org/en/stable/classes/class_gdscriptfunctionstate.html#class-gdscriptfunctionstate-method-resume"><b>resume</b></a>, and the <a href="https://docs.godotengine.org/en/stable/classes/class_gdscriptfunctionstate.html"><b>GDScriptFunctionState</b></a> object.<br>
|
||||||
|
<br>
|
||||||
|
<h2>why use a coroutine? </h2>
|
||||||
|
<br>
|
||||||
|
Coroutines allow for scripted game scenarios that respond dynamically to the player and the changing game world. They let you bounce between functions, step-by-step, and respond to interruptions. This means functions can be automatically called at the completion of other functions, animations, player actions, in-game events, or timers. Add in interruptions and conditionals, and you have a tool for building a responsive game world. <br>
|
||||||
|
<br>
|
||||||
|
<h2>stoplight example </h2>
|
||||||
|
<br>
|
||||||
|
As a basic example of coroutines in Godot Engine, I made a stoplight. Follow along with my code on <a href="https://gitlab.com/chimchooree/stoplight">GitLab</a>. <br>
|
||||||
|
<br>
|
||||||
|
In my example, the light changes every few seconds, going from green, yellow, then finally red. The light changes immediately if the Walk Button is pressed. This project demonstrates methods that can wait, resume, and be affected through player action. <br>
|
||||||
|
<br>
|
||||||
|
<center>
|
||||||
|
<a target="_blank" href="/static/img/ent/stoplight_demonstration.gif">
|
||||||
|
<img src="/static/img/ent/stoplight_demonstration.gif" alt="(gif: demonstration)" width="500" height="281.67">
|
||||||
|
</a><br>
|
||||||
|
</center>
|
||||||
|
<br>
|
||||||
|
<h2>how does it work? </h2>
|
||||||
|
<br>
|
||||||
|
<h3>node hierarchy </h3>
|
||||||
|
<br><center>
|
||||||
|
<img src="/static/img/ent/stoplight_nodehierarchy.png" alt="(image: node hierarchy - Root is a node named Main. It's children are TextureRect BG, AnimatedSprite Stoplight, Sprite WalkButton, and a Label. Stoplight's child is a Sprite. WalkButton's child is a TextureButton.)"><br>
|
||||||
|
</center>
|
||||||
|
<br>
|
||||||
|
I have a TextureRect background, an AnimatedSprite stoplight, a Sprite walk button with a TextureButton, and a label for displaying a timer. Since this is a simple example, most of the code is attached to the root. It's better to have code closer to where it's being used and to watch your separation of concerns in real projects, though. <br>
|
||||||
|
<br>
|
||||||
|
<h3>animation</h3>
|
||||||
|
<br>
|
||||||
|
<center>
|
||||||
|
<img src="/static/img/ent/stoplight_animationframes.png" alt="(image: the AnimatedSprite Stoplight has 4 animations - default (which is no light), green, red, and yellow.)"><br>
|
||||||
|
</center><br>
|
||||||
|
The light is changed by setting its animation to one of these options. Each is one-frame - just the stoplight with the one or none of the lights colored in. <br>
|
||||||
|
<h3>the code </h3>
|
||||||
|
<br>
|
||||||
|
This project has two scripts: Main.gd, which is attached to the root node, and Label.gd, which is attached to the Label. <br>
|
||||||
|
<br>
|
||||||
|
<b>Main.gd</b> - code available on <a href="https://gitlab.com/chimchooree/stoplight/-/blob/master/Main.gd">GitLab</a><br>
|
||||||
|
<center>
|
||||||
|
<img src="/static/img/ent/stoplight_main.png" alt="(image: Main script.)"><br>
|
||||||
|
</center>
|
||||||
|
<br>
|
||||||
|
<b>Label.gd</b> - code available on <a href="https://gitlab.com/chimchooree/stoplight/-/blob/master/Label.gd">GitLab</a><br>
|
||||||
|
<center>
|
||||||
|
<img src="/static/img/ent/stoplight_label.png" alt="(image: Label script.)"><br>
|
||||||
|
</center>
|
||||||
|
<br>
|
||||||
|
<h3>how the code works </h3>
|
||||||
|
<br>
|
||||||
|
At <code>_ready()</code>, <code>wait()</code> is assigned to the GDScriptFunctionState <code>result</code> and is called for the first color, green. <code>_ready()</code> yields until the given function <code>wait()</code> is completed. <br>
|
||||||
|
<br>
|
||||||
|
The wait method yields for the given amount of seconds then sets the stoplight to the given color. <br>
|
||||||
|
<br>
|
||||||
|
At <code>wait()</code>'s completion, <code>_ready()</code> calls <code>wait()</code> for yellow, then red. Each is called one at a time, waiting for the color to complete before moving on. <br>
|
||||||
|
<br>
|
||||||
|
<h3>interrupting the stoplight </h3>
|
||||||
|
<br>
|
||||||
|
The Wait Button interrupts the wait times between colors. Before <code>_ready()</code> yields, it connects the <code>'pressed'</code> signal on the Wait Button. <br>
|
||||||
|
If the Wait Button is clicked during <code>wait()</code>'s yield, the GDScriptFunctionState <code>result</code> resumes immediately, ignoring <code>wait()</code>'s yield timer. This time, <code>result</code> has a string arg <code>'interrupted on green'</code>, so it will print the result, change the stoplight's color, then print <code>'done: green'</code>. The <code>wait</code> method is complete, so <code>_ready()</code> resumes and calls <code>wait()</code> for the next color. <br>
|
||||||
|
<br>
|
||||||
|
<h2>applications </h2>
|
||||||
|
<br>
|
||||||
|
The outcomes in this example can be swapped out with anything. I use coroutines in Blessfrey's skills to manage the flow of phases from activation, different phases of effects, cooldown, and interactions with any counters. I also use it in the basic weapon attack so the character continuously swings at the rate of his attack speed until he cancels, uses a skill, or moves. It could also be used for something like cars that stop and honk when the player walks in front of them then drive off once the path is clear. <br>
|
||||||
|
<br>
|
||||||
|
Coroutines enable lots of practical ways to improve the flow and interactivity of your game, so just keep experimenting. <br>
|
||||||
|
<br>
|
||||||
|
Last updated June 8, 2021 <br>
|
||||||
|
<br>
|
@ -0,0 +1,51 @@
|
|||||||
|
<!--210527,210402-->
|
||||||
|
<h1>common tropes from media </h1>
|
||||||
|
december 10, 2020<br>
|
||||||
|
#writing<br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
I like collecting common tropes from games I play. Maybe it can it root out cliches? Or inspire some game beats? Here's a few tropes spotted in multiple games ~ <br>
|
||||||
|
<br>
|
||||||
|
If you're worried about spoilers, games mentioned are Arcanum, The Cat Lady, Divine Divinity, Dreamfall: The Longest Journey, Fable, Fire Emblem, Guild Wars, Guild Wars 2, Half-Life, Jade Empire, Legend of Zelda: A Link to the Past, The Longest Journey, Mass Effect, Neverwinter Nights 2, Oblivion, Persona 4, Planescape: Torment, RuneScape 2 <br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<h2>opening scene + first level </h2>
|
||||||
|
<ul>
|
||||||
|
<li><b>dream sequence</b> - A Link to the Past </li>
|
||||||
|
<li><b>prison escape</b> - Oblivion, City of Heroes (villains) </li>
|
||||||
|
<li><b>sparring</b> - Jade Empire </li>
|
||||||
|
<li><b>starter village attacked, probably burned down</b> - Fable, Dragon Age: Origins </li>
|
||||||
|
<li><b>toll bandits blocking the bridge/road exiting the starter village</b> - Arcanum, Dragon Age: Origins </li>
|
||||||
|
<li><b>tutorial island</b> - RuneScape, Guild Wars: Factions + Nightfall </li>
|
||||||
|
<li><b>waking up</b> - Divine Divinity, The Longest Journey, Planescape: Torment </li>
|
||||||
|
</ul>
|
||||||
|
<br><br>
|
||||||
|
<h2>protagonist </h2>
|
||||||
|
<ul>
|
||||||
|
<li><b>amnesiac protagonist</b> - every game, especially MMOs. </li>
|
||||||
|
</ul>
|
||||||
|
<br><br>
|
||||||
|
<h2>levels, story points, fights </h2>
|
||||||
|
<ul>
|
||||||
|
<li><b>arena/tournament arc</b> - Fable, Oblivion </li>
|
||||||
|
<li><b>city building</b> - Neverwinter Nights 2's Crossroad Keep, RuneScape's Miscellania + Etceteria, Nightfall's Sunspear Sanctuary </li>
|
||||||
|
<li><b>courtroom hearing (usually resolved with the loser pulling out the 'trial by combat' card)</b> - Guild Wars: Nightfall, Guild Wars 2, Neverwinter Nights 2, Mass Effect </li>
|
||||||
|
<li><b>elevator fight</b> - Half-Life, Metroid </li>
|
||||||
|
<li><b>fight yourself</b> - Guild Wars's Augury Rock, Mask of the Betrayer, Persona 4
|
||||||
|
<li><b>final boss is all previous bosses in one room</b> - Fire Emblem, Nightfall </li>
|
||||||
|
<li><b>infiltrate a fancy party</b> - Guild Wars 2's A Society Function, Mass Effect Kasumi DLC
|
||||||
|
<li><b>maze level</b> - Mask of the Betrayer's Skein, Planescape: Torment </li>
|
||||||
|
<li><b>play as a side character</b> - The Cat Lady's cat, Mass Effect's Joker </li>
|
||||||
|
<li><b>raise someone </b> - the original Kingmaker concept for Oblivion, The Witcher 2 </li>
|
||||||
|
<li><b>solo boss battle</b> - Guild Wars Augury Rock, Neverwinter Nights 2's trial </li>
|
||||||
|
<li><b>trapped for a level, especially without your stuff</b> Divine Divinity's Castle + Dungeon, Mask of the Betrayer's Skein</li>
|
||||||
|
</ul>
|
||||||
|
<br><br>
|
||||||
|
<h2>side content </h2>
|
||||||
|
<ul>
|
||||||
|
<li><b>minigame tournament</b> - The Witcher's Dice Poker, Eye of the North's Polymock </li>
|
||||||
|
<li><b>really long fetch quest</b> - Dreamfall's mulled wine, Planescape Torment's Moridor's Box </li>
|
||||||
|
</ul>
|
||||||
|
<br>
|
||||||
|
last updated: 2/21/2021
|
||||||
|
<br>
|
@ -0,0 +1,43 @@
|
|||||||
|
<!--200806,210527-->
|
||||||
|
<h1>blessfrey graphic updates + mockups </h1>
|
||||||
|
december 24, 2020<br>
|
||||||
|
#mockups #screenshots<br>
|
||||||
|
<br>
|
||||||
|
I iterate over the graphics periodically, so I can practice without worrying about polish. Here's some screenshots of different styles I've tried. (Though April 23, 2019's is actually a mockup, that style did run in-engine for a few weeks.)<br>
|
||||||
|
<br>
|
||||||
|
<center>
|
||||||
|
<a target="_blank" href="/static/img/ent/screenshot_August152018.jpeg">
|
||||||
|
<img src="/static/img/ent/screenshot_August152018.jpeg" alt="(image: Cassia and Bad Cat on the pink carpet tilemap)" width="500" height="313">
|
||||||
|
</a><br>
|
||||||
|
August 15, 2018 - Early experimenting with Godot Engine. Collision was just added for sprites and walls. The buttons to the right are for switching between characters (who each have different skillbars). <br>
|
||||||
|
<br><br>
|
||||||
|
<a target="_blank" href="/static/img/ent/screenshot_January132019.jpeg">
|
||||||
|
<img src="/static/img/ent/screenshot_January132019.jpeg" alt="(image: Angel in a periwinkle room full of Bad Cats)" width="500" height="330.72">
|
||||||
|
</a><br>
|
||||||
|
January 13, 2019 - Videogame perspective is so different from perspective in illustration. Scale of characters vs environment is another quirk of games I had 0 experience with. I was vaguely going for an old Western RPG style with tall, somewhat realistic sprites with non-distinct faces. Something like Divine Divinity. <br>
|
||||||
|
<br><br>
|
||||||
|
<a target="_blank" href="/static/img/ent/screenshot_April232019.png">
|
||||||
|
<img src="/static/img/ent/screenshot_April232019.png" alt="(image: Angel blasting a neighborhood coyote with fire)" width="500" height="375">
|
||||||
|
</a><br>
|
||||||
|
April 23, 2019 - This is a mockup, but the game did look like this for a while. The fireball projectile didn't come until later, though. Here, I was trying to get a little more of a top-down view but not really. Instead of cats, Angel's fighting with a coyote. The government stopped removing coyotes from my old neighborhood, so they killed all the neighborhood cats except one and I saw him running away from a big coyote during a storm, so maybe he's gone now, too. It's just not right. <br>
|
||||||
|
<br><br>
|
||||||
|
<a target="_blank" href="/static/img/ent/screenshot_May252019.png">
|
||||||
|
<img src="/static/img/ent/screenshot_May252019.png" alt="(image: Angel and Chloe in front of a slanted house)" width="500" height="375.18">
|
||||||
|
</a><br>
|
||||||
|
May 25, 2019 - The slanted edition was so annoying. It's not isometric, it's just at an obscure angle because I drew these assets more for fun than to actually be practical. I do reuse the tree + bushes a lot, though. I also tried a more chibi sprite because they are soo common, might as well try it out. <br>
|
||||||
|
<br><br>
|
||||||
|
<a target="_blank" href="/static/img/ent/screenshot_June292019.png">
|
||||||
|
<img src="/static/img/ent/screenshot_June292019.png" alt="(image: Lots of Angels and other characters at a shopping center)" width="500" height="278.66">
|
||||||
|
</a><br>
|
||||||
|
June 29, 2019 - Trying a shopping center level now. It's reeally spaced apart. It's inspired by a real shopping center;; <br>
|
||||||
|
<br><br>
|
||||||
|
<a target="_blank" href="/static/img/ent/screenshot_July252020.png">
|
||||||
|
<img src="/static/img/ent/screenshot_July252020.png" alt="(image: Angel and some slimes in a cavern)" width="500" height="281.04">
|
||||||
|
</a><br>
|
||||||
|
July 25, 2020 - There's some missing in this gap, so I'll add in more pics if I find any. This is the first version of a cavern level for the blessfrey demo. It's inspired by local caves. <br>
|
||||||
|
<br><br>
|
||||||
|
</center>
|
||||||
|
<h2>you're up to date.</h2>
|
||||||
|
<br>
|
||||||
|
Hope you enjoyed seeing the different art I've used for blessfrey over the years. Even if I never really polish anything, it's nice to iterate to get a sense of game art and blessfrey's personal style. Hopefully it ends up looking okay okay when I do start polishing. But until then, in the words of YandereDev, "All art is placeholder."<br>
|
||||||
|
<br>
|
@ -0,0 +1,64 @@
|
|||||||
|
<!--210107,201112-->
|
||||||
|
<h1>refactoring characters: black box </h1>
|
||||||
|
february 18, 2021<br>
|
||||||
|
#character #refactor <br>
|
||||||
|
<br>
|
||||||
|
The character script was one of blessfrey's first scripts. Since it's never seen a serious refactor, it has retained its clueless beginner style to this day. Every single line of code related to characters was buried somewhere in this unmanageable monolith. The time has finally come for refactoring. <br>
|
||||||
|
<br>
|
||||||
|
The two biggest problems with my character script were the lack of structure + encapsulation. <br>
|
||||||
|
<br>
|
||||||
|
<h2>adding structure </h2><br>
|
||||||
|
An entire game can fit in one mega script, but an object-oriented approach is more manageable. It's good to keep your code close to where it is implemented. <br>
|
||||||
|
<br>
|
||||||
|
First, I expanded the character's <b>node hierarchy</b>. I combed through the code, forming abstract categories for each section. These categories became my subnodes. (Nodes are objects in Godot, by the way.) If a subnode was too complex, it was broken into another level of subnodes. Then, I cut+pasted as much code as I could into these subscripts. My tree now looks like this, when before, it just had the AI, Body, and UI nodes: <br>
|
||||||
|
<br>
|
||||||
|
<a target="_blank" href="/static/img/ent/characternodehierarchy.png">
|
||||||
|
<img src="/static/img/ent/characternodehierarchy.png" alt="(image: character's node tree. Character's children are AI, Body, DropTable, Actions, UI, Inventory, Skillbar, Serialization, InspectMenu, Equipment, Properties, Effects, Events, SkillLibrary, and Class. Under Properties are Health, Energy, and XP. Under Skillbar is Skillslot. Under SkillLibrary is Skill. Under Class is FirstClass and SecondClass. Under AI is State. Under Body is ClickableArea, RangeBubble, Camera, Sprite, Hitbox, Feet, Effects, Animation, Light, and Debug. Under the Body's Sprite is Highlight.)" width="500" height="195">
|
||||||
|
</a><br>
|
||||||
|
<br>
|
||||||
|
<h2>adding encapsulation </h2><br>
|
||||||
|
Within the monolith, every part of the character had direct access to every other part. A major step towards getting everything running again was adding <b>encapsulation</b>, or grouping related logic + data into objects then restricting direct access of its internal components from other objects. I did this through designating entrypoints as the only way of performing actions or accessing data. These entrypoints are the character's verbs and setters + getters. <br>
|
||||||
|
<br>
|
||||||
|
<h3>verbs </h3>
|
||||||
|
<b>Verbs</b> are what the object does. Some of them are obvious, like "use skill," "be damaged," and "pick up item," but some of them are more abstract, like "calculate level." <br>
|
||||||
|
<br>
|
||||||
|
The character script should act as a central hub, executing verbs by contacting subnodes for the actual logic and passing back the output. These subnodes should act as <b>black boxes</b>, so the character script only worries about input + output, not how the request is performed. <br>
|
||||||
|
<br>
|
||||||
|
<center><img src="/static/img/ent/blackbox.png" alt="(image: illustration of the concept of a black box: input goes in, output comes out, it doesn't matter what happens inside.)"></center> <br>
|
||||||
|
<br>
|
||||||
|
Before, I didn't apply this concept to the character at all. Outside objects would travel all over the tree to pick out specific methods. That resulted in code like <code>body.get_parent().UI.skillbar.healthbar.label.set_value("100")</code> instead of something like <code>character.set_health(100)</code>. As I modified systems, all the outside references made it difficult to remove old versions. Since everything didn't necessarily use the entrypoint, there was a lot of redundant code + unexpected behavior. <br>
|
||||||
|
<br>
|
||||||
|
<center><a target="_blank" href="/static/img/ent/characterrefactor_oldvsnewcode.png">
|
||||||
|
<img src="/static/img/ent/characterrefactor_oldvsnewcode.png" alt="(image: a code excerpt's before and after, can also be read @ https://pastebin.com/xhJqVVKe.)" width="500" height="122.38">
|
||||||
|
</a></center><br>
|
||||||
|
<br>
|
||||||
|
<h3>setters + getters </h3>
|
||||||
|
Another closely related concept is <b>setters + getters</b>. If you tack a <code>setget</code> followed by method names onto a variable, those methods will be called whenever you access that variable. The first method is the setter and is conventionally prefixed with "set_", while the second is the getter. If you don't want a setter, don't write anything before the comma: <code>setget , get_whatever</code>. If you don't want a getter, don't even add the comma: <code>setget set_whatever</code>. <br>
|
||||||
|
<br>
|
||||||
|
<center><img src="/static/img/ent/setters+getters_dollars.png" alt="(image: code example of setters and getters, can also be read @ https://pastebin.com/y6AJVBMu.)"></center> <br>
|
||||||
|
<br>
|
||||||
|
Setters + getters are related because they increase consistency. If something needs to happen every time a variable is changed, the setter is a good entrypoint. If a variable needs to be obtained in a specific way, that process can be taken care of inside a <code>get_thing()</code>. This way, your variable-specific code is encapsulated, and you are no longer encouraged to manipulate them outside of their little box. <br>
|
||||||
|
<br>
|
||||||
|
Unsurprisingly, using verbs, black boxes, and setters + getters fixed a lot of long-running bugs. <br>
|
||||||
|
<br>
|
||||||
|
<h2>controlling flow </h2><br>
|
||||||
|
Another problem popped up concerning when the tree's nodes initialize. Now that everything isn't in the same script, everything isn't ready at the same time. To see the initialization order, I made a small project. Each node prints its name when at ready, and, as you see, it works from lowest level to highest level, top node to bottom node.<br>
|
||||||
|
<br>
|
||||||
|
<center><img src="/static/img/ent/nodetree_ready.png" alt="(image: The order goes from top-to-bottom node, lowest level to highest level. The tree, code, and output can be read @ https://pastebin.com/Z4VG9ey5.)"></center> <br>
|
||||||
|
<br>
|
||||||
|
To have more control over the flow of my character, dependency-sensitive nodes rely more on a setup method than the _ready method. Also, characters who inherit from the base script use a super _setup method called from the setup method for their own specialized needs. (Super methods are discussed in <a href="https://www.blessfrey.me/diary/entries/201112">tidying up my skill phases</a>.) This way, I can ensure a node is ready and has all its properties set at the moment it's needed. <br>
|
||||||
|
<br>
|
||||||
|
<center><img src="/static/img/ent/setup_base.png" alt="(image: the base character's _ready is broken into more methods. Supermethod are used for calling specialized code at a specific time.)"></center> <br>
|
||||||
|
<center><img src="/static/img/ent/setup_player.png" alt="(image: the player no longer uses _ready. It uses a supermethod called off the base character's setup method.)"></center> <br>
|
||||||
|
<br>
|
||||||
|
<h2>happy script </h2><br>
|
||||||
|
The refactor paid off immediately. So many "known issues" were immediately resolved. The character scripts are way easier to maintain now. The main script's halved in size. And I feel like I've improved a lot since I first started working on blessfrey. I wanted to go through the rest of the program and apply these concepts throughout, but it turned out I was already using them. It was just this ancient mess of a script that was left behind. <br>
|
||||||
|
<br>
|
||||||
|
I'll finish with a before + after of the base character script. <br>
|
||||||
|
<br>
|
||||||
|
<center><img src="/static/img/ent/characterrefactor_before+after.png" alt="(image: 604 lines of spaghetti code vs 374 lines of verbs + setup code)"></center><br>
|
||||||
|
<br>
|
||||||
|
Enjoy your day! <br>
|
||||||
|
<br>
|
||||||
|
last updated 2/21/21<br>
|
||||||
|
<br>
|
@ -0,0 +1,141 @@
|
|||||||
|
<!--210218,210107-->
|
||||||
|
<h1>python writes my skills for me</h1>
|
||||||
|
march 4, 2021<br>
|
||||||
|
#godotengine #json #python <br>
|
||||||
|
<br>
|
||||||
|
Similar to Magic: The Gathering cards, the functionality of my skills is composed of keywords. For instance, a skill with the 'damage' keyword + 'bleeding' keyword will inflict pure damage + a bleeding status effect. <br>
|
||||||
|
<br>
|
||||||
|
Since skills derive their effects from existing keywords, each individual skill has very little to no unique code. Repetitive, formulaic tasks are better left to scripts, so I wrote a skillmaker to write skills for me. <br>
|
||||||
|
<br>
|
||||||
|
<h2>what do blessfrey skills look like? </h2><br>
|
||||||
|
<br>
|
||||||
|
(Click on the shrunk images for an expanded view.) <br>
|
||||||
|
<br>
|
||||||
|
They are Godot scenes composed of a single <a href="https://docs.godotengine.org/en/stable/classes/class_node.html">node</a> with an attached script. <br>
|
||||||
|
<br>
|
||||||
|
<center><img src="/static/img/ent/skillscenetree.png" alt="(image: That's it - the tree's just one node with a script.)"></center> <br>
|
||||||
|
<br>
|
||||||
|
The .TSCN and .GD files look like this in the engine. <br>
|
||||||
|
<br>
|
||||||
|
<center><a target="_blank" href="/static/img/ent/skillsceneshot.png">
|
||||||
|
<img src="/static/img/ent/skillsceneshot.png" alt="(image: Screenshot from the engine - the node tree, attached script, properties, and groups are all neatly displayed.)" width="500" height="281">
|
||||||
|
</a></center> <br>
|
||||||
|
<br>
|
||||||
|
But Godot files are just simple text, so you can open them in any text editor. Really, you could write the bulk of a Godot game in Notepad, if you wanted to. That's perfect for me! <br>
|
||||||
|
<br>
|
||||||
|
The same files look like this in xed (similar program to Notepad): <br>
|
||||||
|
<br>
|
||||||
|
<center><a target="_blank" href="/static/img/ent/skillscene_xed.png">
|
||||||
|
<img src="/static/img/ent/skillscene_xed.png" alt="(image: The same files displayed in xed.)" width="500" height="275">
|
||||||
|
</a></center> <br>
|
||||||
|
<br>
|
||||||
|
The .GD file is identical to what gets displayed in Godot Engine, but the .TSCN is a little cryptic. If I learn how the syntax, I can make a script to write it for me. I don't understand all of it, since I only deciphered what I need for making a skill. <br>
|
||||||
|
<br>
|
||||||
|
<h2>examining the skill scene file </h2><br>
|
||||||
|
<br>
|
||||||
|
<center><a target="_blank" href="/static/img/ent/skillscene_cipher.png">
|
||||||
|
<img src="/static/img/ent/skillscene_cipher.png" alt="(image: The .TSCN file broken into color-coded sections.)" width="500" height="552">
|
||||||
|
</a></center> <br>
|
||||||
|
(You can also see the text @ <a href="https://pastebin.com/1mzmDFM5">Pastebin</a>.) <br>
|
||||||
|
<br>
|
||||||
|
Since I designed Blessfrey skills to have a simple, consistent structure, the .TSCN for any two skills will be very similar. <br>
|
||||||
|
After experimenting in + out of Godot Engine, I've found that that first line is always the same. <br>
|
||||||
|
<br>
|
||||||
|
<h3>red + yellow resources </h3><br>
|
||||||
|
The red + yellow sections are the scene's resources. This includes any .GD files attached to nodes in the scene tree and any resources (textures, fonts, etc) you load as properties. Those are the properties that look like my icon: <br>
|
||||||
|
<br>
|
||||||
|
<center><img src="/static/img/ent/skillscene_resproperty.png" alt="(image: This is what resources look like after being loaded into the properties.)"></center> <br>
|
||||||
|
<br>
|
||||||
|
The script + the resources look so similar because they are both treated as properties of the node. The formula is... <br>
|
||||||
|
<br>
|
||||||
|
<ul>
|
||||||
|
<li><code>[ext_resource path="</code> </li>
|
||||||
|
<li>obviously followed by the path of the resource. If resources are kept in a predictable place, it's easier on the script. </li>
|
||||||
|
<li><code>" type=" </code> </li>
|
||||||
|
<li>whatever the resource type is (examples include Script, Texture, DynamicFontData) </li>
|
||||||
|
<li><code>" id=</code> </li>
|
||||||
|
<li>a unique number (obviously used for referencing) </li>
|
||||||
|
</ul>
|
||||||
|
<br>
|
||||||
|
<h3>green groups</h3><br>
|
||||||
|
Green marks the groups of each node in the scene. You can see the groups are just an array of strings. <br>
|
||||||
|
<br>
|
||||||
|
Broken into pieces, that looks like <br>
|
||||||
|
<br>
|
||||||
|
<ul>
|
||||||
|
<li><code>[node </code> </li>
|
||||||
|
<li><code>name="CuttheGuy"</code> - the name of the node (node.name) goes between the quotation marks </li>
|
||||||
|
<li><code>type="Node"</code> - the type of the node, so something like Node, Node2D, or Label </li>
|
||||||
|
<li><code>groups=[</code> </li>
|
||||||
|
<li><code>"group_name", </code> - each group name is wrapped in quotation marks and separated by commas + line-breaks. </li>
|
||||||
|
<li><code>]]</code> - close all brackets </li>
|
||||||
|
</ul>
|
||||||
|
<br>
|
||||||
|
Two interesting things about the group_name property. One, the engine adds a comma at the end of every line, even to last or only group of the node. Two, if your scene node has no groups, this property will be omitted from the node. If CuttheGuy belonged to no groups, it would just look like <code>[node name="CuttheGuy" type="Node"]</code>. <br>
|
||||||
|
<br>
|
||||||
|
<h3>blue properties</h3><br>
|
||||||
|
The properties are inside blue. Since scripts are treated the same as any other resource property, the attached script is referenced in this section. <br>
|
||||||
|
<br>
|
||||||
|
Resources look like <code>icon = ExtResource( 2 )</code>.
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<code>Icon</code> is my variable. I declared it in a script that the skill inherits from as <code>export(Texture) var icon = preload('res://res/art/skillIcons/EmptySkillSlot.png') setget , get_icon</code>. The <code>script</code> variable was declared by the engine and isn't in my .GD files. <br>
|
||||||
|
<br>
|
||||||
|
The <code>2</code> is an id from the yellow section. It must match the resource's number from earlier. <br>
|
||||||
|
<br>
|
||||||
|
If I store all my skill icons in the skillIcons directory and give them the proper name, they'll be found when they're needed, no manually loading necessary. This is a test skill with a generic skill icon, but it would be named something like <code>CuttheGuy.png</code>. <br>
|
||||||
|
<br>
|
||||||
|
Other properties are more straight-forward. They are the same <a href="https://docs.godotengine.org/en/stable/getting_started/scripting/gdscript/gdscript_exports.html">exported member variables</a> declared in the node's attached .GD script, followed by an equal sign and the value. <br>
|
||||||
|
</ul>
|
||||||
|
<br>
|
||||||
|
<h3>overall </h3><br>
|
||||||
|
They aren't so confusing when read as text files. The Godot team must have planned them to be human-readable. :) <br>
|
||||||
|
<br>
|
||||||
|
<h2>altar of spellmaking </h2><br>
|
||||||
|
So as you modify the groups and properties of a node, the engine is actually just formatting your input into text files. Since I have a functional understanding of how to manually format data, I can write a skill-formatting script to do it for me. <br>
|
||||||
|
<br>
|
||||||
|
The tool is named "Altar of Spellmaking" as an Oblivion reference and uses JSON + Python3. <a href="https://docs.godotengine.org/en/stable/classes/class_json.html">Godot can interpret JSON files on its own</a>, so maybe someday the game will automatically generate skills from on the JSON file at load time. For now, though, I see a frivolous excuse to use Python, so the interpreter is going to be in Python. Its input shall be a JSON file, and its output shall be matching .TSCN + .GD files. <br>
|
||||||
|
<br>
|
||||||
|
<h3>writing a skill with JSON </h3><br>
|
||||||
|
JSON isn't a Turing complete language, just a notation. It looks like a dictionary and uses attribute-value relationships. It's good at storing nested groups of information and can be easily interpretted by other languages. Since my skills can be described as a collection of attributes + keyword effects, they can wrestled into the JSON format. <br>
|
||||||
|
<br>
|
||||||
|
<center><img src="/static/img/ent/skill_json.png" alt="(image: The skill as it appears in the JSON file.)"></center>
|
||||||
|
(You can also see the text @ <a href="https://pastebin.com/aYvSr3NQ">Pastebin</a>.) <br>
|
||||||
|
<br>
|
||||||
|
All the repetitive, obvious, or derivative aspects of the skill do not need to appear in the JSON, since Python will be compensating. <br>
|
||||||
|
<br>
|
||||||
|
I identify the skill by its name in lower-case + underscores just for my sake. The program doesn't care what the skill's id is or if it has an id at all. <br>
|
||||||
|
<br>
|
||||||
|
Inside, I have some basic attributes like the id and associated class + stat. The tags will become some of the node's groups. The keywords take place in the skill's phases, which are lists of dictionaries. To build the skill phase, Python will interate over each item in the dictionary and use the internal data to supply the keyword's necessary input or conditions. <br>
|
||||||
|
<br>
|
||||||
|
Conditional effects look like the image below. <br>
|
||||||
|
<br>
|
||||||
|
<center><a target="_blank" href="/static/img/ent/skill_json_cond.png">
|
||||||
|
<img src="/static/img/ent/skill_json_cond.png" alt="(image: A skill entry with a conditional effect.)" width="500" height="255.24">
|
||||||
|
</a> <br></center>
|
||||||
|
(You can also see the text @ <a href="https://pastebin.com/khSwk8js">Pastebin</a>.) <br>
|
||||||
|
<br>
|
||||||
|
The <code>cond</code> consists of the <code>code</code> (literal GDscript code) and <code>desc</code> (an English translation of that code). It's not perfect, but it's quicker for me to just write the code and write the skill description than try to have Python translate. This does mean my code has to be perfect, and I have pressure to adhere to the standard language of a skill description. That's a problem, but it is much smaller than the problem of not using JSON at all. <br>
|
||||||
|
<br>
|
||||||
|
Eventually, I would prefer the desc to be an id from the translation spreadsheet instead of hard-coded text. (I discussed translation in <a href="https://www.blessfrey.me/diary/entries/201029">blessfrey in japanese</a>.) <br>
|
||||||
|
<br>
|
||||||
|
<h3>writing a Godot scene with Python3 </h3><br>
|
||||||
|
Using Python to format JSON values into a Godot scene + script is kind of ridiculous, so I have 168 lines of code and half of them start with <code>f.write</code>. Now that it's written, though, it's easy to maintain. Since the overall structure doesn't change, all I have to do is tweak a line here or there to reflect refactors. I'll include a picture of the main method, so you can get a sense of the flow of the program: <br>
|
||||||
|
<br>
|
||||||
|
<center><a target="_blank" href="/static/img/ent/skill_python.png">
|
||||||
|
<img src="/static/img/ent/skill_python.png" alt="(image: The main method in the Python script.)" width="500" height="272.73">
|
||||||
|
</a> <br></center>
|
||||||
|
(You can also see the text @ <a href="https://pastebin.com/2kV2LGCn">Pastebin</a>.) <br>
|
||||||
|
<br>
|
||||||
|
The output is a nested directory of all the skills, so I can just copy + paste the <code>skills</code> folder over the one in the engine. And it works! The first skill image's code looks machine-generated because it was. <br>
|
||||||
|
<br>
|
||||||
|
<center><a target="_blank" href="/static/img/ent/skill_dir.png">
|
||||||
|
<img src="/static/img/ent/skill_dir.png" alt="(image: skills>Common>Brawler>DirtyFighting>CuttheGuy, same directories as the engine.)" width="500" height="128.11">
|
||||||
|
</a> <br></center>
|
||||||
|
<br>
|
||||||
|
<h2>is it better than just making skills in the engine? </h2>
|
||||||
|
<br>
|
||||||
|
If I only had ten or eleven skills, then probably not. I'm planning on having around 40 skills per class, though, and I'm only one person. Since the skills are very formulaic, I'd rather have Python do all the copying + pasting for me while I worry about what makes my skills unique! <br>
|
||||||
|
<br>
|
||||||
|
last updated March 6, 2021 <br>
|
||||||
|
<br>
|
@ -0,0 +1,161 @@
|
|||||||
|
<!--210121,210304-->
|
||||||
|
<h1>generating an RSS feed with python bottle</h1>
|
||||||
|
march 18, 2021<br>
|
||||||
|
#bottle #rss #webdev <br>
|
||||||
|
<br>
|
||||||
|
After a few months of quietly running my blog as practice, I want to start sharing my articles with other people. I looked over my favorite gamedev communities and saw that <a href="https://gamedev.net/">GameDev.net</a> apparently allows you to syndicate a blog through RSS. I never thought about making an RSS feed, so why not? <br>
|
||||||
|
<br>
|
||||||
|
<h2>what is RSS? </h2><br>
|
||||||
|
Before the massive centralized content platforms came into the mainstream, the internet was more like a constellation of individual websites. In lieue of algorithm-driven feeds and push notifications from major social media, RSS was designed to bring content from scattered websites into one place. <br>
|
||||||
|
<br>
|
||||||
|
RSS and its predecessors have been around since the 90s. RSS 2.0 (what blessfrey.me uses) was published in 2002. Even through it's old and falling in popularity, it's still used by some large aggregators today like <a href="https://publishercenter.google.com/publications#p:id=pfehome">Google News</a> and <a href="https://podcasters.spotify.com/submit">Spotify</a>. <br>
|
||||||
|
<br>
|
||||||
|
Here's a few examples from around the internet, a mix of large + small news websites and forums: <br>
|
||||||
|
<ul>
|
||||||
|
<li><a href="https://www.theguardian.com/world/rss">The Guardian</a> </li>
|
||||||
|
<li><a href="https://www.gamasutra.com/blogs/rss/">Gamasutra</a> </li>
|
||||||
|
<li><a href="https://www.polygon.com/rss/index.xml">Polygon</a> </li>
|
||||||
|
<li><a href="https://store.steampowered.com/feeds/news.xml">Steam</a> </li>
|
||||||
|
<li><a href="https://www.gamedev.net/tutorials/rss/">GameDev.net</a> </li>
|
||||||
|
<li><a href="https://www.nasa.gov/rss/dyn/chandra_images.rss">NASA's Chandra Mission</a> </li>
|
||||||
|
<li><a href="https://www.temptalia.com/feed/">Temptalia</a> </li>
|
||||||
|
</ul>
|
||||||
|
<br>
|
||||||
|
<h2>what goes into an RSS feed? </h2><br>
|
||||||
|
RSS files themselves are written in XML. They should contain the latest 10-15 entries along with things like their title, link, summary, date, and author. <br>
|
||||||
|
<br>
|
||||||
|
Blogging platforms like <a href="https://wordpress.org/support/article/wordpress-feeds/">WordPress</a> already take care of the RSS feed, but there's no shortage of third-party RSS creators on the internet. Since I have already written code to display + summarize my articles on the <a href="https://www.blessfrey.me/diary">diary</a> page, the 'latest' box in the diary's sidebar, and the 'news' box on the <a href="https://www.blessfrey.me/">index</a> page, I figure I can format the data one more time into an XML file. <br>
|
||||||
|
<br>
|
||||||
|
Here's truncated version of blessfrey.me's feed as an example (also available on <a href="https://pastebin.com/bbrVp58E">Pastebin</a>: <br>
|
||||||
|
<br>
|
||||||
|
<code><?xml version="1.0" encoding="utf-8"?></code><br>
|
||||||
|
<code><rss version="2.0"></code><br>
|
||||||
|
<code><channel></code><br>
|
||||||
|
<code><title>blessfrey.me</title></code><br>
|
||||||
|
<code><link>https://www.blessfrey.me/</link></code><br>
|
||||||
|
<code><description>chimchooree's dev space</description></code><br>
|
||||||
|
<code><language>en-us</language></code><br>
|
||||||
|
<code><webMaster>chimchooree@mail.com (chimchooree)</webMaster></code><br>
|
||||||
|
<code><item></code><br>
|
||||||
|
<code><title>making an rss feed</title></code><br>
|
||||||
|
<code><link>https://www.blessfrey.me/diary/entries//diary/entries/210318</link></code><br>
|
||||||
|
<code><description>After a few months of quietly running my blog as practice, I want to start sharing my articles with ... </description></code><br>
|
||||||
|
<code><pubDate>Thu, 18 Mar 2021 8:05 CST</pubDate></code><br>
|
||||||
|
<code><guid>https://www.blessfrey.me/diary/entries//diary/entries/210318</guid></code><br>
|
||||||
|
<code></item></code><br>
|
||||||
|
<code></channel></code><br>
|
||||||
|
<code></rss></code><br>
|
||||||
|
<br>
|
||||||
|
I'll explain each tag, but they are also described on the RSS Advisory Board's <a href="https://www.rssboard.org/rss-profile">Best Practices Profile</a>. There are more tags, too, so research documentation + examples to see what suits your website. <br>
|
||||||
|
<br>
|
||||||
|
<h3>XML declaration</h3><br>
|
||||||
|
Identifies the document as XML and defines the version + character encoding. It's required and must be the first line. <br>
|
||||||
|
<br>
|
||||||
|
<h3>RSS</h3><br>
|
||||||
|
The top-level element that defines version number. It's required. <br>
|
||||||
|
<br>
|
||||||
|
<h3>channel</h3><br>
|
||||||
|
Nested within the RSS tags and describes the RSS feed. It's required. <br>
|
||||||
|
<br>
|
||||||
|
There's some tags nested within the channel. <code>Title</code>, <code>Link</code>, and <code>Description</code> are required. <br>
|
||||||
|
<br>
|
||||||
|
<ul>
|
||||||
|
<li><b>title</b> - defines the title. Mine is just my website name, but large websites may have multiple feeds. It's required. </li>
|
||||||
|
<li><b>link</b> - defines the link to the channel. So, either your website or a specific area of your website. It's required. </li>
|
||||||
|
<li><b>description</b> - describe the channel in a few words. I used my website's tagline. It's required. </li>
|
||||||
|
<li><b>language</b> - defines the language, using a <a href="https://www.rssboard.org/rss-language-codes">RSS language code</a>. It's optional. </li>
|
||||||
|
<li><b>webMaster</b> - provide the contact email + name for technical issues regarding the feed. It should look something like <code>example@example.com (Name McName). It's optional. </code> </li>
|
||||||
|
</ul>
|
||||||
|
<br>
|
||||||
|
<h3>item</h3><br>
|
||||||
|
Nested within the channel. Each article will be defined with the item. <br>
|
||||||
|
<br>
|
||||||
|
There's some tags nested within the item. <code>Title</code>, <code>Link</code>, and <code>Description</code> are required. <br>
|
||||||
|
<br>
|
||||||
|
<ul>
|
||||||
|
<li><b>title</b> - defines the title of the article. It's required. </li>
|
||||||
|
<li><b>link</b> - defines the link to the article. It's required. </li>
|
||||||
|
<li><b>description</b> - summarize the article in one or two sentences. It's required. </li>
|
||||||
|
<li><b>pubDate</b> - indicates the date and time of publication, conforming to the <a href="https://www.rssboard.org/rss-profile#data-types-datetime">RFC 822 Date and Time Specification</a>. Follow a pattern like Mon, 15 Oct 2007 14:10:00 GMT. </li>
|
||||||
|
<li><b>guid</b> - A unique identifying string, which helps aggregators detect duplicates. Aggregators may ignore this field or use it in combination with the title and link values. </li>
|
||||||
|
</ul>
|
||||||
|
<br>
|
||||||
|
<h2>how to make an RSS feed </h2><br>
|
||||||
|
I'm generating the RSS every time I update my website. That way, it always exists on the server and can be served as a static file. If I used a template, the RSS file would not exist unless it was accessed. I'm not sure if that would be an issue and haven't experimented. <br>
|
||||||
|
<br>
|
||||||
|
Since Bottle is just Python, I can generate the file similar to how I format my articles into diary snippets, pages, and headlines. <br>
|
||||||
|
<br>
|
||||||
|
I'll share the main code, but the full code can be viewed on <a href="https://pastebin.com/E0PvNtbp">Pastebin</a>. <br>
|
||||||
|
<br>
|
||||||
|
<h3>code example </h3><br>
|
||||||
|
def make_rss(): <br>
|
||||||
|
loc = 'diary/entries/' <br>
|
||||||
|
info = {'items': list_items(gather_and_sort(loc)[0:15])} <br>
|
||||||
|
<br>
|
||||||
|
# Return list of items <br>
|
||||||
|
def list_items(articles): <br>
|
||||||
|
f_name = "static/xml/blessfrey.xml" # the RSS file <br>
|
||||||
|
loc2 = 'https://www.blessfrey.me/' <br>
|
||||||
|
loc = 'diary/entries/' <br>
|
||||||
|
loc3 = loc2 + loc <br>
|
||||||
|
result = [] <br>
|
||||||
|
<br>
|
||||||
|
for article in articles: <br>
|
||||||
|
path = loc + article <br>
|
||||||
|
text = [] <br>
|
||||||
|
a = [] <br>
|
||||||
|
length = 0 <br>
|
||||||
|
text = article2list(article, loc) <br>
|
||||||
|
a.append(find_title(text)) <br>
|
||||||
|
a.append(find_url(path)) <br>
|
||||||
|
a.append(clean_tags(prepare_rss_summary(text, path))) <br>
|
||||||
|
a.append(find_timestamp(text)) <br>
|
||||||
|
result.append(a) <br>
|
||||||
|
<br>
|
||||||
|
clear_file(f_name) <br>
|
||||||
|
f = open(f_name, 'w') <br>
|
||||||
|
f.write("<?xml version=\"1.0\" encoding=\"utf-8\"?>" + '\n') <br>
|
||||||
|
f.write("<rss version=\"2.0\">" + '\n') <br>
|
||||||
|
f.write("<channel>" + '\n') <br>
|
||||||
|
f.write("<title>blessfrey.me</title>" + '\n') <br>
|
||||||
|
f.write("<link>https://www.blessfrey.me/</link>" + '\n') <br>
|
||||||
|
f.write("<description>chimchooree's dev space</description>" + '\n') <br>
|
||||||
|
f.write("<language>en-us</language>" + '\n') <br>
|
||||||
|
f.write("<webMaster>chimchooree@mail.com (chimchooree)</webMaster>" + '\n') <br>
|
||||||
|
<br>
|
||||||
|
for r in result: <br>
|
||||||
|
f.write("<item>" + '\n') <br>
|
||||||
|
f.write("<title>" + r[0] + "</title>" + '\n') <br>
|
||||||
|
f.write("<link>" + loc3 + r[1] + "</link>" + '\n') <br>
|
||||||
|
f.write("<description>" + r[2] + "</description>" + '\n') <br>
|
||||||
|
code = r[1].replace(loc,'') <br>
|
||||||
|
code = code.replace('/','') <br>
|
||||||
|
f.write("<pubDate>" + format_rss_time(code) + "</pubDate>" + '\n') <br>
|
||||||
|
f.write("<guid>" + loc3 + r[1] + "</guid>" + '\n') <br>
|
||||||
|
f.write("</item>" + '\n') <br>
|
||||||
|
<br>
|
||||||
|
f.write("</channel>" + '\n') <br>
|
||||||
|
f.write("</rss>" + '\n') <br>
|
||||||
|
f.close() <br>
|
||||||
|
<br>
|
||||||
|
return result <br>
|
||||||
|
<br>
|
||||||
|
# Serve XML <br>
|
||||||
|
@route('/static/xml/<filename:path>') <br>
|
||||||
|
def serve_xml(filename): <br>
|
||||||
|
return static_file(filename, root='static/xml', mimetype='text/xml') <br>
|
||||||
|
<br>
|
||||||
|
## Main ## <br>
|
||||||
|
<br>
|
||||||
|
if __name__ == '__main__': <br>
|
||||||
|
make_rss() <br>
|
||||||
|
run(host='127.0.0.1', port=9001) <br>
|
||||||
|
<br>
|
||||||
|
<h2>double-check </h2><br>
|
||||||
|
The <a href="https://www.rssboard.org/rss-validator/">RSS Advisory Board</a> and <a href="<a href="https://validator.w3.org/feed/">W3C</a> have feed validation services that can check the syntax of Atom or RSS feeds. It's nice to check but don't feel pressured to meet all the recommendations if they don't suit your needs. <br>
|
||||||
|
<br>
|
||||||
|
<h2>double-check </h2><br>
|
||||||
|
Now I have an RSS feed, available at <a href="/static/xml/blessfrey.xml">https:/blessfrey.me/static/xml/blessfrey.xml</a>. Feel free to use it if you prefer to read through an aggregator and contact me if there's technical problems. <br>
|
||||||
|
<br>
|
||||||
|
Last updated March 19, 2021 <br>
|
||||||
|
<br>
|
@ -0,0 +1,63 @@
|
|||||||
|
<!--210527,210402-->
|
||||||
|
<h1>how to make a plugin for godot engine </h1>
|
||||||
|
april 15, 2021<br>
|
||||||
|
#godotengine <br>
|
||||||
|
<br>
|
||||||
|
If Godot Engine doesn't have the functionality you need, you can extend it with your own plugins. You can also use it to make specialized nodes to suit your project. <br>
|
||||||
|
<br>
|
||||||
|
<h2>how to make a plugin </h2><br>
|
||||||
|
<br>
|
||||||
|
Navigate to your project folder. Within it, make a folder called <b>addons</b>. Within the addons folder, make a folder and name it after your plugin. Within the plugin folder, you will need a few files: plugin.cfg, custom_node.gd, a .gd file for each custom node your plugin will add, and a .png icon for your custom nodes. <br>
|
||||||
|
<br>
|
||||||
|
<h3>plugin.cfg </h3><br>
|
||||||
|
Within the plugin file, make a file called <b>plugin.cfg</b>. Open it in a simple text editor like Notepad and use this template to make your config: <br>
|
||||||
|
<br>
|
||||||
|
[plugin]<br>
|
||||||
|
name="World"<br>
|
||||||
|
description="A world system."<br>
|
||||||
|
author="chimchooree"<br>
|
||||||
|
version="0.4"<br>
|
||||||
|
script="custom_node.gd"<br>
|
||||||
|
<br>
|
||||||
|
Obviously, fill the space within the quotation marks with your own information. The script's value should be kept the same, though. Once you have these six lines in your file, save and close. Avoid writing these files in a full word processor like Microsoft Word because they tend to fill your documents with extra characters. Even if they aren't displayed in the word processor, they will make your file unreadable to Godot. <br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<h3>custom_node.gd </h3><br>
|
||||||
|
Within the plugin file, make another file called <b>custom_node.gd</b>. The contents of this node should look like this: <br>
|
||||||
|
<br>
|
||||||
|
tool <br>
|
||||||
|
extends EditorPlugin<br>
|
||||||
|
<br>
|
||||||
|
func _enter_tree():<br>
|
||||||
|
add_custom_type("Room", "Node2D", preload("room.gd"), preload("World.png"))<br>
|
||||||
|
<br>
|
||||||
|
func _exit_tree():<br>
|
||||||
|
remove_custom_type("Room")<br>
|
||||||
|
<br>
|
||||||
|
To add a type, supply the <b>add_custom_type</b> method with 4 parameters: the name of your custom node, the node type (Node2D, AnimatedSprite, etc), your custom node's preloaded script, and a preloaded icon. Every type you add must also be removed. Use this process to add as many custom nodes as you need. <br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<h3>.gd files </h3><br>
|
||||||
|
For each custom node you declare in the custom_node.gd file, you'll need to supply a .gd script. These are going to look just like your other scripts in the editor. You can write them in full now or leave them relatively blank, but at least declare the type at the top. <br>
|
||||||
|
<br>
|
||||||
|
Since my example calls for a Node2D with a "room.gd", I can make a file within the plugins folder called <b>room.gd</b>. I'll keep the contents extremely simple for now: <br>
|
||||||
|
<br>
|
||||||
|
extends Node2D <br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<h3>icon </h3><br>
|
||||||
|
Icon must be .png files with the dimensions of 16x16 pixels. They are just for you to see within the editor's node tree, so you can draw something fancy or just squish down the same Godot head everyone uses. You can also make a unique icon for each node or just use the same one across all nodes. Just make sure the file name matches what's used in custom_node.gd. <br>
|
||||||
|
<br>
|
||||||
|
If you want to use my squished icon, that's 100% fine with me. If you need to check the license for the original Godot head, though, it's on their <a href="https://github.com/godotengine/godot/blob/master/LOGO_LICENSE.md">git repo</a>. <img src="/static/img/ent/plugin_icon.png" alt="(image: Squished Godot head)"> <br>
|
||||||
|
<br>
|
||||||
|
When choosing whether to make something beautiful or lazy, let Colossians 3:23 echo in your head: "And whatever you do, do it heartily, as to the Lord and not to men." <br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<h2>how to add a plugin </h2><br>
|
||||||
|
Now, inside Godot Engine's editor, go to the top-left menu and select Project>Project Settings and go to the Plugins tab. If your files came out okay, your new plugin should appear in the listing with all the data from your plugin.cfg file. Under the Status heading, make sure the plugin is set to "Enable" or "Active". To check whether your plugin's working, try to add a new node to a scene and search for your custom type. It should pop up with your custom icon in the search. If you add it, it will already have its custom script attached and ready to go. <br>
|
||||||
|
<br>
|
||||||
|
Happy coding! <br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
Last updated April 30, 2021 <br>
|
||||||
|
<br>
|
@ -0,0 +1,46 @@
|
|||||||
|
<!--210501,210402-->
|
||||||
|
<h1>an enemy patrol in godot </h1>
|
||||||
|
april 29, 2021<br>
|
||||||
|
#ai #knowledgebase <br>
|
||||||
|
<br>
|
||||||
|
My patrol routes look like a series of waypoints (Position2Ds) which are the children of a patrol (Node2D). The patrol node is in a group named after the patrol route. Whenever the enemy decides to patrol, adds all the waypoints into an array. The nearest waypoint is set as the enemy's next point. The enemy then pathfinds by building a constellation of dots leading to that next point and moves fowards. To determine whether it is successfully moving towards or has reached the next point, the Knowledge Base is called in to help. <br>
|
||||||
|
<br>
|
||||||
|
The Knowledge Base is a system of event handlers and a message bus used originally to accomodate achievements. Since it is a clean, self-contained system that is aware of and can respond toevery event that happens in the game, it has grown to be the driving force behind dynamic events and game progression. <br>
|
||||||
|
<br>
|
||||||
|
<h2>fast explanation of the knowledge base </h2><br>
|
||||||
|
<br>
|
||||||
|
Essentially, individual, granular events are assigned a knowledge key. The Knowledge Base stores all knowledge in the game. Upon the encounter of a key in the wild, the key is used to "learn" a piece of knowledge by the Knowledge Base. Event Handlers are subscribed to the MessageBus, and once certain pieces knowledge are learned, they can generate an outcome. <br>
|
||||||
|
<br>
|
||||||
|
So let's say every tea has a unique knowledge key. Upon drinking mint tea, the "item_used" event is passed to the MessageBus, the Knowledge Base learns the "mint_tea" knowledge, and the tea achievement event handler takes note. The Knowledge Base groups all the tea knowledge arrays into the tea category. Once the tea category is complete, the tea drinker achievement is awarded to the player. <br>
|
||||||
|
<br>
|
||||||
|
I discuss its design and use more in other articles. <br>
|
||||||
|
<br>
|
||||||
|
<h2>why involve the achievement system? </h2><br>
|
||||||
|
<br>
|
||||||
|
The character or its AI could track its own proximity and vectors, but tying character movement into this achievement system allows more creativity in how the game world can respond to patrols. When event handlers can listen for certain characters to reach certain points, events like having guards switch shifts or get suspicious of missing buddies can happen naturally instead of being hard-coded. I could even do silly things like have an "Arbiter of Left" who censures any character who moves left within his zone. At the very least, I could do what many games do and include a step counter somewhere in a statistics menu. <br>
|
||||||
|
<br>
|
||||||
|
<h2>getting back into the patrol flow </h2><br>
|
||||||
|
<br>
|
||||||
|
So, to determine the enemy's progress towards reaching the next point, the enemy's going to generate an event handler for the "moved" event upon entering the patrol state. This event handler is going to be subscribed to "moved." Every time the enemy moves, it's going to publish "moved" and itself to the MessageBus, so the event handler can be notified. The event handler is going to take the enemy and evaluate whether it's reached the next point or an intermediary dot yet. The handler can also evaluate whether it's on course. Once the event handler knows how the enemy's doing, it will shoot off the appropriate signal. <br>
|
||||||
|
<br>
|
||||||
|
The enemy's AI will receive the signal and respond. If it's arrived at the next point, it will get the index of the current next point (the child order of the patrol node) and set the waypoint at the following index as the new next point. Then it can repeat the process of moving to this next point. If the AI finds out the enemy isn't on course, if can check to see if it's stuck on the terrain, affected by a paralysis status effect, or being pushed around by other characters, then respond appropriately. <br>
|
||||||
|
<br>
|
||||||
|
<h2>pathfinding and moving </h2><br>
|
||||||
|
Pathfinding is mostly handled by the Godot Engine, but there's an important distinction to maintain between the waypoints that form a patrol route and intermediary pathfinding points between a character and a waypoint. Lack of clarity in my design led to a few weeks of confusion. <br>
|
||||||
|
<br>
|
||||||
|
The "path_to_object" method handles finding a path between the character and goal point. It sets the given object as the next point and sets the character's "dots" as the discrete line between itself and the next point. These dots are determined by the zone's Navigation2D's built-in get_simple_path method. The dots are stored in the state machine. Upon getting new dots, it sets its current dot by popping the front dot off the array. The patrol state, every time Execute is called, sets the enemy's velocity as the current dot - the character's global position. It also published "moved" to the MessageBus for the event handler to handler. To actually move, the enemy's KinematicBody2D calls move_and_collide from its _process method with the parameter get_velocity() * get_speed() * delta. <br>
|
||||||
|
<br>
|
||||||
|
<h2>cleaning up </h2><br>
|
||||||
|
<br>
|
||||||
|
Since the enemy might switch out of the patrol state at any moment, the setup and teardown are important. I also was sure to use signals when communicating to the patrol state instead of direct calls. <br>
|
||||||
|
<br>
|
||||||
|
Every state in Blessfrey is structured into an Enter, Execute, and Exit method. Enter is called once upon state transition, Execute is called every time the AI's timer ticks, and Exit is called once upon transitioning out of the state. The Enter and Exit are where setup and teardown are called respectively. <br>
|
||||||
|
<br>
|
||||||
|
The setup instances the moved handler, subscribes it to "moved" through the message bus, and connects all the necessary signals between the event handler + patrol state and the patrol state + state machine. The teardown disconnects all these signals and unsubscribes + queue_frees the event handler. That way, all loose ends are tied up. <br>
|
||||||
|
<br>
|
||||||
|
<h2>finally - the enemy patrol! </h2><br>
|
||||||
|
Finally got the enemy patrol into the game. It required a redesign of the character movement system, the development of new features for the Knowledge Base, and tons of debugging, but I'm pretty happy with my little enemies. Next, I'll refactor movement, since there's still some fragments of the old movement system cluttering my AI scripts, but after that, I'll try publishing a release version for HTML5 to put up on my website. Looking forward to it! <br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
Last updated April 30, 2021 <br>
|
||||||
|
<br>
|
@ -0,0 +1,102 @@
|
|||||||
|
<!--200806,201015-->
|
||||||
|
<h1>playing FlightRising with spreadsheets </h1>
|
||||||
|
may 13, 2021<br>
|
||||||
|
#offtopic #spreadsheets #petsites <br>
|
||||||
|
My dragon breeding spreadsheet is really coming together, so I thought sharing it would be a fun break from AI. <br>
|
||||||
|
<br>
|
||||||
|
FlightRising is a petsite where you can breed and raise pet dragons. The dragons' appearances are determined by the breeds, genes, and colors of their parents, which have varying levels of dominance. So if you want a dragon that looks a certain way, you'll probably have to find the closest available matches and carefully breed them. There's a lot of factors to keep up with, so spreadsheets work better than keeping it all in my head. <br>
|
||||||
|
<h2>spreadsheets </h2><br>
|
||||||
|
<br>
|
||||||
|
There's sheets for an overview of breeding pairs, individual parents, each breeding project, and calculators and data. If you want the spreadsheet, too, you can download my template for <a href="/download/fr_projects.ods">Calc</a> or <a href="/download/fr_projects.xlsx">Excel</a>. I use <a href="https://www.libreoffice.org/discover/calc/">LibreOffice Calc</a> for the spreadsheets, but it should work the same as Excel. I'll go over how everything works, so you can modify it to suit your own projects. <br>
|
||||||
|
<h3>individual dragons </h3><br>
|
||||||
|
The Singles sheet has a row for each parent and a field for sex, breeding status, breed, breed's cooldown, date bred, nest ready, date ready to breed again, and a cooldown countdown. I'm not usually interested in this information by itself, but it's used by the Pairs sheet. <br>
|
||||||
|
<center><img src="/static/img/ent/fr_singles.png" alt="(image: screenshot from the Singles tab.)" style="display:block;" width="500"></center> <br>
|
||||||
|
<h4>sex </h4><br>
|
||||||
|
The sex field is limited to ♂ or ♀ and can be selected using a dropdown menu. The content will color the cell blue or pink. <br>
|
||||||
|
<br>
|
||||||
|
I made the dropdown menu through Data Validity (Data > Validity...). <br>
|
||||||
|
<center><img src="/static/img/ent/FR_datavalidity.jpg" alt="(image: Validity>Criteria. Allow: Cell range. Source: $Calculator.$i$3.$I$4.)" width="500"></center> <br>
|
||||||
|
The possibilities are in a column in the Calculator sheet and set the criteria to allow that cell range as the source. The color is dictated by Conditional Formatting (Format > Conditional Formatting). <br>
|
||||||
|
<center><img src="/static/img/ent/fr_conditionalformatting.png" alt="(image: Managing Conditional Formatting. Condition 1: cell value is equal to '♀'. .)" width="500"></center> <br>
|
||||||
|
There's one condition for boys and one for girls. I set the condition to look for 'cell value is' 'equal to' then either "♂" or "♀" with the quotes. For Apply Style, I made a new style with a blue background for ♂ cells and a pink one for ♀s. <br>
|
||||||
|
<center><img src="/static/img/ent/fr_newstyle.png" alt="(image: Apply Style>New Style...>Background.)" width="500"></center> <br>
|
||||||
|
<h4>breeding status </h4><br>
|
||||||
|
Depending on the breed's cooldown and date of last breeding, the cell will say "Ready" or "Cooldown" in green or red. <br>
|
||||||
|
<center><img src="/static/img/ent/fr_status_formula.png" alt="(image: =IF(H2<=TODAY(),'Ready','Cooldown'))"></center> <br>
|
||||||
|
The formula is =IF(H2<=TODAY(),"Ready","Cooldown"). IF takes three parameters here: the condition (if the date ready is today or earlier), the text to display if the condition's true, and the text for false. The colors come from Conditional Formatting again. <br>
|
||||||
|
<h4>breed </h4><br>
|
||||||
|
The breed, like the sex, is a Data Validity-determined dropdown menu. The list of breeds is sourced from a column in Calculator. <br>
|
||||||
|
<center><img src="/static/img/ent/fr_breeds.png" alt="(image: The Calculator Sheet has a column for the Breed and a column for its Cooldown.)" width="500"></center> <br>
|
||||||
|
<h4>breed's cooldown </h4><br>
|
||||||
|
Each breed has a different cooldown duration. The field uses a formula to refer to the Breed field and search in Calculator for the corresponding cooldown information. The formula is =VLOOKUP($D2,$Calculator.$G$3:$H$18,2,0). Here, I take the breed, take it to the breed + cooldown columns in the Calculator sheet, and return with the data from the 2nd column in that group. <br>
|
||||||
|
<center><img src="/static/img/ent/fr_vlookup.png" alt="(image: =VLOOKUP($D2,$Calculator.$G$3:$H$18,2,0))"></center> <br>
|
||||||
|
<h4>date bred </h4><br>
|
||||||
|
Every time I breed a dragon, I type the date in its Date Bred field. If the dragon is unbred, I use its birthday instead. <br>
|
||||||
|
<h4>nest ready </h4><br>
|
||||||
|
This is a simple formula - the date bred + 6 days. It's 6 because 6 days is amount of time it takes for an egg to hatch. <br>
|
||||||
|
<center><img src="/static/img/ent/fr_nestready.png" alt="(image: =F2+6)"></center> <br>
|
||||||
|
<h4>date ready to breed again </h4>
|
||||||
|
This is another simple formula - the date bred + the cooldown. <br>
|
||||||
|
<center><img src="/static/img/ent/fr_dateready.png" alt="(image: =F2+E2)"></center> <br>
|
||||||
|
<h4>cooldown countdown </h4><br>
|
||||||
|
This one is relatively simple. It's just today minus the cooldown, but I added some steps to add " days" after the number. If there are 0 or less days, I opted for it to say nothing because the default "#N/A" is annoying to look at. The formula is =IF($H2-TODAY()>0,CONCAT($H2-TODAY()," days"),""). You can see IF's three parameters: (condition) there's more than 0 days until cooldown ends, (if true) return that number + " days", (if false), return nothing. CONCAT concatenates the two parameters it's given, so it finds the number of days and adds " days". That means it'll use the plural even for 1. I could use another IF to fix that, but I barely refer to this sheet myself anyways. <br>
|
||||||
|
<center><img src="/static/img/ent/fr_cooldown.png" alt="(image: =IF($H2-TODAY()>0,CONCAT($H2-TODAY(),' days'),''))"></center> <br>
|
||||||
|
<h3>dragon pairs </h3><br>
|
||||||
|
The Pairs sheet is the sheet I check every time a nest opens. At a glance, it tells me which pairs are ready and which ones will be soon. It also lets me check whether my goal is within range of the pair and which of their offspring most closely resembles my goal. The fields are Project, Male, Female, Status, Date Ready, Countdown, Colors, Genes, Best Son, and Best Daughter. <br>
|
||||||
|
<center><img src="/static/img/ent/fr_pairs.png" alt="(image: screenshot of the Pairs sheet)" width="500"></center> <br>
|
||||||
|
<h4>project</h4> <br>
|
||||||
|
I add the project, so I know my goal for the pair. It's helpful when prioritizing or sorting. <br>
|
||||||
|
<h4>male + female</h4> <br>
|
||||||
|
I add the dragon pair's names here. Data validity makes sure the name corresponds to a dragon from the Singles sheet. <br>
|
||||||
|
<center><img src="/static/img/ent/fr_dragonvalidity.png" alt="(image: Data Validity by cell range. Source: $Singles.$A:$Singles.$A.)" width="500"></center><br>
|
||||||
|
<h4>status </h4> <br>
|
||||||
|
Both members of the pair must be ready before the pair is ready. =IF(AND((VLOOKUP($B2,$Singles.$A:$C,3,)="Ready"),(VLOOKUP(C2,$Singles.A:C,3,)="Ready")),"Ready","Cooldown"). There's a new function AND, which just takes its parameters and considers them together. All together, the formula wants to take each name in the pair, hunt down that dragon's row in the Singles sheet, and check its status. I use AND so that the condition won't be true unless both dragons are ready. <br>
|
||||||
|
<center><img src="/static/img/ent/fr_status.png" alt="(image: =IF(AND((VLOOKUP($B2,$Singles.$A:$C,3,)='Ready'),(VLOOKUP(C2,$Singles.A:C,3,)='Ready')),'Ready','Cooldown'))"></center> <br>
|
||||||
|
<h4>date ready </h4><br>
|
||||||
|
I find the date for when the pair is ready with =MAX(VLOOKUP($B2,$Singles.$A:$H,8,),VLOOKUP($C2,$Singles.$A:$H,8,)). MAX takes its parameters and returns the larger value. Basically, it wants to use each dragon's name to check when their cooldown will be ready on the Singles sheet. I use MAX because the pair isn't ready until the parent with the longest cooldown is ready. <br>
|
||||||
|
<center><img src="/static/img/ent/fr_ready.png" alt="(image: =MAX(VLOOKUP($B3,$Singles.$A:$H,8,),VLOOKUP($C3,$Singles.$A:$H,8,)))"></center> <br>
|
||||||
|
<h4>countdown </h4><br>
|
||||||
|
The formula =IF($E2-TODAY()>0,CONCAT($E2-TODAY(), " days"),"") is similar to the one from Singles, but this time, I used Conditional Formatting to make countdowns of 1-5 days an eye-catching yellow. <br>
|
||||||
|
<center><img src="/static/img/ent/fr_countdown.png" alt="(image: =IF($E2-TODAY()>0,CONCAT($E2-TODAY(), ' days'),''))"></center> <br>
|
||||||
|
<h4>colors + genes</h4> <br>
|
||||||
|
I can't always find parents that are in range of my goal, so noting the range helps me prioritize. PST stands for Primary, Secondary, and Tertiary, since each dragon's appearance is determined by three colors and three genes. <br>
|
||||||
|
<center><img src="/static/img/ent/fr_genetics.png" alt="(image: a screenshot from FlightRising of a dragon's genetics.)"></center> <br>
|
||||||
|
I really only track cash shop genes, since they can only be obtained through breeding or real world money. If I want an in-game cash gene, it's a lot easier to earn money than to gamble with RNG. <br>
|
||||||
|
<h4>best son + best daughter</h4> <br>
|
||||||
|
Space is limited, so I want to know which dragons contribute the most to their projects. It can take several generations of dragons to get the desired offspring, so I want to make sure I'm narrowing the color and gene range with each generation. <br>
|
||||||
|
<h3>calculator + data </h3><br>
|
||||||
|
The Calculator sheet is a catch-all for data and extra formulas. I have everything needed for Data Validity on other sheets here, and I keep calculators to help know what to write on other sheets. <br>
|
||||||
|
<center><img src="/static/img/ent/fr_calculator.png" alt="(image: screenshot of the Calculator sheet.)" width="500"></center> <br>
|
||||||
|
<h4>colors </h4><br>
|
||||||
|
FlightRising's dragon colors exist in a color wheel. If one dragon has a Cream primary color and mates with an Antique dragon, the offspring can have a primary in Cream, Antique, or White. Since the colors were arbitrarily chosen by the developers, you won't know what to expect without referring to a color chart. The one below was made by <a href="https://flightrising.com/main.php?p=lair&tab=userpage&id=66886">Rauxel</a> and also contains the original, much smaller color range. <br>
|
||||||
|
<center><img src="/static/img/ent/fr_colorwheel.png" alt="(image: the color wheel by Rauxel.)" width="500"></center> <br>
|
||||||
|
When going for a particular color, the parents should be as close to that color as possible. I could manually count the colors in-between, but that's unreasonable when there's almost 200 colors. Instead, I have all the colors in a column and a little calculator to tell me how far over or under the parent's colors are. I enter primary, secondary, and tertiary colors of the goal dragon and dream dragon. The distance's magnitude and direction from the goal are calculated automatically. <br>
|
||||||
|
<br>
|
||||||
|
The formula for magnitude is =ABS(MATCH($C3,$F$2:$F$178, )-MATCH($B3,$F$2:$F$178, )). MATCH takes the color and returns the position within the color chart. ABS gives the absolute value of its parameter. In other words, I subtract the parent's colors from the goal's colors and get the absolute value. <br>
|
||||||
|
<center><img src="/static/img/ent/fr_magnitude.png" alt="(image: =ABS(MATCH($C3,$F$2:$F$178, )-MATCH($B3,$F$2:$F$178, )))" width="500"></center> <br>
|
||||||
|
The formula for direction is =IF((MATCH($C3,$F$2:$F$178,)-MATCH($B3,$F$2:$F$178,))>0,"↓","↑"). Here, it displays ↓ if the parent's colors are below the goal's, and ↑ if otherwise. <br>
|
||||||
|
<center><img src="/static/img/ent/fr_direction.png" alt="(image: =IF((MATCH($C3,$F$2:$F$178,)-MATCH($B3,$F$2:$F$178,))>0,'↓','↑'))"></center> <br>
|
||||||
|
The calculator ignores the fact that the color chart is a circle. Honestly, I don't breed along the extremes (white and pink), so I haven't fixed this yet. If you need to, you can find the distance between the parent color and the distance the nearest extreme (either Maize or Pearl), the distance between that extreme and the goal color, then add them together. If you're not sure which extreme is nearer, complete the process for both then take the smaller number - that's what the final formula's going to do anyways. <br>
|
||||||
|
<h4>breeding info</h4> <br>
|
||||||
|
These are the columns used by the breeding pages for validity and VLOOKUP. <br>
|
||||||
|
<h4>breeding day calculator</h4> <br>
|
||||||
|
If I need to know how long ago a dragon was bred, I can use today's date and the cooldown to find it out. The formula is very simple, just the given date minus the cooldown. <br>
|
||||||
|
<h3>project sheets </h3><br>
|
||||||
|
Here I list the dragons by project, tracking their genealogy, sex, colors, genes, and average distance from goal colors. <br>
|
||||||
|
<center><img src="/static/img/ent/fr_project.png" alt="(image: screenshot of the Boyfriend project sheet)" width="500"></center> <br>
|
||||||
|
<h4>father + mother </h4><br>
|
||||||
|
Dragons cannot breed with relatives within 5 generations. To ensure I'm keeping bloodlines separate, I plan a family tree separately from my spreadsheets. Using the father and mother's names, I can remember which family the dragon's in or if it's related to the other dragons at all. <br>
|
||||||
|
<h4>sex </h4><br>
|
||||||
|
Male or female, with colored conditional formatting so I can scan by sex more easily. <br>
|
||||||
|
<h4>primary, secondary, + tertiary colors </h4><br>
|
||||||
|
I usually need to use the color calculator on Calculators for this information. I list the parent's distance from the goal color in each field. In the first row, I list my goal colors for reference. <br>
|
||||||
|
<h4>gene 1, 2, + 3 </h4><br>
|
||||||
|
If the parent has one of the goal genes, I list it here. That way, I can prioritize by gene. I keep the other fields empty. For expensive genes, I only allow parents with genes of equal rarity, so the chance of passing down the goal gene is always 50/50. If my goal is a gene I can just buy, I don't care to track it since buying is easier than breeding. <br>
|
||||||
|
<h4>average </h4><br>
|
||||||
|
Genes are 50/50, but my goal colors are usually around a 1/20 chance. Consequently, I'm much more concerned about the color of a dragon than his genes. To give me a general evaluation of how close a dragon is to the goal colors, I use =AVERAGE(E3,G3,I3). The AVERAGE function adds its parameters and divides by the quantity of parameters given. If I had a parent with perfect colors, it would have an average of 0, so ideally, dragons with the lowest average are my most valuable for breeding. Genes usually factor in as well. <br>
|
||||||
|
<h2>that's all~ </h2>
|
||||||
|
In closing, I'll share a few of my cutest dragons (all of which are obviously using official FlightRising assets). See you two Thursdays from now! <br>
|
||||||
|
<center><img src="/static/img/ent/fr_dragons.png" alt="(image: Sand (male Sand Giraffe / Shadow Toxin / Rose Smoke Fae), Abbey (male Platinum Skink / Smoke Peregrine / Pearl Basic Tundra), Rune (male Eldritch Sphinxmoth / Eldritch Hawkmoth / Eldritch Runes Veilspun with eternal youth and dark sclera), Laguna (female Honeydew Cherub / Sanddollar Butterfly / Marigold Firefly Skydancer.)" width="500"></center>
|
||||||
|
<br>
|
||||||
|
Last updated May 26, 2021 <br>
|
||||||
|
<br>
|
@ -0,0 +1,39 @@
|
|||||||
|
<!--210318,210304-->
|
||||||
|
<h1>writing a game design document </h1>
|
||||||
|
may 27, 2021<br>
|
||||||
|
#gamedesign #gdd #worldbuilding <br>
|
||||||
|
<br>
|
||||||
|
A game design document (GDD) is a detailed document used to communicate the vision for a videogame. They are used internally by AAA game developers to keep hundreds of people on the same page, but it's worth considering keeping one as a small team or individual. I'll share how I organize mine. <br>
|
||||||
|
<br>
|
||||||
|
<h2>why keep a GDD if everyone's already on the same page? </h2><br>
|
||||||
|
Even small games are complex pieces of software requiring a broad skillset spanning computer science, design, art, music, creative writing, and marketing. The development process can take years, too. A GDD can serve as a single place to collect your thoughts and document the evolution of your design over time. Even as a single person, it's been helpful to give every aspect a little thought as I fill it out. Also, whenever I need to refer back to something, it's a boon to have an organized GDD instead of random notebooks and files. <br>
|
||||||
|
<br>
|
||||||
|
Of course, writing a GDD isn't developing a game. Barely anyone shares their GDD outside of their team, so unless your team or publisher has extra requirements, they only exist to facilitate game development. If you can't keep the document up-to-date with development or it would never be referenced by anyone, consider alternative forms of documentation. Sometimes a GDD is more effective as a game prototype, a mood board, or merely a thought in your head. If you're keeping scattered notes like I did, though, consider compiling them into a single word document or keeping them all in a binder. <br>
|
||||||
|
<br>
|
||||||
|
<h2>download the GDD template </h2><br>
|
||||||
|
Download my <a href="/download/DesignDocumentTemplate.docx">GDD template</a> and make a copy every time you have a new game idea so you never forget any! Obviously, it's just a template. If some parts aren't suitable for your genre or development process, swap them out for something better. <br>
|
||||||
|
<br>
|
||||||
|
<h2>worldbuilding bible </h2><br>
|
||||||
|
I feel it's easier to keep some parts in a different format from my GDD. For worldbuilding, I use a modified version of <a href="https://ellenbrockediting.com/worldbuilding-bible-template/">Ellen Brock's worldbuilding questionnaire</a>. I keep her headings and delete the detailed bullet prompts for less clutter. In general, I try to write my own prompts so they are closely tailored to my fantasy world. If I don't even know where to begin, though, her prompts are a great starting point. <br>
|
||||||
|
<br>
|
||||||
|
<center><img src="/static/img/ent/gdd_worldbuilding.png" alt="(image: Ellen Brock's worldbuilding questionnaire.)"></center> <br>
|
||||||
|
<br>
|
||||||
|
Every nation in my game gets their own copy that's written from their perspective, since different people groups can have different experiences or explanations regarding the same world. <br>
|
||||||
|
<br>
|
||||||
|
<h2>story + dialog </h2><br>
|
||||||
|
The pacing and direction of game narratives are dependent on the player's actions, so the stories are less like monolithic pages of text and more like a series of events strung together. For that reason, I don't keep the story or major events in my GDD. I keep an outline of the story and each scene in individual flowchart documents instead, so I can move the pieces around and connect them freely. <br>
|
||||||
|
<br>
|
||||||
|
<center>
|
||||||
|
<a target="_blank" href="/static/img/ent/gdd_diagram.png">
|
||||||
|
<img src="/static/img/ent/gdd_diagram.png" alt="(image: event diagram with an unnecessary amount of choices)" width="500" height="313">
|
||||||
|
</a></center><br>
|
||||||
|
<br>
|
||||||
|
To make my flowcharts, I open diagramming software like <a href="https://wiki.gnome.org/Apps/Dia">Dia</a> and make a box for the title and one for the goals of the scene. Then, using color coding to separate character dialog, conditional statements, stage directions, and emotes, I write the event box-by-arrow-by-box. <br>
|
||||||
|
<br>
|
||||||
|
Every time I have an idea for a scene, I scribble it in a flowchart to keep with my GDD. Some of them are dumb, but it's never bad to have a giant pile of potential game events. <br>
|
||||||
|
<br>
|
||||||
|
<h2>backing up your GDD </h2><br>
|
||||||
|
Finally, there's no point to keeping everything together in one place if the hard drive loses them. Try to keep a current copy in about 3 places. I have a GDD folder that contains an individual folder for each game. That way, it's easy to push all my GDDs to git at once. Better safe than sorry! <br>
|
||||||
|
<br>
|
||||||
|
Last updated May 26, 2021 <br>
|
||||||
|
<br>
|
@ -0,0 +1,124 @@
|
|||||||
|
<!--210429,210402-->
|
||||||
|
<h1>follow a moving target </h1>
|
||||||
|
june 10, 2021<br>
|
||||||
|
#ai #character #movement <br>
|
||||||
|
<br>
|
||||||
|
After redesigning the movement system to support <a href="../entries/210429">patrols</a>, I realized the path remains static even if the target moves. Time to tweak the design again. <br>
|
||||||
|
<br>
|
||||||
|
<h2>what must be done </h2><br>
|
||||||
|
Autopathing to attack targets, skill targets, and item targets still partially relies on an old version of the movement system. Also, characters never update their pathfinding, so they cannot pursue moving targets. With some changes, the movement system can officially support following any of these targets, no matter where they go. <br>
|
||||||
|
<br>
|
||||||
|
For now, I'll update the movement system so the character can autopath after a moving item then pick it up once within reach. Since autopathing to items works identically to the others, the fix will be the same, too. <br>
|
||||||
|
<br>
|
||||||
|
<h2>upgrading the movement system </h2><br>
|
||||||
|
I can keep the same system more or less, but one function is going to have to be rewritten: the character's path_to_object method. <br>
|
||||||
|
<br>
|
||||||
|
Before, it only set the next waypoint and built a path between the character and the waypoint. <br>
|
||||||
|
<br>
|
||||||
|
<h3>old path_to_object </h3><br>
|
||||||
|
<code># Set Dots Between Character + Given Object<br>
|
||||||
|
func path_to_object(goal):<br>
|
||||||
|
if goal.is_in_group("waypoint"):<br>
|
||||||
|
set_next(goal)<br>
|
||||||
|
else: <br>
|
||||||
|
room.add_waypoint(goal.get_gpos())<br>
|
||||||
|
path_to_position(goal.get_gpos())<br>
|
||||||
|
<br>
|
||||||
|
# Set Dots Between Character + Given Position<br>
|
||||||
|
func path_to_position(goal_pos):<br>
|
||||||
|
set_dots(find_dots_pos(goal_pos))</code><br>
|
||||||
|
<br>
|
||||||
|
In order to follow moving targets, it needs to use the goal object itself. Also, it needs to be know when to only rebuild part of the path. <br>
|
||||||
|
<br>
|
||||||
|
<h3>the character receives a waypoint instead of the target, so he is unaware of his target's movement </h3><br>
|
||||||
|
It took waypoints instead of the goal in the first place for consistency's sake. Since a target can either be a position (as with click-to-move) or an object (as with autopath-to-item) and the movement system only has one entry point, the old system only accepted objects. So when clicking-to-move, a Position2D waypoint is generated at the global mouse position. <br>
|
||||||
|
<br>
|
||||||
|
I took the consistency a step further and also generated waypoints at the position of object targets. If the character only has a waypoint, though, he cannot know whether the target is moving. Fortunately, the system only requires an object with a global position, not a waypoint in particular. Providing the goal directly to the character not only resolved issues but also simplified my code. <br>
|
||||||
|
<br>
|
||||||
|
<h3>constantly updating the path overwhelms the character </h3><br>
|
||||||
|
If the target is moving, pathfinding needs to be reassessed periodically. However, it isn't as simple as calling the pathfinding method every tick. <br>
|
||||||
|
<br>
|
||||||
|
For one, the first point in the path will always be the character's starting position. If pathfinding is performed more quickly than the character can register arriving at the first point, he will either be frozen in place or jittering wildly. <br>
|
||||||
|
<br>
|
||||||
|
For two, it's bad for performance. Generally, the efficiency of a lightweight Godot game on modern hardware is not a critical concern, but it's not like I've never managed to bog down the performance through lazy pathfinding. Probably best to avoid extra pathfinding operations when possible. If the target hasn't moved at all, no need to recalculate anything. If the target has moved closer to the character, maybe only the farthest points need to be reconsidered. <br>
|
||||||
|
<br>
|
||||||
|
The next playable release after the bingo version will have a teleporting boss, so I'll probably need to be more thoughtful about pathfinding then. For now, though, these two fixes should do it... <br>
|
||||||
|
<br>
|
||||||
|
<h3>new path_to_object </h3><br>
|
||||||
|
# Set Dots Between Character + Given Object<br>
|
||||||
|
func path_to_object(goal):<br>
|
||||||
|
# Next Waypoint<br>
|
||||||
|
set_next(goal)<br>
|
||||||
|
var goal_pos = goal.get_gpos()<br>
|
||||||
|
var dots = get_dots()<br>
|
||||||
|
var cd = get_current_dot()<br>
|
||||||
|
# If no current dot, set dots between user and goal<br>
|
||||||
|
if cd == null:<br>
|
||||||
|
set_dots(find_dots_pos(get_gpos(), goal_pos))<br>
|
||||||
|
MessageBus.publish("moved", self)<br>
|
||||||
|
return<br>
|
||||||
|
var last_dot = cd if len(dots) == 0 else dots.back()<br>
|
||||||
|
# Make sure goal has moved significantly<br>
|
||||||
|
if goal_pos.distance_to(last_dot) <= get_intimate_space():<br>
|
||||||
|
return<br>
|
||||||
|
# If goal moved further away<br>
|
||||||
|
if get_gpos().distance_to(last_dot) > find_distance(goal):<br>
|
||||||
|
# If no dots, generate new ones<br>
|
||||||
|
if len(dots) == 0:<br>
|
||||||
|
set_dots(find_dots_pos(get_gpos(), goal_pos))<br>
|
||||||
|
MessageBus.publish("moved", self)<br>
|
||||||
|
return<br>
|
||||||
|
# If dots, only recalculate part of the path<br>
|
||||||
|
var near = get_dots()[0]<br>
|
||||||
|
for dot in get_dots():<br>
|
||||||
|
if dot.distance_to(goal_pos) < near.distance_to(goal_pos):<br>
|
||||||
|
near = dot<br>
|
||||||
|
var i = get_dots().find(near)<br>
|
||||||
|
set_dots(get_dots().slice(0, i - 1) +<br> find_dots_pos(get_dots()[i-1], goal_pos))<br>
|
||||||
|
# If goal moved closer<br>
|
||||||
|
else:<br>
|
||||||
|
set_dots(find_dots_pos(last_dot, goal_pos))<br>
|
||||||
|
MessageBus.publish("moved", self)</code><br>
|
||||||
|
<br>
|
||||||
|
<h2>testing </h2><br>
|
||||||
|
Now let's test it. I don't have any items that move around, so I'll quickly throw that in. I'll add some movement based on a sine wave into the _process method.<br>
|
||||||
|
<br>
|
||||||
|
<code>var time = 0
|
||||||
|
|
||||||
|
func _process(delta):
|
||||||
|
time += delta
|
||||||
|
var mod = Vector2(delta, cos(time) * 2)
|
||||||
|
set_gpos(get_gpos() + mod)</code><br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<h3>the character never stops autopathing, even after picking up the item </h3>
|
||||||
|
<br>
|
||||||
|
Okay, one more fix, then I'll have it. <br>
|
||||||
|
<br>
|
||||||
|
Previously, the movement AI relied on conditional statements in its process to detemine arrival at the goal. Instead, the <a href="../entries/210402">achievement system</a> handles arrival for the new movement system. Since the process is called faster than the event handlers can function, the old AI system picked up and queue_free'd the floor item before the new system could recognize it had arrived at the goal. This meant the character never truly arrived and never knew to halt the movement process or clear movement variables. <br>
|
||||||
|
<br>
|
||||||
|
Moving the conditional statements from _process to the function that handles the outcome of movement events. <br>
|
||||||
|
<br>
|
||||||
|
<h3>new result code </h3><br>
|
||||||
|
<code>#enum MOVED {ARRIVED, NOT_MOVING, TOO_SLOW, WRONG_DIRECTION}<br>
|
||||||
|
func handle_moved_result(result, new_user):<br>
|
||||||
|
if result == 0:<br>
|
||||||
|
new_user.think("UserMove/Arrived")<br>
|
||||||
|
if get_user().get_target() != null:<br>
|
||||||
|
if track:<br>
|
||||||
|
if get_user().find_distance_to_target() <= get_user().get_intimate_space():<br>
|
||||||
|
emit_signal('item_arrived')<br>
|
||||||
|
get_waypoints().pop_front()<br>
|
||||||
|
new_user.clear_movement()<br>
|
||||||
|
return</code><br>
|
||||||
|
<br>
|
||||||
|
<h2>just a few changes makes a big difference </h2><br>
|
||||||
|
<center>
|
||||||
|
<img src="/static/img/ent/follow.gif" alt="(image: Angel adjusts her pathfinding to follow a moving item.)"></center><br>
|
||||||
|
<br>
|
||||||
|
To test, I made some moving items for the player to try to pick up. Now instead of bee-lining for the last known location of an item, she constantly readjusts her path towards the current location of the item. Looks good to me! <br>
|
||||||
|
<br>
|
||||||
|
(I haven't considered moving items before. They're pretty cool. It's like catching butterflies in Skyrim.) <br>
|
||||||
|
<br>
|
||||||
|
Last Updated October 29, 2021
|
||||||
|
<br>
|
@ -0,0 +1,41 @@
|
|||||||
|
<!--210610,200429-->
|
||||||
|
<h1>how to attack a moving target </h1>
|
||||||
|
july 8, 2021<br>
|
||||||
|
#ai #character #combat #design #movement <br>
|
||||||
|
<br>
|
||||||
|
I'll share my tentative design for the attack-movement loop. <br>
|
||||||
|
<br>
|
||||||
|
The attack-movement loop needs to allow the character maintain attack range while attacking. The flow is complicated to follow, but this is how it works for now: <br>
|
||||||
|
<center><a target="_blank" href="/static/img/ent/attack-movement-loop-diagram.png">
|
||||||
|
<img src="/static/img/ent/attack-movement-loop-diagram.png" alt="(image: diagram of the attack movement loop)" width="500" height="233">
|
||||||
|
</a><br></center>
|
||||||
|
The code is color-coded by object. Warm gray is input, orange is the character's action module, yellow is the character, yellow-green is the character's equipment module, blue-green is the attack handler, blue is the AI's attack module, purple is the AI's movement module, pink is the AI, brown is the KnowledgeBase's MessageBus, and cool gray is the character's kinematic body. <br>
|
||||||
|
<br>
|
||||||
|
<h2>the loop explained </h2><br>
|
||||||
|
Upon attack input, the character sets up for attacking and creates an attack timer. On timeout, the character's weapon swings. If the character is out of range, the "out_of_range" signal is emitted. Otherwise, the weapon successfully swings, either emitting "target_dead" or "hit." <br>
|
||||||
|
<br>
|
||||||
|
The AI receives these signals. If the target was out of range, it sets up to follow attack target. <br>
|
||||||
|
<br>
|
||||||
|
Every AI tick, it prompts the character to pathfind to the target then sets the character's velocity to the current_dot (the first node the character is trying to reach in path) minus the character's global_position. <br>
|
||||||
|
<br>
|
||||||
|
Every frame, the character's _process(delta) method calls move_and_collide with velocity * speed * delta. If the character's velocity isn't 0,0, the "moved" event is published to the Knowledge Base's MessageBus. <br>
|
||||||
|
<br>
|
||||||
|
The movement handlers are subscribed to "moved," and will emit signals if the character reached either the next waypoint (the target or the chosen goal point at the end of the path) or the current dot (the first point along the pathfinding between the character and the goal point). <br>
|
||||||
|
<br>
|
||||||
|
The AI receives these signals. If the next waypoint is reached, it's removed from the list of waypoints, the "arrived_at_attack_target" signal is emitted, and movement is cleared. <br>
|
||||||
|
<br>
|
||||||
|
Then the AI receives the "arrived_at_attack_target" signal and prompts the character to begin the attack all over again. <br>
|
||||||
|
<br>
|
||||||
|
<h2>in-game </h2><br>
|
||||||
|
It works in-game, too, but it's pretty janky, especially without animations. If the slime is slow enough, the player character attacks until it gets too far away, moves back in range, and continues attacking. If it's too fast, though, she never gets to attack and jitters constantly after the slime. <br>
|
||||||
|
<br>
|
||||||
|
Too fast: <br>
|
||||||
|
<center><img src="/static/img/ent/attack-follow.gif" alt="(image: Angel follows slime)"></center>
|
||||||
|
<br>
|
||||||
|
I'll work it out sooner or later, dependent on how hectic moving turns out to be. <br>
|
||||||
|
<br>
|
||||||
|
(By the way, that's my first gif recorded and edited entirely in ffmpeg. It's not pretty, but at least I could write my own bash script without relying on copypasta forum code this time. I was trying to follow the documentation website before, but it's arcane. The man page is much easier to understand and search through.) <br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
Last Updated November 13, 2021
|
||||||
|
<br>
|
@ -0,0 +1,40 @@
|
|||||||
|
<!--200917,201112-->
|
||||||
|
<h1>inventory as a system diagram </h1>
|
||||||
|
january 7, 2021<br>
|
||||||
|
#design #systemdiagram #gamemechanics<br>
|
||||||
|
<br>
|
||||||
|
<b>System diagrams</b> illustrate how components interact within a system. It saves so much headache to step back and plan with a system diagram before jumping into code. <br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<h2>stop + plan before coding </h2><br>
|
||||||
|
I want to move blessfrey's inventory into an app on the player character's smartphone. Before, it was displayed in a random pop-up window. It was poorly planned, so the programmatic inventory and the UI were too tightly coupled to easily pop into the phone screen. Instead of wrestling, it's easier to start over and actually plan before I code this time. <br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<h2>list out your nouns </h2><br>
|
||||||
|
A simple way to start thinking about a system is to list out its nouns + verbs. Jot down the entities that interact with your system. <br>
|
||||||
|
<br>
|
||||||
|
<center><img src="/static/img/ent/systemdiagram_inventory.jpeg" alt="(image: system diagram for inventory)" width="500" height="250"></center> <br>
|
||||||
|
<br>
|
||||||
|
For blessfrey's inventory, that's the inventory (programmatic), the player character, the inventory app (UI), and base items. The inventory app is related to the smartphone and inventory items. Items are related to floor items, which are related to rooms. <br>
|
||||||
|
<br>
|
||||||
|
(blessfrey has three different kinds of items. <br>
|
||||||
|
<br><ul>
|
||||||
|
<li><b>Base Item</b>: holds all the data regardless of how the item is currently expressed </li>
|
||||||
|
<li><b>Floor Item</b>: sits on the ground and gets picked up by characters. </li>
|
||||||
|
<li><b>Inventory Item</b>: displayed in inventories, store windows, containers, etc </li>
|
||||||
|
</ul><br>
|
||||||
|
Floor + Inventory Items hold a base item inside them that gets popped out and traded around as the item gets expressed in different forms.) </br>
|
||||||
|
<br>
|
||||||
|
<h2>connect your nouns with verbs </h2><br>
|
||||||
|
I wrote the entities in pink and moved them around until the placement was decently readable. Then I connected the concepts with arrows. These arrows represent the verbs, which I explicitly labeled in yellow. <br>
|
||||||
|
<br>
|
||||||
|
The flow of these arrows can be very important. If you are modeling AI, for instance, no matter what path the program takes, there shouldn't be dead-ends. Seeing mistakes like that is easier with a diagram than lines of code. Otherwise, the flow is always generally useful for figuring out which methods are needed for each class and how they connect. <br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<h2>write the code </h2><br>
|
||||||
|
At this point, coding is a bit easier now that it's in some ways a transcription of the diagram. The entities are data (classes or objects), and the arrows are logic (methods). <br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<h2>conclusion </h2><br>
|
||||||
|
Your diagram doesn't have to be fancy or formal to get the point across to yourself, and it just takes a minute to save all the headache later. Don't skip this step or you'll have to rewrite the inventory system just to display it in a different window. <br>
|
||||||
|
<br>
|
@ -0,0 +1,91 @@
|
|||||||
|
<!--220310,220224-->
|
||||||
|
<h1>hostility</h1>
|
||||||
|
january 27, 2022<br>
|
||||||
|
#design #mechanic<br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<h2>what is hostility? </h2><br>
|
||||||
|
Hostility is a state that a character's AI state machine can enter. More specific states will inherit from the hostile state to prompt targeting, aggressive, and defensive behavior. It's a very similar concept to <a href="https://wiki.guildwars.com/wiki/Aggro">aggro</a> in Guild Wars because weaving through patrol patterns and balling mobs is one of my favorite things from any game. <br>
|
||||||
|
<br>
|
||||||
|
<h3>when does a character become hostile? </h3><br>
|
||||||
|
NPCs generally will not seek out the player for combat. They will either stand stationary or follow their patrol route, oblivious of the player until becoming hostile. <br>
|
||||||
|
<br>
|
||||||
|
Usually, if an NPC is hostile, that means a threat got too close. Currently, proximities in Blessfrey mirror <a href="https://en.wikipedia.org/wiki/Proxemics">Edward T. Hall's zoning</a> for interpersonal distances. Intimate distance is the range for physical interaction and melee attacks and social distance is the range for assessing hostility and ranged attacks. <br>
|
||||||
|
<center><a href="https://en.wikipedia.org/wiki/Proxemics#/media/File:Personal_Space.svg"><img src="/static/img/ent/Personal_Space.svg" alt="(image: A visualization of proxemics by WebHamster of Wikipedia. Around someone are 4 concentric circles with varying diameters: within 25 feet is their public space, 12 feet is their social space, 4 feet is their personal space, and 1.5 feet is their intimate space.)" width="500" height="267.72"></a></center>
|
||||||
|
(By <a href="//commons.wikimedia.org/wiki/User:WebHamster" title="User:WebHamster";>WebHamster</a> - <span class="int-own-work" lang="en">Own work</span>, <a href="https://creativecommons.org/licenses/by-sa/3.0" title="Creative Commons Attribution-Share Alike 3.0">CC BY-SA 3.0</a>, <a href="https://commons.wikimedia.org/w/index.php?curid=6147809">Link</a>) <br>
|
||||||
|
<br>
|
||||||
|
An NPC will become hostile under a few conditions: <br>
|
||||||
|
<ul>
|
||||||
|
<li>An enemy faction member enters its social distance. Every character has a RangeBubble (Area2D) representing its social space. When a character enters range, its AI will assess both set of factions and change states upon finding a conflict. If there is a significant level disparity in the opponent's favor, though, the character will remain idle. </li>
|
||||||
|
<li>Someone attacked it or dealt damage to it. If their opponent isn't in range, they will become hostile and begin searching or ambushing. (There is no friendly fire, so teams aren't going to implode.) </li>
|
||||||
|
<li>Someone was damaged or attacked by it. Once again, if their opponent isn't in range, they will begin the hunt. </li>
|
||||||
|
<li>When the majority of a team is hostile towards an opponent, any team of the same faction or type entering the social space of the hostile team will also become hostile towards it. </li>
|
||||||
|
<li>When one member of a team becomes hostile, the others remain idle until aggravated. This allows skilled players to pull individual opponents away at a time without alerting the others and divide and conquer. This also prevents one foolhardy teammate from programmatically pulling aggro onto its entire team. </li>
|
||||||
|
</ul>
|
||||||
|
<br>
|
||||||
|
<h3>what changes when a character is hostile? </h3><br>
|
||||||
|
A hostile NPC will enter a combative AI state, usually with the goal of pursuing its opponent until either is killed or out of range. During combat, its passive health regeneration will slow, while energy regeneration will remain constant. Maybe certain skills and items can't be used during combat. If it has hostility towards multiple targets, it will prioritize targets according to its targeting AI state, probably favoring the nearest and weakest opponents. <br>
|
||||||
|
<br>
|
||||||
|
Each NPC will express hostility differently. Broadly, hostile behavior falls into 3 groups: offensive, defensive, and targeting. Offensive states may involve melee or ranged attacks, weapon attacks or offensive skill usage, single-target attacks or AoE, frontlining or backlining, and so on. Defensive states may involve buffing or healing, fleeing or kiting, protecting themselves or their teammates, and so on. The targeting state will allow the character to have unique opinions on who is most deserving of its attention. <br>
|
||||||
|
<br>
|
||||||
|
When a character becomes hostile toward the player, there will be feedback, such as battle music playing, the opponent's name turning red, or a sound effect playing. (Now I have the <a href="">Aion aggro sound effect</a> in my head.) <br>
|
||||||
|
<br>
|
||||||
|
<h3>when is a character no longer hostile? </h3><br>
|
||||||
|
An NPC will lose hostility under a few conditions:
|
||||||
|
<ul>
|
||||||
|
<li>Enough distance has spread between the NPC and its opponent. Usually, an NPC will not pursue an opponent very far. The farther the NPC is led from its idle route, the less tolerance it has to pursue its opponent. However, if pulled slowly, a few steps at a time, it can be led almost anywhere. </li>
|
||||||
|
<li>Its opponent exits the NPC's territory. Some NPCs have lines they will not cross. </li>
|
||||||
|
<li>Its opponent or itself dies. </li>
|
||||||
|
<li>The opponent no longer belongs to a conflicting faction. </li>
|
||||||
|
</ul>
|
||||||
|
<br>
|
||||||
|
<h3>what happens after a character is no longer hostile? </h3><br>
|
||||||
|
The character will return to its idle position or the nearest waypoint of its patrol route. Its passive health regeneration returns to its usual rate. Possibly some skills and items will be combat-only and will become disabled. <br>
|
||||||
|
<br>
|
||||||
|
<h2>hostility in action </h2><br>
|
||||||
|
This next section is mostly for me. Let's run through some faction clash examples after illustrating the relationship of teammates and allies.<br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<h3>team hierarchy </h3><br>
|
||||||
|
Teams have a leader. If there are additional teammates, they will be followers of the leader. Teams can be allied with other teams. Generally, the leader of the ally team will become a follower of the main team's leader, while its teammates will be followers of their local leader. If a teammate has a pet, raises a minion, casts a summon, or triggers an event that grants it an ally, that character will be the leader of its own group, allied with the responsible teammate. Alliances can be gained or broken. <br>
|
||||||
|
<br>
|
||||||
|
Teams will be more or less oblivious to their allies' position and lose them if they aren't keeping up. They will support allies in combat, but they characters usually prioritize teammates over allies. <br>
|
||||||
|
<br>
|
||||||
|
<center><img src="/static/img/ent/team_hierarchy.png" alt="(A diagram of team hierarchy. )" width="500" height="375"></center> <br>
|
||||||
|
<br>
|
||||||
|
<h3>scenarios </h3><br>
|
||||||
|
<center><img src="/static/img/ent/teams_collide.png" alt="(An illustration of the chain resulting from paladins allying with a necromancer who summons a vampire who converts a ghoul who is friends with a chaos dwarf who build a war golem.)" width="500" height="375"></center> <br>
|
||||||
|
<br>
|
||||||
|
A, B, and C are in team 1. D is allied with them. They are preparing to attack the idle team 2 (V, W, and X with ally Y). Both teams will be spotted by the patrolling team 3 (M, N, and O with ally P) in a minute. Team 2 and 3 are members of the same faction, while Team 1 is of an opposing faction. A, B, and D are within range of either other, while C is investigating something just out of range. V, W, and X are within range, while Y is stuck behind a rock just out of range. M, N, and O are patrolling, while P walks too slowly to be in immediate range of the others. A, V, and M are leaders of their teams. They lead, while their teammates flock with them. Allies are members of their own team(s). Ally team leaders will follow the team leader of the main team, while their teammates flock with them. Allies can have their own allies, resulting in large trails of allied groups. Allies will not engage against allies or support direct enemies of their allies. Allies may engage against allies removed or support direct enemies of allies twice removed, though. <br>
|
||||||
|
<br>
|
||||||
|
Technically, allies are in their own team by themselves. Allies will flock within their own team and follow the general position of the main team or the individual teammate they are allied with. The main team will flock within their own team and ignore the positions of their allies. Allies will only share hostility with the main team through the coincidental case of being the same faction or type. <br>
|
||||||
|
<br>
|
||||||
|
<ul>
|
||||||
|
<li>A attacked V. V and A are hostile towards each other. B and D gain hostility towards V from being in range of A. W and Y gain hostility towards A from V. Now A, B, and D are opposed to V, W, and Y and vice versa and begin fighting. C and X are bound to cluster closer or be approached by opponents and join in soon. </li>
|
||||||
|
<li>A lays a trap. V walks into the trap and takes damage. A and V are now hostile towards each other. A and V will prepare for battle and begin hunting for each other. Their RangeBubbles will extend farther during this time. If they search for too long or travel too many steps without finding an opponent, they will resume their idle positions. If A finds an opponent, any teammates or allies within their RangeBubble will gain hostility towards the opponent and move within range. Combat will ensue. If very few or no members were in range of A, its team will idle on without it. If most members were in range, the remaining members will flock to their team and inevitably gain hostility and enter combat. </li>
|
||||||
|
<li>A one-hit-kills V from a safe distance. A becomes hostile and searches for an opponent. Team 2 is oblivious. </li>
|
||||||
|
<li>Teams 1 and 2 are hostile towards each other. Team 1 changes to the same faction. Teams 1 and 2 lose hostility. They will now consider each other friendly and possibly support each other. If one team gains hostility towards another team, the other will share hostility if still in range. </li>
|
||||||
|
<li>Teams 1 and 2 are friendly with each other and following each other. Team 1's faction changes to an adversarial one. They are all so close, they all gain hostility towards the other team's members immediately. </li>
|
||||||
|
<li>All of team 1 is hostile towards all of team 2. Team 3 enters range of team 2. All of team 3 gains hostility towards team 1 the moment a member of team 2 enters range of a single member of team 3. </li>
|
||||||
|
<li>Team 1 and team 2 are hostile towards each other with team 3 approaching. Ally P intersects with ally Y, triggering hostility between them. Teams 1 and 2 remain oblivious towards Team 3 and vice-versa. If team 3 starts going somewhere else, P will be left behind. Members of team 3 will gain hostility on a case by case basis as they flock nearby and inevitably get too close. </li>
|
||||||
|
<li>Team 1 and team 2 are hostile towards each other with team 3 approaching. Teammate M intersects with ally Y, triggering hostility between all of team 3 and ally Y. Team 2 will eventually enter the fray coincidentally. </li>
|
||||||
|
</ul>
|
||||||
|
<br>
|
||||||
|
<h3>ally chains </h3><br>
|
||||||
|
<br>
|
||||||
|
<center><img src="/static/img/ent/ally_chain.png" alt="(An illustration of the chain resulting from paladins allying with a necromancer who summons a vampire who converts a ghoul who is friends with a chaos dwarf who build a war golem.)" width="500" height="375"></center> <br>
|
||||||
|
<br>
|
||||||
|
If a necromancer is allied with a team of paladins, they will only enter hostility through their own terms. For instance, the paladins may attack a bear, but the necromancer will ignore it until the bear is a direct threat to itself. If the paladins attack another necromancer, the enemy necromancers will likely ignore the paladin-allied necromancer and never pose a direct threat. The allied necromancer may express idle supportive behavior like buffing the paladins, but it will remain idle. The politics of the main team trump that of other roaming teams. The allied necromancer will not enter combat with the paladins merely because it is the same type as the necromancers. It will also not idly heal the team that is currently directly opposed to its paladin allies. If the enemy necromancers were fighting a team of merchants, and the paladins completely ignore both groups, the necromancer ally will enter the fray in support of the necromancers and against the merchants. This may draw the paladins in, who will support the merchants and fight the necromancers. The allied necromancer will immediately lose hostility towards anyone directly opposed to the main team's leader. <br>
|
||||||
|
<br>
|
||||||
|
If the necromancer raises 2 liches, each lich is the leader of its own team, and each team is allied with the necromancer. Let's say the liches summon vampiric minions, one of which converts someone to an allied ghoul whose alliance remains constant with its chaos dwarf buddy who in turn is allied with its war golem. The paladin will respect the necromancer and liches but become hostile towards the vampires and all other evil monsters and vice-versa. In the case all these groups were factionless, the paladin would be neutral towards the vampires and everything else down the chain. Neutral characters are generally low priority in combat, but AoE attacks can easily whip up hostility. <br>
|
||||||
|
<br>
|
||||||
|
If the necromancer dies, the lich and paladins will no longer restrain themselves from becoming hostility towards each other. <br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<h2>what I learned </h2><br>
|
||||||
|
Before, I was trying to force the combat state into flat booleans, but clearly there is more nuance to the issue. A character can be hostile but not in active combat. A character can be in combat but not hostile. Also, hostility is more of a product of a character's surroundings, actions, and actions inflicted on it. Structurally, it needs to resemble (extremely obviously) an AI state rather than any kind of variable. <br>
|
||||||
|
<br>
|
||||||
|
The characters' state machines now behave more like the above scenarios, and I'd love to make a cutscene-like demo for Blessfrey's second demo that sets differently factioned groups on clashing patrols to make sure it all works. Look forward to it! <br>
|
||||||
|
<br>
|
||||||
|
Last updated January 31, 2022
|
||||||
|
<br>
|
@ -0,0 +1,140 @@
|
|||||||
|
<!--210610,200429-->
|
||||||
|
<h1>css grid with angular (break into 2?)</h1>
|
||||||
|
may 5, 2022<br>
|
||||||
|
#angular #css #webdev <br>
|
||||||
|
<br>
|
||||||
|
<h2>start from the beginning</h2><br>
|
||||||
|
<a href="https://angular.io/guide/setup-local">Angular's documentation</a> shares the process for beginning a new project, but I'll explain it, too. <br>
|
||||||
|
<br>
|
||||||
|
<ul>
|
||||||
|
<li>Installation process will differ depending on your operating system. These commands are for Linux Mint, downstream from Ubuntu and Debian. </li>
|
||||||
|
<li><a href="https://nodejs.org/en/">Install Node.js </a> </li>
|
||||||
|
<li><code>sudo curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash</code></li>
|
||||||
|
<li><code>sudo apt-get install -y nodejs</code></li>
|
||||||
|
<li><code>sudo apt install -y build-essential</code></li>
|
||||||
|
<li><code>sudo npm install -g @angular/cli</code> (you can decide whether to share analytics) </li>
|
||||||
|
</ul>
|
||||||
|
<br>
|
||||||
|
<h2>grab the template </h2>
|
||||||
|
Now you're ready to start an Angular project. Begin in the folder you'd like your project folder to be. Instructions and files are also available on <a href="https://github.com/angular/angular-cli">Angular's github</a>. <br>
|
||||||
|
<br>
|
||||||
|
<ul>
|
||||||
|
<li><code>ng new Example</code> (accept Angular routing and CSS) Change out the name 'Example' with your project name. </li>
|
||||||
|
<li>Now you'll have a folder named 'Example,' containing your skeleton. If you didn't already have git initialized in the folder, it'll automatically do so for you. </li>
|
||||||
|
</ul>
|
||||||
|
<h2>test run </h2>
|
||||||
|
Now you can run your example project in the browser. <br>
|
||||||
|
<br>
|
||||||
|
<ul>
|
||||||
|
<li><code>cd Example/ </code></li>
|
||||||
|
<li><code>npm start </code><li>
|
||||||
|
<li>Now you can browse to your project. By default, it used port 4200, so it's viewable @ <a href="http://localhost:4200/">http://localhost:4200/</a> </li>
|
||||||
|
<li>When you're done, stop the script by entering ctrl + c into the terminal. </li>
|
||||||
|
</ul>
|
||||||
|
<br>
|
||||||
|
If you see the page below, you have everything you need to get started on your own project. <br>
|
||||||
|
<center><a target="_blank" href="/static/img/ent/angular_examplepage.png">
|
||||||
|
<img src="/static/img/ent/angular_examplepage.png" alt="(image: the example webpage that comes with the skeleton files.)" width="500" height="505">
|
||||||
|
</a></center><br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<h2>adding custom components </h2>
|
||||||
|
With a basic HTML/CSS website (like blessfrey.me), the CSS Grid allows you to align its nested divs into rows and columns. Angular allows you to break each area into a <a href="https://angular.io/guide/component-overview">component</a> to be styled by the grid. <br>
|
||||||
|
<br>
|
||||||
|
I'll use the petsite browser game I'm making as an example: <br>
|
||||||
|
<center><a target="_blank" href="/static/img/ent/lemonland_cssgrid.png">
|
||||||
|
<img src="/static/img/ent/lemonland_cssgrid.png" alt="(image: planned layout of my example page: two buffers at the sides named Left and Right, Header at the top, Footer at the bottom, Chatter in between Header and Footer and left of Right, Zone and PDA in the second row from the top, Alert in the third, and Action and Partner in the fourth.)" width="500" height="478">
|
||||||
|
</a></center><br>
|
||||||
|
<br>
|
||||||
|
This webpage has some major elements that will be reused on many pages, which I would like to keep in a general grid. <br>
|
||||||
|
<ul>
|
||||||
|
<li>header - wgill hold the logo, home page link, account links </li>
|
||||||
|
<li>left - a centering buffer, will have a background </li>
|
||||||
|
<li>right - a centering buffer, will have a background </li>
|
||||||
|
<li>footer - hold copyright, footer links </li>
|
||||||
|
</ul>
|
||||||
|
Then I'd like to nest a grid inside to contain elements more specific to this kind of page. <br>
|
||||||
|
<ul>
|
||||||
|
<li>zone - a graphic representing the player's location </li>
|
||||||
|
<li>pda - a control panel with news, email, friend's list, quick links, inventory, etc </li>
|
||||||
|
<li>alert - a window for dialog, system messages, etc </li>
|
||||||
|
<li>action - a window containing player choices and dialog responses </li>
|
||||||
|
<li>partner - basic info about the active pet </li>
|
||||||
|
<li>chatter - a live-time chat box </li>
|
||||||
|
</ul>
|
||||||
|
<br>
|
||||||
|
Each of these will need to be added as components via the terminal. <br>
|
||||||
|
<br>
|
||||||
|
<ul>
|
||||||
|
<li><code>cd app/ </code></li>
|
||||||
|
<li><code>ng g c header </code></li>
|
||||||
|
<li><code>ng g c zone </code></li>
|
||||||
|
<li>...and so on. </li>
|
||||||
|
</ul>
|
||||||
|
These commands fill your app folder with components for your grid. <br>
|
||||||
|
<br>
|
||||||
|
<center><a target="_blank" href="/static/img/ent/angular_componentdir.pngid.png">
|
||||||
|
<img src="/static/img/ent/angular_componentdir.png" alt="(image: screenshot of each folder generated for each component inside the app folder.)" width="500" height="273">
|
||||||
|
</a></center><br>
|
||||||
|
<br>
|
||||||
|
<h2>writing the css grid </h2>
|
||||||
|
There's a few HTML files and a few CSS files in your project folder. <br>
|
||||||
|
<ul>
|
||||||
|
<li><code>src/index.html</code> is the basic webpage </li>
|
||||||
|
<li><code>src/app/app.component.html</code> is the app displayed in the webpage </li>
|
||||||
|
<li>Then there's an HTML file for each component, like <code>src/app/partner/partner.component.html</code> </li>
|
||||||
|
<br>
|
||||||
|
<h3>index.html + styles.css </h3>
|
||||||
|
I'm keeping these two very general, since most of the content on the page is part of the game. <br>
|
||||||
|
<code><!doctype html></code><br>
|
||||||
|
<code><html lang="en"></code><br>
|
||||||
|
<code><head></code><br>
|
||||||
|
<code> <meta charset="utf-8"></code><br>
|
||||||
|
<code> <title>lemonland</title></code><br>
|
||||||
|
<code> <base href="/"></code><br>
|
||||||
|
<code> <meta name="viewport" content="width=device-width, initial-scale=1"></code><br>
|
||||||
|
<code> <link rel="icon" type="image/x-icon" href="favicon.ico"></code><br>
|
||||||
|
<code></head></code><br>
|
||||||
|
<code><body></code><br>
|
||||||
|
<code><router-outlet></router-outlet></code><br>
|
||||||
|
<code> <div class="app-container"></code><br>
|
||||||
|
<code> <app-root></app-root></code><br>
|
||||||
|
<code> </div></code><br>
|
||||||
|
<code></body></code><br>
|
||||||
|
<code></html></code><br>
|
||||||
|
<br>
|
||||||
|
Then the CSS... <br>
|
||||||
|
<code>* {</code><br>
|
||||||
|
<code> padding: 0;</code><br>
|
||||||
|
<code> margin: 0;</code><br>
|
||||||
|
<code> font-family: verdana;</code><br>
|
||||||
|
<code> font-size: 18px;</code><br>
|
||||||
|
<code>}</code><br>
|
||||||
|
<code>body {</code><br>
|
||||||
|
<code> background-color: black;</code><br>
|
||||||
|
<code> color: MidnightBlue;</code><br>
|
||||||
|
<code>}</code><br>
|
||||||
|
<br>
|
||||||
|
<h3>app.component.html + app.component.css </h3>
|
||||||
|
These two contain all the subcomponents generated earlier. <br>
|
||||||
|
<code><div class="container"></code><br>
|
||||||
|
<code> <app-header></app-header></code><br>
|
||||||
|
<code> <app-left></app-left></code><br>
|
||||||
|
<code> <router-outlet></router-outlet></code><br>
|
||||||
|
<code> <div class="content-grid"></code><br>
|
||||||
|
<code> <app-zone></app-zone></code><br>
|
||||||
|
<code> <app-pda></app-pda></code><br>
|
||||||
|
<code> <app-chatter></app-chatter></code><br>
|
||||||
|
<code> <app-alert></app-alert></code><br>
|
||||||
|
<code> <app-action></app-action></code><br>
|
||||||
|
<code> <app-partner></app-partner></code><br>
|
||||||
|
<code> </div></code><br>
|
||||||
|
<code> <app-right></app-right></code><br>
|
||||||
|
<code> <app-footer></app-footer></code><br>
|
||||||
|
<code></div></code><br>
|
||||||
|
<br>
|
||||||
|
Then the CSS grid takes place in the CSS file. <br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
Last updated April 12, 2022
|
||||||
|
<br>
|
@ -0,0 +1,67 @@
|
|||||||
|
<!--201224,200806-->
|
||||||
|
<h1>making of blessfrey.me</h1>
|
||||||
|
october 15, 2020<br>
|
||||||
|
#bottle #css #html #simpletemplate #webdesign #webdevelopment<br>
|
||||||
|
<br>
|
||||||
|
Blessfrey.me is a personal website I use to showcase projects and blog my process. It was originally written in PHP, but now it uses <a href="https://bottlepy.org/docs/dev/">Bottle</a>, a Python web framework. <br>
|
||||||
|
<br>
|
||||||
|
<h2>why not use a premade blogging platform like WordPress? </h2><br>
|
||||||
|
Blessfrey.me's needs are fairly simple - some static pages and a blog page. Generalized blogging platforms are overkill for my purposes. I don't need support for multiple authors, comments, localization, e-commerce, and so on. Unused features bog down the website at best and contribute to security vulnerabilities at worst. <br>
|
||||||
|
<br>
|
||||||
|
Also, it's fun to write my own platform. I'm learning a lot as I take my website from initial sketches, to Hello World, to various prototypes, to something polished enough to show my friends. Regardless, since it can be considered my programming portfolio, it makes sense that it itself should be something I programmed. <br>
|
||||||
|
<br>
|
||||||
|
<h2>why Bottle? </h2><br>
|
||||||
|
I originally wrote Blessfrey.me in PHP. I switched to Bottle after looking for a templating engine. Bottle comes with SimpleTemplate and can do everything PHP can do but faster and with less verbosity. Plus, I get to write in Python, a much more enjoyable language. <br>
|
||||||
|
<br>
|
||||||
|
<h2>how does blessfrey.me work? </h2><br>
|
||||||
|
<h3>SimpleTemplate </h3><br>
|
||||||
|
Instead of existing as a static collection of HTML pages on my server, Blessfrey.me's pages are constructed upon request by the website's Bottle script from SimpleTemplate templates. <br>
|
||||||
|
<br>
|
||||||
|
Every page uses the frame template pictured below, so the basic skeleton is consistent without requiring copy-pasted code. Each page is individualize through bringing in a new template within <code>{{!base}}</code>. (Double curly brackets refer to variables, and exclamation marks disable escaping.) <br>
|
||||||
|
<br><center>
|
||||||
|
<img src="/static/img/ent/blessfrey_website_alltemplate.png" alt="(image: basic template code.)"><br>
|
||||||
|
</center>
|
||||||
|
(The code can be found on <a href="https://pastebin.com/mQuGX3Xa">Pastebin</a>.) <br>
|
||||||
|
<br>
|
||||||
|
The header template below (brought in at <code>% include('header.tpl')</code>) has some variables, too, which are supplied by the Bottle script. If Bottle doesn't provide a title, it defaults to 'blessfrey.me.' Variables can also be used in paths and URLs. <br>
|
||||||
|
<br><center>
|
||||||
|
<img src="/static/img/ent/blessfrey_website_headertemplate.png" alt="(image: header template code.)" width="500" height="54"><br>
|
||||||
|
</center>
|
||||||
|
(The code can be found on <a href="https://pastebin.com/JcEU4xTm">Pastebin</a>.) <br>
|
||||||
|
<br>
|
||||||
|
You can insert Python code into the templates for dynamic pages. Below is an excerpt of the template for the diary page. This code fills the page with diary entry previews using a for loop. Not shown is the first line <code>% rebase('frame.tpl')</code>, which tells SimpleTemplate to insert this content at the <code>{{!base}}</code> variable in the frame template. <br>
|
||||||
|
<br><center>
|
||||||
|
<img src="/static/img/ent/blessfrey_website_snippettemplate.png" alt="(image: diary snippet code from diary template.)" width="500" height="401"><br>
|
||||||
|
</center>
|
||||||
|
(The code can be found on <a href="https://pastebin.com/ckEj9Bf2">Pastebin</a>.) <br>
|
||||||
|
<br>
|
||||||
|
The Bottle script sends the max number of snippets per page (the <code>limit</code>) and a list of lists containing all the diary snippets. It receives the page number from the URL. For the snippets that will appear on the given page, it converts the list data into HTML code to be displayed in the browser. <br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<h3>Bottle </h3><br>
|
||||||
|
Bottle takes URLs and generates the corresponding web page upon request. Each URL is tied to a method, which returns a template and a dictionary of data to be used in the template. Since Bottle is a Python framework, you have access to all the Python libraries you need. <br>
|
||||||
|
<br><center>
|
||||||
|
<img src="/static/img/ent/blessfrey_website_diaryscript.png" alt="(image: Bottle script excerpt for diary routes.)" width="500" height="288"><br>
|
||||||
|
</center>
|
||||||
|
(The code can be found on <a href="https://pastebin.com/Bf96F9Ha">Pastebin</a>.) <br>
|
||||||
|
<br>
|
||||||
|
This is what the methods for specific routes look like. So every time you go to Blessfrey.me/diary, one of the above methods is called, depending on whether also you supplied an integer. To generate the page content, it calls a lot of Python functions to find my diary entries, convert them into previews for the snippets section and headlines for the sidebar, and get the current time for the footer. <br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<h3>CSS </h3><br>
|
||||||
|
The website is styled using CSS, heavily relying on the CSS Grid and a bit of Flexbox. CSS Grids can be used inside CSS Grids, so my pages are generally blocked out, with smaller internal grids managing specific content. <br>
|
||||||
|
<br><center>
|
||||||
|
<img src="/static/img/ent/blessfrey_website_cssgrid.png" alt="(image: CSS Grid traced over screenshot of projects page.)" width="500" height="491"><br>
|
||||||
|
</center>
|
||||||
|
<br>
|
||||||
|
The projects page is an example of nested grids. Almost every page uses the yellow general layout. The content unique to the projects page is mapped out in green, with a section for the header, featured projects, and other projects. The other projects use a 2-column grid in blue to evenly space out all the little thumbnails. <br>
|
||||||
|
<br>
|
||||||
|
The CSS code for project's general grid and the nested unfeatured grid are shown below.<br>
|
||||||
|
<br><center>
|
||||||
|
<img src="/static/img/ent/blessfrey_website_projectcss.png" alt="(image: projects css code.)"><br>
|
||||||
|
</center>
|
||||||
|
(The code can be found on <a href="https://pastebin.com/pVgkmT5k">Pastebin</a>, but you can always see a web page's CSS by right-clicking and viewing the source.) <br>
|
||||||
|
<br>
|
||||||
|
Last updated June 8, 2021 <br>
|
||||||
|
<br>
|
@ -0,0 +1,15 @@
|
|||||||
|
<!--201224,201015-->
|
||||||
|
<h1>free pixel font </h1>
|
||||||
|
august 20, 2020<br>
|
||||||
|
#assets<br>
|
||||||
|
<br>
|
||||||
|
<b><a href="https://fontstruct.com/fontstructions/show/1596262/pixel-joy">pixel joy</a></b> is a small, round sans-serif pixel font, made using <a href="https://fontstruct.com/">FontStruct's FontStructor</a> for an old version of Blessfrey. Freely use and edit for your personal and commercial projects. No credit needed. <br>
|
||||||
|
<br>
|
||||||
|
Download it from <a href="https://fontstruct.com/fontstructions/show/1596262/pixel-joy">FontStruct</a>.<br>
|
||||||
|
<br>
|
||||||
|
<center><img src="/static/img/ent/pixeljoy.jpeg" alt="image: pixel joy font preview"></center> <br>
|
||||||
|
<br>
|
||||||
|
Font released under public domain. Sample text in the preview image is from Imogen Heap's "Pocket Sun," and the colors are from <a href="https://lospec.com/palette-list/aap-64">Adigun Polack's AAP-64 palette</a>. :)<br>
|
||||||
|
<br>
|
||||||
|
Last updated June 8, 2021 <br>
|
||||||
|
<br>
|
@ -0,0 +1,39 @@
|
|||||||
|
<!--210218,201029-->
|
||||||
|
<h1>tidying up my skill phases </h1>
|
||||||
|
november 12, 2020<br>
|
||||||
|
#programming #skills<br>
|
||||||
|
<br>
|
||||||
|
In Godot Engine, you can call methods from a parent class by prefixing it with a period (.). So to access the <code>move()</code> method from a parent, call <code>.move()</code>. This is called a <b>super method</b>. <br>
|
||||||
|
<br>
|
||||||
|
Being called super doesn't mean there isn't a better way, though. I used to use super methods to build customs skills in blessfrey, but subfunctions is tidier. <br>
|
||||||
|
<br>
|
||||||
|
(Just so you know, blessfrey's skills have a number of phases of effects that must happen in order: skill press > activation > initial phase > main phase > end phase > cooldown.) <br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<h2>the old way </h2><br>
|
||||||
|
Initially, I used super methods to give each phase custom effects and call the next phase. This was messy. If I ever redesigned the flow of the skill phases, I'd have to edit every single skill script. It also causes a lot of repetitive code in the individual skill scripts while the base script is tiny. The one-time script being the small one is no fair. <br>
|
||||||
|
<br><center>
|
||||||
|
<img src="/static/img/ent/supermethod_old.png" alt="(image: GDscript code using old method)"><br>
|
||||||
|
</center>
|
||||||
|
(You can see the old method's code on <a href="https://pastebin.com/DDu1Q7Q6">Pastebin</a>.)
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<h2>the new way </h2><br>
|
||||||
|
<br>
|
||||||
|
Instead, I can bring all the repetitive steps into the base class, sandwiching in a subfunction where the custom effects would take place. Now I only need to add what makes my skill unique inside the subfunction. <br>
|
||||||
|
<br>
|
||||||
|
I named the subfunction after its main function and added an underscore to the front. So in the base script, I fit <code>_functionality(user, action_target)</code> into <code>functionality(user, action_target)</code>. Then the individual skill scripts only need the subfunction, none of the other repetitive code from before. The subfunction is empty in the base class and filled with unique code in the child classes. Since skills inherit, the unique <code>_functionality</code> subfunction will be called automatically from the base script's <code>functionality</code>. <br>
|
||||||
|
<br>
|
||||||
|
<center>
|
||||||
|
<a target="_blank" href="/static/img/ent/supermethod_old.png">
|
||||||
|
<img src="/static/img/ent/supermethod_new.png" alt="(image: GDscript code using new method)" width="500" height="355.36">
|
||||||
|
</a>
|
||||||
|
</center>
|
||||||
|
(You can see the new method's code on <a href="https://pastebin.com/teeYn9jP">Pastebin</a>.)
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<h2>problem solved! </h2><br>
|
||||||
|
<br>
|
||||||
|
The base script holds all the lengthy code in one place, while I only need to write the unique effects in all the hundreds of skill scripts. That saves me time making, refactoring, and maintaining skills. Yay. <br>
|
||||||
|
<br>
|
||||||
|
|
@ -0,0 +1,49 @@
|
|||||||
|
<!--200806,200903-->
|
||||||
|
<h1>pretendOS - a game inspired by windows XP </h1>
|
||||||
|
november 26, 2020<br>
|
||||||
|
#sideproject<br>
|
||||||
|
<br>
|
||||||
|
Getting started with blessfrey's AI was overwhelming, so I took a break and worked on a new game. I didn't get very far. It's not even on my hard drive anymore. It's cute, though. <br>
|
||||||
|
<br>
|
||||||
|
Unlike blessfrey, where coding takes up 90% of the effort, pretendOS is mostly graphical assets + sounds with barely any functionality. It's a UI game, after all. Since it's a game requiring a completely opposite skill-set, I was able to make progress on <i>something</i> while reading up on game AI. I also got to work on new things like particle effects. <br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<h2>Inspiration </h2>
|
||||||
|
<br>
|
||||||
|
<h3>Windows XP</h3>
|
||||||
|
The game takes strong aesthetic inspiration from Windows XP. It almost fits the 90s vaporwave trend, but I'm too young to really remember Windows 95. I obviously have nostalgia for it as my first operating system (as far as I remember - I was a toddler then), but I mostly knew it as the Lego Island machine propped up in the dining room. As my family upgraded computers over the years, Windows never really impacted me as anything more than something that can run some videogames and whatever popular web browser of the time. That is, until Windows XP. <br>
|
||||||
|
<br>
|
||||||
|
XP really hooked me. It was the first operating system I spent as much time exploring as I spent using software. XP's edition of Paint was my favorite yet, I loved fiddling with the themes and accessibility options, especially Microsoft Sam, Rover, and Clippy. I started watching YouTube videos on how to use the Command Prompt. XP was just fun to use. I've found better desktop environments and operating systems since, but I never got over that aesthetic: smooth, blended graphics with hard pixel edges in 32-bit color with an alpha channel, right at the cusp between pixel art and vector graphics. Vaporwave for me is Luna olive green, or the "Fisher-Price interface" as Ars Technica users called it. <br>
|
||||||
|
<br>
|
||||||
|
<img src="/static/img/ent/wikipedia_luna.png" alt="(image: Wikipedia excerpt: Critics who did not like the theme characterized it as a 'Fisher-Price interface'.)"><br>
|
||||||
|
(screenshot from <a href="https://en.wikipedia.org/wiki/Windows_XP_visual_styles">Wikipedia</a> - referencing articles from <a href="https://web.archive.org/web/20091008081626/http://www.pcworld.com/article/117427/full_disclosure_your_take_on_windows_worst_irritations.html">PCWorld</a> and <a href="https://arstechnica.com/information-technology/2014/04/memory-lane-before-everyone-loved-windows-xp-they-hated-it/">Ars Technica</a>)<br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
There were a few other experiences behind the game, too. <br>
|
||||||
|
<br>
|
||||||
|
<h3>Mother's Day E-Card</h3>
|
||||||
|
I finally got my husband to try Godot Engine, and he used it to make a digital Mother's Day card for his mom. (Cute.) Opening the card displayed a 3D heart (a "cardioid") that bounced to the beat of a song while 2D cardioid particles rained down. It was all programmatically generated using geometry instead of 3D + 2D assets, so the application was very small. He made the graphics side of things look really interesting, and I wanted to play around with particles, too. Just...not as fancy;; <br>
|
||||||
|
<br>
|
||||||
|
<h3>Secret Little Haven</h3>
|
||||||
|
I also just played a cute pretend OS game on itch called <a href="https://ristar.itch.io/secret-little-haven">Secret Little Haven</a> It's quite short, very story-driven, and kind of buggy, telling Alex's struggle with gender identity through IMs. Honestly, pretend OS games and coming-of-age chat sims are nothing special, but Secret Little Haven's believability makes it really stand out. Instead of defending against cheesy + inaccurate deep web hacking attempts, you use an in-game terminal to get around child locks set by Alex's dad. Those terminal puzzles are the most realistic and relatable hacking I've seen in these games. SLH isn't super sophisticated or in-depth, but it shows how cute and believable a pretend OS game can be. I'd love to make a little environment like that, that's as fun to fiddle around with as XP. <br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<h2>pretendOS </h2>
|
||||||
|
<br>
|
||||||
|
So what's pretendOS? Like I said, not much besides pictures. I only worked on it for a few days. <br>
|
||||||
|
<br>
|
||||||
|
What really bogged it down was Cat Chat. I wanted an AI chat personality that talked like a cat. I could do that, but that kind of thing takes a lot of time, when this project was really just procrastinating working on AI. <br>
|
||||||
|
<br>
|
||||||
|
The rest is cute, though. The icon winks when you click it, the cursor's kinda 2000s-free-animated-cursors, and it's got those Fisher-Price colors and bubbly sounds everywhere that I liked as a kid. <br>
|
||||||
|
<br>
|
||||||
|
<center>
|
||||||
|
<a target="_blank" href="/static/img/ent/pretendOS_catchat.jpeg">
|
||||||
|
<img src="/static/img/ent/pretendOS_catchat.jpeg" alt="(image: pretend desktop with a Cat Chat application)" width="500" height="281.25">
|
||||||
|
</a><br>
|
||||||
|
</center>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<h2>the future</h2>
|
||||||
|
<br>
|
||||||
|
It's cute. I'd like it to be finished in some way. I went ahead and cloned my old repo. Maybe I'll fill it out with more applications during a game jam or something or at least finally get that cool Secret Little Haven curved screen shader working. <br>
|
||||||
|
<br>
|
@ -0,0 +1,13 @@
|
|||||||
|
<!--210318,200806-->
|
||||||
|
<h1>web development resources</h1>
|
||||||
|
january 21, 2021<br>
|
||||||
|
#accessibility #color #css #html #webdesign<br>
|
||||||
|
<br>
|
||||||
|
I'll collect frequently used resources for web design here. <br>
|
||||||
|
<br>
|
||||||
|
<ul>
|
||||||
|
<li><a href="https://cssgrid-generator.netlify.app/"><b>CSS Grid Generator</b></a> - build a basic CSS Grid by changing the number of columns + rows, adjusting their dimensions, and adding containers. </li>
|
||||||
|
<li><a href="https://validator.w3.org/nu/#textarea"><b>W3's Nu Html Checker</b></a> - automatically check the validity of your HTML + CSS. </li>
|
||||||
|
<li><a href="https://webaim.org/resources/contrastchecker/"><b>WebAIM's Contrast Checker</b></a> - check whether your font color contrasts well against your background color, at least in the eyes of a computer. </li>
|
||||||
|
<li><a href="https://coolors.co/75dbcd-c9dbba-dcdba8-f5cda7-faa381"><b>Coolors</b></a> - select and lock colors then generate palettes by pressing space. Click on a color to view shades + tints, so you can tweak contrast without losing hues. </li>
|
||||||
|
</ul>
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,20 @@
|
|||||||
|
<!--220310,220224-->
|
||||||
|
<h1>goalless games </h1>
|
||||||
|
january 14, 2022<br>
|
||||||
|
#game-design #philosophy<br>
|
||||||
|
<br>
|
||||||
|
<h2>goalless games </h2><br>
|
||||||
|
Some developers are happy to make loose, meandering sandbox games without no true win or fail state. The concept of goalless games is controversial among semantic people, but I feel that the genre with Garry's Mod and all the dressup games and physics simulators is enviably successful if not totally legitimate. It's not like the overwhelmingly popular Minecraft and old RuneScape don't share one foot in this genre, either, with their relative lack of direction and gameplay dominated by self-driven goals. I don't even feel like a central story or main objectives would improve these games (especially after watching Minecraft tack on some tedious hunger mechanic and an awkward final boss). <br>
|
||||||
|
<br>
|
||||||
|
<h2>my need for structure </h2><br>
|
||||||
|
I'm just not a goalless game designer myself. <br>
|
||||||
|
<br>
|
||||||
|
It sounds nice to provide an open world where the player can set out after his own goals without the dev placing horse blinders on his head. In reality, though, a game designer can't force a player to share the game's goals in the first place, so there's no need to purposefully design a game to be goalless. For me, I feel like neglecting to set a game's goal reflects a lack of a game development goal. A goal is helpful to not only the player but also to the developer. A central vision of the game's progression will imbue each piece of the game with more purpose and help them fit together more seamlessly as a whole. It's a safeguard against filling a game with pointless, incongruent clutter at whim. Obviously not every developer needs a goal-oriented approach, but I work better with one. <br>
|
||||||
|
<br>
|
||||||
|
No matter what philosophy the game designer has, though, a player will do what he wants to do, even if it has nothing to do with the goal of the game. For example, roleplayers are prominent members of MMO communities, and they might never max out a character or finish the main storyline. They throw out all the game designers' work and focus on finding the perfect backdrop and acting out their own scene instead. There are plenty of screensaver simulators and 3D chat servers out there for them, but they turn up in "real" goal-driven games, too. There are touches of this aberrant behavior in everyone who doodles with bullet holes, names their character something funny to harvest out-of-context dialog screenshots, or hoards a useless item. <br>
|
||||||
|
<br>
|
||||||
|
<h2>players do whatever they want anyway </h2><br>
|
||||||
|
So in a way, game designers really don't need to design a goalless game. They can trust players to forge their own fun from even the most rigid hallway simulator. In my opinion, deliberately not designing goals runs the greater risk of making players too lost, bored, or overwhelmed to find their own fun or not even finding incentive to try the game in the first place. A better approach is in the middle, building towards a purpose while taking a tip from goalless games by filling the world with choices, interesting tools, and interactibles that are fun for fun's sake. At the end of the day, though, obviously do what works for your players! <br>
|
||||||
|
<br>
|
||||||
|
Last updated January 12, 2022
|
||||||
|
<br>
|
@ -0,0 +1,28 @@
|
|||||||
|
<!--200806,201224-->
|
||||||
|
<h1>designing blessfrey's first demo </h1>
|
||||||
|
january 20, 2022<br>
|
||||||
|
#demo <br>
|
||||||
|
<br>
|
||||||
|
<h2>my goals </h2><br>
|
||||||
|
The systems and game mechanics in Blessfrey are mostly present and functional, so I feel like it's a good time to practice releasing, hosting, and supporting a game. The first release will just be a tech demo. I want it to showcase the features in the game, have structure and goals, and have some of the same gameplay feel of future releases. Hopefully it's fun, but we'll see when it's all bolted together. <br>
|
||||||
|
<br>
|
||||||
|
The core of the game is curating from a wide variety of skills and combining them into a viable skillbar. I'm going to try to get away with skimping on content to put more focus on the release process, but skill variety deserves the most attention. I'll shoot for 30 skills for now, or in other words, 3-4 skillbar's worth without repeats. <br>
|
||||||
|
<br>
|
||||||
|
<h2>finding structure </h2><br>
|
||||||
|
I could release a <a href="https://www.blessfrey.me/diary/entries/220114">goalless demo</a>, but I fear people wouldn't explore long enough to discover any depth. If I could rip a level from the finished game, structure and goals would already be built in, but this demo will be more or less original content. I'll have to build structure as I go along. <br>
|
||||||
|
<br>
|
||||||
|
This demo should showcase more gameplay than story and world, so it should probably have more in common structurally with a tutorial room. I'll just fallback on the tutorial bingo approach again and hope it never loses its luster. Bingo is a great excuse to list gameplay prompts for the player while giving them some choice on how to proceed. It's also a game in itself, so I don't need to take my game design much further: every full consecutive set of activities is a little win with a prize and a full board bingo is a big win with a big prize. I'm not going to worry about a fail state, so the demo can be more like a playground than a challenge. <br>
|
||||||
|
<br>
|
||||||
|
The demo shouldn't just resemble a tutorial - it should <em>be</em> a tutorial. It is the first time unguided players will be able to play, so there needs to be lots of in-game guidance. I'll test a stealth tutorial and see how it compares to a more conventional tutorial. Ideally, the tutorial should be easy enough for veterans to breeze through without noticing it's even there, but nudges and tips are given to those who need it. It'd be even cooler if the tutorial doesn't break immersion, coming from level design, believable dialog, or from passers-by setting an example. Maybe it's a terrible idea and there's a reason why all games start out feeling like post No Child Left Behind era public school, but I want to try it out. Since I have the <a href="https://www.blessfrey.me/diary/entries/210402">event system</a>, it's just a matter of listening for lack of movement, unnecessary key spamming, stalling in quest progress, or any other apparent failures and sending someone over to help out. <br>
|
||||||
|
<br>
|
||||||
|
<h2>world and story </h2><br>
|
||||||
|
I won't be taking an exerpt of the story or using fully realized locations, but the demos should at least conceivably fit within the full game. This time, I'm just going to use two of the major supporting characters, Chloe and Night, (both healers) and enough of the school to service a demo. The courtyard is a relatively safe area with some easier enemies to the far side of the map, guarding collectible items. If you die, the checkpoint is in this field, managed by Chloe. The nurse's office is an item shop, run by Night. The storage room is a maze full of monsters and treasure. The areas, monsters, and dialog will be more involved than anything I've ever tested before, and it'll include a stress test skill. Let's see how the engine handles it! <br>
|
||||||
|
<br>
|
||||||
|
<h2>ugly </h2><br>
|
||||||
|
Of course, it's going to be pretty rough overall. It's the first alpha build released to the public, so there are a few bugs I know are still in there, some capable of crashing the game. Also, there are no frills when it comes to the audiovisual side - barely any animations, all audio is from the public domain, effects are carelessly placed, and so on. Like Yandere Dev says, "all assets are placeholder." :) <br>
|
||||||
|
<br>
|
||||||
|
Anyway, back to working on the demo! <br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
Last updated January 20, 2022
|
||||||
|
<br>
|
@ -0,0 +1,30 @@
|
|||||||
|
<!--220127,210708-->
|
||||||
|
<h1>applied skills vs. applied keywords </h1>
|
||||||
|
march 24, 2022<br>
|
||||||
|
#skill <br>
|
||||||
|
<br>
|
||||||
|
<h2>monitoring skills </h2><br>
|
||||||
|
In Blessfrey, entities like to know where effects come from. <br>
|
||||||
|
<br>
|
||||||
|
For one, knowing the source of a killing blow allows for cute little feedback like the Binding of Isaac's death screen. <br>
|
||||||
|
<center><img src="/static/img/ent/bindingofisaac_deathscreen.png" alt="(image: the death screen in Binding of Isaac looks like a torn diary page, showing a crude illustration of what killed him and a vague description of the area where he died.)"</center> <br>
|
||||||
|
More importantly, it allows mechanics to interact more freely. If the bleeding status effect on a character is linked with the Cat Scratch skill, used by Bad Cat, that opens up possibilities. Maybe there could be a skill that reflects target's status effects back unto the original dealer. Maybe there could be an effect that negates skills from animal sources. Maybe a skill could heal an ally for even more you are both suffering from the same skill. The source is important if I want to do anything beyond the basic "cure bleeding." <br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<h2>how should the data be bundled? </h2>
|
||||||
|
Skills themselves contain keywords, references to the user and target, and any data that could possibly be needed. It's immediately obvious to just directly tack the still onto the back of an array on the target. It can never be that easy, though. Skills contain keywords, all of which have different durations and aren't necessarily in the tree at the same time. Also, there's not a lot of applications for cycling through all the applied skills, besides making something like Guild Wars's <a href="https://wiki.guildwars.com/wiki/User_interface#Effects_monitor">effects monitor</a> and <a href="https://wiki.guildwars.com/wiki/User_interface#Damage_monitor">damage monitor</a>. <br>
|
||||||
|
<br>
|
||||||
|
<center><img src="/static/img/ent/guildwars_skillmonitor.png" alt="(image: Guild Wars's UI. At the top-left corner are all the icons of skills actively applied on your character. Along the left side of the screen is a list of the sources from which you recently took damage.)"</center> <br>
|
||||||
|
<br>
|
||||||
|
There aren't a lot of cases where the game would be looking to remove any instances of "Cat Scratch." However, there would plenty of skills looking to remove bleeding or status effects. <br>
|
||||||
|
<br>
|
||||||
|
It's a more direct approach to treat keywords and skills separately in the code. Characters can keep an array with references to the applied keywords instead of applied skills. The other data can be bundled with it as an array: [keyword, source, [tags]]. <br>
|
||||||
|
<br>
|
||||||
|
The keyword is the important part because, in bleeding's case, it's the main culprit for the actual health degeneration of the character. It also has its own duration, so there's no conflicts with holding onto bleeding even if all other keywords from its skill expiring. The source is just a noncommittal reference, so it and its data can be accessed as needed. Maybe the keyword and skill contain everything needed, but I'm going to pull out some frequently accessed attributes into tags to see if that helps with efficiency. <br>
|
||||||
|
<br>
|
||||||
|
<h2>it'll be better this way </h2>
|
||||||
|
It's always a good idea to separate keywords from their governing skill. When skills and keywords are decoupled, each can run their course without interference. <br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
Last updated April 7, 2022
|
||||||
|
<br>
|
Loading…
Reference in New Issue