Skip to content

Geckolib 4 Changes

Tslat edited this page Oct 15, 2024 · 27 revisions

Upgrading from GeckoLib 3.1.x to 4.0

GeckoLib 4.0 was a substantial overhaul of the entire library and all of its associated classes. Because of this, there will be some things you will need to change in your mod to update your mod to work with GeckoLib 4.0+

Table of Contents

Change Summary

Due to the extent of the overhaul in 4.0, there's not really any point in listing out all changes. Instead we can give a quick summary of the things that are most likely to impact you as a mod dev.

The below list is not an exhaustive list of changes, these are just the ones that are likely to be the most important ones

Class Renames

  • AnimationData -> AnimatableManager
  • AnimationFactory -> AnimatableInstanceCache
  • IGeoRenderer -> GeoRenderer
  • GeoLayerRenderer -> GeoRenderLayer
  • ExtendedGeoEntityRenderer -> DynamicGeoEntityRenderer
  • LayerGlowingAreasGeo -> AutoGlowingGeoLayer
  • AnimatedGeoModel -> GeoModel
  • GeckoLibIdTracker -> AnimatableIdCache
  • AnimationBuilder -> RawAnimation
  • IAnimatable -> GeoAnimatable
  • IBone -> CoreGeoBone
  • ILoopType -> Animation$LoopType
  • AnimationEvent -> AnimationState

Class Merges/Removals

  • AbstractLayerGeo -> Merged into GeoRenderLayer
  • IAnimatableModelProvider -> Removed, functionality moved into renderers
  • GeoModelProvider -> Removed, functionality moved into renderers
  • AnimatedTickingGeoModel -> Removed, functionality moved into renderers
  • EasingManager -> Merged into EasingType
  • EasingFunctionArgs -> Merged into EasingType
  • IAnimationTickable -> Removed, functionality moved into renderers
  • IAnimatableModel -> Merged into GeoModel
  • EDefaultLoopTypes -> Merged into Animation$LoopType
  • GeoProjectilesRenderer -> Merged into GeoEntityRenderer

Conceptual Changes

  • Animatable classes now specify the subtype they belong to via interface (GeoEntity, etc)
  • GeoModels now define the default render type for themselves, not the renderer
  • The numerous model classes have all been collapsed into one
  • Native data syncing now exists
  • Server-triggerable animations now exists
  • Render layers have expanded functionality and are now supported on all geo renderers
  • Defaulted model classes now exist
  • Animation controller/manager extra data is now generified and specific
  • Glowlayers now supports pre-made mask images instead of using mcmeta (optional, can still use mcmeta)
  • LoopTypes now support json-defined loop types
  • Custom LoopTypes and EasingTypes now supported natively
  • AnimationFactory split into two classes after being renamed, uses factory method to get new instance
  • Bedrock Animation Format is now supported, along with with catmullRom/smooth easing
  • AnimationControllers are now registered to a registrar, as opposed to directly to the AnimatableManager, and animation controllers are now reliably ordered in operation

Creating your Animatable class

Previously in GeckoLib3, you would implement IAnimatable. In GeckoLib4, there are a handful of specific interfaces that you select from depending on your animatable type. These are important because they hold useful builtin methods that will likely be critical for your interaction with GeckoLib

Animatable Type Interface
Entity GeoEntity
Projectile GeoEntity
BlockEntity GeoBlockEntity
Item GeoItem
Armor GeoItem
Existing Entity GeoReplacedEntity

E.G.

public class ExampleEntity extends PathfinderMob implements GeoEntity

Creating your AnimatableInstanceCache

With exception to the very newest of the GeckoLib3 versions, you previously instantiated your AnimationFactory with its constructor. In GeckoLib4, this class has been renamed, and you instantiate your cache with GeckoLibUtil.createInstanceCache(Animatable). This allows GeckoLib to create a cache of an appropriate type for your animatable

E.G.

private final AnimatableInstanceCache cache = GeckoLibUtil.createInstanceCache(this);

Creating your RawAnimation (Previously AnimationBuilder)

In GeckoLib3, animations to pass to the AnimationController were created by using the class's constructor, then chaining addAnimation. As of GeckoLib4, this is a little different.

