Welcome, Guest. Please login or register.

Login with username, password and session length

 
Advanced search

1411471 Posts in 69369 Topics- by 58423 Members - Latest Member: antkind

April 23, 2024, 10:02:51 AM

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsCommunityDevLogsYear In The Trees
Pages: 1 ... 7 8 [9] 10
Print
Author Topic: Year In The Trees  (Read 42774 times)
Luno
Level 1
*


now comes good sailing


View Profile WWW
« Reply #160 on: December 01, 2018, 12:14:44 PM »

Thanks hermit!

Uh, hmm, it certainly wasn't something that happened all at once...it's been an ongoing thing where every time I need a new effect I take a weekend and figure out how to write it myself, cementing and improving my understanding of how it all works and what can be done.

The first mention of "shader" in my commit history is actually from November of 2016! Of course, it won't take you 2 years, I work on shaders pretty sparsely.

Apparently, the first thing I tried to do was to emulate overlay and a few other photoshop blend modes...it looks like it took me about a week (and also note that I have a day job). I remember that I wanted these effects for my sprites because I was prototyping all the visual polish and post-effects in Photoshop and I was using overlay everywhere.

I found this article on Google and thought, "OK well I guess I have to learn these shader things then". At the time I couldn't find any other tutorials for this effect detailed enough for a complete beginner, so I was literally working off of Unity's docs, that article, and Nvidia's CG docs (these aren't great, Microsoft's HLSL reference is much better).

I wasn't even writing much code that week either, I definitely remember spending a lot of time reading articles and introductions trying to understand how the hell any of this worked.

I was truly starting from 0; I'd never really done any computer graphics or 3D before and I honestly didn't even know what shaders or even vertices/meshes were. Like I knew the term "mesh" from unity components, but I don't think I could have told you what it was... "like, something to do with 3D rendering I guess?" and I had been doing 2D stuff in Unity for about 7 months at that point!

I think you can definitely start getting useful results for your game in a weekend, especially with the right intros and guides. Like I had made 3 or 4 useful shaders for the game early on and I was still pretty confused and unclear about how a lot of it worked.
Logged

litHermit
Level 1
*


1


View Profile WWW
« Reply #161 on: December 01, 2018, 01:36:44 PM »

That's a very thorough insight into your learning process, thanks! Smiley Very illuminating
Logged

xix
Level 5
*****


View Profile
« Reply #162 on: December 01, 2018, 01:52:14 PM »

Is there anything you wanted to know specifically?

It looks like you have different times of day for zones, but you're using the same tileset. That is super interesting to me.
Logged


Get the demo itch.io
Follow @lunarsignals on twitter
Luno
Level 1
*


now comes good sailing


View Profile WWW
« Reply #163 on: December 01, 2018, 09:33:27 PM »

It looks like you have different times of day for zones, but you're using the same tileset. That is super interesting to me.

Ah yeah, believe it or not that's 100% just blending a color over the entire scene with different math. All of the weather is that + particle systems too. Season changes are palette swaps (and enabling/disabling a few additional things like snow sprites for instance).
Logged

Mochnant
Level 0
***



View Profile
« Reply #164 on: December 07, 2018, 08:26:19 AM »

Lovely waterfall, @luno.  I don't know a lot about shaders and I found this devblog very insightful, thank you!
Logged
Luno
Level 1
*


now comes good sailing


View Profile WWW
« Reply #165 on: February 13, 2019, 04:25:18 PM »

Blergh, I really need to update this thing!

Just wanted to check in and say that I'm still here and the project is still cookin'.

I'm in one of those phases right now where almost everything I'm adding is not GIF-able. Mostly audio stuff (SFX, ambiance, implementation, audio "engine"), design work, and various related infrastructure.

I know I always say, "I really want to do a post on X" and then half the time I don't get around to it, but...I really want to do a post on the audio "engine" code that I wrote on top of Unity's audio and my experience with that. I decided not to use any middleware like fmod or wwise because Unity's system is flexible enough (although super bare bones) and I know exactly what I want and how to build it (a rare situation). At the very least I will post a little demo video showing some of the audio soon.

Stay tuned. Wizard
Logged

Mochnant
Level 0
***



View Profile
« Reply #166 on: March 18, 2019, 09:32:11 AM »

Any news on the game's progress, @Luno?
Logged
Luno
Level 1
*


now comes good sailing


View Profile WWW
« Reply #167 on: March 23, 2019, 05:06:44 PM »

Any news on the game's progress, @Luno?

Yes @Mochnant! I just got back from GDC. It was weird timing because I'm going through some personal struggles, but I had a really good time that made me super sad and super happy at the same time. An emotional roller coaster, if you will.

Anyways, here's a few updates crammed into one (also, gentle reminder that I've been posting about this stuff a lot more frequently on Twitter if you wanna' follow me @lunoland):



1. Buffs
First up, I added buffs into the game! The work for this was mostly UI, but also involved a fair bit of programming to build out the backend.

Since I've been working on this project for so long, the way that I write code has evolved a lot. In the beginning I was trying out a lot of design patterns and OOP stuff because of Unity and the fact that I hadn't done OOP in a long time (also, I read Game Programming Patterns around that time). Now I'm settling into a better way of writing C# that jives more with the procedural style I'm used to working in.

