EtG Modding Guide
Search…
⌃K

Creating An Enemy

Difficulty: 7/10

Firstly, you need to download EnemyAPI:
EnemyAPI.zip
11KB
Binary
IN YOUR MODULE YOU NEED THIS:
EnemyAPI.Hooks.Init();
EnemyAPI.EnemyTools.Init();
EnemyAPI.EnemyBuilder.Init();

There are two very good tutorials by Neighborino for making custom enemies:

If you want to make an enemy without a gun, use this:
If you want to make an enemy with a gun, use this:

Sprite Setup

Sprite setup is the most tedious part of setting up an enemy as the amount of setup required increases with the amount of sprites/ animations you plan to add. However, tedious is all it is, as you end up following a similar pattern for everything you do, which means after practice you will be able to pinpoint mistakes should you make any.
The first part of the setup is creating an array that houses all of the sprite paths to all of your sprites:
private static string[] spritePaths = new string[]
{
};
In here you place *every* sprite path you will use for your enemy. To save yourself time, it is *important* that you keep this array organized, at keeping it organized will *MASSIVELY* help when setting up animations. This is a good example of good organization etiquette. Comments (Or anything after the //) do NOT count as entries in the array, letting you use them to keep track of things. The in the numbers in comments at the end of the sprite paths are used to keep track of what entry the sprite path is in. So, for example, if, from the list above, I wanted every array entry corresponding to the sprites of the pitfall, I would start with the value 40, as it is the 41st entry in the array. (Arrays start at 0)

Actually setting up animations:

For starters, you will want to use *this* AddAnimation method if you are still using this version of setting up sprites. (As of writing there is no automatic way of doing this.)
public static tk2dSpriteAnimationClip AddAnimation(tk2dSpriteAnimator animator, tk2dSpriteCollectionData collection, List<int> spriteIDs,
string clipName, tk2dSpriteAnimationClip.WrapMode wrapMode = tk2dSpriteAnimationClip.WrapMode.Loop)
{
if (animator.Library == null)
{
animator.Library = animator.gameObject.AddComponent<tk2dSpriteAnimation>();
animator.Library.clips = new tk2dSpriteAnimationClip[0];
animator.Library.enabled = true;
}
List<tk2dSpriteAnimationFrame> frames = new List<tk2dSpriteAnimationFrame>();
for (int i = 0; i < spriteIDs.Count; i++)
{
tk2dSpriteDefinition sprite = collection.spriteDefinitions[spriteIDs[i]];
if (sprite.Valid)
{
frames.Add(new tk2dSpriteAnimationFrame()
{
spriteCollection = collection,
spriteId = spriteIDs[i]
});
}
}
var clip = new tk2dSpriteAnimationClip();
clip.name = clipName;
clip.fps = 15;
clip.wrapMode = wrapMode;
Array.Resize(ref animator.Library.clips, animator.Library.clips.Length + 1);
animator.Library.clips[animator.Library.clips.Length - 1] = clip;
clip.frames = frames.ToArray();
return clip;
}
You also want to set up a sprite collection for your enemy, as the above method requires a tk2dSpriteCollectionData to work.
You can set one up by, firstly, adding a static tk2dSpriteCollectionData in your enemies code:
Then, in your enemies setup code, you will want to run this piece of code:
The above code will add all of the sprites that you have set in your array (shown earlier) and add them to the collection.
To add an animation to your enemy, you run this method in your enemies setup code: (An example using the shown above sprite path list and collection will be used here)
SpriteBuilder.AddAnimation(enemy.spriteAnimator, CurseblobCollection, new List<int>
{
0,
1,
2,
3,
4,
5,
}, "idle_back_left", tk2dSpriteAnimationClip.WrapMode.Loop).fps = 9f;
A rundown of every argument required in this method: The Tk2dSpriteAnimator: simply give it the enemies TK2DSpriteAnimator and you're good to go.
The Tk2dSpriteCollectionData: the enemies sprite collection, which I've demonstrated above how to set up.
The List of ints: This is VERY important. Every int you add to the list corresponds to an entry in the enemies spritePath array you set up. So, in the code block shown above, the ints in the list correspond to the 0th (Arrays start at 0), 1st, 2nd, 3rd, 4th and 5th place in the array, which corresponds to *these* spritepaths.
The string: this is the NAME of your animation, this will be important for later so make sure you name it something appropriate for your animation.
The tk2dSpriteAnimationClip.WrapMode: the wrap mode of your animation, setting to to: Loop: will loop the given animation until its overriden by another animation being told to play.
Once: Will play the animation *once*.
LoopSection: will loop a section of the animation. Where itll start looping is something you have to set yourself. You can set it with:
enemy.spriteAnimator.GetClipByName(YourAnimationName).loopStart = 5;
This will make it so that it will loop the animation starting from the *6th* frame in your animation.
These 3 are probably the ones you'll use the most, so I will not explain the rest of them. Feel free to experiment.

More final required setup.

You will now need to add directional animations to your enemy for them to use them properly.
Firstly, add this block of code to a suitable toolbox class:
public static DirectionalAnimation AddNewDirectionAnimation(AIAnimator animator, string Prefix, string[] animationNames, DirectionalAnimation.FlipType[] flipType, DirectionalAnimation.DirectionType directionType = DirectionalAnimation.DirectionType.Single)
{
DirectionalAnimation newDirectionalAnimation = new DirectionalAnimation
{
Type = directionType,
Prefix = Prefix,
AnimNames = animationNames,
Flipped = flipType
};
AIAnimator.NamedDirectionalAnimation greg = new AIAnimator.NamedDirectionalAnimation
{
name = Prefix,
anim = newDirectionalAnimation
};
if (animator.OtherAnimations == null){
animator.OtherAnimations = new List<AIAnimator.NamedDirectionalAnimation>{greg};}
else{animator.OtherAnimations.Add(greg);}
return newDirectionalAnimation;
}
This will help in setting up directional animations, somewhat. This will only add directional animations for storage into the OtherAnimations storage of your enemies AIAnimator. This code isn't too complicated, however, so you can easily modify it to work for Movement animations or idle ones. To add a new directional animation to your enemy, you run this method inside your enemies setup code:
A rundown of every argument required in this method: The AIAnimator: simply give it the enemies AIAnimator and you're good to go.
The string: This will be the animation name of the *collection* of animations. Directional animations have multiple animations stored in them, and the game chooses which one to use based on what you give it.
The array of strings: this array will contain all of the animations you want the directional animation to use. These have to be in a *VERY* specific order depending on which DirectionalAnimation.DirectionType you will use. The DirectionalAnimation.FlipType: simply the amount of entries in the array of strings you set for this method, however can be a bit finicky, (Of note, this value does NOT start at 0, meaning for single direction animations you will use a 1.)
The DirectionalAnimation.DirectionType: the direction type your directional animation will use. Theres too many to list here, so I will simply show the specific DirectionTypes order of animations. (Remember what I said in the "The array of strings" part? Take note.)
Single
TwoWayHorizontal:
TwoWayVertical
FourWay
SixWay
EightWay
SixteenWay
Reminder, your array of strings has to follow the EXACT order that you set your DirectionalAnimation.DirectionType to.

Important Enemy Setup knowledge

-To prevent enemies from damaging the player with contact damage while the enemy is still spawning, the enemy must: --Have an animation that's called "awaken". --Have its AwakenAnimType set to AwakenAnimationType.Awaken - Enemies also have 3 types of spawn states, which can be modified with the reinforceType field in an AIActor. The 3 types are: --FullVFX: this will play the full "shoot down from the sky" spawn in VFX *and* play the awaken animation if told to.
--SkipVFX: this will skip the "shoot down from the sky" VFX that plays when an enemy is spawned *and* play the awaken animation if told to
--Instant: this probably skips both the "shoot down from the sky" VFX and any awaken animations.
==================================================================== --Below this text will be a toolbox method for generating a new shootpoint that an enemy can use:
public static GameObject GenerateShootPoint(GameObject attacher, Vector2 attachpoint, string name)
{
GameObject shootpoint = new GameObject(name);
shootpoint.transform.parent = attacher.transform;
shootpoint.transform.position = attachpoint;
return attacher.transform.Find(name).gameObject;
}
This will return a Gameobject you can use as a reference in attack behaviors that require a shootpoint gameobject, or can be used inside of a bulletscript to fire projectile from that specific shootpoint. (Example shown below)
The Offset will search for a transform position/ shootpoint object with the name you gave it.
====================================================================
--Below this text will be a toolbox method for adding a AIBeamShooter component to your enemy:
public static AIBeamShooter AddAIBeamShooter(AIActor enemy, Transform transform, string name,Projectile beamProjectile ,ProjectileModule beamModule = null ,float angle = 0)
{
AIBeamShooter bholsterbeam1 = enemy.gameObject.AddComponent<AIBeamShooter>();
bholsterbeam1.beamTransform = transform;
bholsterbeam1.beamModule = beamModule;
bholsterbeam1.beamProjectile = beamProjectile.projectile;
bholsterbeam1.firingEllipseCenter = transform.position;
bholsterbeam1.name = name;
bholsterbeam1.northAngleTolerance = angle;
return bholsterbeam1;
}
AIBeamShooters are used in most beam related attack behaviours enemies use. Of note, enemies CAN have multiple AIBeamShooterComponents, it is how beam behaviours are able to fire more than 1 beam.
A rundown of the arguments in this method (IMPORTANT YOU READ THIS)
-The AIActor: simply the enemy you're attaching the AIBeamShooter to.
-The transform: the transform position in which your AIBeamShooter will be located and will fire beams from.
-The string: simply a name you can give to your AIBeamShooter. not of much purpose.
-The Projectile: this is important, as you will need to pull a projectile that houses a beamController from an enemy, or a custom one if you so please but I have not tested it with a custom beam. You can obtain a specific enemies AIBeamShooter projectile with the code below:
AIActor actor = EnemyDatabase.GetOrLoadByGuid("21dd14e5ca2a4a388adab5b11b69a1e1");
Projectile enemiesBeamShooterProjectile = actor.GetComponent<AIBeamShooter>().beamProjectile;
This code will obtain the projectile used in the beam from the Shelleton, which is loaded with its GUID.
-The ProjectileModule: the projectileModule of the beam that is used in the process of firing the beam. This can be obtained similarly to the Projectile of the beam, just by simply switching the "beamProjectile" with a "beamModule" in the code. The reason why this method returns null by default is because a few *very* specific enemy beams do not use a beamModule, but the rest do.
-The float: this would usually be the starting angle of your beam when its fired, however, save for a few specific instances, this is usually overwritten for a different angle. You can simply not add anything here if not needed.
==================================================================== --To give your custom enemy a custom sprite for their hand, if the enemy will have a hand while active, you can use the following code:
AIActor aIActor = EnemyDatabase.GetOrLoadByGuid("01972dee89fc4404a5c408d50007dad5");
PlayerHandController handObj = SpriteBuilder.SpriteFromResource(spritePathyToYourHandSprite, new GameObject("Hand Obj")).AddComponent<PlayerHandController>();
FakePrefab.MarkAsFakePrefab(handObj.gameObject);
handObj.ForceRenderersOff = false;
UnityEngine.Object.DontDestroyOnLoad(handObj.gameObject);
var yah = enemy.transform.Find("GunAttachPoint").gameObject;
yah.transform.position = enemy.aiActor.transform.position;
yah.transform.localPosition = new Vector2(0f, 0f);
EnemyBuilder.DuplicateAIShooterAndAIBulletBank(enemy.gameObject, aIActor.aiShooter, aIActor.GetComponent<AIBulletBank>(), IDofGunYouWantEnemyToHold, yah.transform, null, handObj);
This will give your enemy a custom hand that they will use to hold a gun. Note that the enemy might still contain a remnant hand from EnemyBuilder code, so you will have to destroy it.