You now create your RawAnimation via RawAnimation.begin(), followed by chaining one of the various methods available in the RawAnimation class.

Default loop type

One notable change is that by default; animations will now use the loop type defined in the animation json. This means that if you use the thenPlay method in RawAnimation, your animation will loop in whatever manner your animation json defined, allowing for users to customise animations in resourcepacks to a greater degree.

As always, you should cache your RawAnimation instances in a static field so you're not recreating the instance every render frame.

E.G.

private static final RawAnimation SPIN_ANIM = RawAnimation.begin().thenPlay("misc.spin");

Making a GeoArmor

In GeckoLib4, we make use of some inline render providers for armor rendering, instead of arbitrarily registering classes to the armor renderer.

Forge

public void initializeClient(Consumer<IClientItemExtensions> consumer) {
		consumer.accept(new IClientItemExtensions() {
			private GeoArmorRenderer<?> renderer;

			@Override
			public @NotNull HumanoidModel<?> getHumanoidArmorModel(LivingEntity livingEntity, ItemStack itemStack, EquipmentSlot equipmentSlot, HumanoidModel<?> original) {
				if (this.renderer == null)
					this.renderer = new MyArmorRenderer();

				// This prepares our GeoArmorRenderer for the current render frame.
				// These parameters may be null however, so we don't do anything further with them
				this.renderer.prepForRender(livingEntity, itemStack, equipmentSlot, original);

				return this.renderer;
			}
		});
}

Fabric/Quilt

TBA

Other notable changes

Custom RenderType for animatables

GeckoLib4 allows for animatables to define the default RenderType for their object in the GeoModel. This means that you do not need to extend or anonymously override the renderer to change the render type.

Custom LoopTypes

LoopType now supports custom loop types, allowing developers to handle looping in their own specific way. This is done by registering a new instance of LoopType to GeckoLib via GeckoLibUtil.addCustomLoopType(String, LoopType). This must be called during your mod constructor, otherwise the loop type will not be registered in time for GeckoLib to use it when deserialization the animation jsons

Custom EasingTypes

EasingType now supports custom easings, allowing developers to apply custom easings to animations if needed. This is done by registering a new instance of EasingType to GeckoLib via GeckoLibUtil.addCustomEasingType(String, EasingType). This must be called during your mod constructor, otherwise the easing type will not be registered in time for GeckoLib to use it when deserialization the animation jsons

Render Layers

Render Layers have been expanded greatly in GeckoLib4. Not only does the base layer have much more information provided to you at render time, it also comes with additional hooks that allow for pre-render operations, as well as a hook to render right in the middle of when a bone is being rendered.

In addition to this, several additional layers have been bundled in for use, such as a layer for dynamically hiding/showing bones, or one to render items or blocks alongside the model.

DynamicGeoEntityRenderer

Previously in GeckoLib3, ExtendedGeoEntityRenderer bundled several additional rendering functions for more advanced animatable rendering. In GeckoLib4, about half of that functionality has been split into various GeoRenderLayers allowing for any animatable to take advantage of the functionality. Additionally, the class has been renamed to DynamicGeoEntityRenderer. DynamicGeoEntityRenderer is now primarily used for per-bone render overrides, such as custom textures or render types.

Defaulted GeoModels

GeckoLib4+ provides several built-in animatable model subclasses. These defaulted classes allow for easy and consistent handling of animatable asset paths. These are completely optional to use, but it is recommended to use them as they encourage consistency of use.

Example entity usage:

public class MyEntityModel extends DefaultedEntityGeoModel<MyEntity> {
	public MyEntityModel() {
		super(new ResourceLocation(MyMod.MOD_ID, "monster/my_entity"));
	}

        // This creates a new GeoModel with the following asset paths:
        // Animation Json: assets/mymod/animations/entity/monster/my_entity.animation.json
        // Model Json: assets/mymod/geo/entity/monster/my_entity.geo.json
        // Texture: assets/mymod/textures/entity/monster/my_entity.png
}

As an added bonus, the DefaultedEntityGeoModel class can also automatically handle head rotation for you, by simply using the alternate constructor with a boolean in it. This will then automatically rotate the entity's "head" bone to the position it should be facing.

