Jump to content
  • Advertisement


  • Content Count

  • Joined

  • Last visited

Community Reputation

13142 Excellent


About JTippetts

  • Rank

Personal Information

  • Role
    3D Artist
  • Interests


  • Github

Recent Profile Visitors

62557 profile views
  1. JTippetts

    OOP is dead, long live OOP

    I'd upvote this even more if I could. Thanks, Hodgman, you're a beautiful person for going above and beyond like this.
  2. JTippetts

    Drunks Standing Around Punching Each Other

    This is how it has been until this journal post. I think I just let myself be head-faked too badly by the auras. I ran into the case where GC was able to walk through a shaman's damaging aura without taking a hit, and it felt too weird so I busily set about over-complicating things, as is my normal reaction to weird stuff.
  3. JTippetts

    Combat Stats

    I am currently working on combat stats. I've worked on them before, but never really fleshed out a system I've been satisfied with. For the most part, I've gotten by with half-assed, or at best three-fourths-assed systems that always kinda change as my requirements change, but with no clear structure or rules. All of that half-assed code and system is gone now. In its place is basically a spreadsheet. In fact, it would make sense to define the data files underpinning it all as an actual spreadsheet. There are problems with that, though; namely that I'm terrible at spreadsheets, since I've never really used them. Sure, I have LibreOffice Calc on my laptop, and sure I've even fired it up once or twice. But I find the spreadsheet format to be clunky, and I really don't want to write a bunch of code to pull fields and expressions out of an XML document. So, I'm writing my data files by hand. It's actually not bad so far, though I can see the need to write a tool for it in the future. So, stats. As a jumping off point, I want to implement a system similar to Path of Exile. My goal is to support player-side stats with a similar level of depth/and complexity, and to also support monster-side stats with the ability to scale enemies based on player level. In order to support rapid iteration and testing of concepts, the stats need to be mostly defined in data. Some underlying concepts (damage types, damage scaling, etc...) are hard-coded, but the stat relations are defined in JSON files that are specified as attributes of the CombatStats component that all combat-enabled units possess. If you are not familiar with Path of Exile, their system is a 3-stat based (Strength, Intellect, Dexterity) system, with 4 types of damage mitigation: armor (to mitigate physical), evasion (to dodge stuff), energy shield (to absorb damage) and resistances (for elemental damage types and chaos damage). Dexterity contributes to Evasion, Intellect contributes to Energy Shield, and Strength contributes to Life (which is stupid, imo; it should contribute to Armor instead, for the sake of balance). Body Armor, Boots and Gloves all have score requirements for Str, Dex and Int; there are high-strength armors that provide high Armor rating, high-int armors for Energy shield, etc... So in order to equip certain things, you need sufficient quantities of the relevant stat(s). Classes are also aligned with the three base stats, with one character class per stat, and one character class for each hybrid (ie, Templar is a Strength/Int hybrid class). Resistances stand outside of the 3-stat system, with each resistance being increased by mods on gear or from the passive talent tree. They are a percentage-based mitigation, with each having a maximum cap (starts at 75%) that can be raised through talents or equipment. Aside from the 3 stats, there are other ways to build a character. Simple passives such as +%increased Spell Damage, or +%increased Melee damage, and other simple number increases are available. More importantly, there are Keystone talents available on the talent tree, and special mods available on certain unique items, that provide more complicated stat alterations than simple number increases/decreases. For example, the Avatar of Fire keystone dictates that 50% of all physical, lightning or damage the player deals is converted to Fire damage, and the character deals nothing but Fire damage. This has a huge effect on how the rest of the character can build. In conjunction with an item that provides 50% of physical damage is converted to Fire, it means you can build a straight melee brawler who can deal massive physical damage, but have all of that damage convert to Fire with a possibility for Fire damage to ignite targets, causing a DoT Burn effect. So, in light of the fact that I would like to build something similar, I've come up with a fairly simple system. I have implemented a StatSet structure, which owns an unordered_map of stats. The stats are keyed by a string hash, so that they can be requested either by name or by the hash of the name. Requesting by name is useful for debugging/testing, and requesting by the hash is marginally more performant. A Stat is implemented simply as a linked list of mods. In earlier versions, I implemented Stat as a value plus a list of mods, where each mod was either a flat addition to the stat, or a multiplier. The final value of the stat, then, was calculated as (Base + (SumOfFlatMods)) * (1.0 + SumOfMultMods) In this setup, you can have mods that add a flat amount (say, +10) to a given stat, or you can have mods that increase a given stat by some percentage. Specifying a Mult mod as 0.5, for example, is tantamount to increasing the stat value by 50%. In the current iteration, I have done away with the Base value, since it's basically just a Flat mod in disguise. Now, a Stat is simply a list of mods that either apply a flat additive value or a multiplier. These mods can either be simple numeric values, or they can be derived from other stats. I have also added a mod type of Scale, which can be set to scale the final result of a mod by a value. This Scale value is normally 1, so that it has no effect, but if a mod of type Scale with a value of 0 is applied, then it can set the stat to 0. This facilitates stats such as the previously mentioned Avatar of Fire, by applying a mod of 50% to the ConvertPhysicalToFire (and other conversion stats), and by supplying a mod of 0 to the Scale field of the other damage types, as an example. Thus, a Stat's value is finally calculated as ((SumOfFlatMods) * (1.0+SumOfMultMods))*ScaleMod. (Scale mods are not calculated as sums, but rather as a direct replacement. You can specify multiple Scale mods, but only the last one encountered will apply.) Also allowed are mods that can fix the value of a stat to some specified maximum or minimum. By this means, I can cap a stat (for example, a resistance capped at 75%) and use another stat to specify the cap value (resistance max). I can also cap the bottom end of a stat, for example by clamping a resistance to a -1.0 value, meaning that you can't go any lower, and it's not possible to do more than double damage against a target due to negative resistance. This basement cap can, of course, be modified, opening up the potential for builds that are very powerful against negative-resistance enemies. These various relations are specifiable in a JSON file. In fact, I can load multiple JSON files into a StatSet. This allows me to break off common functionality into one file, and load class-specific files per-unit. The syntax of my data files is still a little bit clunky, but here is an example: { "MajorStatPerLevel": [{"Type": "Flat", "Value":5}], "MinorStatPerLevel": [{"Type": "Flat", "Value":2}], "Bravado": [{"Type": "Flat", "Value": 5}, {"Type": "StatFlat", "ModType": "CalcLinear", "Stat": "Level", "Scale": "MajorStatPerLevel"}], "Arrogance": [{"Type": "Flat", "Value": 3}, {"Type": "StatFlat", "ModType": "CalcLinear", "Stat": "Level", "Scale": "MinorStatPerLevel"}], "Cunning": [{"Type": "Flat", "Value": 3}, {"Type": "StatFlat", "ModType": "CalcLinear", "Stat": "Level", "Scale": "MinorStatPerLevel"}], } In this example, I define two 'utility' stats, MajorStatPerLevel and MinorStatPerLevel, as flat values (5 and 2). Then I define 3 core stats (Bravado, Arrogance and Cunning, which are goblin-ish reskins of the more familiar Strength, Int and Dex). These core stats are defined using 2 mods apiece: a flat base mod, and a mod that applies a flat amount calculated as Level multiplied by a scaling factor. (The Level of a combatant is just another stat; in this way, by changing the Level stat, I 'automatically' get level-based scaling.) This snippet would scale Bravado higher than Arrogance and Cunning, and so would be appropriate for a Bravado-centric character class. A different file could be loaded for Arrogance or Cunning-based classes. (I could, of course, provide stats for hybrids, but I think 3 classes is quite enough for a solo developer to try to tackle.) This setup means that with each 1 increase in experience Level, the unit gains 5 Bravado and 2 of Arrogance and Cunning, on top of a base of 5, 3 and 3. To get the current value of Bravado, I can simply call StatSet::GetStat(name)::Get(), and the scaling occurs depending on the value of Level. To implement something along the lines of the Avatar of Fire keystone, I might do something like this: { "AvatarOfFire": [{"Type": "Flat", "Value": 0}, {"Type": "Max", "Value": 1.0}], "AvatarOfFireConversionScale": [{"Type": "Flat", "Value": 0.5}], "ConvertCrushToBurn": [{"Type": "StatFlat", "ModType": "CalcLinear", "Stat": "AvatarOfFire", "Scale": "AvatarOfFireConversionScale"}], "CrushDamageFinal": [{"Type": "StatScale", "ModType": "CalcOneMinusStat", "Stat": "AvatarOfFire"}], } In this case, Avatar of Fire acts as a simple flag. If 0, no real effect is applied. The CrushToBurn conversion is ignored in damage calculations since it's equal to 0. A damage value of type Crush will be processed, amplified using the CrushDamage stat (not shown here, but all damage types have one which is used to boost damage based on things such as +%increased Crush damage), and then scaled by the value of CrushDamageFinal, which is calculated as (1-AvatarOfFire)). Since AoF is 0, this amounts to a simple multiply by 1 operation. However, simply by adding a Flat mod valued at 1 to the AvatorOfFire stat, the behavior of the other stats changes. Now, the ConvertCrushToBurn stat is non-zero, with a value of 50%, so in the damage calculation after CrushDamage is applied, then 50% of the intermediate value is converted to a damage record of type Burn. Any remaining damage of type Crush, then, is scaled by CrushDamageFinal which is now equal to 0 (ie, 1-AoF), meaning that any Crush damage remaining becomes effectively 0. By supplying a similar set of mods for the other damage types, I have effectively modeled the behavior of PoE's Avatar of Fire passive talent. StatSets can be merged. For example, I can have the base StatSet for a character, then each piece of equipment can have its own mini StatSet that contains mods for just the stats it's interested in modifying. For example, a sword could have a mod for the JabDamage stat that increases Jab damage by 15%. When an attack is made using that sword, the combat system would copy the character's base stat set, then merge the swords stat set into it to get the final working set. This allows me to have equipment that can modify any of the stats, including flag-type stats such as Avatar of Fire. Thus, I could do things like "Equipping this armor grants Avatar of Fire". Removing the armor removes that particular set of stats from the merge. The system still has some jankiness. It's pretty clunky writing JSON files (my poor " key), plus the syntax of the various mods leaves quite a bit to be desired. Also, for the moment, the ability to merge StatSets applies only to simple numeric mods (can't use equipment to apply mods that rely on other stats, for example.) I don't see this being a problem at the moment, but it could be in the future. Still, for the relative simplicity of the underlying structure, it's given me quite a lot of flexibility in structuring the stat systems. I've got level-scaling of monsters, monsters that scale differently based on their JSON spec files, monsters with randomized mods, etc... It's pretty cool how it's all coming together. (Forgive the potato-quality of the stat sheet in the accompanying screen shot. This crap is way too much in flux right now for me to waste time building a custom UI widget yet.)
  4. JTippetts

    Drunks Standing Around Punching Each Other

    In practice, in my own personal playtesting plus some informal testing by a couple others, it feels weird to have it structured the way I describe. The number one source of weirdness seems to be the fact of DoTs ticking while other units are acting. This includes ground effects. In fact, it's almost infuriating to be having all this damage occur while the player can't do anything about it, and it makes having DoT damage sort of pointless. The whole point of a DoT is to provide damage but still give the target of the damage options and time in order to mitigate it. But I'm finding that the vast majority of DoT damage occurs during periods when the unit is not in control, making it impossible to mitigate except in advance. I'm considering reverting DoTs and ground effects to only ticking when the unit acts, but have auras tick/apply whenever a unit is in range (whether the unit is acting or the aura owner is acting.) This feels better, even if it's technically inconsistent. As long as it's understood up front that's the rule, maybe I can get away with it. If that feels weird, my fallback will be to have the auras apply only when the owner is acting, and just handwave away the inconsistencies.
  5. JTippetts

    My brief tour through 3d engines

    There is a Blender exporter for Urho3D at https://github.com/reattiva/Urho3D-Blender if you want to give that engine a second look. I pretty much use Urho3D exclusively these days, and find there is not much I can't do with it.
  6. JTippetts

    Entity Component Systems and Data Oriented Design

    You'll see that pretty much anytime someone posts an article or discussion or even mentions the term ECS. It's a lightning rod for smug disdain and towering superiority from people on both sides of the debate trying to put out an air of being a crusty old experienced veteran who knows his shit. They'll denounce the other side using some vague, incomprehensible generality intended to make them look smart, but the real details and specifics tend to stay pretty sparse. It's just another holy war.
  7. Just out of curiosity, if you don't want your strings stored in the exe, and you don't want to read them from a file, where exactly do you plan to get them from?
  8. JTippetts

    Fixing your Timestep and evaluating Godot

    I had a similar issue with retrofitting Urho3D to use a fixed timestep. I'm not doing that now, but when I was doing it my solution was to implement a special component to store last and current transforms and work through that. It worked well enough, but my games tend to not use physics, and as you mentioned, physics is where the madness lurks. Something built into the native Node would be better, I think, but would require modifying the library.
  9. A lot of years ago, I witnessed a fascinating exchange between three drunk college students of my acquaintance. The setting was a bonfire kegger in the hills, the setup was a masculine test of bravado and toughness, and the payload was funny as hell. These three fellows (who were all so drunk, they could barely stand up) thought it would be funny to see who was the toughest by punching each other in the face. No, it wasn't a fight. Blows weren't exchanged, fast and furious and drunkenly inaccurate. Instead, each would take a turn standing there motionless while one of the other two were allowed one shot. The receiver could spend a few minutes fortifying himself as he thought necessary (many beers were consumed), then he had to stand motionless while the other two guys did their level best to knock him down. In the end, it was pretty funny to watch. The entire exchange took probably an hour, and I think that if the participants had been less drunk, some real damage might have been inflicted. As it was, it was a comedy of errors: wild swings, missed punches, a couple cuts and a black eye. It was stupid, the kind of stupidity you might expect from such a setting, but what was remarkable to me is that each of the three respected the rules. When it was your turn to get hit, you took whatever was dealt out. This absurd scene came back to me (no, I was not a participant, though I was invited to partake) yesterday as I was working on Auras in Goblinson Crusoe. Auras are character-centered area effects that can apply any of a number of effects to other combatants within the radius: degens/damage, stat buffs or debuffs, slows or speeds, etc... They suffer from much of the same disjointed weirdness as I had to deal with when implementing damage-over-time and heal-over-time effects, all stemming from the abstraction of thinking of a turn in a turn-based game as having some sort of temporal coherency. You see, since the beginning of this project I have thought of a turn in terms of everything ostensibly happening at once, even if the actual actions are split out into seperate sections per combatant. Enemy A goes, then GC goes, then Enemy B, and so forth until the end of the turn and it all starts again, but it all happens 'at the same time'. However, introducing Auras is yet one more thing to come around and illuminate the holes in such an abstraction. Consider the case where Enemy A is wearing an Aura of Rotting Degeneration, which applies a damage-over-time payload of Rot damage (I have revised my damage types; more on that later) to all in the radius. During his turn, Enemy A runs at Goblinson Crusoe, takes a wild swing with a rusty sword then, laughing, runs away to try to put some space in between. Then, during Goblinson Crusoe's turn, GC runs after the laughing enemy to deal his own drubbing. There are a few ways I can deal with the Aura in this case: 1) Apply the aura's effect only whenever the enemy bearing the aura acts. In this case, the aura will apply Rot damage to GC every step, starting from when the enemy draws near enough to place GC within the radius. As soon as the enemy retreats far enough away, stop applying the aura. In this situation, when GC takes his turn and runs after the enemy, the effects of the Aura will not be applied because it is not the enemy's turn to act. 2) Apply the aura's effect only whenever the target/victim acts. In this case, when the enemy runs of to GC, smacks him, and runs away, the aura will not apply an effect. However, when GC chases after the enemy, as soon as he reaches aura range the aura will start hitting him with Rot damage. Of course, both of these cases seem absurd on the face. The aura should be hitting GC whenever he is within range to be hit, so whether the enemy is approaching or evading, or whether GC is pursuing, he should be getting hit with the degen. Which leads to the third case. 3) Apply the aura's effects both when the enemy is acting AND when GC is acting. So while the enemy approaches, smacks with a sword, and runs away, GC takes Rot damage at each step. Then, when GC pursues, once he comes back within range, he starts taking Rot hits again. Of course, the drawback to this third scenario is this: if all of this is ostensibly taking place "at the same time", then it is likely that GC will take more Rot hits during the space of the turn than he realistically should. For example, say a turn is 10 steps. Say that for 8 of those steps, GC is within range of the aura while the enemy is attacking. Then, say that for 5 of them he is within range of the aura while he is pursuing. That comes out to 13 hits of Rot during a 10 step turn, or 3 more than he should possibly be able to take. Chasing a solution to this led me down the usual rabbit-hole of kludges, attempting to total up times during the space of the turn when GC should take the hit, yada yada yada. Resolving these temporal idiosyncracies is a recipe for madness. And so, following the example of those drunken college boys, I threw the temporal abstraction right out the window. I no longer think of fights in a GC combat as happening as a melee, with actions being performed at the same time during a round. There just seems no way for me to happily resolve the contradictions. Instead, now I think of a GC combat as a whole bunch of drunk college students standing around, taking turns throwing punches at each other. When it's your turn to stand, you stand and take what you get coming to you, and if it knocks you out then that's the game. And when its your turn to act, you chug a beer, ball up your fist, and do your level best to knock somebody else right the #*@! out. However, if I switch abstractions like this, then I need to revise how I do DoTs. You see, if the combatants really are just waiting around and taking turns, then if GC has a DoT in place, he should be taking that DoT at every step of action for all the other combatants, until the DoT runs out. This is a change that I am definitely going to need to playtest soon; sadly, my usual playtesters aren't really available anymore and that means I might need to finally, after all these years, step outside the circle of my immediate friends and family for some feedback.
  10. JTippetts

    sprite sheet

    Please do not use these question threads as progress updates, or to ask general, wide-ranging subjective advice about design. Once a question has been answered, if you have further questions unrelated to the original question, start a new thread. Confine progress updates to a blog; you have been warned about that before.
  11. JTippetts

    sprite sheet

    Im guessing you made your sprite sheet with actual lines between the frames, which are being picked up by texture filtering. Solution: erase those lines. They serve no practical purpose. Also, check your filter settings. If you are drawing your sprites without scaling, and if there is the possibility of bleeding from adjacent frames even with the lines erased, nearest filtering is the way to go.
  12. JTippetts

    sprite sheet

    in that case, your texture coordinates are still wrong. You should be using some variation of (0,0), (0.125,0), (0,1), (0.125,1) to snip out the first sprite.
  13. JTippetts

    sprite sheet

    You are passing values like (0,0), (0.125, 0), (0.125,0), (0.125, 0.125) to glTexCoord, which is fine if your sprite sheet is laid out as 8x8 sprites. Since we can't see your sprite sheet, we can't know for sure if that is correct; I suspect, though, that your sheet is actually laid out as 4x4 sprites. If that's the case, you would use coordinates like (0,0), (0.25,0), (0.25,0), (0.25,0.25) instead. Basically, each sprite in the sprite sheet would be sized 1.0 / NumberOfSprites, so in a sprite sheet that holds 4x4 sprites that comes out to 0.25, whereas a sheet that holds 8x8 sprites works out to 0.125.
  14. My argument was purely about perception. I didn't read the thread in question, because in my experience such things are always the equivalent of monkeys slinging feces, and nobody in the history of ever has been hit by primate shit and thought, "you know what? Maybe I'm wrong about my opinions." But if I were a newbie, and I saw the unread content listing just absolutely dominated by crap slinging from people whose leftist dogma I typically find quite repugnant, like it has been since that thread was started, why would I be willing to give the site a second chance? More importantly, why would anyone trying to build up a site like this want to potentially drive away participants? If having the "discussions" is so important, at least maybe they should be excluded from the unread content list by default, so that people who don't want to sift through the same tired old arguments don't have to.
  15. As a conservative, if I were just coming to the site now as a fresh youngster looking to learn a new hobby, and I clicked the unread content button, only to see an entire page of either posts in a Trump bashing thread, or upvotes/down votes in the same thread, as I saw a number of times before that thread was closed, I would leave and probably not return regardless of the actual character of the discussion in that thread. I wouldn't stop and think "gosh, maybe this will be a hearty, respectable discussion where I can argue the nuances of my ideological opinions in a comfortable and safe environment with no danger of a heavily one sided pile on." Nope, I would think "oh great, yet another social media shit fest where I can go to be called a racist or a bigot, be accused of wanting to turn the country into the handmaid tale, be accused of bitterly clinging to my guns and bible, or be called deplorable." And I would check out without a backward glance; sadly, such is the level of toxicity of social media right now. Now, I have been here for over 15 years so I know the community is much better than that, but that is knowledge gained over half my lifetime. A newbie won't have that; he will just have the contents of the unread contents page to go by, and in this day and age there are a thousand different sites a young conservative can choose to go to in order to either self-censor, or be called a -phobe Trumpist. Why would he want to wade into that here, when he just wants to learn to make games?
  • Advertisement

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!