Access and use nested values in a JSON file/array?

Not favoritedFavorited Favorited 0 favourites
  • 7 posts
From the Asset Store
Convert multiple Excel files into JSON files and import them into construct3.
  • I've tried looking through the manual and watched a YouTube tutorial, and my brain hurts (lol). Help is appreciated!

    Background context:

    For my music-based game, I've made specific character animations for when a song is playing (a "perform" animation, for when it's just an instrumental section, and a "sing" animation for when there are vocals taking place).

    As of right now, for my current methodology, I am tracking the current duration of the playing song via a variable.

    The dilemma:

    I'm trying to create a JSON array to serve as time stamps for each song (since there will be around 20 songs in the game). Basically, I need to find the right object (i.e. "Song1"), and access it's "Stamps" Array. Every time SongDuration == one of the ".start" values, I want the character to enter the "sing" animation, and every time it hits one of the ".end" values, the character will swap to the "perform" animation -- thus, cycling between the two throughout the song.

    The songs are all different obviously, and there will be different numbers of "Stamps," so, I need somehow pull the array size from the JSON file, correct?

    Am I on the right track?:

    I have a global variable, called GME_SongPlay, which is storing the song that is ready to play (i.e. "Song1", "Song2", etc.) -- this song name will correspond with the JSON value.

    So, from my understanding, I need to create a function?..

    On Function createSongTimeStamps →

    JSON set array → Path: GME_SongPlay, Size: 0,

    JSON set path → GME_SongPlay

    Tagged:

  • Yeah, first you set path to SongName&".Stamps" , after that you can access the array using just "."

    To cycle through the array:

    Local Var inRange=0
    For "n" from 0 to JSON.ArraySize(".")-1
     If CurrentDuration between values JSON.Get("."&loopindex&".start") and JSON.Get("."&loopindex&".end")
     ----> Set inRange to 1
     ----> Stop loop
    
    If inRange=1 : Set "Sing" animation
    Else : Set "Idle" animation
    
    
  • Hey, Famous

    So, if I understand, in the game, you have various songs that play, and each song has segments of time that are either vocals or just instrumental.

    You have a character in the game, and you want the character animations to match up with those segments in the song.

    Specifically, the character's animation should be set to the "sing" animation during the vocals segments in the song, and the "perform" animation during instrumental segments in the song.

    Finally, you have the time stamps for the start and end of each segment, which you currently have stored in a JSON file, or string.

    I think you're on the right track for how to get it working.

    I have a few thoughts on how you might be able to simplify things a bit.

    JSON to JS

    One thing that might help simplify the process of getting data from JSON, is to convert it to nested native JS objects and stuff.

    You can use "JSON.parse()" to convert a JSON string, to the equivalent nested JS objects and arrays.

    e.g.

    // We have a string containing the JSON.
    const songsJson = '{ "Song1": { "Stamps": [ {"start": 14.0, "end": 30.0}, {"start": 32.0, "end": 40} ] } }';
    // We feed it to JSON.parse(), and get back a hierarchy of 
    // JS stuff that exactly matches the JSON.
    const songsData = JSON.parse( songsJson );
    console.log( songsData.Song1.Stamps[0].start );
    // Logs: 14
    

    The official-ish term for what JSON.parse() returns is a "JS object hierarchy", which is a little more general than it sounds, in that arrays and stuff can be part of that hierarchy, exactly like they can in JSON.

    Basically it's exactly the same as JSON, but made out of native Javascript stuff. And so it has the nice benefit that you can access all the data just using regular JS dot and bracket syntax.

    Also, just to be clear, a "JS object hierarchy" is not some special thing. It is literally just a JS object, with properties that can store other JS objects, or arrays or whatever. It's exactly the same thing you would get if you just made it by hand. Which brings me to my next suggestion...

    JS instead of JSON

    An alternative is to skip JSON all together, and just store the timestamps directly in nested JS objects and arrays, to begin with.

    e.g.

    const songsData = { 
    	Song1 : { 
    		Stamps : [ 
    			{ start : 14.0 , end : 30.0 } , 
    			{ start : 32.0 , end : 40 } ,
    		] ,
    	} ,
    };
    console.log( songsData.Song1.Stamps[1].start );
    // Logs: 32
    

    This is basically the same as the prior example, but it cuts out a step, and it's usually easier to write and read, because you don't need quotes around all the object property names.

    This is probably what I'd recommend going with, as if you use JSON, the best way to access it is to convert it into JS data anyway.

    Timestamps

    If the song can be broken into segments, like "vocals" and "instrumental", then instead of storing timestamps for the start and end of each segment, you can just store the timestamps for the start of each segment, and what kind of segment it is, "sing" or "perform". The start of a new segment can be treated as the end of the prior segment.

    You can also add more segment types, like idle, If you want to have the character idle before the song starts, or after it ends.

    So the JSON for this format might look like this:

    const songsData = { 
    	song1 : { 
    		stamps : [ 
    			{ time : 0 , anim : 'idle' } , 
    			{ time : 14 , anim : 'sing' } , 
    			{ time : 30 , anim : 'perform' } , 
    			{ time : 32 , anim : 'sing' } ,
    			{ time : 40 , anim : 'idle' } , 
    		] 
    	} 
    }
    
    console.log( songsData.song1.stamps[2].time );
    // Logs: 30
    console.log( songsData.song1.stamps[2].anim );
    // Logs: perform
    

    Making it work

    So if you use the format above, you have the song broken into segments, with each segment's info (start time and animation) stored in an array of segments. The first segment is at index zero, next segment is at index 1, etc.

    To get it working, you need the game to know what segment you're on. Well, almost...

    You actually only need to know what segment comes next, because you need to know when you reach it. Whatever the current segment is, it's already started, so it can't change anything anymore. Only the next segment can change the animation when it arrives. So the next segment is the one we care about.

    Anyway, we'll need a variable to store the nextSongSegmentIndex, since that's the one we need to be checking for.

    It will start at index zero, (before the songs starts, segment zero really is the next segment to play), and each tick, you can check to see if it should switch to that next segment.

    When that check determines that you do need to switch to the next segment, you grab the animation name out of that segment's "anim" property, and make the character sprite play that animation by name. So if that segment's "anim" property stored the name "sing", then your character will play the sing animation. You also add 1 to the nextSongSegmentIndex.

    The code might look something like this:

    // The data for all the songs. 
    // (I only have one song filled in here for this example.)
    const songsData = { 
    	song1 : { 
    		stamps : [ 
    			{ time : 0 , anim : 'idle' } , 
    			{ time : 14 , anim : 'sing' } , 
    			{ time : 30 , anim : 'perform' } , 
    			{ time : 32 , anim : 'sing' } ,
    			{ time : 40 , anim : 'idle' } , 
    		] ,
    	} ,
    	song2 : {} , // More data for song 2.
    	song3 : {} , // More data for song 3.
    };
    
    // Name of the current song. For looking up its data. 
    currentSong = null;
    
    // We need to know when the song started playing, so we can 
    // calculate how long it's been playing.
    songStartTime = null;
    
    // The array index of the next segment to play. 
    // Should be set to 0 before a song starts.
    nextSongSegmentIndex = 0;
    
    // The game tick, in which we'll do the check for advancing 
    // to the next song segment, and set the character animation.
    function tick ( runtime ) {
    	// We'll assume that the "currentSong" variable has 
    	// been set to the currently playing song, "song1"
    	// in this case.
    
    	// "songCurrentTime" is the time in seconds since the 
    	// song started. This line below assumes we saved the 
    	// "runtime.gameTime" value in the "songStartTime" 
    	// variable earlier, when the song started. 
    	// We subtract the current time from the song's start 
    	// time to find out how long it's been playing.
    	const songCurrentTime = runtime.gameTime - songStartTime;
    
    	// We need to get the next song segment's data.
    	const currentSongData = songsData[ currentSong ];
    	const currentSongStamps = currentSongData.stamps;
    	const nextSegmentData = currentSongStamps[ songSegmentIndex ];
    	// Segment start timestamp.
    	const nextSegmentTime = nextSegmentData.time; 
    	// The animation to play.
    	const nextSegmentAnim = nextSegmentData.anim; 
    
    	// Check if we need to switch to the next song segment.
    	// If the song time equals or passes the start time of 
    	// the next segment, then we switch to it and play it's
    	// animation. 
    	if ( songCurrentTime >= nextSegmentTime ) {
    		// Make the character sprite play the animation 
    		// named by that segment.
    		characterSprite.setAnimation( nextSegmentAnim );
    		// Advance to the next segment.
    		nextSongSegmentIndex += 1;
    	}
    }
    

    It's a bit simplified, but that's the basic framework, and I think it does more or less what you're looking for.

    Hope it helps out. :)

  • fisholith What's the point of posting AI-generated responses?

  • Try Construct 3

    Develop games in your browser. Powerful, performant & highly capable.

    Try Now Construct 3 users don't see these ads
  • Hey dop2000,

    No part of my response or my code is AI generated.

    I'm not sure if you're joking or if your serious, but I'll assume that you're serious just in case. No hard feelings either way though.

    I have been making help and tutorial posts with this same kind of structure and style for Construct 1, 2, and 3, going all the way back to 2009.

    (15-ish years. That feels weird to realize.)

    e.g.

    Here's an example from 8 years ago, where, for part of my reply explaining alpha blend modes, I built an application in Construct 2 where you could interact with all possible alpha blend mode combinations in a sandbox environment.

    construct.net/en/forum/construct-2/how-do-i-18/help-object-blend-modes-121928

    e.g.

    Here's an example from 6 years ago, where I was asking a question, using the same structure and style, and you helped me troubleshoot my problem. Also, thanks again, by the way. Your suggestion of looking at the JavaScript console error log helped save my project.

    construct.net/en/forum/construct-2/how-do-i-18/solved-preview-crash-139313

    e.g.

    My solution write-up for that problem you helped me with, is likewise basically the same style I used above in my reply to Famous:

    construct.net/en/forum/construct-2/how-do-i-18/solved-on-preview-i-get-a-cras-139313

    The majority of my long form posts are in this style, and have been for over a decade.

    That's basically the style I use whenever I'm trying to explain something with a bunch of parts, mainly because I want to keep things clear and easy to follow. I don't know that I always succeed at that, but that's what I'm generally trying for.

    As an aside, I actually kind of miss the older Construct 2 forum software, that supported custom text colors, because I frequently color coded variable names in my examples and replies, to make them easier to visually keep track of for anyone reading. After the migration to the newer forum software, the color coding in my older posts was lost. There were several post I made on color math, where I actually color coded the R G and B values. I probably would have backed those up in their original form if I'd realized the formatting was going to be stripped. Granted, probably not that important in the long run.

    Anyway, again no worries. I am a real person though, and that is my actual work.

  • fisholith Oh, I'm sorry, my mistake. It's just at the first glance your comment looked very similar to what ChatGPT generates when I ask it programming questions. I can see now that it's not, apologies again.

    Although I don't understand why your proposed solution is in Javascript. It's clear from the OP's post that they are using the event system.

  • No worries at all, dop2000 :)

    Also, Looking again, I see what you mean, that Famous's original question is in terms of events.

    I've talked with Famous a few times in the past about this project, and in each case it involved in-event-sheet JavaScript, so I think I just assumed it was still kind of taking the JavaScript route, but you're right. That also makes way more sense to me now, because I kept wondering what that little arrow "→" was. I was thinking some kind of psudocode.

    So, ImPrettyFamous, I think most of my solution will work as in-event-sheet JS, with the only major difference being in the last block of code. Where the code outside the tick() function is basically just some global variables. And the code inside the tick() function, (just the code inside, and not the tick() function as a whole), you'd place in the event sheet as JS triggered by an "Every Tick" event.

    You could also just convert it all to C3 events, as it's really just a matter of keeping the index of the next song segment, and the check for when to switch. Though I think the nested song data is a little easier to interact with directly in JS.

    Also, just as an extra note, if you rebuild the JS code example but as event sheet code, all the variables I created as a "const" in the JS example, would be regular variables in the event sheet, not constants. (In JS a constant can be assigned a value from an expression, but in Construct they are static values you type in, so they can't be decided while the program is running.)

Jump to:
Active Users
There are 1 visitors browsing this topic (0 users and 1 guests)