To give a flavor, there's a static class called Buffs that contains all of the buff-related logic and data types (which are just structs with no behavior). The necessary context is passed into these static functions, e.g. instead of mob.ApplyBuff(buffName) you'd write Buffs.Apply(mob, buffName)

You might be tempted to call this "dependency injection" or "functional", but really it's just the typical pre-OOP way of doing things. It's a subtle inversion, but there are some advantages (organizationally, conceptually, and even performance-wise) to preferring these static function libraries over your typical object hierarchies & virtual methods in many cases. I think I've discussed this in detail elsewhere, but I'm totally here for this growing trend to reject OOP. So basically everything you hear about "composition over inheritance" and "data oriented design" really just boils down to "hey maybe OOP was a mistake let's go back to the 80's" Cheesy


Making all these icons before knowing exactly which buffs I'll need isn't great but hey, I was having fun.
Sometimes you just gotta, er, ride the wave?




This GIF demonstrates the UI element of the buff-system.
I referenced a bunch of MMOs (thanks Guild Wars 2) for the design.



2. Armor
I also recently designed all the armor that will be available in the game. My goals for these modular character sprites were:

- Get as much variation as possible via mixing tops + bottoms and palette swapping (while also keeping things simple enough to animate)

- Allow for neutral, femme, and masculine looks from the same base sprites (for player expression and efficient NPC creation)

- Make players feel good because their character looks rad

- Define visually distinct roles by the equipment they will tend to use


Also, if you've been following along on twitter you may have noticed me having a crisis of identity about the character design around this time. This is because I was also moving all of my animation over to Aseprite (I still do environments in Photoshop) and felt this was the last chance to modify the character. In the end I kept the old design of course XD

I'm not sure if this game will ever have explicit classes re: scope, but I've been trying to think about how I can carve out different kinds of implicit roles and playstyles from the activities and items in the game instead. As a result, all of the armor designs come from that thinking and are meant to compliment the stat system (more on that below).


In addition to the player's basic clothing options, I designed 6 hair styles and 6 armor sets.
At a minimum, I know I can launch with at least 3 armor sets, which take ~1 hour per animation to add!




Since I changed my animation workflow, I revisited some older animations like the player's walk cycle.
It's now 6 frames up from 4, and a lot more interesting now.



3. Stats
You know what's funny? Working on an RPG for years and not having any stats in the game. Hilarious.

In the end, this was probably the most time I've spent on a pure "design" related task, and it was tough because there was no art and little code to show so it didn't feel as immediately valuable.

In my current design, I have 3 main "composite" stats that are very similar to DOTA/Warcraft 3 (strength, agility, and intelligence). I was strongly considering going with a 4 stat system (power, life, speed, energy) for a while but it didn't fit my design goals as well, which were:

- Be able to define different playstyle "profiles" (more on this below)

- Respect the player's intelligence (no arbitrarily large numbers...a bit controversial perhaps)

- Be simple and easy to understand

- Feel powerful and valuable





I was heavily influenced by this GDC talk on stats in Pillars of Eternity, which I think crystallized a lot of my beef with typical RPG stat systems.


I decided to have linear stat growth because it met a lot of my goals (ease of understanding, value, no large numbers). This means that a point of strength always gives the same bonuses, but there will still be some curves baked into the items (for example +5 of a stat on tier 1 armor, +9 on tier 2, and +11 on tier 3). I'd like to avoid players needing to find a formula on a wiki in order to optimize their character.


***WARNING, NOW ENTERING HIGHLY SPECULATIVE ZONE***
So mechanically-speaking, stats, damage types, and mitigation are all in the game now. What I'm going to talk about next is the high-level concept of how I want to use these stats in the game. I don't know how much of this is realistic so don't get too excited, but I wanted to talk about some of the things I've been thinking about at least.

So I've had these 3 "player profiles" in mind as I've been designing all these game systems. The profiles are defined by different kinds of activities you do in game. Each profile broadly corresponds with a different kind of person that has expressed interest in the game, and is mapped to a different core stat.

You could almost think of it as an RPG with 3 classes, but it's a bit fuzzier because you'll have to do a little bit of each. The harvest moon domestic-type activities like cooking and farming correspond to intelligence, combat to strength, and exploration/nature survival to agility. I'm (perhaps naively) trying to provide a different flavor for each profile.

I'm consistently embarrassed by this because of the ambitious scope, but I'm excited because there seems to be a lot of synergy too. We'll see what happens, but here's how I'm thinking of it now:

Strength: Health, damage with most melee weapons, block chance.
The tank-y weapons fighter stat. The biggest strength bonuses are found on plate armor which is mainly acquired through combat. You'll need a lot of ore, which is most often found in cave areas dense with enemies. You have to sit there and wail on the node with a pickaxe to get the ore, so unlike foraging you can't just run through and collect stuff: you'll want to clear the mobs first.

Agility: Speed, damage with bows and daggers, dodge chance.
This is sort of your rogue/ranger stat. Leather armor gives high bonuses to agility, and the ingredients to craft it come from hunting/foraging. Since you'll need a lot of pelts, you have to gather the favorite foods of various animals to feed them and spawn more. To make this distinct from the combat-focused strength build, I'm imagining that many animals flee quickly from you instead of fighting, so speed + range make you a more efficient hunter/forager, but more of a glass cannon in combat.

