A high-level cross-platform 2D game development API

Overview

mini2Dx

Continuous Integration

A high-level game development API for LibGDX inspired by Slick

Goals

The main objective of mini2Dx is to provide a beginner-friendly, master-ready framework for rapidly prototyping and building 2D games in Java.

What's New

20th April 2019

1.9.0 has been released as another in-between version of the 1.x API and 2.0 API. It is recommended upgrading to 1.9.0 to ease the transition to 2.0 later on. As previous stated, 1.9.x will only receive bug fixes and optimisations going forward. Once 2.0 is released, 1.9.x will be 100% community-driven.

10th November 2018

1.8.0 has been released as an in-between version of the 1.x API and 2.0 API. It is recommended upgrading to 1.8.0 to ease the transition to 2.0 later on. As previous stated, 1.8.x will only receive bug fixes and optimisations going forward. Once 2.0 is released, 1.8.x will be 100% community-driven.

10th June 2018

This is repository will be focused on development of the next generation of mini2Dx - 2.0. For 1.x development, see the mini2Dx Vintage Edition repository. 1.x will receive bug fixes and optimisations during 2.0 development. Once 2.0 is released, 1.x will be 100% community-driven.

License

The project is licensed under the BSD 3-Clause License.

Assets used for UATs are licensed under Creative Commons (Kenney.nl, Alexander Ehlers, Juan Rodriguez)

However, some classes are based on LibGDX's code and are licensed under the Apache License Version 2.0 per LibGDX's license. Such classes are placed under com.badlogic.gdx packages.

Contributing

See the Contributing Page