Default Animations

GeckoLib4 comes bundled with a suite of example/template animations. These can all be found in the DefaultAnimations class. They are completely optional to use, but they do encourage consistency of animation naming, as well as saving you from needing to instantiate new RawAnimations for basic animations. It also contains several generic animation controller factories, allowing you to quickly instantiate a basic controller with minimal additional effort

E.G.

// Here we register two controllers that automatically handles walking, idling, and attacking for us
public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
	controllers.add(
		DefaultAnimations.genericWalkIdleController(this),
		DefaultAnimations.genericAttackAnimation(this, DefaultAnimations.ATTACK_STRIKE)
	);
}

DataTickets

GeckoLib4 makes use of a functionality called DataTickets. These are generified objects that act as 'keys' for transient data in GeckoLib. For example, the AnimationState class no longer has a getExtraDataOfType method. Instead, it's replaced with a getData method that takes a DataTicket key. This returns the exact data that has been stored either by you or GeckoLib previously, without requiring you to filter out potential unwanted values.

GeckoLib stores a handful of default DataTicket implementations in the DataTickets class, but you can create your own at any time without any registration required.

E.G.

Entity entity = state.getData(DataTickets.ENTITY);

Native data syncing

New to GeckoLib4 is native data-syncing for animatables. This allows for mod devs to efficiently send quick data bites to relevant clients for handling on the client side without needing to register synched entity data or custom packets.

This is done through SerializableDataTickets. Unlike normal DataTickets, the serializable variants do require registration with GeckoLib to function. This can be done through GeckoLibUtil.addDataTicket at any time.

GeckoLib itself pre-registers several potentially useful SerializableDataTickets however, so you may not need to register one of your own. These can be found in the DataTickets class

To use this system, simply call setAnimData in your animatable class, providing the ticket and the data you want to send. GeckoLib will handle the rest for you. You can also call this on the client side to set and store data there if wanted

E.G.

setAnimData(DataTickets.ACTIVE, true);

Note that for singleton animatables (Items and replaced geo entities), you must additionally register your syncable animatable with GeckoLib in your class constructor via SingletonGeoAnimatable.registerSyncedAnimatable

E.G.

public ExampleItem(Properties properties) {
    super(properties);

    SingletonGeoAnimatable.registerSyncedAnimatable(this);
}

Server-triggerable animations

New to GeckoLib4 is the ability to trigger animations in an animatable, from the server! This opens a lot of interesting options for more complex objects and animations. Triggered animations will override the AnimationState calls for the AnimationController that is running it for the duration that it is being run, so it is recommended that you only trigger non-looping animations, but it can still be stopped by calling stop() on the controller at any stage.

To use this system, we must first tell our controller that they have a triggerable animation. This is done by calling .triggerableAnim on the controller when we instantiate it, giving it a trigger name, and the RawAnimation associated with the trigger

E.G.

public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
    controllers.add(new AnimationController<>(this, "shoot_controller", state -> PlayState.STOP)
        .triggerableAnim("shoot", SHOOT_ANIM));
}

Once that has been done, we can then call triggerAnim from the server side at any time, providing the controller name and the trigger name.

E.G.

public InteractionResultHolder<ItemStack> use(Level level, Player player, InteractionHand hand) {
    if (level instanceof ServerLevel serverLevel)
        triggerAnim(player, GeoItem.getOrAssignId(player.getItemInHand(hand), serverLevel), "shoot_controller", "shoot");

    return super.use(level, player, hand);
}

Note that for singleton animatables (Items and replaced geo entities), you must additionally register your syncable animatable with GeckoLib in your class constructor via SingletonGeoAnimatable.registerSyncedAnimatable

E.G.

public ExampleItem(Properties properties) {
    super(properties);

    SingletonGeoAnimatable.registerSyncedAnimatable(this);
}

Examples

Example Entity

public class ExampleEntity extends PathfinderMob implements GeoEntity {
    private final AnimatableInstanceCache cache = GeckoLibUtil.createInstanceCache(this);

    public ExampleEntity(EntityType<? extends ExampleEntity> type, Level level) {
        super(type, level);
    }