Intelligence: Energy, damage with magic, resist all.
This is the caster stat. Bonus intelligence comes from silk armor which can be acquired through farming/trading. Anyone can use magic, but it will drain your energy quickly. As a result, the pure caster playstyle needs to eat special meals that restore energy and provide survival buffs. This should cater more to the harvest moon fan.


All of the stats should be useful regardless of how you want to play.

In addition to the three pure armor types discussed here, I also designed some hybrid armor types which you can see in the previous section's GIFs. These armor types are bone (str + int), chain (agi + str), and cloth (int + agi). As you've already guessed, the materials/ingredients for these armors require a mixture of two activity types, and provide smaller bonuses to each stat. They're also the most likely to be cut Sad

I will try to make the intelligence/magic playstyle a bit easier for the less combat-focused folks (range + aoe, resistant to elemental damage so you can stand in the fire). Meanwhile, the agility nature survival build would be more appropriate for "core" players who like to be precise glass cannons. Strength builds should fall somewhere in between.

Each of these playstyles ultimately makes you more effective in combat, so combat is still the core activity (audience sighs). Despite that, I'm still very committed to having these additional activities that improve your character's viability beyond just killing monsters for loot/xp (though there's plenty of that if you want it, I know I do).

One final word on stats! I'm also planning a few types of items that provide damage which is not affected by a stat. These are mechanical devices like exploding potions, traps, and flintlock guns. These items provide high damage that falls off as your stats improve. My goal with these items is to make something appealing for the early game, speedrunners, or strength builds that need some kind of range damage.


4. Dodge roll
Alright well after that treatise about stats, there isn't much to say here other than I'm trying to put a dodge roll in the game and it's hard to animate!


Here's me trying to sketch out some key frames and wow the human body is fucked up.


Putting it all together. I did this animation on Train Jam and I've already tweaked it twice since this GIF


4. Upgradable cabin
This is an ongoing feature, but one of the remaining few steps to close some major loops in the game. I've done quite a bit of concept for what all the upgrades will be and how they might affect gameplay, but I'm not ready to share the details just yet. Suffice to say, the primary function of upgrading the cabin is to unlock new crafting stations and more storage. Here's a lil' sneak peak, though I've only just started working on the art.


Here's a sketch of the main cabin (though there are a few other upgradable buildings planned ^^)



Some WIP of what the final art might look like, which is the result of my 4 hour plane ride home from GDC (I'm slow AF).
« Last Edit: March 25, 2019, 12:25:38 PM by Luno » Logged

MegaTiny
Level 1
*


Wew lad


View Profile
« Reply #168 on: April 13, 2019, 12:12:26 PM »

Just posting to follow. What an awesome devlog, thanks for taking the time to write it! Game is looking  fab too.
Logged

Luno
Level 1
*


now comes good sailing


View Profile WWW
« Reply #169 on: September 22, 2019, 08:06:16 AM »

Cripes it's been so long since my last long-form post, but the project is still alive and well! I've been showing new stuff every week on Twitter but I haven't made time for much blogging lately, sorry.

NOTE: If you want to follow my progress, please go to my Twitter @lunoland, as it's the easiest place for me to consistently post updates. I am quite literally begging you to follow me on Twitter because this blog took me 12 actual hours to write and nobody's got time for that. If you just want the big updates like the release date or when public beta begins, sign up for the email list.

---

This devlog entry is all about my character animation system, replete with helpful tips and code snippets!

In the last 2 months I've created a whopping 9 new player actions (fun stuff like shield blocking, dodge rolling, and weapons), 17 new held items, 3 armor sets (plate, silk, and leather), and 6 hair styles. I also finished all the requisite automation and tools and everything is in the game. Here's how I did it!


I hope this information proves useful, and demonstrates that it doesn't take a genius to build some of these tools/automation yourself.



The Pitch
The original idea was to see if it was at all feasible to have stacked sprites with synchronized animators to create a sort of modular character with interchangeable hair, equipment, and held items. Snappy, hand-draw pixel animation with a truly customizable character.

Of course these features are commonly found in 3D, 2D games with pre-rendered sprites, or 2D games with paper-doll animation, but as far as I know there aren't many games with hand-drawn animation and modular characters (usually because it's too tedious).


I unearthed this ancient GIF from my first month of Unity. This modular sprite thing really was one of my very first experiments in game dev!


I built the prototype using Unity's animation system and then added a single shirt, a single pair of pants, one hair style, and 3 items to prove the concept. This required 26 individual animations.

At the time I was doing all my animation in Photoshop and I hadn't bothered to automate anything so it was very tedious. Afterwards I thought, "Ok the basic idea works, I'll do more animations and equipment later." Years later, apparently.

Back in March I designed a lot of the armor (per my previous post) and I could see ways in which this process could be made more manageable. I kept holding off on implementing anything because even with automation I was nervous it wouldn't work out.

I was kind of expecting to have to chuck the customization and create a single protagonist that you play as like most other hand-animated games. But I had my roadmap, and it came time to see if I could tackle this beast!




Spoiler: Everything worked out famously. Read on to discover my ***secrets***



Modular Sprite System

I. Know Your Limits
I did a lot of art testing and time tracking ahead of time to figure out what was required for this thing to look good and if that level of quality would even be feasible for me.

I wrote down all my animation ideas and organized them into a spreadsheet where I ranked them across different dimensions like utility, cool factor, and re-usability. Somewhat surprisingly, the animation for throwing an item ranked highest on this list (potions, bombs, knives, axes, a ball...checks out).

I came up with a score for each animation and tossed anything that ranked poorly. I had originally planned to do 6 armor sets, but quickly realized that would be too much and cut 3 of them.

The time tracking piece of this was huge and I really recommend it to help you set bounds on questions like "How many enemies can I have in the game?". I was able to extrapolate a good estimate from just a few tests. I continued to track my time and re-calibrate my expectations as I worked on the animations.

To put my money where my mouth is, I'm sharing a copy of my work log for the last 2 months. Note that this time is in addition to my regular job where I work 30hrs a week:
https://docs.google.com/spreadsheets/d/1Nbr7lujZTB4pWMsuedVcgBYS6n5V-rHrk1PxeGxr6Ck/edit?usp=sharing



II. Palette Swapping For A Better Tomorrow
With clever use of color in your sprite design, you can draw a single sprite and get many different variations via palette swapping. Different colors sure, but you can also create various elements that can be turned on and off (e.g. by swapping colors with transparency).

Each armor set has 3 variations, and by mixing and matching the tops and bottoms many looks are possible. I plan to implement a system where you can collect one set of armor for your character's appearance, and another set of armor for stats (a la Terraria).


As I worked, I was pleasantly surprised to discover all kinds of cool combinations. Equipping a plate top with a silk bottom results in a battle mage vibe.


The best way to do palette swapping is to use the colors in your sprite to encode a value that you will later use to lookup the actual color from a palette. I'm glossing over the details here, but this video is an OK place to get started:






So I won't bother with all the ins and outs, but what I will discuss instead are the ways in which you can apply this technique in Unity, and the trade offs for each.

1. Lookup texture for each palette
This is the best strategy for variant enemies, backgrounds, and anything where many sprites will share the same palette/material. Different materials can't be batched even if they use the same sprite/atlas. Dealing with textures is kind of a pain, but you can change palettes at run time by swapping materials, using SpriteRenderer.sharedMaterial.SetTexture, or MaterialPropertyBlock if you need different palettes for each instance of a material. Sample shader fragment function here:

Code:
sampler2D _MainTex;
sampler2D _PaletteTex;
float4 _PaletteTex_TexelSize;

half4 frag(v2f input) : SV_TARGET {
half4 lookup = tex2D(_MainTex, input.uv);
half4 color = tex2D(_PaletteTex, half2(lookup.r * (_PaletteTex_TexelSize.x / 0.00390625f), 0.5));
color.a *= lookup.a;
return color * input.color;
}


2. Color array
This was the solution I settled on since I need to swap the palettes every time the player's appearance changes (e.g. equipping items), and build some palettes dynamically (to reflect the player's choice of hair and skin color). For these purposes, I found arrays to be much easier to work with at runtime and in the editor.