Comments
  • TextButtons without text

    TextButtons without text

    Heyho,

    the last days I stumbled across a small bug (or my fault). If I load a modal like this:

    uiContainer = new UiContainer(gc, assetManager);
    
    try
    {
        AlignedModal modal = Mdx.xml.fromXml(Gdx.files.internal("uis/MainMenu.xml").reader(), AlignedModal.class);
        TextButton newGameButton = (TextButton) modal.getElementById("newGameButton");
        TextButton loadGameButton = (TextButton) modal.getElementById("loadGameButton");
        TextButton optionsButton = (TextButton) modal.getElementById("optionsButton");
        TextButton exitButton = (TextButton) modal.getElementById("exitButton");
        newGameButton.addActionListener(createActionListener(GameMenuScreen.ID));
        loadGameButton.addActionListener(createActionListener(GameMenuScreen.ID));
        optionsButton.addActionListener(createActionListener(ConfigScreen.ID));
        exitButton.addActionListener(new ActionListener() {
             @Override
             public void onActionBegin(ActionEvent event) { Gdx.app.exit(); }
    
             @Override
             public void onActionEnd(ActionEvent event) {
    
             }
        });
    
        uiContainer.add(modal);
    } catch (SerializationException e)
    {
        e.printStackTrace();
    }
    
    mainStage = gc.createStage(new FitViewport(viewWidth, viewHeight));
    uiStage = gc.createStage(new FitViewport(viewWidth, viewHeight));
    

    Everything is fine except for the last TextButton (exitButton) as it has no text... If I add another TextButton the exitButton shows its text (without changing its code), but the newly added one doesn't. It does function though (If I click it, the game stops).

    Now the question... is it a bug or have I forgotten something?

    opened by Zweistein2 11
  • Add ARM architecture support (LWJGL3)

    Add ARM architecture support (LWJGL3)

    Mini2DX would be an excellent platform for developing 2D games targeting the Raspberry Pi and similarly powered SBC's.

    Adding support for the ARM (armhf/aarch64) architectures would hugely expand the supported devices for Mini2DX.

    I've seen somewhere mention of possibly porting some backends to BGFX. It says on the overview page that Raspberry Pi is a supported platform. I would need to look further into it to see the exact capabilities and work involved.

    Otherwise, in keeping with the current Libgdx backends, there is this open PR on the libgdx github page for adding arm support for the libgdx backends. This would make things probably easier than doing it through bgfx. Though bgfx also seems to support the SteamLink. It would be fantastic to be able to build games in Mini2DX to target SteamLink natively!

    opened by Torbuntu 8
  • Sprites upside down after update to version 1.9.0-beta.8

    Sprites upside down after update to version 1.9.0-beta.8

    After updating from 1.8.0 to 1.9.0-beta.8 all rendered sprites are upside down. No huge problem, because fliiping after animation was load fixes the problem. But it breaks in some kind the API. Was this intended. I scanned the changelog but could not find a direct hint.

    opened by klaushauschild1984 7
  • Custom UiTheme results in NullPointerException

    Custom UiTheme results in NullPointerException

    Issue details

    When trying to load your custom UiTheme using the AssetManager java exits with a NullPointerException. What i did (simplified):

    • I created a atlas full of different buttons using the Tool (GDX-Texturepacker) this resulted in a buttons.atlas file in my assets directory.
    • As i need to load a JSON full of styling information in my application i've created a buttons.json -> where i specified the different types of buttons and there respective styling -> here's a example with the button -> default "class":
          "rules": {
            "XS": {
              "normal": "Start",
              "hover": "Start_p",
              "action": "Start_p",
              "disabled": "Start",
              "fontSize": 12,
              "font": "default",
              "textColor": "254,254,254",
              "paddingTop": 0,
              "paddingBottom": 0,
              "paddingLeft": 0,
              "paddingRight": 0,
              "marginTop": 12,
              "marginBottom": 12,
              "marginLeft": 12,
              "marginRight": 12
            }
          }
        },
    
    • Than in my MainMenu.java file i've created an assetManager and try to load my buttons.json file. -> This contains something like that:
       AssetManager assetManager;
    
        @Override
        public void initialise(GameContainer gc) {
            FileHandleResolver fileHandleResolver = new FallbackFileHandleResolver(new ClasspathFileHandleResolver(), new InternalFileHandleResolver());
            assetManager = new AssetManager(fileHandleResolver);
            assetManager.setLoader(UiTheme.class, new UiThemeLoader(fileHandleResolver));
            assetManager.load("ui/buttons/buttons.json", UiTheme.class);
            UiContainer.setTheme(assetManager.get("ui/buttons/buttons.json", UiTheme.class));
    
    • also i've added the assetManager.update() function to my update() function which should load all assets (which it does referring to the console output)

    • When running my program, i get an error message saying:

    AL lib: (WW) FreeDevice: (0x12a583000) Deleting 3 Buffer(s)
    Exception in thread "LWJGL Application" com.badlogic.gdx.utils.GdxRuntimeException: java.lang.NullPointerException
    	at com.badlogic.gdx.assets.AssetManager.handleTaskError(AssetManager.java:582)
    	at com.badlogic.gdx.assets.AssetManager.update(AssetManager.java:383)
    	at com.my.identifyer.MainMenu.update(MainMenu.java:96)
    	at org.mini2Dx.core.screen.BasicScreenManager.update(BasicScreenManager.java:62)
    	at org.mini2Dx.core.game.ScreenBasedGame.update(ScreenBasedGame.java:38)
    	at org.mini2Dx.core.game.GameWrapper.update(GameWrapper.java:66)
    	at com.badlogic.gdx.backends.lwjgl.DesktopMini2DxGame.executeGame(DesktopMini2DxGame.java:249)
    	at com.badlogic.gdx.backends.lwjgl.DesktopMini2DxGame$1.run(DesktopMini2DxGame.java:118)
    Caused by: java.lang.NullPointerException
    	at com.badlogic.gdx.assets.loaders.TextureAtlasLoader.getDependencies(TextureAtlasLoader.java:55)
    	at com.badlogic.gdx.assets.loaders.TextureAtlasLoader.getDependencies(TextureAtlasLoader.java:34)
    	at com.badlogic.gdx.assets.AssetLoadingTask.handleSyncLoader(AssetLoadingTask.java:99)
    	at com.badlogic.gdx.assets.AssetLoadingTask.update(AssetLoadingTask.java:88)
    	at com.badlogic.gdx.assets.AssetManager.updateTask(AssetManager.java:507)
    	at com.badlogic.gdx.assets.AssetManager.update(AssetManager.java:381)
    	... 6 more
    

    I've tried to trace this issue back to where it came from but did came closer than to the update function of the AssetManager file.

    Version of mini2Dx and/or relevant dependencies

    1.9.0

    Stacktrace

    AL lib: (WW) FreeDevice: (0x12a583000) Deleting 3 Buffer(s)
    Exception in thread "LWJGL Application" com.badlogic.gdx.utils.GdxRuntimeException: java.lang.NullPointerException
    	at com.badlogic.gdx.assets.AssetManager.handleTaskError(AssetManager.java:582)
    	at com.badlogic.gdx.assets.AssetManager.update(AssetManager.java:383)
    	at com.my.identifyer.MainMenu.update(MainMenu.java:96)
    	at org.mini2Dx.core.screen.BasicScreenManager.update(BasicScreenManager.java:62)
    	at org.mini2Dx.core.game.ScreenBasedGame.update(ScreenBasedGame.java:38)
    	at org.mini2Dx.core.game.GameWrapper.update(GameWrapper.java:66)
    	at com.badlogic.gdx.backends.lwjgl.DesktopMini2DxGame.executeGame(DesktopMini2DxGame.java:249)
    	at com.badlogic.gdx.backends.lwjgl.DesktopMini2DxGame$1.run(DesktopMini2DxGame.java:118)
    Caused by: java.lang.NullPointerException
    	at com.badlogic.gdx.assets.loaders.TextureAtlasLoader.getDependencies(TextureAtlasLoader.java:55)
    	at com.badlogic.gdx.assets.loaders.TextureAtlasLoader.getDependencies(TextureAtlasLoader.java:34)
    	at com.badlogic.gdx.assets.AssetLoadingTask.handleSyncLoader(AssetLoadingTask.java:99)
    	at com.badlogic.gdx.assets.AssetLoadingTask.update(AssetLoadingTask.java:88)
    	at com.badlogic.gdx.assets.AssetManager.updateTask(AssetManager.java:507)
    	at com.badlogic.gdx.assets.AssetManager.update(AssetManager.java:381)
    	... 6 more
    

    Please select the affected platforms

    • [ ] Android
    • [ ] iOS (robovm)
    • [ ] iOS (MOE)
    • [ ] HTML/GWT
    • [x] Windows
    • [x] Linux
    • [x] MacOS
    opened by lukasknutti 6
  • LwjglApplicationConfiguration is missing

    LwjglApplicationConfiguration is missing

    I tried to set up a maven project following the documentation here:

    https://github.com/tomcashman/mini2Dx/wiki/Maven-Support-0.9.1-release

    I see that the class supposed to be a part of libgdx but it isn't there. Here is my pom.xml:

    
        <repositories>
            <repository>
                <id>gdx-nightlies</id>
                <name>libgdx</name>
                <url>http://libgdx.badlogicgames.com/nightlies/maven</url>
            </repository>
            <repository>
                <id>mini2Dx-thirdparty</id>
                <name>mini2Dx-thirdparty</name>
                <url>http://mini2dx.org/nexus/content/repositories/thirdparty</url>
            </repository>
            <repository>
                <id>mini2Dx</id>
                <name>mini2Dx</name>
                <url>http://mini2dx.org/nexus/content/repositories/releases</url>
            </repository>
        </repositories>
        <dependencies>
            <dependency>
                <groupId>org.mini2Dx</groupId>
                <artifactId>mini2Dx-core</artifactId>
                <version>0.9.1</version>
            </dependency>
            <dependency>
                <groupId>org.mini2Dx</groupId>
                <artifactId>mini2Dx-tiled</artifactId>
                <version>0.9.1</version>
            </dependency>
            <dependency>
                <groupId>org.mini2Dx</groupId>
                <artifactId>mini2Dx-dependency-injection</artifactId>
                <version>0.9.1</version>
            </dependency>
            <dependency>
                <groupId>org.mini2Dx</groupId>
                <artifactId>mini2Dx-ecs</artifactId>
                <version>0.9.1</version>
            </dependency>
            <dependency>
                <groupId>org.mini2Dx</groupId>
                <artifactId>mini2Dx-dependency-injection-desktop</artifactId>
                <version>0.9.1</version>
            </dependency>
        </dependencies>
    
    
    opened by akoskm 6
  • CollisionPoint finishes interpolation

    CollisionPoint finishes interpolation

    Greate that CollisionPoint has a built-in interpolation that makes my first steps with this library so easy. But now I am struggeling: I use CollisionPoint.interpolate to move a sprite smoothly over the screen. Now I need a way to determine when the interpolation is done. I could track the old, new and current position outside but then I could implement the whole interpolation by myself. There is a private field boolean interpolate that stores this information but could not access from outside. I would be nice if you could

    • provide a solution based on the current API or
    • expose the inner interpolation state

    Many thanks

    opened by klaushauschild1984 5
  • Gid of TileObjects to large for int

    Gid of TileObjects to large for int

    I got a tiled map with the following objects

    <object id="30" name="city" gid="73" x="222" y="288" width="56" height="72"> <properties> <property name="Owner" value="Red"/> </properties> </object>

    <object id="42" name="jeep" gid="2147483735" x="732" y="219" width="52" height="44"> <properties> <property name="Owner" value="Blue"/> </properties> </object>

    Obviously the generated gid value of the second object (2147483735) is too big for a normal int. This leads to the following error:

    Error:java.lang.NumberFormatException: For input string: "2147483715"

    when trying to access the .getGid()-method

    opened by Zweistein2 5
  • Improve test coverage of Rectangle and Circle classes

    Improve test coverage of Rectangle and Circle classes

    Currently test coverage is below 50% for both classes. To prevent regression and resolve any unknown bugs, further tests need to be added to RectangleTest and CircleTest.

    improvement good first issue 
    opened by tomcashman 5
  • Scalling tilemaps

    Scalling tilemaps

    I want use g.scale in order to makes tilemap looking exacly the same on different resolutions. After I set scale tilemap is not rendered fully. I think there should be option to include graphics scale in draw tilemap calculations.

    bug 
    opened by Mierzmit 5
  • Add few missing methods/class from slick2d

    Add few missing methods/class from slick2d

    I'm trying to port two games from slick2d. I'm missing few method /classes: g.fillShape(shape); (it will be faster to type than each time g.fillRect(rect.getX(),rect.getY(),rect.getWidth(),rect.getHeight());. g.texture(); (shape with texture) Method contains(x,y) in circle class. And the my main problem is missing Polygon.class In slick2d it was very useful to creating any shapes and making collision.(polygon.addPoint(x,y)....) Is it possibile to add them? Thanks

    improvement new-feature 
    opened by Mierzmit 5
  • Added interpolators

    Added interpolators

    Hello,

    I noticed that Mini2Dx has a great emphasis on the interpolation step, however aside from the basic interpolation provided by Mini2Dx, it's a bit left to the developer's imagination. Additionally, the 'previous value' changes when you lerp it on the current implementation (Ow!) For instance, the following fails:

        @Test
        public void testLerping() {
            CollisionBox box = new CollisionBox();
            box.set(1,2,3,4);
            box.rotateAround(new Point(22, 55), 22);
    
            box.preUpdate();;
            box.set(5,6,7,8);
            box.rotateAround(new Point(55, 22), -22);
    
            box.interpolate(null, 0.55f);
            float renderX = box.getRenderX();
            float renderY = box.getRenderY();
    
            box.interpolate(null, 0.55f);
            Assert.assertEquals(renderX, box.getRenderX());
            Assert.assertEquals(renderY, box.getRenderY());
        }
    

    I understand the 'mucking' with the values is to reduce the object allocation, so I built an interpolation system that is both easy to implement new types, doesn't muck with your values, and supports the flyweight approach.

    Here's a sample of usage with primitives (i.e.: no flyweights):

      @Test
      public void testInterpolatedValues() {
        InterpolatedFloatValue value = new InterpolatedFloatValue(Interpolators.linearFloat());
        value.setInitialValue(initialValue);
        value.setTargetValue(targetValue);
        for (float alpha = 0.0f; alpha <= 1.0f; alpha += 0.05f) {
          value.setAlpha(alpha);
          Assert.assertEquals(initialValue + alpha * (targetValue - initialValue), value.getInterpolatedValue(), 0.0f);
        }
      }
    

    Here's a sample of usage with non-primitives:

      @Test
      public void testInterpolateValues() {
        interpolatedValue.setInitialValue(initialValue);
        interpolatedValue.setTargetValue(targetValue);
    
        Circle flyweight = new Circle(0f, 0f, 0f);
        for (float alpha = 0.0f; alpha <= 1.0f; alpha += 0.05f) {
          interpolatedValue.setAlpha(alpha);
    
          float x = initialValue.getX() + alpha * (targetValue.getX() - initialValue.getX());
          float y = initialValue.getY() + alpha * (targetValue.getY() - initialValue.getY());
          float radius = initialValue.getRadius() + alpha * (targetValue.getRadius() - initialValue.getRadius());
    
          interpolatedValue.getInterpolatedValue(flyweight);
          Assert.assertEquals("x at " + alpha, x, flyweight.getX(), 0.05f);
          Assert.assertEquals("y at "+ alpha, y, flyweight.getY(), 0.05f);
          Assert.assertEquals("radius at "+ alpha, radius, flyweight.getRadius(), 0.05f);
        }
      }
    

    Currently, the InterpolatedValue class has a 'initial value' and 'target value' which may be a bit strange. Maybe 'initial value' and 'current value' makes more sense?

    In any case, I haven't dabbled in the serialization aspects of it (CollisionCircle doesn't seem to have serialization-specific details.) Maybe implements Serializable is enough?

    A side note, I couldn't replicate the CollisionBox linear interpolation. The Rectangle class under the hood does some wicked things with the rotation. As soon as I add in linear interpolation of a moving rotational center, the x-y coordinates get 'rotated' and I lose my 'initial values'. For the time being, I left it out. CollisionBox uses some private methods to set up the interpolated value.

    Thanks! I appreciate your thoughts on the matter.

    opened by wleroux 5
  • Potential bug in text rendering

    Potential bug in text rendering

    I'm experimenting with ScreenBasedGame. I have created a loading screen exactly as described here, with the only difference that I changed the text that gets displayed as follows:

    g.drawString("A GTPware game", 32, 32);
    

    Note the space between the first two words. When I launch the game, that space is not there: font error

    opened by GTP95 0
  • Cannot run project using InteliJ with Java 11

    Cannot run project using InteliJ with Java 11

    There is a problem while running the intellij project in ubuntu giving a "non-zero exit code 127" error when running the command "/usr/lib/jvm//usr/lib/jvm/java-11-openjdk-amd64/bin/java" I tried following the tutorial on how to run from IntelliJ but I believe that the tutorial page is outdated since it specifies to use classpath desktop while there is no classpath desktop.

    opened by the-unfactoring-guru 1
  • Issue #59 - graphics texture

    Issue #59 - graphics texture

    Noticed that issue #59 was tagged with help wanted so I jumped in to help. Here is a brief description of what I changed and some additional questions that I have.

    What Changed

    • Added another overloaded Graphics.drawTexture method that applies the x, y, width, and height values of a provided shape to the texture being drawn.
    • Updated the CHANGES file with details regarding this additional functionality

    Questions

    • Code-related
      • Should the shape's rotation also be applied to the texture somehow?
      • How should I go about writing unit tests for this?
    • General
      • Which branch should I point this PR to?
      • Does this change require cutting a maven central release?

    Thanks for your time, hopefully this is helpful. 😄

    opened by bgordley 3
  • OrthographicCamera in Mini2Dx 2.0

    OrthographicCamera in Mini2Dx 2.0

    I am looking for a camera that comes with Mini2DX V2.0 and that follows the player, do I have to use the OrthographicCamera from Libgdx or does Mini2DX already have a special camera to follow the player?

    opened by kevin4dhd 1
Releases(v2.0.0-beta.14)
LWJGL is a Java library that enables cross-platform access to popular native APIs useful in the development of graphics (OpenGL, Vulkan), audio (OpenAL), parallel computing (OpenCL, CUDA) and XR (OpenVR, LibOVR) applications.

LWJGL - Lightweight Java Game Library 3 LWJGL (https://www.lwjgl.org) is a Java library that enables cross-platform access to popular native APIs usef

Lightweight Java Game Library 4k Dec 29, 2022
Mesh is a cross-version minecraft mod development platform for Forge and Fabric.

Mesh is a cross-version minecraft mod development platform for Forge and Fabric. Supported Versions Mesh will eventually be able to run on most f

null 12 Dec 13, 2022
Small mod for Minecraft Forge 1.16.5 that sends messages of in-game events to a channel in your Discord server. This mod also enables cross-chatting between Minecraft and Discord.

DiscordSync Small mod for Minecraft Forge 1.16.5 that sends messages of in-game events to a channel in your Discord server. This mod also enables cros

AeonLucid 4 Dec 20, 2022
A fairly Simple Game made in Java,You can adopt Pets, name them, and take care of them for XpPoints and level up!

Introducing PetGame! A simple console based game made by @denzven in Java ☕ About the Game PetGame is my first big project in Java, the rules are simp

Denzven 11 Jun 7, 2022
Desktop/Android/HTML5/iOS Java game development framework

Cross-platform Game Development Framework libGDX is a cross-platform Java game development framework based on OpenGL (ES) that works on Windows, Linux

libgdx 20.9k Jan 1, 2023
A complete 3D game development suite written purely in Java.

jMonkeyEngine jMonkeyEngine is a 3-D game engine for adventurous Java developers. It’s open-source, cross-platform, and cutting-edge. 3.2.4 is the lat

jMonkeyEngine 3.3k Dec 31, 2022
A Java game development framework that deploys to JVM, HTML5, Android and iOS.

PlayN PlayN is a cross-platform Java game development library written in Java that targets HTML5 browsers (via GWT), desktop JVMs, Android and iOS dev

null 237 Dec 9, 2022
Jetserver is a high speed nio socket based multiplayer java game server written using Netty and Mike Rettig's Jetlang.It is specifically tuned for network based multiplayer games and supports TCP and UDP network protocols.

Note New version of Jetserver is called Nadron and is in a new netty 4 branch of this same repo. JetServer is a java nio based server specifically des

Abraham Menacherry 1.2k Dec 14, 2022
Creates high-resolution isometric screenshots of Minecraft's game objects

Isometric Renders Overview Isometric Renders allows you to take isometric screenshots, or rather, create renders of game objects like parts of world,

glisco 74 Dec 18, 2022
contents based on article cross-team collaboration

camelk-team-collaboration This repository contains all the sources showcased in the following Red Hat Developers article: https://developers.redhat.co

null 7 Apr 27, 2022
My Game Engine tested via my Cubecraft Game

My Game Engine tested via my Cubecraft Game Install: mvn -P {your OS name} clean install mvn -P mac clean install mvn -P linux clean install mvn -P wi

null 30 Oct 3, 2022
BattleShip-Game - This repository contains the code of the BattleShip (Bataille Navale) game programmed in Java.

Bataille Navale Auteur : SABIL Mohamed Amine Comment générer la documentation ? Placez vous dans le sous-dossier src : pour générer la documentation d

Mohamed Amine SABIL 1 Jan 4, 2022
Snake-Game - A simple snake game written in java.

What's this? A simple snake game written in java. How to play Download the latest release. Ensure that a compatible java runtime is installed (optimal

Patrick260 4 Oct 31, 2022
Othello PvP (2-Player) Game Application created in Java using Swing and AWT, for playing the Othello game

Othello PvP (2-Player) Game Application created in Java using Swing and AWT, for playing the Othello game, a simple game that played on an 8 by 8 checkered board with 64 double-sided black and white discs. The game is easy to learn, but it takes time to master and develop winning strategies.

Soumyadeep Pal 1 Feb 28, 2022
The game is a 2D side scrolling system, where a player moves left, right, vertically or diagonally to traverse the game.

The game is a 2D side scrolling system, where a player moves left, right, vertically or diagonally to traverse the game.

Ravi Mandal 7 Sep 11, 2022
A simple puzzle game made with Unity to practice the game engine

A simple puzzle game made with Unity to practice the game engine.

Eyüb Salih Özdemir 1 Mar 30, 2022
A basic representation of the 0-player game, "Game-of-Life", a simple example of basic cellular automata

Game-Of-Life-Basic A basic representation of the 0-player game, "Game-of-Life", a simple example of basic cellular automata. A cellular automaton is a

Nikhil Narayanan 5 Oct 27, 2022
Mod development kit for the Tecknix Client Modding API

Tecknix Mod Loader API THIS API IS NOT COMPLETE AND WILL NOT YET ALLOW YOU TO MOD THE CLIENT What is this? This is our modding MDK for Tecknix Client.

Tecknix Client 6 Sep 12, 2022
Golden Axe (1989) game implemented in java using only standard libraries (Java 2D, Swing, AWT & Java Sound API)

Golden Axe (1989) game implemented in java using only standard libraries (Java 2D, Swing, AWT & Java Sound API), so no external libraries required. Video: https://youtu.be/uevIVLNhQqs

Leo 101 Jul 21, 2022