Fraymakers

Fraymakers

58 ratings
Fraytools - How Do I...? Guide
By SaltLevelsMax and 14 collaborators
Guide put together with the help of multiple members of the community to assist new (or old) Fraytools users figure out how to add various mechanics to their creations. If there is anything you would like to see added to this guide, please feel free to leave a comment below.
2
3
   
Award
Favorite
Favorited
Unfavorite
Setup / Basics
Official Setup / Deep Dive Guide
https://docs.google.com/document/d/1o6CE04FRaqQyksD2uX7zxOQXYiZ_M2YCwtqK0mMoARY/edit#heading=h.2qfy7vmqm7cm
Official API Docs: https://github.com/Fraymakers/fraymakers-api-docs

Community API Documentation
Communit API Docs: https://shifterbit.github.io/fraymakers-api-docs/classes/
These are the same same as official docs but searchable + some hscript guides

Video From Bowler - Basics of Character Creation

Uploading to the Workshop
In order to upload your creation to the workshop, there are two main things you need in your creation's 'custom' folder besides the .fra project file. First is the meta file which should be automatically created in the custom folder; you will want to make sure this has a version number, is set to "public" if you want everyone to see it, or "unlisted" if you want only people with the link to see it, and a description that will be used for the Steam Workshop page. The other thing that is required is a PNG titled thumb.png exactly, there is a file size limit on this image (1MB) so the recommended size is typically 1000x1000 pixels.

Once you have these things in your custom folder, to upload your content to the workshop, all you have to do is open the game, go to the custom content section on the main menu, click the Local tab, find your creation and click on it. The game will ask you to fill in the version number which should be prefilled to the correct number based on your meta file as long as you updated that. After this the game will freeze for a short time and stop you from going back to the main menu, once it is done you will get a notification that your creation has been uploaded and you will be able to find it on the Steam Workshop page (generally there is a 10-30 minute delay before other users will be able to see the creation).
Keyboard Shortcuts
Ctrl -
New Project: Ctrl+N
New Project From Template: Ctrl+Shift+N
Open Project: Ctrl+O
Close Workspace: Ctrl+W
Save: Ctrl+S
Save All: Ctrl+Shift+S
Publish Project: Ctrl+Enter
Undo: Ctrl+Z
Redo: Ctrl+Y
Copy Frame(s): Ctrl+C
Paste Frame(s): Ctrl+V
Cut Frame(s): Ctrl+X
Zoom In/Out: Ctrl+Scroll Wheel or Ctrl+Plus/Minus

Function Keys -
Add Frame: F5
Delete Frame: Shift+F5
Add Keyframe: F6
Clear Keyframe: Shift+F6
Add Blank Keyframe: F7

General -
Clear Frame: Delete
Next Frame: .
Previous Frame: ,
Projectiles
Adding a new Projectile
Unfortunately, adding a new projectile is a bit cumbersome right now. There are examples of projectiles on the assist and character templates, but if you want to a second projectile (or to add one to a stage) you need to make new one from scratch. You may be tempted to just copy the files from the template, but I would suggest not doing that as you need to fix several things afterwards that are hard to debug. Instead try one of these two methods:

Using the custom plugin
Instructions and download are on discord here: https://discord.com/channels/271284605531717632/1142975026841264148

If you don't want to go on discord for some reason I've rewritten them here:
Originally posted by awesoe:
Quick Projectile Creator
A plugin that makes adding new projectiles much less of a hassle.
-------------------------------------------------------------------------------------------
Instructions on how to use:

1: Download the plugin[github.com] and extract the folder to your plugins folder under FrayToolsData (see the deep dive[docs.google.com] for how to install plugins).

Once again, the path to your plugins folder should look something like this:
.png]

2: Open FrayTools and enable the plugin via Plugins -> Plugin Manager


3: After enabling the plugin, click on Projectile Creator inside of the Plugin Manager.


4: Give a name to your projectile.


5: Link your character's MANIFEST.JSON.


6: Click Save. a ZIP file will be downloaded with everything you'll need for a projectile.


7: Extract the files to your character's Library folder.


8: Replace your old MANIFEST.JSON with the updated one.


9: Move the remaining files to their respective places.


...And that's all!
ping me here if you have any problems

10: Be ABSOLUTELY CERTAIN to change the costume ID!

(if the ID didn't change there shouldn't be issues anyway!)

Manually
A manual method that can be used is:
  1. In fraytools, create a new folder (within the scripts folder)
  2. Create a new entity file for the projectile, and also give it an ID. as well, change the entity type to projectile. Create 2 animations, one for "idle" and one for "destroying"
  3. Also create 4 new script files within. Name them such that one for "projectilestats", "animationstats", "hitboxstats" and "script". While creating each file, open the properties and change the ID field to match the name (avoid spaces or special characters in IDs)
    • You can copy the contents of the corresponding template scripts into this file. Can get the template versions from here[github.com].
    • It is very important to change "charactertemplateprojectile" in your projectile stats file to match the ID of the entity file in step 2.
    • Also in this file, change "projectileIdle" to whatever you called the idle animation, and same for "projectileDestroy".
  4. In your manifest, add the following code at the end:
    { "id": "id_of_your_projectile", "type": "projectile", "objectStatsId": "id_of_projectile_stats_script", "animationStatsId": "id_of_animation_stats_script", "hitboxStatsId": "id_of_hitbox_stats_script", "scriptId": "id_of_projectile_script", "costumesId": "id_of_costume_file" }
    You can leave the "costumesId" line out if you want, just remember to remove the comma before it. Otherwise "id_of_costume_file" should either match what your assist/character is using or creating a new file matching that ID.

    Your manifest will look something like this if done correctly. If something goes wrong here, then you may not be able to export or you'll have issues with trying to spawn it.

Using the new projectile
The typical method is to copy and rename everything related to the projectile in the script:
var neutralSpecialProjectile = self.makeObject(null); // Tracks active Neutral Special projectile (in case we need to handle any special cases)
//offset projectile start position var NSPEC_PROJ_X_OFFSET = 40; var NSPEC_PROJ_Y_OFFSET = -50;
function fireNSpecialProjectile(){ neutralSpecialProjectile.set(match.createProjectile(self.getResource().getContent("id_of_projectile"), self)); neutralSpecialProjectile.get().setX(self.getX() + self.flipX(NSPEC_PROJ_X_OFFSET)); neutralSpecialProjectile.get().setY(self.getY() + NSPEC_PROJ_Y_OFFSET); }
and then use "fireProjectile()" (or whatever you renamed the function) in your script.

You can simplify things a bit generally by just doing the following when you want to spawn it:
var proj = match.createProjectile(self.getResource().getContent("id_of_your_projectile"), self); proj.setX(self.getX() + self.flipX(X_OFFSET)); proj.setY(self.getY() + Y_OFFSET));
The last two lines are not necessary, but are for making sure the projectile spawns relative to your character/assist. Can leave them out if done on a stage or the initial position doesn't need to be relative.
X_OFFSET and Y_OFFSET should be replaced by numbers based on where you want it to relatively spawn
And, if doing from a stage (for a hazard) or don't want the projectile to have an owner, should do " match.createProjectile(self.getResource().getContent("id_of_your_projectile"));" instead of the first line.

TODO: Need to add pictures for manual method



Controlling Projectile with Player Inputs

If you would like to control the movement or other various mechanics of your projectile, look at the following example:

if(self.getOwner().getHeldControls().UP){ self.setYVelocity(-5);}

So long as you are the owner of the projectile, this code will cause it to move upwards at a speed of 5 while holding up. You can add more if statements to add right/left/down to apply velocity in the corresponding directions as well to have full control over your projectile.

If your projectile is reflectable, and you do not want to have the funny situation of your opponent now being able to control the projectile themselves with their inputs, you will want to change the code so that it is only the original owner that it looks for. To start, in the script of your projectile, you will need to have the variable of:

var originalOwner = null;

Then, inside function initialize of your projectile script you will put the following code:

originalOwner = self.getOwner();

This will set the original owner as yourself as soon as the projectile / entity is spawned. So even if the owner of the projectile changes, the original owner stays the same.
After this, you will update the code for checking player inputs to a variation of the following:

if(originalOwner.getHeldControls().UP){ self.setYVelocity(-5);}

This will ignore the current owner, and only look for the inputs of the original owner.

Sounds (SFX)
Make sure your sound files are either .wav or .ogg, then set the ID for it by double clicking the file inside of Fraytools, opening the properties tab on the right side of the window, and entering any name you would like in the ID box.

Global SFX

You can apply a Global Sound effect in your frame script like this:

AudioClip.play(GlobalSfx.AIRDASH);

Custom SFX -

To play a custom sound during your frame script, or elsewhere, use this:

AudioClip.play(self.getResource().getContent("Sound ID"));

Hit Sound Override -

If you want to replace the hit sound for a specific attack with a global sound effect, you will use the following code in hitbox stats:

hitSoundOverride: GlobalSfx.AIRDASH

or if you would like to use a custom hit sound effect instead of global, you will type it like this:

hitSoundOverride: "Sound ID"

Stop sounds -

In order to stop sounds midway through, you will need to set the sound as a variable. In your script.hx you can put:

var whateversoundvariablename = null;

Then when you are ready to play the sound of your choosing, in the frame script you would type:

whateversoundvariablename = AudioClip.play(self.getResource().getContent("Sound ID"), {loop: true});