Code:
sampler2D _MainTex;
half4 _Colors[32];

half4 frag(v2f input) : SV_TARGET {
half4 lookup = tex2D(_MainTex, input.uv);
half4 color = _Colors[round(lookup.r * 255)];
color.a *= lookup.a;
return color * input.color;
}

I represent my palettes with a ScriptableObject type and use a MonoBehaviour tool to edit them. After editing palettes a lot in Aesprite as I worked on the animations I got a feel for what tools I needed and designed these scripts accordingly. If you want to make your own palette editing tool, these are the must-have features I recommend implementing:

- Updating the palettes on various materials as you edit the colors, showing the changes in real time.
- Name and reorder the colors in the palette (use a field to store the color's index, not its order in an array).
- Select and edit multiple colors at once. (PROTIP: You can copy/paste Color fields in Unity: just click one color, copy, click another color, paste, now they're the same!)
- Apply an overlay color to the entire palette
- Write the palettes to texture.


3. Single lookup texture for all palettes
If you want to switch palettes on the fly but also need batching to reduce draw calls, you can use this technique. It might be helpful on mobile, but it's also kind of a pain in the ass to use.

First, you need to pack all of your palettes into a single big texture. Then, you use the color that you set on Unity's SpriteRenderer component (AKA the vertex color) to determine the row that you'll read from the palette texture in your shader. Thus, the palette for that sprite is controlled by SpriteRenderer.color.Vertex color is the only property on a SpriteRenderer you can change without breaking batching (provided the materials are all the same).

In most cases it's best to use the alpha channel to control the index since you probably won't need to have a ton of sprites with different transparency.

Code:
sampler2D _MainTex;
sampler2D _PaletteTex;
float4 _PaletteTex_TexelSize;

half4 frag(v2f input) : SV_TARGET {
half4 lookup = tex2D(_MainTex, input.uv);
half2 paletteUV = half2(
lookup.r * _(PaletteTex_TexelSize.x / 0.00390625f),
input.color.a * _(PaletteTex_TexelSize.y / 0.00390625f)
)
half4 color = tex2D(_PaletteTex, paletteUV);
color.a *= lookup.a;
color.rgb *= input.color.rgb;
return color;
}



Ah, the wonders of palette swapping and layered sprites. So many Looks.



III. Automate everything and use the right tools

Automation was absolutely essential for this feature because I ended up with some 300 animations, and thousands of sprites.

The first step was to build an exporter for Aesprite to handle my crazy scheme of layered sprites using the handy command line interface. It's just a perl script that loops through the layers and tags in my an Aesprite file and exports the images with a certain directory structure and naming convention that I can read in later.

Next I wrote an importer for Unity. Aesprite outputs a helpful JSON file with the frame data, so you can build animation assets programmatically. It was a bit annoying to go through the Aesprite JSON and write out this data type, so I'm including it here. You can easily load it in Unity using JsonUtility.FromJson<AespriteData> just be sure to run Aesprite with the --format 'json-array' option.

Code:
[System.Serializable]
public struct AespriteData {
[System.Serializable]
public struct Size {
public int w;
public int h;
}

[System.Serializable]
public struct Position {
public int x;
public int y;
public int w;
public int h;
}

[System.Serializable]
public struct Frame {
public string filename;
public Position frame;
public bool rotated;
public bool trimmed;
public Position spriteSourceSize;
public Size sourceSize;
public int duration;
}

[System.Serializable]
public struct Metadata {
public string app;
public string version;
public string format;
public Size size;
public string scale;
}


public Frame[] frames;
public Metadata meta;
}


From the Unity side, there were 2 places where I really got stuck: Loading/slicing a spritesheet, and building an animation clip. A clear example would have helped me immensely, so here's a snippet from my importer to spare you that pain:

Code:
TextureImporter textureImporter = AssetImporter.GetAtPath(spritePath) as TextureImporter;
textureImporter.spriteImportMode = SpriteImportMode.Multiple;

SpriteMetaData[] spriteMetaData = new SpriteMetaData[aespriteData.frames.Length];

// Slice the spritesheet according to the aesprite data.
for (int i = 0; i < aespriteData.frames.Length; i++) {
AespriteData.Position spritePosition = aespriteData.frames[i].frame;

spriteMetaData[i].name = aespriteData.frames[i].filename;
spriteMetaData[i].rect = new Rect(spritePosition.x, spritePosition.y, spritePosition.w, spritePosition.h);
spriteMetaData[i].alignment = (int)SpriteAlignment.Custom; // Same as "Pivot" in Sprite Editor.
spriteMetaData[i].pivot = new Vector2(0.5f, 0f); // Same as "Custom Pivot" in Sprite Editor. Ignored if alignment isn't "Custom".
}

textureImporter.spritesheet = spriteMetaData;
AssetDatabase.ImportAsset(spritePath, ImportAssetOptions.ForceUpdate);

Object[] assets = AssetDatabase.LoadAllAssetsAtPath(spritePath); // The first element in this array is actually a Texture2D (i.e. the sheet itself).
for (int i = 1; i < assets.Length; i++) {
sprites[i - 1] = assets[i] as Sprite;
}


// Create the animation.
AnimationClip clip = new AnimationClip();
clip.frameRate = 40f;
float frameLength = 1f / clip.frameRate;

ObjectReferenceKeyframe[] keyframes = new ObjectReferenceKeyframe[aespriteData.frames.Length + 1]; // One extra keyframe is required at the end to express the last frame's duration.

float time = 0f;
for (int i = 0; i < keyframes.Length; i++) {
bool lastFrame = i == keyframes.Length - 1;

ObjectReferenceKeyframe keyframe = new ObjectReferenceKeyframe();
keyframe.value = sprites[lastFrame ? i - 1 : i];
keyframe.time = time - (lastFrame ? frameLength : 0f);

keyframes[i] = keyframe;

time += lastFrame ? 0f : aespriteData.frames[i].duration / 1000f;
}

EditorCurveBinding binding = new EditorCurveBinding();
binding.type = typeof(SpriteRenderer);
binding.path = "";
binding.propertyName = "m_Sprite";
AnimationUtility.SetObjectReferenceCurve(clip, binding, keyframes);

AssetDatabase.CreateAsset(clip, "Assets/Animation/" + name + ".anim");
AssetDatabase.SaveAssets();


If you haven't yet, it's easy to get started creating your own tools. The basic trick is to chuck a GameObject into your scene with a MonoBehaviour on it that has the [ExecuteInEditMode] attribute. Add a quick button and you're off to the races! Remember that your own personal tools don't need to look good, they can be purely utilitarian:

Code:
[ExecuteInEditMode]
public class MyCoolTool : MonoBehaviour {
   
    public bool button;

    void Update() {
        if (button) { button = false; DoThing(); }
    }
}

For sprite stuff, it's not that hard to automate common tasks (for example, creating palette textures or batch replacing colors across many sprite files). Here's an example to get you started modifying your sprites:

Code:
string path = "Assets/Whatever/Sprite.png";
Texture2D texture = AssetDatabase.LoadAssetAtPath<Texture2D>(path);
TextureImporter textureImporter = AssetImporter.GetAtPath(path) as TextureImporter;
if (!textureImporter.isReadable) {
textureImporter.isReadable = true;
AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate);
}

Color[] pixels = texture.GetPixels(0, 0, texture.width, texture.height);
for (int i = 0; i < pixels.Length; i++) {
// Do something with the pixels, e.g. replace one color with another.
}

texture.SetPixels(pixels);
texture.Apply();
textureImporter.isReadable = false; // Make sure textures are marked as un-readable when you're done. There's a performance cost to using readable textures in your project that you should avoid unless you plan to change a sprite at runtime.

byte[] bytes = ImageConversion.EncodeToPNG(texture);
File.WriteAllBytes(Application.dataPath + path.Substring(6), bytes);
AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate);



