Welcome, Guest. Please login or register.

Login with username, password and session length

 
Advanced search

1411562 Posts in 69384 Topics- by 58443 Members - Latest Member: junkmail

May 03, 2024, 09:07:26 PM

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


now comes good sailing


View Profile WWW
« Reply #180 on: June 26, 2020, 12:48:22 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

Thank you Ishi! Gomez
Logged

Luno
Level 1
*


now comes good sailing


View Profile WWW
« Reply #181 on: June 27, 2020, 12:47:34 PM »

Addendum to my last post on after-image effects: Someone on reddit ended up asking for a beginner-friendly code example in Unity for an effect like this. Since I went through the trouble of writing it up, I figured it'd be good to paste here.

https://pastebin.com/i6cZYDD5

Code:
// Enjoy! For context, this code was created in response to https://www.reddit.com/r/gamedev/comments/hge51j/creating_a_flexible_system_for_afterimage_shadow/fw66qxn/

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class AfterImages : MonoBehaviour {

// Something to notice here: The Request and Image classes in this script are just data containers; They don't have any methods/behavior.
// Most tutorials would probably teach you the object oriented way of solving this problem where you would add an AfterImage component to your image object prefab and scatter your behavior and fields between that class and this one.
// Stop that! It only ever leads to madness and it's worse design along almost every dimension (extra ceremony, over-generalized, speculative, harder to understand/modify, slower, more memory, worse cache-coherence).
// In my version, you'll use just one MonoBehavior (instead of one per image or worse), and all the code is in the same place right where it's used, inline. Simple :)
public class Request {
public Transform targetTransform;
public SpriteRenderer targetSpriteRenderer;
public float elapsed;
public float duration;
public float imageFrequency;
public float imageFrequencyElapsed;
public float imageDuration;
}

public class Image {
public Transform transform;
public SpriteRenderer spriteRenderer;
public float elapsed;
public float duration;
}


#if UNITY_EDITOR
[Header("Tests")]
public Transform targetTransform;
public SpriteRenderer targetSpriteRenderer;
public float effectDuration = 10f;
public float imageFrequency = 0.1f;
public float imageDuration = 2f;

[Space(10)]
public bool pressThisButtonToTest;

// Here's an easy way to test your code! OnValidate is run every time you change something on this script in the inspector.
// I wish someone had shown me this when I was starting out in Unity. I used to bind everything to a key press :)
void OnValidate() {
if (pressThisButtonToTest) {
pressThisButtonToTest = false;
PlayEffect(targetTransform, targetSpriteRenderer, effectDuration, imageFrequency, imageDuration);
}
}
#endif


// Create a prefab that is just a single GameObject with a SpriteRenderer attached. Assign that prefab to this variable in the inspector.
// You could also create this simple object in script, i.e. instantiate a new GameObject and then add the SpriteRenderer component, but it's slower and there's no real advantage.
[Header("References")]
public GameObject imagePrefab;


List<Request> requests = new List<Request>(32);
List<Image> images = new List<Image>(128);


public void PlayEffect(Transform targetTransform, SpriteRenderer targetSpriteRenderer, float duration, float imageFrequency, float imageDuration) {
Request request = new Request();
request.targetTransform = targetTransform;
request.targetSpriteRenderer = targetSpriteRenderer;
request.duration = duration;
request.imageFrequency = imageFrequency;
request.imageDuration = imageDuration;

requests.Add(request);
}

void Update() {

// Notice that we're going through the active requests in reverse here since we might be removing some along the way.
for (int i = requests.Count - 1; i >= 0; i--) {
requests[i].elapsed += Time.deltaTime;

if (requests[i].elapsed > requests[i].duration) { // We're done with a request when its duration elapses.
requests.RemoveAt(i);
continue;
}

if (requests[i].imageFrequencyElapsed == 0f) {
GameObject imageObject = Instantiate<GameObject>(imagePrefab, transform); // With object pooling, this is where you'd get an inactive image prefab from your pool. You'd instantiate a bunch of images to re-use in awake.
Image image = new Image();
image.transform = imageObject.GetComponent<Transform>();
image.spriteRenderer = imageObject.GetComponent<SpriteRenderer>();
image.duration = requests[i].imageDuration;

// Use this new image to "clone" the target of the effect. Exactly which fields you copy over or modify will depend on your game and what you want the effect to look like.
image.transform.localPosition = requests[i].targetTransform.position;
image.transform.localScale = requests[i].targetTransform.lossyScale;

image.spriteRenderer.sprite = requests[i].targetSpriteRenderer.sprite;
image.spriteRenderer.sortingLayerID = requests[i].targetSpriteRenderer.sortingLayerID;
image.spriteRenderer.sortingOrder = requests[i].targetSpriteRenderer.sortingOrder - 1;

Color color = requests[i].targetSpriteRenderer.color;
color.r *= 0.66f; color.g *= 0.66f; color.b *= 0.66f;  // You can tweak this later, but let's start out by making the clone's color a bit darker.
image.spriteRenderer.color = color;

images.Add(image);
}

// Every imageFrequency seconds of game time that passes, we'll reset imageFrequencyElapsed to 0f and create a new image next frame.
// With an imageFrequency of 0.1f, and a request with a duration of 10f, we will end up creating and destroying 100 objects over the lifetime of the effect.
// If you had a few enemies using this effect at the same time you could be creating and destroying hundreds of these image objects (which is slow and creates garbage). That sounds needlessly wasteful!
// So, you can see why object pooling to re-use images would be desirable here.
requests[i].imageFrequencyElapsed += Time.deltaTime;
if (requests[i].imageFrequencyElapsed >= requests[i].imageFrequency) {
requests[i].imageFrequencyElapsed = 0f;
}
}

// After we're done going through the active requests, we'll update all the active images in a similar fashion.
for (int i = images.Count - 1; i >= 0; i--) {
images[i].elapsed += Time.deltaTime;

if (images[i].elapsed > images[i].duration) { // This image's lifetime has expired.
Destroy(images[i].transform.gameObject); // With an object pool, this is where you'd just deactivate the image's game object and save it for later.
images.RemoveAt(i);
continue;
}

// Here is where you can update your images over their lifetime. Maybe you want your images to fade out over their duration, like so:
Color color = images[i].spriteRenderer.color;
color.a = 1f - (images[i].elapsed / images[i].duration);
images[i].spriteRenderer.color = color;
}

}
}


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