If you don't want the sound to loop, then use the example above for playing sounds but with the variable

To stop this sound, you just need to use these two lines of code:

whateversoundvariablename.stop(); whateversoundvariablename = null;

If you would like to make sure a sound stops when not in a specific animation you can check for that like so:

if(self.getAnimation() != "Animation Name" && whateversoundvariablename != null){ whateversoundvariablename.stop(); whateversoundvariablename = null; }

This bit would go inside the function "update" of your Script.hx to constantly check for it.
Visual Effects (VFX)
Global VFX -
To spawn one of the global visual effects during an animation, you will put the following code in your frame script:

match.createVfx(new VfxStats({spriteContent: "global::vfx.vfx", animation: GlobalVfx.DUST_SPIN, x:0, y:0, scaleX:1, scaleY:1, layer:VfxLayer.CHARACTERS_FRONT}), self);

Included in this example code, but not necessarily required are the X, Y, scaleX, scaleY, and layer options. X and Y are used to determine placement of where the VFX spawns, scaleX and scaleY are used for the horizontal/vertical size of the sprite, and layer can be either "front" or "behind" this places the VFX in front of or behind the character/entity that spawned it.


Custom VFX -
Similar to the global VFX above, you will place the following code in the frame script when you want your custom animation to spawn and play out. You can use the same, x, y, scaleX, layer, etc. modifiers as above. You can either make a new animation inside your character's animation list to use as VFX, or use a separate entity animation list, just enter the corresponding entity ID, and animation name.

match.createVfx(new VfxStats({spriteContent: self.getResource().getContent("Entity ID"), animation: "Animation Name"}), self);

Hit Effect Override -
If you would like to replace the default hit effects Fraymakers offers with one of your own, one of the other global VFX, or nothing at all, you will edit the hitboxstats.hx for your attack with one of these:

For your hit effect to display as any of your own animations:
hitEffectOverride: self.getResource().getContent("Entity ID") + "#AnimationName"
Make sure the # is included before your animation name.

To use a global VFX:
hitEffectOverride:GlobalVfx.LIGHT_NORMAL_HIT

For no VFX to play at all on hit:
hitEffectOverride:"#n/a"


After Images -
Making an after image is one of the easiest and the most satisfying look for fast moves or speedy characters. You'll be able to edit pretty much everything about it if you want to as well, such as: shrinking, scaling, the fade, and much more.

You'll need two variables above your
function update()

var frames_since_last_showed_img = 0; var frame = self.getCurrentFrame();

You're going to want to update these every frame and find the animation for it
so inside your
function update()
will look like this

// Since update runs every frame this will run every frame frames_since_last_showed_img += 1; frame = self.getCurrentFrame(); // After Image VFX if (self.getAnimation() == "whatever animation you want") { // This makes it so if its not in this animation the after image will not happen if (frames_since_last_showed_img >= 3) { // The more frames the more spread out the after image will be var vfx = match.createVfx(new VfxStats({ spriteContent: self.getResource().getContent("Entity ID"), animation: "" + self.getAnimation(), fadeOut: true, timeout: 12, layer: VfxLayer.CHARACTERS_BACK }), self); // timeout will be how long until the image fully fades vfx.playFrame(frame); vfx.addShader(self.getCostumeShader()); vfx.pause(); vfx.setAlpha(0.6); frames_since_last_showed_img = 0; } }

This is just an example, Exo uses these numbers a lot. You can tweek everything to your desire

You can simplify this code by using the following code in the framescript on each frame you want an afterimage to appear, or by using a timer to automate how often it appears.

var afterimage = match.createVfx(new VfxStats({ spriteContent: self.getResource().getContent("character"), animation: self.getAnimation(), fadeOut: true, timeout: 12, layer:VfxLayer.CHARACTERS_BACK }), self); afterimage.playFrame(self.getCurrentFrame()); afterimage.addShader(self.getCostumeShader()); afterimage.pause();
RNG / Randomness
Example Code:
var random = Random.getValue(); if(random <= .20){ self.playAnimation("option 1, 20% chance"); } else if (random <= .30){self.playAnimation("option 2, 10% chance"); } else if (random <= .60){ self.playAnimation("option 3, 30% chance"); } else if (random <= .95){ self.playAnimation("option 4, 35% chance"); } else { self.playAnimation("option 5, 5% chance"); }

Random.getValue picks a random value between 0 and 1. Use 'if / else if' statements to determine how likely you want that option to be.
For the above example, having if(random <= .20) makes the first option a 20% chance of happening as it is looking for the value to be less than or equal to .20. After the first option you will use 'else if' statements so they will play out if the previous option was false.
The final option can be written with 'else if', but using 'else' will simplify it as it no longer needs to check the value.

Other random codes:
Random.getChoice
Example:
//Play any of 2 sounds at random AudioClip.play(self.getResource().getContent( Random.getChoice(["sound1", "sound2"])));

Random.getInt
Example:
var chance = Random.getInt(1, 10); //Setting sound to have a 20% chance to play. if(chance <= 2){ AudioClip.play(self.getResource().getContent("g")); }
Cooldowns
There are a few different ways to do cool downs, but one of the shortest ways would be to add this to the script.hx:

var moveCooldown = self.makeObject(null);

Then inside the framescript of the move, on the frame you want the cool down to begin you will put the following:

moveCooldown.set(self.addStatusEffect(StatusEffectType.DISABLE_ACTION, CharacterActions.AERIAL_FORWARD)); self.addTimer(60,1, function() { self.removeStatusEffect(StatusEffectType.DISABLE_ACTION, moveCooldown.get().id); }, {persistent:true});

Replace AERIAL_FORWARD with whichever character action you wish to disable, and the 60 would be replaced with however many frames you would like the cool down to last.

Command Grabs
First you will need two animations: the animation where they do the grab, and the animation when the grab connects.

The grab animation:
This will work differently from the default grab animation. Let's say you want side special as a grab, you will need to do the following:

1. Add a grab box to the animation.



2. Put a frame script with this line of code. Include the name of the grab followup animation you want to make. Make sure this line is before the grab box.
self.addEventListener(GameObjectEvent.GRAB_DEALT, function(){ self.playAnimation("grab_followup_animation"); });


Now you have the grab part down, next will be the followup animation.

Follow up animation:
In this part, you will need to place a grab hold point layer and a hitbox. And example of how it will look is looking at the template throw_forward, throw_back, etc.

1. Add in a grabholdpointlayer and a hitbox. Make sure you make the point type a grab hold point on the properties tab.




you can move the grab point around to position the opponent during the follow up animation.

2. On the first frame of the frame script, add this line of code (this is also in the throw template).
//brings grabbed character in front of your character self.getGrabbedFoe().bringInFront(self); //forces enemy character into frame 12 of the "hurt_thrown" animation if (self.getGrabbedFoe().hasAnimation("hurt_thrown")) { self.getGrabbedFoe().playAnimation("hurt_thrown"); self.getGrabbedFoe().playFrame(12);

You can interchange self.getGrabbedFoe().bringInFront(self); to self.getGrabbedFoe().sendBehind(self) if you want the grabbed opponent to appear behind your character.
In self.getGrabbedFoe().playFrame(12);, you can interchange what frame of hurt thrown you seem would fit the opponent position will be.

3. You can place the hitbox to where you want the followup animation to deal damage/throw/release the opponent. Make sure you remove the point layer where the hitbox is, unless if the followup is a multihit move. Make sure you add a hitbox stat for the follow up animation.



Counters


This goes on first frame of the frame script when your counter window begins. You can use various kinds of body statuses, but invincible grabbable is typically what is used for counters to allow yourself to be grabbed as a punish, but getting hit will activate it.

self.updateAnimationStats({bodyStatus:BodyStatus.INVINCIBLE_GRABBABLE});

Function to play the counter attack animation when receiving a hit. The Follow-up animation will also need to have some sort of invincibility or armor to keep it from getting immediately interrupted.

function downspecialcounter(event){ self.resetMomentum(); self.playAnimation("Counter Attack Animation Name"); } self.addEventListener(GameObjectEvent.HIT_RECEIVED, downspecialcounter);

This next part goes in the frame script on the frame you want the counter window to end. Removes event listener for hit received and removes invincible grabbable body status.

self.removeEventListener(GameObjectEvent.HIT_RECEIVED, downspecialcounter); self.updateAnimationStats({bodyStatus:BodyStatus.NONE});


Color Palettes
When you are ready to add different colors to your character/assist, you will head over to costumes.palettes by double clicking on it.

The first thing you'll notice is that the preview sprite will most likely be way off center, and/or the wrong image. Here is how to fix both:

Adjusting the placement of your preview image

To move the preview image around, you can simply click and hold the scroll wheel / middle mouse button and drag it to the desired location. If you do not have a scroll wheel, you can also hold the space bar and left click, then drag your image around.
If you would like to zoom in or out of your image, hold CTRL and either use the scroll wheel, or press + or -.

Changing the preview image

Navigate to the properties tab at the top right of your Fraytools window.


From here, you can change the image by clicking and dragging a sprite/image from wherever it is located inside of your fraytools project on the left and placing it in the light gray box below where it says Preview Image.


If you see various colored boxes instead of the preview image option, you will need to click the painters palette icon at the top of your properties.


Adding / Importing Colors From Your Image