Outgrowing Mecanim: A Rant
Over time, the prototype version of this modular sprite thing that I built using Mecanim had become the single biggest pain point when upgrading Unity because the API kept changing wildly and it was poorly documented. For a simple state machine you might think it would be reasonable to be able to inquire about which clip is in each state, or to change clips at runtime. Nope! For performance reasons, Unity bakes the clips into their states and makes you use this wonky override system to change them.

Mecanim isn't a bad tool per se, but I think it fails to deliver on its core promise of simplicity. The idea was to replace something that was perceived to be complicated and painful (scripting) with a simple thing (a visual state machine). However:

- Any non-trivial state machine quickly becomes an unwieldy spiderweb of nodes and connections with logic scattered across different layers.

- Simple cases get bogged down by the generalized requirements of the system. You have to create a new controller and define states/transitions just to play one or two animations. There is some performance overhead too of course.

- Comically, you end up writing code anyways because in order for your state machine to do anything interesting you need a script that calls Animator.SetBool et al.

- Re-using a state machine with different clips means duplicating it and swapping the clips manually. Moving forward, you have to make the same change in multiple places.

- If instead you'd rather modify what's in a state at runtime, you hit the wall. The solution is either a bad API or an insane graph with one node for every possible animation.




Rare footage of the Firewatch devs arriving in visual scripting hell. The funniest thing is when he shows the cleaner examples and they STILL look nuts. The audience is literally groaning at 12:41. Factor in the colossal maintenance effort described in the talk and, well, that's a big yikes from me.