Islet Sound
Level 0
**


View Profile WWW
« Reply #182 on: July 10, 2020, 01:24:12 PM »

Wow, beautiful stuff in all aspects!
Logged

Anders Hedenholm - Composer | Soundcloud | Twitter
Mochnant
Level 0
***



View Profile
« Reply #183 on: November 13, 2022, 05:36:40 PM »

How's the development going, @Luno?  Any news you can share?
Logged
Luno
Level 1
*


now comes good sailing


View Profile WWW
« Reply #184 on: November 13, 2022, 09:58:51 PM »

How's the development going, @Luno?  Any news you can share?

Hey Moch, thanks for following up. I'd sort been keeping quiet since no one had really asked, but I guess now's as good a time as any to say that I ended up pausing development on Year In The Trees (a giant-sized game) to make a different, medium-sized game that I can actually finish solo. I've been working on this new game pretty frantically in the mean time. I'm going to do a much longer post about this when I'm ready to announce the new game relatively soon (keyword relatively).

To make a long story short though, I very nearly had funding in hand from a major pub but the deal ended up being cancelled (just kinda fell through, there was no drama or anything). After that, I spoke with some other publishers but it wasn't really going anywhere. I decided that maybe I should try Kickstarter and started working on a trailer...I actually planned to announce by changing the name of the game and even bought the domain and built a new website!

In the middle of creating material for the Kickstarter though, I had a number of personal setbacks and had to face up to the hard fact that even the smallest version of this game was not gonna be possible for me to finish solo nights and weekends. Amidst increasing health problems (not related to burn out per se), there was also about a 0% chance a Kickstarter campaign could cover my health care costs and let me quit my job.

Needless to say, having to start over hit me pretty hard and there was a lot of emotional turmoil that I'll get into in another post. With the clarity of hindsight now though, it was absolutely the right decision; working on a game I can actually finish and release is honestly probably the best thing I can do to put me in a position where I can complete Year In The Trees some day.

So after a few months of tinkering, I was able to pick myself back up and I hit the ground running on a promising new idea using everything I've learned over the last however many years. I've posted a few vague art assets on Twitter, and I'll share those here too:






I've decided to keep this project a secret so I haven't published any footage of the prototypes (it started as a text-only game), but you can sometimes catch me streaming it on Twitch.

The development on this new game has been very different, but in a good way. On the previous game, I put a lot of pressure on myself to get noticed and produce something that was visually impressive.

After all that struggle I finally feel like I've proven to myself I'm capable of making a good-looking GIF, so this time around I've been free to focus on the nuts and bolts first and get off the ground much more quickly. The black & white art was a part of that too, though it will be full-color soon (I'm planning to keep that darker, NES-style look using high-contrast colors).

I've also been working with other solo devs for production guidance which has really helped me keep a laser focus on the work that matters most to finishing the game. Everything has been going relatively well, especially lately. At this point, the core loop is completely done and I've had a couple private demos that were way, way more well-received that I had hoped (even calling the gameplay "already too deep" Cheesy). Right now I'm wrapping up missing visual communication and polish, and I just finished the save system. Next I'll be working on the early game pacing and tutorial stuff.

Once that's all done I'll let anyone interested play a semi-public demo (which I will post about here), and then I will announce the game! Following that, there will be another art pass, sound, some boilerplate, a little more content, and then the first public demo will be ready (I'll probably adjust the scope again based on the reception).

I'm still pretty nervous people are going to be mad at me that I had to start a new game, or that it's just not going to be as appealing to people who were anticipating the old game since it's a different genre. Without saying too much though, to me this game almost has the feeling of playing a JRPG or something but it all takes place on a single screen and everything has been designed from the ground up to help me make content as efficiently as possible. There are already 10 enemies, 3 dungeons, and 79 (!!) abilities, woo Wizard
Logged

Alain
Level 10
*****



View Profile WWW
« Reply #185 on: November 15, 2022, 09:52:19 PM »

I stumbled across this devlog for the first time. I am sorry to hear the funding did not work out and about your health problems. It is great things seem to be better now and I am excited to hear about your new project!
Logged

Luno
Level 1
*


now comes good sailing


View Profile WWW
« Reply #186 on: November 16, 2022, 06:55:56 PM »

I stumbled across this devlog for the first time. I am sorry to hear the funding did not work out and about your health problems. It is great things seem to be better now and I am excited to hear about your new project!

Thanks for the kind words! I'm looking forward to being able to share more soon; honestly the early play testing is already going so much better than the previous project that I'm feeling pretty energized.
Logged

Mochnant
Level 0
***



View Profile
« Reply #187 on: November 23, 2022, 05:39:28 PM »

Thanks for the news, Luno.  I'm glad you're still working on a game, even if is a different one!  Getting something finished really is the most important thing!

I'll keep an eye out for you on Twitch.
Logged
Pages: 1 ... 8 9 [10]
Print
Jump to:  

Theme orange-lt created by panic