Open the palettes tab on the left side of your screen, opposite the properties tab, then click on any of the other palettes to change your properties tab to the Palette Map screen, then return to your default color palette.

The first thing you'll want to do is click the red X's next to each of the default colors put in there from the template character to remove them.

There are three options here to take colors from your image, and add them to the Palette Map to edit. These three options are the eyedropper, Add, and Import.



The quickest way to add all the colors you'll be using is the Import button which will automatically add every color from your preview image onto the palette map. If you would like to add them manually then you can use the eyedropper tool to click on the exact color you would like to add from your palette preview, or use the Add button to enter the hex code directly.

Once the colors have been added, you can click in the box where it says Untitled Palette Color to replace it with any name you like to help keep track of what you are changing. You can also change the Palette Name. If you would like to adjust the order of your colors in the map, just click on the up or down arrows to the right of the color name.



Changing the Colors

After your mapped colors are added, changing them to the desired color for each alternate costume is simple. All you have to do is click on the right colored box (the left colored box is the mapped color so you will not want this changed), a color selection box will pop up and you can adjust the slider, or enter a hex code directly to pick any color you wish. If you would like to a make a certain color a varying degree of transparent, you can click the up/down arrows button in the color selector to switch to RGBA, where the A is the alpha value and can be changed to something lower than 1 to apply transparency to the color.




Adding Additional Color Palettes

To add another color palette, look at the buttons at the top left of your palettes tab, you will click on the circle with a plus in the middle, this will add a new palette for you.




R/G Map Limitation of Fraymakers

The template should have it selected by default, but if not, you will want to go to your project settings in the file tab and change the Palette Shader Mode to R/G Map, as this is what Fraymakers uses.

Fraymakers is blind to the blue value in the RGB of your hex code. As an example, a color with the hex code of #92FF65 will be seen as the same color as #92FF14 despite being very different colors because they share the same first 4 digits.

When creating a character / assist it is important that those first four digits of each hex code is at least 1 letter / number different from all of your other colors, or else it will be replaced and they will share the same swapped color.
Timers
Timers are a great tool that allows you to run functions automatically at the interval and duration of your choosing, as well as having the option to keep those functions running even if your entity exits the animation it starts from.

Adding a Timer

Example Code:
self.addTimer(1,8, functionname);

With the above example placed inside your frame script on the frame you wish for the timer to begin repeating code, it will run the function named "functionname", every 1 frame, for 8 frames total. If you would like your timer to continue indefinitely until the current animation ends, you will change the code slightly to the following:

self.addTimer(1, 0, functionname);

Changing the second number to a 0 will cause the timer to continue until the animation changes or until the timer is manually removed. So this above code will play the function every 1 frame, until interrupted.

Where to Place the Function

The function you are wanting your timer to use can be placed in a few different places.
On the same frame of the frame script, above where the timer is added, so as an example:
function functionname(){ self.setXSpeed(4); } self.addTimer(1,8, functionname);

Your function can also simply placed inside of your script.hx so that it can be used in multiple animations without needing to copy/paste it. If you are only wanting to use the function for a single animation, then the quickest way to add it would be to insert it inside the code for the timer itself, like so:

self.addTimer(1, 8, function(){ self.setXSpeed(4); });

Stopping / Removing a Timer Manually

If you would like to interrupt your timer mid-way through, you'll want to change your timer into a variable like so:

var timername = self.addTimer(1, 0, functionname);

Then on the frame of the framescript, or any other instance, where you want the timer to stop, you will use this:

self.removeTimer(timername);

Continue Timer Throughout Multiple Animations

If you would like to make sure your timer does not stop even when exiting the animation it was added in, simply include persistent true in your timer code. So as an example it would look like this:

self.addTimer(2,30, functionname, { persistent: true });

This will continue running the function every 2 frames for 30 frames, even if the original animation is interrupted in some way. Like before, if you would like that timer to run indefinitely, just change the 30 to a 0 and it will continue until you use the remove timer function.

B-Reverse
Fraymakers already has B-Reverse built into the game, but the window for this is extremely tight and not at all reliable for most players / controller types. We can add our own, longer, window for B-Reverse with just this small amount of code:

self.addTimer(1,7, function(){ if(self.getPressedControls().LEFT && self.isFacingRight() || self.getPressedControls().RIGHT && self.isFacingLeft()){ self.flip();}})

This will go on the first or second frame, in the framescript of the move you wish to have the reversing mechanic on (generally just neutral special). You can replace the 7 in the code to make the window smaller or larger if desired.
It is checking to see if they are pressing left and (&&) facing right, or (||) pressing right and facing left, if that is true then it flips the character horizontally.