Many of these problems aren't even the fault of the Mecanim devs, they're just the natural result of incompatible ideas: you can't have a generalized system that is also simple, and representing logic with images is more complicated than using words/symbols (UML flow charts anybody?). I'm reminded of this bit from Zak McClendon's 2018 Practice NYC talk, and if you have time to watch the whole thing I highly recommend it!


I get it though. Visual scripting is always being slammed by the write-your-own-engine aggro nerds who like, don't understand an artist's needs man. Also it's pretty undeniable that most code looks like inscrutable techno-jargon.

If you're kind of a programmer already and you're making a game with sprites, maybe think twice though. When I first started, I was intimidated into thinking that I could never write any engine-related thing better than the Unity devs.

Guess what buddy, turns out a sprite animator is just a script that changes a sprite every however many seconds.

Anyways, I did eventually write my own. I've since added animation events and some other project-specific features, but the basic version that I wrote in an afternoon covers 90% of what I need, is only 120 lines, and is available here for free: https://pastebin.com/m9Lfmd94

Thank you for coming to my TIG talk. Until next time!
« Last Edit: September 23, 2019, 10:48:57 AM by Luno » Logged

litHermit
Level 1
*


1


View Profile WWW
« Reply #170 on: September 23, 2019, 02:23:33 AM »

I loved your tig talk, again very illuminating.

Building custom tools in unity is great, though I find execute in edit mode to sometimes be an overkill for what OnValidate() can handle just fine (and neatly contained)
Logged

Mochnant
Level 0
***



View Profile
« Reply #171 on: September 23, 2019, 05:26:51 AM »

A great update as always, Luno.  Thanks!
Logged
Luno
Level 1
*


now comes good sailing


View Profile WWW
« Reply #172 on: September 23, 2019, 09:12:01 AM »

I loved your tig talk, again very illuminating.

Building custom tools in unity is great, though I find execute in edit mode to sometimes be an overkill for what OnValidate() can handle just fine (and neatly contained)

Thanks, and yes, you're totally right!

Happily you can get away with a lot in edit mode since Update only runs when you change something in the editor. It's funny when you need [ExecuteInEditMode] or a shader to run in real time in the editor so you just grab a window and keep resizing it to see the animation.
Logged

Luno
Level 1
*


now comes good sailing


View Profile WWW
« Reply #173 on: September 23, 2019, 09:12:31 AM »

A great update as always, Luno.  Thanks!

No prob!  Wizard
Logged

Fufanu
Level 0
**


View Profile
« Reply #174 on: September 24, 2019, 10:19:14 AM »

I ended up going with 1 state and taking the override route, then swaping the sprite out in the animator.. this should be easier, but once it's setup I don't find it to be a big deal. I also ran into the 1 animation 1 state thing and hated it, so decided to go w/ 1 state period.
Logged
Luno
Level 1
*


now comes good sailing


View Profile WWW
« Reply #175 on: September 24, 2019, 02:56:03 PM »

I ended up going with 1 state and taking the override route, then swaping the sprite out in the animator.. this should be easier, but once it's setup I don't find it to be a big deal. I also ran into the 1 animation 1 state thing and hated it, so decided to go w/ 1 state period.
The single state idea is good, I hadn't tried that! Maybe the API for the override controllers is finally stable now, I'm still on 2017 but I started using the overrides in 5.4 or something so my experience has been that every time I've upgraded Unity they broke. At least at the time, the documentation was very sparse and I've spent quite a few hours hunting through forum posts by mecanim-dev Smiley

