DiegoM's Forum Posts

  • You might be able to get around that by creating a temporary image, rather than using the one from the cel.

    aseprite.org/api/image

    I am thinking of creating a new image, making it the size of the frame so it includes all the empty space, and then use image.drawImage to paste the original one in the correct position. Not sure where to take the "correct" position from though, but it should be there somewhere.

    The position should be Cel.position

    aseprite.org/api/cel

  • I haven't looked closely enough to the documentation or even tried anything but I think that making an extension to visually edit image points and collision polygons should be possible since there is functionality to draw custom things on a canvas in a dialog, as well as access to mouse events... it does sound like a ton of work though, so I don't think I will be doing that.

    I might do some experiments to figure out if it is possible or not.

  • I figured a first attempt would miss the mark, specially because I am sure there are a few popular workflows that people like to use when making their animations. Just wanted to make something because it's easier to tweak an existing script than coming up with it from scratch.

    Generating a unique zip file for each tag sounds like it would fit in nicely with what the API has to offer. That would prevent using the tag information for C3 though. Fortunately I saw in the documentation of Aseprite, that you can assign custom data to frames. That custom information could be used by the exporter to generate C3 tags for each frame.

  • Should have bolded and quoted "RELATIVELY".

    Anyway, I got interested in this and cobbled this:

    -- Get the active sprite
    local sprite = app.sprite
    
    if sprite == nil then
    	print("No active sprite found.")
    	return
    end
    
    local spriteName = string.match(sprite.filename, "[^/\\]+$")
    spriteName = spriteName:gsub("%..*$", "")
    
    -- Create a new dialog
    local dlg = Dialog("Export animation for C3")
    dlg:entry{ id="animationName", label="Animation name:", text=spriteName }
    dlg:check{ id="loop", label="Loop" }
    dlg:check{ id="pingPong", label="Ping Pong" }
    dlg:number{ id="repeatCount", label="Repeat count:", text=string.format("%i", 1), decimals=0 }
    dlg:number{ id="repeatTo", label="Repeat to:", text=string.format("%i", 0), decimals=0 }
    dlg:button{ id="ok", text="OK", focus=true }
    dlg.bounds = Rectangle(dlg.bounds.x - 50, dlg.bounds.y, dlg.bounds.width + 100, dlg.bounds.height)
    dlg:show()
    
    -- Get the data from the dialog
    local data = dlg.data
    
    -- Check if the user cancelled the dialog
    if data == nil then
    	print("Export canceled by user.")
    	return
    end
    
    if not data.ok then
    	print("Export canceled by user.")
    	return
    end
    
    -- Extract the directory path of the original sprite file
    local originalFilePath = sprite.filename
    
    local outputFolder = app.fs.joinPath(string.match(originalFilePath, "^(.-)([^\\/]-%.?([^%.\\/]*))$"), "tmp-export-for-c3")
    if outputFolder == nil then
    	print("Failed to determine output folder.")
    	return
    end
    
    -- Use the name that was set in the dialog or the file name if no name was specified in the dialog
    local defaultAnimationName = data.animationName and data.animationName or spriteName
    
    -- Construct the JSON data
    local jsonData = {
    	["use-raw-folder-names"] = true,
    	["animation"] = {
    		["name"] = defaultAnimationName,
    		["speed"] = 0,
    		["loop"] = data.loop,
    		["ping-pong"] = data.pingPong,
    		["repeat-count"] = data.repeatCount and data.repeatCount or 1,
    		["repeat-to"] = data.repeatTo and data.repeatTo or 0,
    		["frame-durations"] = {},
    		["frame-tags"] = {}
    	}
    }
    
    local frameTags = {}
    
    for i, tag in ipairs(sprite.tags) do
    	local frame = tag.fromFrame;
    	frameTags[frame.frameNumber] = tag.name
    end
    
    local fps = math.floor((1000 / (sprite.frames[1].duration * 1000)) + 0.5)
    
    jsonData["animation"]["speed"] = fps
    
    -- Flatten to merge all layers
    sprite:flatten()
    
    for i, cel in ipairs(sprite.cels) do
    	local filename = app.fs.joinPath(outputFolder, i .. ".png")
    
    	cel.image:saveAs(filename)
    
    	local lfps = math.floor((1000 / (cel.frame.duration * 1000)) + 0.5)
    
    	table.insert(jsonData["animation"]["frame-durations"], math.floor((fps / lfps) + 0.5))
    
    	local frameTag = frameTags[cel.frame.frameNumber] and frameTags[cel.frame.frameNumber] or ""
    
    	table.insert(jsonData["animation"]["frame-tags"], frameTag)
    end
    
    -- Undo the flatten done earlier
    app.command.Undo()
    
    -- Write JSON data to file
    
    local jsonFilename = app.fs.joinPath(outputFolder, "c3-import-settings.json")
    local jsonFile = io.open(jsonFilename, "w")
    if jsonFile then
    	jsonFile:write(json.encode(jsonData))
    	jsonFile:close()
    else
    	print("Failed to generate JSON file.")
    	return
    end
    
    local pathSeparator = package.config:sub(1, 1)
    -- Choose the appropriate zip command based on the operating system
    local osType = (pathSeparator == "\\") and "Windows" or "Unix-based"
    
    local zipCmd
    
    local zipFilename = app.fs.joinPath(outputFolder, defaultAnimationName .. ".zip")
    
    if osType:find("Windows") then
     -- Use PowerShell on Windows
     zipCmd = 'powershell Compress-Archive -Path "' .. outputFolder .. pathSeparator .. "*" .. '" -DestinationPath "' .. zipFilename .. '" -Force'
    else
     -- Use the zip command on Unix-based systems
     zipCmd = 'zip -r "' .. zipFilename .. '" "' .. outputFolder .. pathSeparator .. "*" ..'"'
    end
    
    -- Execute the zip command
    os.execute(zipCmd)
    
    -- Move the zip file to the new destination path
    local directoryPath, fileName = zipFilename:match("(.+)[/\\]([^/\\]+)$")
    local newZipFilePath = app.fs.joinPath(directoryPath, "..", fileName)
    
    local moveCommand
    
    if osType:find("Windows") then
     -- Windows system
     moveCommand = "move".. " " .. zipFilename .. " " .. newZipFilePath
    else
     -- Unix-like system (Linux, macOS, etc.)
     moveCommand = "mv".. " " .. zipFilename .. " " .. newZipFilePath
    end
    
    os.execute(moveCommand)
    
    -- Remove the temporary folder
    local removeCommand
    
    if osType:find("Windows") then
     -- Windows system
     removeCommand = "rmdir /s /q" .. " " .. outputFolder
    else
     -- Unix-like system (Linux, macOS, etc.)
     removeCommand = "rm -rf" .. " " .. outputFolder
    end
    
    os.execute(removeCommand)
    

    That produces a zip file with all the images and the JSON so C3 knows what to do with it.

    I found a couple of issue which can not be reconciled:

    1. There is no explicit FPS for the whole animation, instead each frame has a time in milleseconds. To fill in a value I calculate a tentative FPS from the duration of the first frame and then use that value to calculate durations for the rest of the frames which are relative to that first measure.
    2. The way Aseprite uses settings such as Loop is per tag, so you can have multiple different ones in ranges of the whole timeline. C3 just has one value for the whole animation. So instead of using the values from Aseprite, the export script just shows a dialog with the settings that can't be extracted in a way that makes sense.
    3. The export script can use the tag information, but not completely because Aseprite is more advanced that C3 in that regard. In C3 you only have a tag for each frame, that's it. In Aseprite you can define a range. To do something useful with the data, the frames where a tag range starts, get the value, while frames covered by the range, are left empty. The information could be used differently.
    4. The script flattens all the layers to export all the image information. This could be done differently, like for instance letting the script just export a specified layer.

    You can try it out and see if it's useful to you.

  • I don't know anything about noise, but I did a little bit of digging and found what looks like a rather elegant solution for your problem, so elegant that I wouldn't have been able to figure it out in a million years :P

    gamedev.net/blog/33/entry-2138456-seamless-noise

    Towards the end of the article there is this bit of code:

    for x=0,bufferwidth-1,1 do
    	for y=0,bufferheight-1,1 do
    		local s=x/bufferwidth
    		local t=y/bufferheight
    		local dx=x2-x1
    		local dy=y2-y1
    
    		local nx=x1+cos(s*2*pi)*dx/(2*pi)
    		local ny=y1+cos(t*2*pi)*dy/(2*pi)
    		local nz=x1+sin(s*2*pi)*dx/(2*pi)
    		local nw=y1+sin(t*2*pi)*dy/(2*pi)
    
    		buffer:set(x,y,Noise4D(nx,ny,nz,nw))
    	end
    end
    

    I am not too sure what language is that, but whatever it is, it should be easy to translate it to JavaScript using simplex-noise.js

    // Going to need 4D noise
    import { createNoise4D } from "./simplex-noise.js";
    
    const noise4D = createNoise4D();
    
    // The size of the noise data we are going to generate
    const width = 250;
    const height = 250;
    
    // Shortest way I found to declare a 2D array to store the result
    const noiseTexture = Array.from({ length: width }, () => new Array(height).fill(0));
    
    for (let x=0; width-1; x++)
    {
    	for (let y=0; height-1; y++)
    	{
    		// Do some sick math here!
    		let s = x / width;
    		let t = y / height;
    		let dx = x2-x1;
    		let dy = y2-y1;
    
    		let nx = x1 + Math.cos(s*2*Math.PI) * dx / (2*Math.PI);
    		let ny = y1 + Math.cos(t*2*Math.PI) * dy / (2*Math.PI);
    		let nz = x1 + Math.sin(s*2*Math.PI) * dx / (2*Math.PI);
    		let nw = y1 + Math.sin(t*2*Math.PI) * dy / (2*Math.PI);
    		
    		// Store the result for each coordinate
    		noiseTexture[x][y] = noise4D(nx,ny,nz,nw);
    	}
    }
    

    That should produce seamless noise data you can use elsewhere.

    I got the link to the article from one of the answers found here:

    gamedev.stackexchange.com/questions/23625/how-do-you-generate-tileable-perlin-noise

    The snippet is explained in "simple" terms in there

    Basically, map the X coordinate of your pixel to a 2D circle, and the Y coordinate of your pixel to a second 2D circle, and place those two circles orthogonal to each other in 4D space. The resulting texture is tileable, has no obvious distortion, and doesn't repeat in the way that a mirrored texture would.

    That should be obvious XD

    By the way, I haven't tried any of this, but it looks like it might just work!

  • I didn't try it out myself and didn't notice the main file in the repo is Typescript and that the type definitions are no where to be seen. You need those so TypeScript can work.

    Anyway, it looks like you can't just copy and paste from the repo, because the code you end up using needs to be generated using a build script that is also in there.

    Instead of doing that, you should download it from from NPM. That way you get the built library in both JavaScript and TypeScript. To get this working I think it's easier to just look for the Javascript file and import that in C3.

    To load the library I did the following:

    1. Install NPM
    2. Create an empty folder somewhere, the desktop is an easy place.
    3. Open that folder
    4. Open a command line window in that folder (In Windows if you press SHIFT while right-clicking, you should see an "Open Powershell" option)
    5. In the command line type npm i -S simplex-noise
    6. That will create a node_modules folder with all the files you are looking for
    7. I took the .js file in ..\node_modules\simplex-noise\dist\esm
    8. Import that file into C3

    After all of that, I imported in the main script with:

    import { createNoise2D } from "./simplex-noise.js";
    

    I don't know if the library will be useful to you, but you can load it like this and try it out.

    I assumed you know about NPM and how to install it. If you need help with that let me know!

  • That last snippet that you are not sure what it is, is what classes used to look in Javascript and they are used just like classes defined with the newer syntax.

    	let grad = new Grad(1, 2, 3, 4);
    	grad.dot2(0, 1);
    

    Maybe you can try this module github.com/jwagner/simplex-noise.js , it looks promising.

  • Looking back on this... I can't remember what was the reason to skip implementing it. It should work just as Create Object with the minor difference that the newly created instance would have the angle of the picked instance instead of the angle of the template, everything else should work the same.

  • That's because what the import keyword is doing is creating a variable, and the variable can only be seen in the scope it is created in. Just like a regular variable can not be seen outside of the function it is created in.

    There is a caveat though. If the module you are trying to use is an older one, the way they export things is by attaching properties to the global scope. So in that case, it doesn't matter were the import statement is, because the module will just add things globally.

    For example, if you take the library you are trying to use and add this to the very end:

    globalThis.FastNoiseLite = FastNoiseLite;
    

    A FastNoiseLite property will be created in the global scope, then it doesn't matter in which file you import the module, globalThis.FastNoiseLite will be available.

  • A couple of things to get this working.

    1) Use the import in the file you need it.

    import * as FastNoiseLite from "./fastNoiseLite.js";
    

    That creates the variable FastNoiseLite in the script where it is written. So instead of putting it in your main.js you put it at the top of importForEvents.js

    2) This part is tricky.

    The module you are using is exporting a class using the default keyword

    export default FastNoiseLite {
    	...
    }
    

    When you do

    	import * as FastNoiseLite from "./fastNoiseLite.js";
    

    What is happening is that all the things the module is exporting become part of the new FastNoiseLite variable.

    Because what you are looking for was set as the default thing to export, what you need to write to access the object you are looking for is this:

    	// Use FastNoiseLite.default to access the default export from the module
    	
    	const noise = new FastNoiseLite.default();
    	noise.SetNoiseType(FastNoiseLite.default.NoiseType.Perlin);
    

    In this particular case where the module you want to use has a default export, it is much better to use this to import:

    	import FastNoiseLite from "./fastNoiseLite.js";
    

    Doing that just places the default export of the module into the FastNoiseLite variable, and is used like you expect it should be used.

  • The Animations editor already supports bulk importing.

    construct.net/en/make-games/manuals/construct-3/interface/animations-editor

    The basic use case is just being able to drop a folder or a zip file onto the animations pane and have an animation created. The animation is named after the folder or zip file.

    For more complex use cases, the importer supports placing a configuration file next to the files. The configuration file is a JSON file which can include several settings to be applied on the created animations.

    I haven't tried it myself, but it should be relatively easy to have Aseprite (or any other program that supports some kind of scripting) to package all the files together in a way C3 will understand.

  • You can open and close all the bars from the main menu.

    Menu -> View -> Bars -> Tilemap bar

  • Try Construct 3

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

    Try Now Construct 3 users don't see these ads
  • This happens when you open the latest beta and then go back to using the stable version. The latest betas are adding a little bit of extra information to the Animations editor save state. Older versions don't understand that, and crash when they try to load it.

    Clearing the browser cache works because it allows C3 to start from scratch again.

    Since most of the user base only uses stable versions, most people aren't seeing this problem.

  • This issue has been mentioned before, but we haven't been able to figure out what the problem is. On top of that it doesn't seem to affect a lot of people so it's harder to find out what the problem might be.

    I mentioned Firefox as a workaround, because we are not really sure how to fix this one, mainly because we haven't been able to reproduce it ourselves.

  • Have you tried using a different browser? Firefox would be a good try, as it is completely different from Chrome. Other browsers, like Edge or Opera might run into the same problem as they use the Chromium engine under the hood, which makes them pretty much identical to Chrome.