If you would like to have your movement speed flipped as well (to act similar to something like Diddy Kong's neutral special b-reverse) you will just add this to the line above or below self.flip();

self.setXVelocity(self.getXVelocity()*-1);

This will take the positive value of your X velocity (speed that is not changed by which direction you are facing), and changes it into the same value but negative, and vice versa.
Multi-Hit Attacks
To set up a multihit attack. You will need this line of code after the first hitbox.

self.reactivateHitboxes();

Place this code anywhere after the first, and before the succeeding hitboxes, to reactivate them. An example is in Fraynkie's, the template character's, jab 3. Make sure you set your hitboxstats first.


Before the last hitbox, if you want to update the hitbox damage, knockback, angle, etc., you put this code with the reactivate code.

self.updateHitboxStats(0, {damage: 4, angle: 37, baseKnockback:50, knockbackGrowth:60, hitstop:-1, selfHitstop:-1, hitstopNudgeMultiplier: 1}); self.reactivateHitboxes();

You are free to update the values to your liking. This will change the stats of your attack without having to list a separate hitbox in your hitbox stats.

Recommendations:

To make sure your multi-hit attacks connect more accurately, you can add an auto link point layer. Add the points to where a hitbox is.


After putting the layer, adjust the hitbox stats by setting the angle into a special angle, as seen here:


I'd recommend using angle:SpecialAngle.AUTOLINK_STRONGER however you can test which fits more.


If a move uses multiple hitboxes, it is possible to only refresh specific hitboxes by putting the hitbox's index number in the parentheses of the reactivation code.
For example, if I only want hitbox 0 to refresh, I will put in the frame I want to update:
self.reactivateHitboxes(0);
Wind Boxes
Wind boxes (hit boxes that push the opponent without dealing damage or hit stun so that they are able to act instantly) are very simple.
All you will need to add to your hitbox stats for the attack / move you want to be a windbox is flinch:false, and hitstun & hitstop at 0. Here is an example of a wind box hitbox stats:

hitbox0: { flinch:false, damage: 0, knockbackGrowth: 0, baseKnockback: 65, angle: 90, hitstop:0, hitstun:0, reversibleAngle: false, directionalInfluence: false, hitSoundOverride: "#n/a", hitEffectOverride: "#n/a"}

So in this example, the opponent will not flinch, will not take any damage, and not be stunned / stuck in hit stop, but will still be sent straight upwards (angle:90) by the base knockback of 65.

Two other stats you will most likely want to change for these would be hitSoundOverride, and hitEffectOverride. Setting the two of these to "#n/a" like in the example will cause no sound or visual effect to play when the attack lands instead of the standard Fraymakers hit sounds / effects. You can of course change these to anything you like, refer to the SFX and VFX sections above.

Meter & UI
Firstly, you will need your meter sprite. Lets use CVS2 as a sample.



Next, we will make a sprite entity.

First, go to the plus sign/Create Asset buttonon the top left of fraytools. There, a menu will pop.
Click on sprite entity so we make the entity for meter. Go to the entities folder then name the meter to whatever you want.



Now go to the meter entity you have and put the sprite on the image layer. Give the meter entity an ID and change the object type to Custom Game Object. Make sure your entity is exporting.

Note: Turn on the export toggle and close the tab/fraytools then open again to check.


Next, we go to your character script. We make a variable for the meter sprite so make a variable, name it whatever and place the id of the meter entity like this:
var meter = Sprite.create(self.getResource().getContent("idofmeterentity"));

Next, we want to place the offset of the meter. you can adjust them later. Place this code inside/outside initialize. use the variable then .scaleX, scaleY, etc. to set up the offsets.
meter.scaleX = 1; meter.scaleY = 1; meter.x = 10; meter.y = -5; self.getDamageCounterContainer().addChild(meter);

Here is how it would look like.

or



Now check in game if the meter is appearing. If you want to move the meter around, go back to script and change the numbers or you can go to the meter entity and adjust the size and position.




Setting up the meter
To make the meter work, first you have to animate the meter filling up. This can be done with the help of tweening. For now, lets set the meter to fill at 300, so we'll be putting the end on 300 frames. As shown here:


Next, we want to make the meter fill by for example, hitting an opponent. So now, we set up a variable for when the meter is 0 that will add up and when the meter is full. Go to the character script and make the variables. Here is an example.
var addMeter = 0; var meterMax = 300;

Now we want the meter to fill when an opponent is hit. For that, we need to make both the variable and the frames of the meter's animation to add up. We can set up an HIT_DEALT event listener to fill the meter. So in initialize, lets put it there.
self.addEventListener(GameObjectEvent.HIT_DEALT, function(){ addMeter = addMeter + 1; meter.currentFrame = meter.currentFrame + 1; }, {persistent: true});

The code right now might look something like this.
Note: This is an example of a different code but the premise is still the same.


Now let's up a function for if the meter is full and you don't want to add more. Here is a custom function, there are still other ways. Put anywhere, just outside initialize.
function fullMeter() { if (addMeter >= 300) { addMeter = meterMax; meter.currentFrame = 300; } }

Now you can put fullMeter(); in the HIT_DEALT event listener. Would be something like this
self.addEventListener(GameObjectEvent.HIT_DEALT, function(){ addMeter = addMeter + 1; meter.currentFrame = meter.currentFrame + 1; fullMeter(); }, {persistent: true});

Alternative Method
You can check the community's open source projects here. Wouldn't recommend if you're new to coding stuff since there's no tutorial to teach you how they made the meters or how they work but its a good reference.
https://ajewelofrarity.github.io/FraymakersDocs/docs/opensource/Readme.html
WIP
Camera Effects
Shake
camera.shake(4,6); or camera.verticalShake(4,6);

Above is a basic example of camera shake. The first number (4) is the intensity of the shake, while the second number (6) is the duration. You can also omit the second number to just have a short, default length camera shake.

Zoom

Camera zooming is very simple, but there are a couple of things you need to know in order to do it without the camera bugging constantly. To simply zoom in the camera, all you need to do is change the camera mode.

The default camera mode is 0 (camera.setMode(0)), and if you want full zoom in you simply change it to camera.setMode(1). However, zooming in to something specific requires an additional step. Let's take the player character as an example. If you want to zoom in on the player, then that player would become the "target" for the camera to focus on. In this case, we want to write the code camera.addForcedTarget(self). The addForcedTarget camera function takes any entities that are established, and in this case that entity would be "self". Another thing to keep in mind is that you'll want to write camera.addForcedTarget(self) BEFORE camera.setMode(1). You wouldn't want the camera to zoom in without a target to follow in this case. Now that the camera is zooming in on the player, we'll want to make sure it gets set back to normal once this event is done. Setting up the conditionals for it depends on what you're going for, but you want to remove the forced target and add it back as a normal target so that the camera will follow both players again. In that case, you want to type "camera.deleteForcedTarget(self)", and then right under that, "camera.addTarget(self)". Now that that's done, set the camera mode back to normal via the code, "camera.setMode(0)".

Congratulations! You have a functioning camera zoom ready to use for your character! Keep in mind it's VERY important to cover all your bases when removing the camera zoom. Think of any possible way something could interrupt your entity that would cause the camera to stay zoomed in, and add in the conditionals (if statements, for statements, whatever you need) to prevent that from happening.

Slow Motion
self.addTimer(4,80, function() { match.freezeScreen(2, [self,camera]); }, {persistent:true});
Adding this timer code will cause the screen to freeze for 2 frames, every 4 frames, 80 times.
The exceptions list (the square brackets inside the freeze screen function) will tell the game what it should not slow down, so in this case it is the entity that started it, and the camera. In the case of an assist or projectile that slows time, you would also want to add self.getOwner() to the list unless you would like them to be slowed down as well.
If there are any VFX that you want to make sure plays out correctly during slowed / stopped time then you'll want to play out the VFX as a variable and then add the variable name to the exception list.
Stance / Form Changing
First thing needed to handle alternate move/form is a way to to determine whether to use the alternate move/stance.An easy way to do that is to add a variable to check which formyou're in.

For example would have the following at the top of your script
var form = 1;

Then changing forms can be done by just changing the value of the variable. For example would have the following in the frame script of the stance change move.
if(form == 1){ form = 2; } else if(form == 2){ form = 1; }
You can simply just add more of these if statements if you have more than 2 stances/forms.

Now in the animations you would like to have changed when the variable is true (in this example i'll have it play a new forward air animation), on frame one of the frame script you will check the variable, and if true, it does the thing.

if(form == 2){ self.playAnimation("newforwardairmove"); }
Don't forget to add in animation and hitbox stats for any additional attacks/animations you add


Extra tips
I would also suggest adding a pattern for the extra forms i.e. can add "_form2" to the end of the existing animation name. Then you can simplify the code to something that's easy to copy to multiple animations.
if(form == 2){ self.playAnimation(self.getAnimation() + "_form2"); }


Also, If you're changing every animation, it's very likely you want both form1/2 to have the same animation stats. To make that easier, can copy the function from https://gist.github.com/PsnDth/1b4e96b2cd912df1fcb207eaff381560 and add it to your Script. Then applying this code and the idea above, you'll get the following snippet to add to all of the base moves

if(form == 2){ changeAnimation(self.getAnimation() + "_form2"); }

Should note, that this only goes in frame 1 of base character animations and NOT the form animations. Also if you do not want to copy over animation stats for some reason (i.e. different specials) should use the playAnimation code instead of changeAnimation.
Exports
Exports are a great way to have different entities interact with each other's code, by allowing one entity to get and set the variable of another.

To start, you will need to put the following code inside function initialize in your character's script.hx:
self.exports = { //sets up the field exports for editing. };

If you know event listeners, it's a similar idea to event.data.foe where "data" is something in the event that stores all the data. In this case however, it sets up a field that can be accessed by other entities. It's formatted similar to animation or hitbox stats. Now to start changing and viewing variables between entities, add something like the following example:

getNSpecFired: function(){ return nspecfired;}, setNSpecFired: function(var){ nspecfired = var;}

By writing this, we set the value "getNSpecFired" to be equal to a function that returns the variable nspecfired, which is somewhere else in the entity; and we set the value "setNSpecFired" to be equal to a function that sets the variable nspecfired to the inputted "var".

After calling the object which has the exports, you can use .exports to access anything stored inside it, like so:

self.getOwner().exports.setNSpecFired(false);

In this case, setNSpecFired, and we give it the variable "false". This makes the called object run the function stored inside setNSpecFired, using "false" as the first variable
Zair / Grab Aerial
First, we add a stat in Character Stats so that grab in the air works. We add this line of code in the character stats:
grabAirType: GrabAirType.GRAB,

Would look something like this.

A pop up will also appear that will recommend you to make a grab_air animation so make a new animation with that animation name.


Next we go to your animation and add a grab box.


Last step is to go to grab_hold animation. This is needed since grab_air will always default transitioning to grab_hold.

Put on the grab_hold framescript something like this for example.

if (!self.isOnFloor()){ self.toState(CState.THROW_DOWN, "grab_followup"); }

This is to help detect that your character is on grab_hold but in the air, which will default to the next CState and animation. You can interchange what CState you want whether UP, DOWN, BACK, or FORWARD. Add a comma after and the animation name of the throw followup.

Zair (Like ZSS Zair)
For this case, you can do the same but make the grab_air animation have a hitbox instead.
All you have to do next is add a hitbox stat for grab_air.

You don't need to add things in grab_hold since it's now an aerial move.
Charge Attacks / Moves
There are several methods to make a chargeable attack. Can combine this with the following section to create chargeable projectiles as well.

Method 1 is simplest for attacks that don't need to store charge between uses.

Method 2 is simplest for attacks that you want to be able to cancel and keep charging on later uses.

Method 1: Using the engine
Similar to smash attacks, you can actually just use animation stats to create a chargeable attack.

  1. Create an animation for the startup frames of the attack. If there are no startup frames, then can skip this animation. Nothing is required for the frame script of the startup. In animation stats, add the following:
    // both animation names are up to you. // If you using an existing attack i.e. `special_neutral` then use that animation name instead chargeable_move_in: {nextAnimation: "chargeable_move_loop"},
  2. Create an animation for the looping frames of the attack. Similar to smash attacks this should probably be a single image frame. Afterwards, add a single framescript keyframe. This keyframe should check for inputs to end the animation early. For example, the following is a framescript for a move charged by holding special:
    var curr_charge_frames = self.getAnimationStat("chargeFramesTotal"); var max_charge_frames = self.getAnimationStat("chargeFramesMax"); // the first part of this check is if they let go of special // the second part is if they charged for the max allowed // - if u remove this, u can keep holding the attack at max charge // if either of the above are true then play the attack if (!self.getHeldControls().SPECIAL || curr_charge_frames == max_charge_frames) { self.playAnimation("chargeable_move_attack"); } else { // otherwise loop the animation self.playFrame(1); }
    Along with this you should add animation stats for defining this attack as a chargeable one. For example:
    // chargeFramesMax is the number of frames you want to allow charging // chargeGlow/chargeShake are to add the engine filters that smash attacks normally use chargeable_move_loop: {chargeFramesMax: 60, chargeGlow: true, chargeShake: true},
  3. Finally you can add the attack animation, this animation's hitbox will be scaled similar to smash attacks. Can modify "maxChargeDamageMultiplier" in hitbox stats in order to modify how much this charge impacts the resulting stats.

    Additionally if you want to know "how much charge do they have", then within the animation you can do
    self.getAnimationStat("storedChargePercent")
    0 represents no charge, 1 represents full charge. 0.5 would represent half charge for example.



Method 2: Using a variable
Similar to the above, you can actually just manage the charge yourself by incrementing a variable. This method is preferred if you want to store the charge if they cancel the animation early. Will use the example of Samus' charge shot/DK giant punch to illustrate this method.

For example, at the top of your script can add the following variables:
// both names can be whatever you want, just make sure everything matches var MOVE_MAX_CHARGE = 60; // max charge we want to allow, putting in caps since it's not changing var move_curr_charge = 0; // current amount of charge for the move

As with Method 1, the startup is not necessary, but useful here since on the last frame of the animation, can choose to either charge or directly go into the attack depending on the amount of charge. i.e. on the last frame would have the following:
if (move_curr_charge == MOVE_MAX_CHARGE) { self.playAnimation("chargeable_move_attack"); } else { self.playAnimation("chargeable_move_loop"); }

In the charge/looping animation, you want to have the following in the framescript to increase the charge whenever the animation loops (or for every frame, depends on what you want)
if (move_curr_charge < MOVE_MAX_CHARGE) { move_curr_charge += 1; // Can increase by any number, but this increases by 1 if under max charge }

Similar to Method 1, can add a condition on the last frame for whether to loop the animation or not. In this case, it's a bit simpler to simply set "endType: AnimationEndType.LOOP" and then set the following in the last frame:
if (self.getPressedControls().SHIELD || move_curr_charge == MOVE_MAX_CHARGE) { // normally it'd go to shield/airdodge but just cancelling to endlag here self.playAnimation("chargeable_move_cancel_endlag"); } else if (self.getPressedControls().SPECIAL) { // If they press special again before finished, then play the attack self.playAnimation("chargeable_move_attack"); }

The above sets the conditions for when you want to end charge early and the animation continues looping by default.

And then in frame 1 of the attack can update hitbox stats based on the charge. This may require some math, but to simplify things, you can use the following formula to smoothly scale values from min/max charge:
// NOTE: You need to replace MAX_VALUE and MIN_VALUE with numbers that you choose (MAX_VALUE - MIN_VALUE)*(move_curr_charge/MOVE_MAX_CHARGE) + MIN_VALUE

for example, for the move to do 50dmg at max charge and 2 dmg at no charge:
self.updateHitboxStats(0, {damage: (50 - 2)*(move_curr_charge/MOVE_MAX_CHARGE) + 2 });
Chargeable Projectiles
If you have a projectile which you want to take account for the charge of the move that spawns it, then you need to transfer the variable to the projectile. Recommended way to transfer variables from character to projectile is exports. Charging moves, creating projectiles and using exports are discussed in detail earlier in the guide.

NOTE: You sometimes don't actually need to export the variable to the projectile and can instead just modify it directly. You can use this strategy if there's only 1 animation it needs to be used in, and you don't need to do anything complex with the value (i.e. projectile has no spawn animation and don't need charge value beyond projectile spawn).

Example here does the same thing as described below
// in `chargeable_move_attack` on the frame 30 framescript w/ Method 2: fireChargeableMoveProjectile(); var chargePercent = move_curr_charge / MOVE_MAX_CHARGE; // Resize the projectile to be 5x as big at max charge cmoveProjectile.get().setScaleX((5-1)*chargePercent + 1); cmoveProjectile.get().setScaleY((5-1)*chargePercent + 1); // Set the damage to be 50 at max charge and 2 at min charge // The projectile is currently in "projectileIdle" animation // since it has the hitbox, we can modify its stats directly cmoveProjectile.get().updateHitboxStats(0, {damage: (50 - 2)*chargePercent + 2 });

Below is an example for a projectile that grows based on the charge. Fired from the chargeable attack:
// in `chargeable_move_attack` on the frame 30 framescript: fireChargeableMoveProjectile();
This corresponds with the following function in the `Script.hx`
function fireNSpecialProjectile(){ cmoveProjectile.set(match.createProjectile(self.getResource().getContent("cmoveProjectile"), self)); cmoveProjectile.get().setX(self.getX() + self.flipX(CMOVE_PROJ_X_OFFSET)); cmoveProjectile.get().setY(self.getY() + CMOVE_PROJ_Y_OFFSET); }


First should setup the export on the projectile side to accept the charge of the attack. In the projectile script, create and sets a variable based on the charge.


At the top of the script there's a variable for charge:
var charge = 0; // default to 0, will be set with the actual charge later
along with adding a function that changes the size of the projectile along with setting the variable for later use.
function setCharge(chargePercent) { // change scale so that max charge is 5x bigger, and min charge is 1x. // using the formula from method 2 var scale = (5-1)*chargePercent + 1; self.setScaleX(scale); self.setScaleY(scale); charge = chargePercent; }
Then in initialize can setup the export so the character can use this function:
function initialize() { self.exports = { setCharge: setCharge, }; }
Additionally can update hitbox stats on the active animation to use the charge
// on projectile idle f2, same formula as mentioned in method to have max damage as 50, min damage as 2. // don't want to do f1 because setCharge() is called after the f1 framescript runs. self.updateHitboxStats(0, {damage: (50 - 2)*chargePercent + 2 });

Instead of just firing the projectile, should also set the charge of the projectile to match the move. Note this is using the projectile's variable used in the fire script. Getting the charge of the move depends on the method used. Based on the method described in the previous section, you could do the following:
  1. With Method 1
    // in `chargeable_move_attack` on the frame 30 framescript: var chargePercent = self.getAnimationStat("storedChargePercent"); fireChargeableMoveProjectile(); cmoveProjectile.get().exports.setCharge(chargePercent);

  2. With Method 2
    // in `chargeable_move_attack` on the frame 30 framescript: var chargePercent = move_curr_charge / MOVE_MAX_CHARGE; fireChargeableMoveProjectile(); cmoveProjectile.get().exports.setCharge(chargePercent);
Float
Princess Peach Float
There are a few different ways to pull off a float type mechanic, here is how I pulled off Peach's float mechanic from Super Smash Bros. in Fraytools.
Start with adding a new animation for your float, for simplicity sake, you can just name it "float" so you can copy and paste more of the sample code.

On frame 1 of the frame script I have all of the following code (not all of this is required, be sure to read through the explanation below and adjust with what you would like your float to do):
self.resetMomentum(); self.updateAnimationStats({gravityMultiplier:0}); floatused = true; function floatmechanic(){ if(!self.getHeldControls().JUMP_ANY || floattimer >= 4){ self.playFrame(31); } if(self.getHeldControls().RIGHT){ self.setXVelocity(5); } if(self.getHeldControls().LEFT){ self.setXVelocity(-5); } } var floatmechanictimer = self.addTimer(1,0, floatmechanic);

Explaining this, starting from the top, this will reset the momentum of the character, then update the gravity to 0 so they stop going up or falling down.
It then adds a function that will play the final frame of the animation if the jump button is released, or if the float timer reaches the end.
If right or left is held, it will set the velocity in the corresponding direction.
Lastly, it adds the timer for the floatmechanic function so that on every frame it checks if the jump button is still held or not.

Next, on the second to last frame of this animation (Frame 30 in the example image), I have the following code:
floattimer++; self.playFrame(2);

This will raise the float timer variable by 1 and set the animation back to frame 2 so that it loops the animation again. So long as the button is not released, and the float timer variable does not reach 4, it will keep raising the variable and replaying the animation.

The final frame of the animation plays when the variable reaches the max amount entered, or if the button is released, this frame will simply make sure to remove the timer, and then your character proceeds to their falling animation. To remove the timer you will just put:
self.removeTimer(floatmechanictimer);

Other than that, in the animation stats, you can add allowMovement:false to make sure the only movement being applied is the velocity being set by the first frame in the frame script, but that is optional.


That finishes up the animation for float, next we will add a few things to the character's script.hx.
The following code will go inside the function update section of the script to make sure that the timer for float is reset if the character is on the ground.

if(self.isOnFloor()){ floattimer = 0; floatused = false; }

After this, further down in the script outside of any of the functions you will have this code:
var floattimer = 0; var floatused = false; function usefloat(){ if(floatused == false && self.getHeldControls().DOWN && self.getHeldControls().JUMP_ANY){ self.toState(CState.JUMP_LOOP, "float"); } } function floatcancel(){ if(!self.getHeldControls().JUMP_ANY){ self.updateAnimationStats({gravityMultiplier:1}); } }
This adds the variables for your float timer and whether or not the float has already been used, and sets up the function we will be using in any of the animations you would like to be able to be cancelled by float by checking if float used is false, that you're holding down and that you are holding jump (down and jump can be replaced by any combination of buttons you would like for the player to hold to activate the float. Then sets up the function for float cancel I have for the follow up attack animations in a timer, so that if anytime during the follow up attack, you let go of jump it will set the gravity back to normal, this function is optional.


Finally all you need to complete the float mechanic is to place the following code in any of the animations you would like to have the float mechanic cancel it:
self.addTimer(1,0, usefloat);
Rotation
Set Rotation / Get Rotation

If you would like to rotate your character manually through code rather than through animation, then you will use: self.setRotation(); and self.getRotation();.
Setting the rotation of your character, besides for the image, will also rotate the hurtboxes and hitboxes. Get rotation can be used for various things as a check, for an example if you have a move where you're able rotate your character, you can have getRotation check if it has been rotated beyond a certain point and activate something if so.

Here is a simple example of using both:
if(self.getRotation() < 20 && self.getHeldControls().RIGHT){ self.setRotation(80); }
In this example, if on the frame you place this code, they are holding right, and their rotation is not already at 20 degrees or higher, it will set the rotation to 80 degrees.

Smooth Rotation

If you would like an animation that rotates smoothly based on direction / button held, you will use a timer, getRotation, and setRotation. See the example below:
self.addTimer(1,30, function(){ if(self.getHeldControls().RIGHT){ self.setRotation(self.getRotation()+5); } if(self.getHeldControls().LEFT){ self.setRotation(self.getRotation()-5); } });
With this timer, so long as the player holds right, the game will check what the current rotation is and set it to 5 extra degrees every frame, for 30 frames. And in the case they hold left, it will subtract 5 degrees from the current rotation every frame. You can change the numbers to whichever you like if you want faster or slower rotation.

Now in order to have the upwards movement affected by the rotation, like with Diddy Kong in Super Smash Bros, or with Bomberman in Fraymakers, you can use the following code instead:
self.addTimer(1,0, function(){ self.setYSpeed(Math.calculateYVelocity(-13,self.getRotation() + 90)); if (self.getHeldControls().RIGHT) { self.setRotation(self.getRotation() + 2); } if (self.getHeldControls().LEFT) { self.setRotation(self.getRotation() + -2); } if (self.isFacingLeft()){ self.setXSpeed(Math.calculateXVelocity(13,self.getRotation() + 90)); } else{ self.setXSpeed(Math.calculateXVelocity(-13,self.getRotation() + 90)); } });
Aerial Shield
One of the ways to accomplishing shielding / blocking while in the air is to change your state to shielding, and have a check inside your shield loop animation to make sure if you let go of the shield button, it transfers to shield out (as by default it keeps you stuck in shield loop until you land if done in the air).

You can start with the following code on frame 1 of your airdash_forward frame script (this is the animation that plays by default if you do not hold a direction while using airdash, so we will use override it with shielding if the player is not holding forward.
if(!self.getHeldControls().RIGHT && self.isFacingRight() || !self.getHeldControls().LEFT && self.isFacingLeft()){ self.toState(CState.SHIELD_IN, "shield_in"); }
If they are not holding right while facing right, or holding left while facing left (aka holding forward) they will transition to the shield_in state and animation.

Next you can add the check for your shield_loop animation to have the shielding animation stop when you let go of the button. On frame 1 of shield_loop (generally the only frame in the shield loop animation, but if not, you will need to have it on all frames) add this code:
if(!self.isOnFloor() && !self.getHeldControls().SHIELD){ self.toState(CState.SHIELD_OUT, "shield_out"); }
If they are not on the ground, and they are not holding shield, it will go to your shield out state and animation.
Wall Jumps / Wall Clings
First we set up an event where if you hit a wall, you set up for a wall cling/jump. In initialize:

self.addEventListener(EntityEvent.COLLIDE_WALL, onWallHit, { persistent: true });

Next, we make the onWallHit function. Make this function outside initialize.
function onWallHit(){ //When the player touches a wall mid-air, start clinging if (!self.isOnFloor() && self.inState(CState.FALL) || self.inState(CState.JUMP_LOOP) || self.inState(CState.JUMP_MIDAIR)) && self.getAnimation() != "wall_cling" && self.getAnimation() != "wall_jump"){ self.playAnimation("wall_cling"); } }

Now lets set up a wall_cling and wall_jump animation. So make the animations with those name/what is in the script.


Now lets say you want to make yourself jump out of wall cling. We can set up a function. (This code can be seen in MVS template. Credits to snuffysam).

In script.hx, we can make this function:
function playWallJump(){ //When the player wall jumps, essentially perform a ledge jump (but with the wall jump animation) self.playAnimation("wall_jump"); self.resetMomentum(); Common.applyLedgeJumpSpeed(); }

Now go to the wall_cling animation and put this code in the frame script. This will make you jump out of wall cling.
self.resetMomentum(); var heldControls:ControlsObject = self.getHeldControls(); if(heldControls.JUMP_ANY){ playWallJump(); }

This code makes it go to wall_jump when you press jump.

Now lets go to the animation stats. This is a sample for what stats I set up for clings and wall jump. Wall cling will infinitely make you stay on the wall and wall jump sends you to fall after the wall jump animation ends.


If you want more wall_clings and jump things to check, try checking out the MVS template. This makes wall clings work like MVS where you slide down a bit.

https://ajewelofrarity.github.io/FraymakersDocs/docs/opensource/Readme.html

Tracking / Following
The most basic homing code has 3 main components.
  1. Selecting a target
  2. Aiming at the target
  3. Moving toward the target

STEP 1: Selecting a target

In this guide, we'll be making a projectile that selects the nearest foe when it's spawned, and constantly turns to chase it. Here are some stats we'll need.
target = null; //Target
The player that has been targeted. Since there isn't a target yet, it's set to null.
var maxspd = 7; //Maximum Speed.

This projectile will have a bit of inertia. Rather than always moving directly at the target, it will slowly turn towards it. This makes it a bit easier to avoid.
var accel = .5; //Acceleration. // The rate at which it accelerates towards the target. var baseSpeed = 3.5; //Base Speed. //How fast the projectile will when first spawned. var ang = null; //Angle //The angle between the projectile and it's target.

Since we're only selecting a target once, I'm putting the code for target selection directly in initialize.
function initialize(){ owner = self.getRootOwner();

Now lets make some variables we'll need specifically while targeting a foe.
var pos = Point.create( self.getX() , self.getY() + self.getEcbLeftHipY() ); //Position
This variable is a "point" which is a type of variable that stores an X and Y value. We're defining it as the position of our projectile's center
The X is already centered, but your projectile probably isn't centered around the ECB's foot, which is what getY would give you. To make up for that, I'm defining it's Y value as the position of the ECB's hip. This means your projectile is targeting from the center of it's ECB.



var distance=Math.POSITIVE_INFINITY; //distance between you and the targeted foe.
Since we don't have a targeted foe yet, I'm setting it to Infinity. That was, every foe that's tested will be closer. Technically, this is going to be distance squared, but don't worry about it.

for(player in owner.getFoes()){// player is any one of the owner's "foes". This will iterate through all of them. var tpos = Point.create( player.getX() , player.getY()+player.getEcbLeftHipY()); //target position // the position of the current foe, centered like before. var dis=pos.distanceSquared(tpos); //distance between you and the current foe... kinda. tpos.dispose();//dispose of points whenever you are done using them.

Distance is calculated as
Distance = Square Root of( ( pos.x - tpos.x)^2 + ( pos.y - tpos.y )^2 )
Taking the square root of something is kinda costly, and since all we want to know is which is closer, using distanceSquared allows us to avoid it. Basically, it's as accurate for our job, and it's faster, but it's not the exact distance.

if(dis<distance){ // if the current foe is closer than the targeted foe, then change the target and distance to be this foe. target=player; distance=dis; }
All of this code is run on every foe, so by the end, the game has determined which foe is closest, and set it as the target

The game doesn't automatically dispose of points when it's done with them, which can cause performance issues if they build up. Since we are now done with the point tracking our position, we should dispose of it ourselves.
pos.dispose();//dispose of points whenever you are done using them.

if you want your projectile to start with a bit of speed, then just run the aim and move code immediately.
aim(); move(baseSpeed);

In order to keep moving, and keep tracking the foe, run the code for aiming and moving in the update function as well
function update() { aim(); move(accel); }


STEP 2: Aiming at the target

function aim(){ var pos = Point.create(self.getX(),self.getY() + self.getEcbLeftHipY()); var tpos = Point.create(target.getX(),target.getY() + target.getEcbLeftHipY()); //as with the targeting code, we're defining the positions of both the player, and the target as points. ang = Math.getAngleBetween(pos, tpos); //Yes, finding the angle between 2 points is really this simple. //specifically, it finds the angle that the first point would have to move to reach the second point. Which is exactly what we want. pos.dispose();//dispose of points whenever you are done using them. tpos.dispose();//dispose of points whenever you are done using them. }


STEP 3: Moving towards the target

function move(spd:Number){ self.setXVelocity( self.getXVelocity() + Math.calculateXVelocity( spd , ang) ); self.setYVelocity( self.getYVelocity() + Math.calculateYVelocity( -spd , ang) );
These two lines are a lot of stuff, and might be difficult to parse.
First, Math.calculateXVelocity takes 2 variables. A speed, and an angle. It calculates how much of the velocity is horizontal. The same is true for Math.calculateYVelocity, except with vertical velocity. And for some reason it calculates it upside-down, so you have to use a negative speed value.
Basically, they and Math.getAngleBetween do all that messy trig stuff so we don't have to :) So these two lines of code just add those to the current X and Y velocities.
Note: removing the self.get # Velocity()+ would make your projectile move at a constant speed equal to the accel variable, directly towards the target
The code I wrote would endlessly build speed, but homing attacks that go at mach 10 aren't much fun to deal with, so it's best if we cap the speed somehow.

var spdang = Math.calculateSpeed(self.getXVelocity(), self.getYVelocity());// Speed at Angle (current speed)
This goes the opposite direction we just went, giving us our current total speed.

if(spdang > maxspd){
OK, so, if we just cap our X and Y speeds at maxspd, then our projectile will be able to move faster at a 45 degree angle. That's not cool, so we're gonna do math to make things cap correctly.
It's logical, that if we cut the X Speed in half, and the YSpeed in half, then our current speed is halved, right? So if our current speed is twice our maximum speed, we want to multiply the X and Y speeds by (1/2).And if our current speed is 3 times our max speed, then by (1/3). If our current speed is five fourths of our max speed (1.25x) then we want to multiple by (1/(5/4)) or (4/5). If our current speed is 5, and our max speed is 4, then we're moving five fourths of our max speed.
So (current speed) / (max speed) tells us how much faster we're moving, and (max speed) / (current speed) can be used to scale down our speed values.

self.setXVelocity(self.getXVelocity()*maxspd/spdang); self.setYVelocity(self.getYVelocity()*maxspd/spdang);
Crouch Walk
Firstly, we set up a function in crouch_loop. Here is a sample of a code set up in the frame script of crouch_loop (this code assumes crouch loop only has one frame):
self.resetMomentum(); if (self.isFacingLeft()) { if (self.getHeldControls().RIGHT) { self.resetMomentum(); self.playAnimation("crawl_back"); } if (self.getHeldControls().LEFT) { self.resetMomentum(); self.playAnimation("crawl_forward"); } } else if (self.isFacingRight()) { if (self.getHeldControls().RIGHT) { self.resetMomentum(); self.playAnimation("crawl_forward"); } if (self.getHeldControls().LEFT) { self.resetMomentum(); self.playAnimation("crawl_back"); } }

Next is we make animations for forward and backward crouch walk. In the code I used, it is crawl_forward and crawl_back.


Laslty, we go to the animation stats to make the crawl animations move and loop. Here is another sample in the animation stats for the crawls. Just adjust the numbers to change speed.
crawl_forward:{endType:AnimationEndType.LOOP, allowMovement:true, groundSpeedCap: 2.75, interruptible:true}, crawl_back:{endType:AnimationEndType.LOOP, allowMovement:true, groundSpeedCap: 2.75, interruptible:true},

Now you have a crouch walk.
Healing / Damaging Effects
You can use addDamage to give or take away damage, use a positive number to take damage, and use a negative number to heal.
If you would like your character to take damage without getting hit, you would write it like this:
//Damages yourself by 20. self.addDamage(20);
If you would like to make an assist that heals or damages the player, you can write that like this:
//Heals owner by 20 damage self.getOwner().addDamage(-20);

Damage / Healing Over Time
When you want to take or heal damage over a longer period of time instead of instantaneously, you can use a timer like so:
//Heals 1 damage every 4 frames, repeating 20 times (80 frames total length). self.addTimer(1,20, function(){ self.addDamage(1); }, { persistent: true });
This will heal 1 damage every 4 frames for the next 80 frames. You can adjust these numbers however you see fit. This timer also has persistent:true so that if the animation is interrupted, it still continues, you can remove that if you want to make sure it stops as soon as the animation is interrupted.

Applying Damage Over Time to Foe / Poison Damage
WIP

function applyPoison(foe){ // Saving this to a variable so that the entity applying poison can despawn // before the end of the timer. // NOTE: Any other "global variables/functions" may also need the same treatment var EFFECT_TYPE = StatusEffectType.DAMAGE_OVER_TIME; // Add poison effect var poison_effect_id = foe.addStatusEffect( EFFECT_TYPE, AMOUNT_OF_DAMAGE, {params: {interval: TIME_BETWEEN_DAMAGE}} ).id; // Timer to remove the effect foe.addTimer(LENGTH_OF_POISON_EFFECT, 1, function() { foe.removeStatusEffect(EFFECT_TYPE, poison_effect_id); }, {persistent:true}); }
Teleporting
Orcane-Style Teleport

First you need to set up these four variables in the main character Script:

var teleportAirUsed = false; var teleportGroundUsed = null; var playerX = null; var playerY = null;

The teleportAirUsed variable detects whether or not you have teleported in the air, we will use this to make you not be able to make a new teleport point after teleporting in the air. The teleportGroundUsed variable will check whether or not you set up your teleport point.

The playerX and playerY variables will be used to know the point you will teleport to.

Next we will set up the teleport. First add this to the framescript of your teleport animation

if (playerX != null & playerY != null){ } else { }

Now we will set up the creation of a teleport point, this is where you will teleport to.

if (playerX != null & playerY != null){ } else { //this resets the teleport point just in case playerX = null; playerY = null; //this sets up the teleport point playerX = self.getX(); playerY = self.getY(); //this notes that your teleport point was made on the ground, if you are making your teleport a aerial move, set it to true teleportAirUsed = false; //this notes that you made your teleport point on the ground, if you are making your teleport a aerial move, set it to false teleportGroundUsed = true; }

Next add this code to it, this will set up the teleport

if (playerX != null & playerY != null){ //moves to the teleport point self.setX(playerX); self.setY(playerY); //makes it so you can’t teleport to this area again playerX = null; playerY = null; //detects if you are teleporting to a point in the air if (teleportGroundUsed == false){ teleportAirUsed = true; //you can use this area to add any other events you want to take place after you teleport in the air, such as another jump } //if you want any special sound or vfx for your teleport add it here } else { //this resets the teleport point just in case playerX = null; playerY = null; //this sets up the teleport point playerX = self.getX(); playerY = self.getY(); //this notes that your teleport point was made on the ground, if you are making your teleport a aerial move, set it to true teleportAirUsed = false; //this notes that you made your teleport point on the ground, if you are making your teleport a aerial move, set it to false teleportGroundUsed = true; //if you want any special sound or vfx for your teleport point creation add it here }

The last thing you need to add is this piece of code to all land animations, tech animations, ledge_grab, and revival to reset the teleport after landing. If you have any other animations where you land or grab the ledge add it to those animations as well.

teleportAirUsed = false;

You now have a functional Orcane puddle style teleport.
Assists
WIP
Simple Assist Template
A version of the assist template that only has 1 animation and 1 state, so if your doing a assist that something like Josef or Fancy Pants Man, you can use this. Created by Drake Waffle

https://discord.com/channels/271284605531717632/1143298602924527637

Line Segment Structures
WIP, have this code for now until its done

var linefloor = null;

linefloor = match.createLineSegmentStructure([-28, -55, 28, -55], new StructureStats({ structureType: StructureType.FLOOR, leftLedge: false, rightLedge: false, startX: self.getX(), startY: self.getY()}));

linefloor.destroy();
Stage Hazards
WIP

Making Stage Hazards is basically the same as making a projectile but instead having the stage summon it, you can use the projectile section on the guide as a reference to make them or steal them from the Assist/Fighter Template

In the stages function initialize make sure you put the code listed below in it
match.createProjectile(self.getResource().getContent("stageHazard"));
Pummel
Default Pummel; Fraymakers already has a functional pummel, all you need to do is add a grab_pummel animation to your character, it can be performed by pressing attack in the grab_hold animation. This default pummel currently follows the grab release timing, so there's a hard limit of about a second before the opponent is released.

Custom Pummel; This pummel has its own time length which is determined by the grabbed opponent's damage.

Required animations
- pummel
- pummel_release (should have backwards movement and a hit to mimic the default grab release)

In your character's main script; this sets a variable used for pummel's release
var pummelRelease = -1;

In the startup of your character's grab animation; this sets a variable for use later
self.addEventListener(GameObjectEvent.GRAB_DEALT, function () { pummel = 0; });

In your character's grab_hold animation; allows inputting pummel with either the Grab or Attack button
if (self.getPressedControls().GRAB || self.getPressedControls().ATTACK){ self.toState(CState.THROW_FORWARD, "pummel"); }

On Frame 1 of your character's pummel; sets the timer until a forced grab release occurs, and sets the pummel variable to 1 so that it doesn't get re-triggered with each subsequent pummel
if (pummel == 0){ pummelRelease = 40 + self.getGrabbedFoe().getDamage() * 0.5; pummel = 1; }

Optional: Max pummel time cap; An example for if you'd like your pummel to never exceed a certain amount of time, place this after pummel's time is calculated or in your character's update script.
if (pummelRelease >= 401){ pummelRelease = 400; }

On the last frame of your character's pummel; checks if your character should release the opponent, or return to grab_hold. This check being on the last frame prevents a release from interrupting the pummel before it hits.
if (pummelRelease == 0 || pummelRelease == -0.25 || pummelRelease == -0.5 || pummelRelease == -0.75) { self.toState(CState.AERIAL_UP, "pummel_release"); } else { self.toState(CState.GRAB_HOLD, "grab_hold"); }

On any animation your character can do out of grab_hold (I.e. throws); Resets the pummelRelease variable so that a release doesn't occur during a throw or other animations
pummelRelease = -1;
Directional and Misc. Jump Animations
Directional Jumps; These are most suited for Frame 1 of jump_in and jump_midair (X Speed values can be tweaked if you'd like to change how easy it is for them to occur)

if (self.getXSpeed() >= 2){ self.toState(CState.FALL, "jump_forward"); } else if (self.getXSpeed() <= -2){ self.toState(CState.FALL, "jump_backward"); }

Misc. Jumps; If you'd like your character's jump animation to have a similar effect to the normal jump where it does not progress until your character has started falling, you can add this code. This code makes it so that it will not progress past a certain frame until your character's Y speed is at or below zero.

Example of a single-frame hold Frame 2 if (self.getYSpeed() >= 0){ self.playFrame(2); } else { self.playFrame(1); } Example of a multi-frame loop hold Frames 1-8 if (self.getYSpeed() >= 0){ self.playFrame(9); } Frame 9 if (self.getYSpeed() >= 0){ self.playFrame(9); } else { self.playFrame(1); }
Command Inputs
WIP
Text Generation
Prerequisites - PLEASE READ

Download the required files[github.com]

  • extract the contents of `font.zip` into wherever you put your sprites in your project
  • Add `text.entity` to your entity folder


The Code

The code can be copied from here[gist.github.com]

How to use this

First you need to create an empty array of sprites.
You need one for each individual body of text you wish to manage, otherwise it'll get overridden So if you want a separate body of text you will have to create a new array corresponding to it and reference it later;
var textSprites: Array<Sprites> = [];
Example Calls
In this case we'll just render it starting at the center of the screen, so this is what a call would look like:
var renderData = renderText("Hey I'm displaying text on screen!", textSprites, camera.getForegroundContainer(), { x: camera.getViewportWidth() / 2, y: camera.getViewportHeight() / 2, });

Animated Rendering
if you want the text to be animated, as in render character by character you can also set the delay option, which is the frame delay between each character render:
var renderData = renderText("Hey I'm displaying text on screen!", textSprites, camera.getForegroundContainer(), { x: camera.getViewportWidth() / 2, y: camera.getViewportHeight() / 2, delay: 3 });
[h4] Modifying the timer owner[/h4]
Internally, the render function makes use of a timer, and the timer is added to "self" by default, however, stages and assist controllers cannot directly use timers so you can pass another entity on which the timer would be run on, for example a vfx you create to be excluded from a screen freeze.
Using the global vfx as an example:
var vfx: Vfx = match.createVfx(new VfxStats({ spriteContent: self.getResource().getContent("global::vfx.vfx"), loop: true }), null); var renderData = renderText("Hey I'm displaying text on screen!", textSprites, camera.getForegroundContainer(), { x: camera.getViewportWidth() / 2, y: camera.getViewportHeight() / 2, delay: 3, owner: vfx });

New lines
If you want newlines to automatically be created given a character count
var renderData = renderText("Hey I'm displaying text on screen! Also this this might probably cause a line break don't you think?", textSprites, camera.getForegroundContainer(), { x: camera.getViewportWidth() / 2, y: camera.getViewportHeight() / 2, delay: 3, autoLineWrap: 40 });
You can also use the neat `renderLines` function to do that for you:
var renderData = renderLines(["This will be the first line", "This will be the second line", "This is line three!"], textSprites, camera.getForegroundContainer(), { x: camera.getViewportWidth() / 2, y: camera.getViewportHeight() / 2, delay: 3, });

Erasing Text

Erasing text works as follows
disposeSprites(renderData.sprites);


Displaying Text on top of a vfx
var sprites: Array<Sprite> = []; var vfx: Vfx = match.createVfx(new VfxStats({ x: camera.getViewportWidth() / 2, y: camera.getViewportHeight() / 2, spriteContent: self.getResource().getContent("text"), animation: "base", loop: true }), null); vfx.setX(vfx.getX() - vfx.getSprite().width / 2); // This ensures the text-box is in the middle camera.getForegroundContainer().addChildAt(vfx.getViewRootContainer(), 0); var renderData = renderText(mission, sprites, vfx.getViewRootContainer(), { delay: 3, autoLinewrap: 40, x: 0, y: 0 });

For reference, this is where text would start rendering relative to the vfx, in case you wish to make your own custom text boxes

Text Formatting syntax and Newlines

The text render also supports basic color formatting via hex codes, and the syntax works as follows:
var formttedString = "<#HEXCODE>Text Body Here</>";
replacing `#HEXCODE` with a hex code such as `#FF0000`, you can have as many formatting tags as you want, but nesting them will not work when you have:
<#FFFFFF>Text <#FF0000>Inner</> </>

What you can do instead is just have tags one after another like:
<#FF0000> I'm red</> Now I'm not <#0000FF>Now I'm Feeling Blue</> <#00FF00>but no so green</>

If there's a syntax error in your formatting such as:
<obviouslywrong>Test</>
An error would be displayed in the logs(only in training mode), but also within the rendered text itself, so take care when writing them
Smash Bros. Type Shield
First, create 3 new animations.

shield_vfx is your shield bubble centered around the origin, (this is a looping VFX so feel free to make it as long as you want)

shield_break is the bounce before the dizzy, typically it uses something similar to tumble. In animation stats put:
shield_break: {leaveGroundCancel:false, landAnimation:"shield_break_land", endType:AnimationEndType.LOOP,},

shield_break_land is your shield break dizzy animation, and have that last around 150 frames.

Next create a new Nine Slice asset, set the ID as blank_ns, and set the image asset as a png with nothing in it, a 1x1 transparent pixel works. then check export.

In character stats just set your shieldCrossupThreshold to something big like 999999, and for both the shieldFrontNineSliceContent and shieldBackNineSliceContent put self.getResource().getContent("blank_ns"),

Code stuff
var shieldLife = 1; var shieldVfx:Vfx = null; var shieldChargeCooldown = 0; function initialize(){ self.addEventListener(GameObjectEvent.SHIELD_HIT_RECEIVED, function(event:GameObjectEvent) { shieldLife += event.data.hitboxStats.damage * 3; shieldChargeCooldown = 30; if (event.data.foe.getX() > self.getX() && self.isFacingLeft()){ self.setXKnockback(-self.getXKnockback()); } else if (event.data.foe.getX() < self.getX() && self.isFacingRight()){ self.setXKnockback(-self.getXKnockback()); } }, {persistent:true}); shieldVfx = match.createVfx(new VfxStats({spriteContent: self.getResource().getContent("charactertemplate"), animation: "shield_vfx", y:-40, loop:true})); shieldVfx.attachTo(self); } function update(){ shieldVfx.setScaleX(1 - ((shieldLife - 1) / 179) * 0.75); shieldVfx.setScaleY(1 - ((shieldLife - 1) / 179) * 0.75); if (self.inStateGroup(CStateGroup.SHIELD) && !self.inState(CState.SHIELD_OUT)){ shieldVfx.setVisible(true); if (shieldLife <= 180){ if (self.inState(CState.SHIELD_LOOP)){ shieldLife += 1; shieldChargeCooldown = 30; } } else { shieldLife = 1; self.toState(CState.TILT_UP, "shield_break"); self.setYSpeed(-11); //change depending on gravity prob } } else { shieldVfx.setVisible(false); } if (!self.inStateGroup(CStateGroup.SHIELD)){ if (shieldChargeCooldown < 1){ if (shieldLife > 1){ shieldLife -= 0.5; } } else { shieldChargeCooldown -=1; } } }
Screen / Star KOs
animations are aligned so the origin is the top middle of the screen, everything else seems pretty self explanatory.

function update(){ if (self.getY() < stage.getDeathBounds().getY() && (self.inState(CState.HURT_MEDIUM) || self.inState(CState.HURT_HEAVY))){ if (Random.getInt(1,10) == 1){ self.toState(CState.DISABLED); if (Random.getInt(1,2) == 1){ var koVfx = match.createVfx(new VfxStats({spriteContent: self.getResource().getContent("charactertemplate"), animation: "star_ko", x:320})); camera.getBackgroundContainer().addChild(koVfx.getViewRootContainer()); koVfx.addShader(self.getCostumeShader()); koVfx.addTimer(90,1, function() { self.toState(CState.KO); }, {persistent:true}); } else { var koVfx = match.createVfx(new VfxStats({spriteContent: self.getResource().getContent("charactertemplate"), animation: "screen_ko", x:320})); camera.getForegroundContainer().addChild(koVfx.getViewRootContainer()); koVfx.addShader(self.getCostumeShader()); koVfx.addTimer(15,1, function() { camera.verticalShake(20); }, {persistent:true}); koVfx.addTimer(60,1, function() { self.toState(CState.KO); match.createVfx(new VfxStats({spriteContent: "global::vfx.vfx", animation: GlobalVfx.KO_BLAST, x:camera.getX(), y:stage.getDeathBounds().getY() + stage.getDeathBounds().getRectangle().height, layer:VfxLayer.FOREGROUND_EFFECTS})); AudioClip.play(GlobalSfx.KO_BLAST); }, {persistent:true}); } } }
137 Comments
ZAK ATTACK 26 Jun @ 9:53pm 
How do i make an assist chase a player?
Random guy 26 Jun @ 5:32pm 
Thanks! I do have another another question though, how would I make a entity that can be spawned, and its behavior would be shooting projectiles for a couple seconds then despawn?
SaltLevelsMax  [author] 26 Jun @ 3:49pm 
Just gotta check the variable like any other.
if (metervariable >= 100){
do the thing;
}
Random guy 26 Jun @ 1:06pm 
I have another question, how do I check if my meter is full when preforming an attack to either (if full) play the attack or play a fail animation.
SaltLevelsMax  [author] 23 Jun @ 11:35am 
Yeah that'll be the issue, you'll go to the new animation and if the opponents attack is still active you will immediately be interrupted
Random guy 23 Jun @ 11:34am 
It worked! Thanks
Random guy 23 Jun @ 7:43am 
no but I'll try that
SaltLevelsMax  [author] 23 Jun @ 7:41am 
Did you make sure to have an invincible body status on the first frames of the follow up attack animation?
Random guy 23 Jun @ 5:34am 
I'm not sure why but none of the counters for my most recent characters don't work, I put all the code the guide says I should put and I placed it correctly in the frame script layer, but for some reason when being hit, instead of the counter, my character just goes into hitstun and takes no damage for some reason. Could someone help me with this?
Seelever 16 Jun @ 4:12pm 
Thx @Random Guy i will try that