Mostly I've just gotten burned by it too many times. Man, I remember at one point there were several disruptive memory leak bugs with override controllers and the fix involved yet another barely documented code change found on the forums. It could be mostly fine in 2019, it just kinda feels like a house of cards.

It really affects my mood in a big way for a few days to go from everything running smoothly to things being pretty broken after upgrading. I think that's part of why I'm extra sensitive about my own systems: writing glue code and chasing down docs about the giant iceberg of an API I'm sitting on is my very least favorite part of programming. It's feels like a necessary evil most of the time, I just really want to minimize that pain I think.


STORY TIME! I think the last straw for me happened while I was on trainjam last year. I was feeling really ill and had just gone through this big breakup. The train was delayed for 20 hours (!!!) AND the car I was sleeping in had no heat/power for the entire trip. To make matters worse, there were maybe 8 of us whose seats were on the lower-level of the powerless car, and as we passed through the rockies it was quite literally freezing cold.

Sometime in the afternoon on the fourth, extra day of the trip, after a grueling 3 nights sleeping in a chair, I thought "You know what, fuck it, I'm always complaining about mecanim, why don't I finally just try to make that sprite animator." I have no fucking clue why that was my watershed moment, but here we are.

Anyways, I finished the version in that pastebin from my last post so quickly that I just felt like, damn, I should have done this ages ago. I'm free! At that moment I vowed I would never be made to feel afraid or ashamed of rolling my own system again  Tears of Joy
Logged

bryku
Level 3
***


View Profile WWW
« Reply #176 on: September 25, 2019, 06:06:24 AM »

Awesome job with the palette swapping thing! I will be facing the same challenge in the future (currently I'm good with having separate models as there's not a lot of them).

I was considering using a shader-based approach or some kind of regular sprite swapping techniques. Now that I can see using shaders does the trick, I will surely be following the same route.
Logged

Busy working on xcom-like, steampunk/vermintide pixel art strategy game called "Shardpunk".

Devlog | Twitter | Webpage
Luno
Level 1
*


now comes good sailing


View Profile WWW
« Reply #177 on: October 24, 2019, 11:46:58 AM »

Hey all, quick blog post today. I just wanted to jot down a few helpful notes on the projectile system I just finished since projectiles seem like a pretty common thing in games.

Also, a quick progress update: If you've been following along on Twitter, you may have noticed that I've been working a lot on implementing behavior for all the new weapons and actions. I added the dodge mechanics, shield block and parry, and several new weapons. In the process, I also ended up refactoring my old Action system and adding some new tools to make all of this easier and faster!

My current plan is to finish up as much new weapon behavior as possible this week, spend next week polishing the VFX and item art, and then go back to working on all the creatures I said I was going to add 2 months ago before I got sidetracked with all this tech!

---

So anyways, projectile system. My goals for this thing were really just to be able to do a few simple features and to have a lot of these bad boys on screen with a reasonable frame rate. It's not a bullet-hell game or anything, but I was kinda keeping Diablo 2 in mind as a benchmark (e.g. multishot).

In this GIF there's a limit of one thousand active projectiles at a time, but it was still staying pretty close to 60 in the editor even with 100 skeletons. Actually the thing that lagged it the most was the gold spawning (which I plan to optimize similarly later).



Multishot, eat your heart out.


Initial Design
My first design was a pool of prefabs, where the individual prefabs were responsible for updating themselves. Each prefab had a kinematic Rigidbody2D, a BoxCollider2D set to IsTrigger, and a MonoBehaviour to keep track of all the references and receive the OnTriggerEnter2D message. The MonoBehaviour also used Update to move and manage the projectile during its lifetime.

This is sort of the classic Unity solution, and was quick to set up, but I implemented it already knowing I would change it later. It had OK performance, but it would create a huge lag spike when a bunch of projectiles were fired at the same time (probably because of the physics objects waking up?). I tried messing with some settings like "sleepMode", but this didn't really change much, possibly because I was disabling inactive projectiles in the pool...there may be some penalty you always have to pay when enabling a physics object.

The method also has the limitation that there are basically two speeds for collision detection, "continuous" and "discrete". If your projectile moves fast enough, you need to use the "continuous" setting. It wasn't exactly clear to me from the way the docs explain this (they talk about detection happening relative to the object changing position), but from the behavior I can only assume that continuous means check every frame, whereas discrete is check every fixed update.

I'm not using the physics simulation anywhere in the game though, I just wanted to move the transform. For slow moving projectiles, this means I could actually check a lot less frequently than the default fixed timestep (which is 0.02 seconds).


Optimizations
So the first thing I tried was ditching the physics components and just shooting a ray out from the projectile every however many seconds. Initially I wrote this to do continuous (every update) raycasting on every projectile. This got rid of the huge spikes when a burst of projectiles was fired, but it was slower overall and crapped out faster with a ton of a projectiles.