	@Override
    public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
        controllers.add(new AnimationController<>(this, "idle", 5, state -> state.setAndContinue(DefaultAnimations.IDLE)));
    }

    @Override
    public AnimatableInstanceCache getAnimatableInstanceCache() {
        return this.cache;
    }
}

Example Item

public final class ExampleItem extends Item implements GeoItem {
	private final AnimatableInstanceCache cache = GeckoLibUtil.createInstancecache(this);

	public ExampleItem(Properties properties) {
		super(properties);

		// Register our item as server-side handled.
		// This enables both animation data syncing and server-side animation triggering
		SingletonGeoAnimatable.registerSyncedAnimatable(this);
	}

	@Override
	public void initializeClient(Consumer<IClientItemExtensions> consumer) {
		consumer.accept(new IClientItemExtensions() {
			private ExampleItemRenderer renderer;

			@Override
			public BlockEntityWithoutLevelRenderer getCustomRenderer() {
				if (this.renderer == null)
					this.renderer = new ExampleItemRenderer();

				return this.renderer;
			}
		});
	}

	@Override
	public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
		controllers.add(new AnimationController<>(this, "shoot_controller", state -> PlayState.STOP)
				.triggerableAnim("shoot", DefaultAnimations.ATTACK_SHOOT));
	}

	@Override
	public InteractionResultHolder<ItemStack> use(Level level, Player player, InteractionHand hand) {
		if (level instanceof ServerLevel serverLevel)
			triggerAnim(player, GeoItem.getOrAssignId(player.getItemInHand(hand), serverLevel), "shoot_controller", "shoot");

		return super.use(level, player, hand);
	}

	@Override
	public AnimatableInstanceCache getAnimatableInstanceCache() {
		return this.cache;
	}
}

Example Armor

public final class ExampleArmor extends ArmorItem implements GeoItem {
	private final AnimatableInstanceCache cache = GeckoLibUtil.createInstanceCache(this);

	public ExampleArmor(ArmorMaterial armorMaterial, EquipmentSlot slot, Properties properties) {
		super(armorMaterial, slot, properties);
	}

	@Override
	public void initializeClient(Consumer<IClientItemExtensions> consumer) {
		consumer.accept(new IClientItemExtensions() {
			private ExampleArmorRenderer<?> renderer;

			@Override
			public @NotNull HumanoidModel<?> getHumanoidArmorModel(LivingEntity livingEntity, ItemStack itemStack, EquipmentSlot equipmentSlot, HumanoidModel<?> original) {
				if (this.renderer == null)
					this.renderer = new ExampleArmorRenderer();

				this.renderer.prepForRender(livingEntity, itemStack, equipmentSlot, original);

				return this.renderer;
			}
		});
	}

	@Override
	public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
		controllers.add(DefaultAnimations.genericIdleController(this));
	}

	@Override
	public AnimatableInstanceCache getAnimatableInstanceCache() {
		return this.cache;
	}
}

Example BlockEntity

public class ExampleBlockEntity extends BlockEntity implements GeoBlockEntity {
	private final AnimatableInstanceCache cache = GeckoLibUtil.createInstanceCache(this);

	public ExampleBlockEntity(BlockPos pos, BlockState state) {
		super(TileRegistry.EXAMPLE_BLOCK_ENTITY.get(), pos, state);
	}

	@Override
	public void (AnimatableManager.ControllerRegistrar controllers) {
		controllers.add(new AnimationController<>(this, state -> {
			if (getLevel().getDayTime() > 23000 || getLevel().getDayTime() < 13000) {
				return state.setAndContinue(DefaultAnimations.REST);
			}
			else {
				return state.setAndContinue(DefaultAnimations.IDLE);
			}
		}));
	}

	@Override
	public AnimatableInstanceCache getAnimatableInstanceCache() {
		return this.cache;
	}
}

Table of Contents

Geckolib 3
Geckolib 4

Hosted By: Cloudsmith

Package repository hosting is graciously provided by Cloudsmith.

Cloudsmith is the only fully hosted, cloud-native, universal package management solution that enables your organization to create, store and share packages in any format, to any place, with total confidence.

Clone this wiki locally