Next I came up with this scheme where I would determine how often to raycast for each projectile based on its speed. This was a huge win! I found that the slowest I could get away with checking, and still have it feel responsive when the projectile would hit stuff, was 0.2f. For slow projectiles, that's 10x less checking than the fixed update physics stuff (obviously you can slow down the fixed update timestep in the project settings, but if you are planning to use physics anywhere else you won't want to do that). I also figured out that in my game, continuous checking needed to start at a speed of around 300 pixels per second, and 0.2f checking was good enough for projectiles that were 50 pixels per second or slower. For anything in-between I just lerp this range, so a projectile with a speed of 175 pixels per second updates every 0.1f seconds. To determine these numbers I just fired a bunch of projectiles while tweaking the speed and raycast frequency in the inspector, and kept adjusting until none of the projectiles were going through their targets.

That was the biggest performance boost, but I did a few more tweaks to make it ever faster. In the first version, I had a thousand projectiles in my pool with a thousand MonoBehaviours updating each one. This is obviously silly, I refactored it to just be an array of structs, where each member had the individual references and data needed to update that projectile. In a single update in the projectile system script, I loop over the array and update any projectiles that are active. Even just doing this was a fine speed up!

I kept going along those lines and did a sort of hot/cold split of my projectile data type. I wish I had profiled this change individually, but my main reasoning for doing this was to make the logic clearer. I had one array of a data type for all the static data for each projectile in the pool, which is really only ever needed to set up a projectile right before it's fired. Then I had a second data type for the things needed to update the projectile when it's active, and that data is added to a list of active projectiles. The main performance advantage is of course that now you only need to loop over the active projectiles, but theoretically it's also more CPU friendly.



Projectiles in action.


So that's it! I might do more with this as the needs evolve, but the whole thing is about 200 lines and took 2 evenings. I didn't include any code examples this time, so let me know if there are any questions or I can clarify something. 'Till next time!
« Last Edit: October 26, 2019, 09:34:55 AM by Luno » Logged

Luno
Level 1
*


now comes good sailing


View Profile WWW
« Reply #178 on: June 26, 2020, 11:30:43 AM »

Yikes, I just got the "Warning: this topic has not been posted in for at least 120 days" message while writing this. Apologies to everyone following this thread, I just haven't had the spare time to dedicate to blogging. If you want more blogs, let me hear from you so I know someone's getting something out of it Cheesy

I just wanted to pop in to say that the project is alive and well and I've been working like a dog on it (and my regular job) the entire time. Still trying to release an early public version ASAP, but I'm not going to pretend I know when anymore. When that time comes I'll be posting here about it so follow the thread if you're interested in being a tester (or signup on the website). Per usual, daily/weekly updates are still happening over on my Twitter.


Anyways, I did this write up about a new system I just finished for after-image / "shadow clone" effects and thought someone here might find it helpful/interesting.


A few of the preset effects from my after-images system.


I used a pool of sprite renderers for the after-images and a list of effects requests (these are both just implemented as arrays of structs).
 
Every frame you go through and update any active requests or images.
 
For each request, while the effect is active you emit an image every however many seconds according to the effect. The image is alive for some duration determined by the effect and then returns to the pool when it expires.
 
"Emitting an image" here just means getting a sprite renderer from the pool, copying the target of the effect's current sprite and position to the renderer, and then making any other adjustments you want (e.g. changing the renderer's color, setting some shader properties on its material, etc.)
 
For each active image, you will update it according to the effect that emitted it, i.e. maybe it needs to fade out over its duration, flicker, change color...whatever you can think of really.

 
For "shadow clones" style effect, it's a bit more complex. You actually emit all the images at once at the start of the effect, and then have them copy the target but with each image using an increasingly longer delay, e.g. the first image is 0.1 seconds behind the target, the second image is 0.2 seconds behind the target, and so on.
 
In order to get your images to copy what the target was doing however many seconds ago, you need to sample the target and store enough data for the clone with the longest delay, e.g. if you sample the target 30 times a second and you've got 4 clones and each clone is 0.1 seconds behind the previous clone, then the last clone is 0.4 seconds behind the target so you need to be able to store at least 12 samples (30 * 0.4 = 12)
 
How much data you need to copy from the target depends on your game, but in my case a "sample" was just struct with a sprite, a position, an x scale (for flipping), and a sorting order.
 
My sample "buffer" was just a fixed size array that I filled circularly, i.e. the next sample gets written to (lastSampleIndex + 1) % sampleArrayLength
 
Once the effect begins I start filling this sample buffer with the target's data. Each image that is emitted updates to whatever is in that buffer with an offset relative to how far behind that image is from the target. The images do not begin updating until their delay has elapsed.
 
e.g. If we wrote the target's latest sample to index 11, the first image which is 0.1 seconds behind the target will update to the sample at index 8 (30 * 0.1 = 3 samples, 11 - 3 = 8 ), the second image which is 0.2 seconds behind the target will update to sample 5, etc. An image doesn't update until the current sample index is >= its sample offset.
Logged

Ishi
Pixelhead
Level 10
******


coffee&coding


View Profile WWW
« Reply #179 on: June 26, 2020, 12:14:48 PM »

Only done a brief skim through the devlog but looks really polished and nicely put together. Coffee Posting to follow and to remind myself to look through more thoroughly in the future Smiley
Logged

Pages: 1 ... 7 8 [9] 10
Print
Jump to:  

Theme orange-lt created by panic