NiceEngine is a 2D Game Engine, its purpose is for learning purposes, making it easy for those who are beginner of game programming to understand how a Game Engine works. In addition, it can also be applied in teaching game programming subjects
- Easily create simple 2D games, e.g. mario, contra,...
- Image rendering support from OpenGL
- Support collision handling and physics
- Supports creating spritesheet-based game objects, e.g. game items, bricks,...
- Supports AudioManager from OpenAL
- Support 2D animation
- And more,.... (developing)
- Java: the main language
- OpenGL: the Open graphic library help rendering
- Dear Imgui: graphic user interface library
- OpenAL: cross-platform 3D audio API
- ...
- Vũ Đức Trung - Full-stack developer, PM
- Lương Mạnh Hùng - Full-stack developer
- Installation Instructions
- Basic concepts
- NiceEngine Interface
- Create New Project
- Game Object
- Component
- Script
- Input from Mouse & Keyboard
- BodyType in Rigidbody2D
- EditorScene & GamePlayingScene
- Transient Variable
- Serialize Game Object
- Prefab
- Working with Spritesheet
- Working with Console & Debug.Log
- Working with Inspector
- Working with Hierarchy
- Run Flow of Engine
To use the engine, please follow these steps:
-
Clone the project.
-
Ensure that JDK 17 is installed on your machine. If not, please install JDK 17
-
Choose an Integrated Development Environment (IDE) to run the code. (We recommend IntelliJ)
-
Once the project is cloned, locate the "Main.java" class file in the directory:
.\NiceEngine\src\java\Main.java. -
Open the project and run the program by executing the "Main.java" class.
Make sure install all dependencies in Gradle and JDK 17 is selected as the project's Java SDK in the IDE's settings.
⬆️ Back to Top
In simple terms, a GameObject is an entity within a game. It represents characters, enemies, items, environments, and more. Each GameObject typically has its own attributes, behaviors, and abilities, which are defined using components and scripts.
A Component is an independent element that is attached to a GameObject to provide specific functionality to that GameObject. Components help to separate the functionality and behavior of a GameObject into discrete and reusable units during game development.
Each Component typically performs a specific function, such as handling collisions, controlling movement, rendering graphics, managing events, and more. A GameObject can have multiple Components attached to it, allowing for a rich and diverse set of behaviors and capabilities.
Components enable modularity and encapsulation within a game. They allow you to break down complex behaviors into smaller, manageable units, making it easier to develop, maintain, and modify game objects.
For example, in a racing game, a GameObject representing a car can have various Components attached to it. These may include a "Rigidbody" Component for physics and collision handling, a "CarController" Component for controlling the car's movement, a "Collider" Component to define the car's collision area, and a "Renderer" Component to display the car's visuals on the screen.
By combining and configuring different Components on a GameObject, you can customize its behavior and create interactive gameplay experiences. Components are a fundamental concept in Unity that allows for modular and flexible game development.
A Script is a piece of code that contains logic to control the behavior and interactions of a GameObject to which it is attached. Script can also be considered as a Component.
For example, a GameObject can have a Script that controls the behavior of the main character in a game. This Script may contain code to handle the character's movement, process interactions with items, check conditions to activate special events, and change the character's state based on player actions.
Scripting in NiceEngine is implemented using Java
A Prefab (Pre-fabricated) is a GameObject that has been created with all its components and properties fully set up and save is as a Prefab for reuse (if necessary). For example, in your Mario game, if you need to create 100 goomba enemies, you shouldn't create 100 individual goombas or copy-paste a single one. Instead, you should create one goomba, save it as a Prefab, and then instantiate 100 instances of it. If any changes need to be made, you can simply modify the Prefab, and all instances will be updated accordingly.
A Spritesheet is a collection of sprites arranged next to each other on a large image. The sprites within a spritesheet are typically used to represent the animation of a character in a game.
Animation can be understood as a sequence of sprites arranged together to create a smooth motion, action, or expression. Animations are often defined using spritesheets.
The NiceEngine working interface consists of 7 windows:
-
GameViewport This window includes two sections, EditorScene and GamePlayingScene. By default, when you open NiceEngine, it will show the EditorScene. If you click the "Play" button, the GamePlayingScene will replace the EditorScene. Details about these two scenes will be explained later. In general, when you are editing your game, you will work with the EditorScene, and when you press Play and the GamePlayingScene appears, that is your actual game being played.
-
Hierarchy: This windown displays the existing Game Objects in the Scene.
-
Inspectors: This window shows the properties and components of the selected Game Object.
-
Assets: This window functions like a mini Explorer, helping you manage game resources such as images, sounds, etc.
-
Console: This window allows for easy debugging during game development.
-
Prefabs: This window displays the available prefabs.
-
Spritesheet: This screen shows the spritesheets.
User can easily rearrang or dock these window into other place in screen as desired. You can customize the interface to suit your preferences.
⬆️ Back to Top
To create a new project, press Ctrl + N (or select File -> New) in the engine. A popup will appear asking you to enter the name of the new project.
To open a different project, you can press Ctrl + O (or select File -> Open) in the engine. A popup will appear. Double-click on the project you want to open.
⬆️ Back to Top
- For an image, simply click on that image in the Assets window, then move the mouse to the desired position in the EditorScene and click the left mouse button to confirm the creation of the Game Object. The same applies to the Spritesheet screen.
- In Prefab window, select the prefab you want to create a Game Object from, right-click, and choose the "Create a child game object" option.
- Press Ctrl+D to duplicate game object is selected.
- In Script, you need to use Constructor of class GameObject
Use can also add sprite to game objectHere is the example, after create new game object, remember to add it to scene to use.
We create a GameObject named "mario" using the findGameObjectWith function and pass MarioMoving.class as a parameter. The result of this function is to find the first GameObject in the list that has a component of MarioMoving. Typically, we should use this statement to retrieve a specific GameObject, such as "Mario" or "HUDController" game objects, because they have only one instance.
⬆️ Back to Top
To create a Component in Script, simply use Construtor as create an instance of a class and set some properties if needed:
In Inspector window, just select Game Object you want to add Component and press "Add component" then select component you want to add
In Script, use removeComponent function
In Game Object in Inspector window, press "X" button
1. Transform
Explanation:
- Position: Stores the position of the game object on the scene.
- Scale: Stores the size of the game object on the scene.
- Rotation: Rotation of the game object in degree value.
- Z-index: A value used for layering game objects. The higher the Z-index value, the more the game object will be displayed in front of objects with lower Z-index values.
2. SpriteRenderer
Explanation:
- Color picker: Represents the color overlay applied to the sprite of the game object.
- Sprite: Refers to the visual representation of the game object.
A StateMachine can be a list of AnimationState và a Default state to show in default when run start (Read more in Script)
⬆️ Back to Top
- AnimationState is a list of Frame, title and a Loop checkbox to determine Animation will loop or not.
- Frame include Sprite và time to show of this Sprite.
⬆️ Back to Top
Include information:
- Velocity: Velocity of object.
- BodyType: The type of body, including Dynamic, Static, Kinematic. The meanings of these BodyType options will be explained in the next section.
- GravityScale: The coefficient of gravity - default value is 1, representing the effect of gravity multiplied by the default gravity scale.
- IsSensor: Can be understood as a sensor. If its value is set to true, it will not contact with other objects in the physics world.
- FixedRotation: If set to true, it fixes the rotation, meaning it cannot rotate along any axis in the physics world.
Box2dCollider represents a collision shape in the form of a box in a two-dimensional space. Include information:
- Offset: The positional difference between the center of the Box2dCollider and the Game Object that contains it.
- Size: The size of the Box2dCollider.
CircleCollider represents a circle collision shape in a two-dimensional space. Include information:
- Offset: The positional difference between the center of the CircleCollider and the Game Object that contains it.
- Radius: The Radius of the CircleCollider.
Capsule2dCollider represents a collision shape in the form of a capsule in a two-dimensional space. Capsule2dCollider is a combination of a Box2dCollider in the middle and two CircleColliders at the top and bottom.
⬆️ Back to Top
To create a script, simply create a new class file, and always remember to extend Component to make it a Component to using it in Game Object:
To add a script to a game object, on the Inspectors screen, select "Add Component", find the name of the script you have created, and choose it:
In code, just add Script same as a Component by using addComponent function:
The start function is called once when the object is created and initialized in the game. When the start function of a game object is called, it goes through all the components and runs the start function of each component.
Similarly, when a component is added to a game object, the start function of that component will also be called. Additionally, when the Engine is first launched, the start function is also called once.
You should set value for variable and clear array, list here to ensure script run correctly.
The update function will be called in every frame while the game is running, specifically when the GamePlayingScene is being executed. The time difference from the previous frame to the current frame is called delta time, which is passed beforehand as the value of the dt parameter in the update function.
The start and update functions are two important functions that are extensively used while writing scripts. Therefore, it is necessary to have a clear understanding of how they work.
This function will be called when this game object starts colliding with another game object in the physics world. It allows you to perform actions or calculations if necessary when a collision occurs. For example, playing a collision sound, creating visual effects, or initiating some event. This function is extensively used while writing scripts.
In the above function, collidingObject is the game object that just collided, Contact is used to store information about this collision based on the parameters of the Jbox2d library, and contactNormal is used to indicate the collision direction.
EndCollision is called when two objects are no longer in contact with each other. You can use this function to perform actions when the collision between objects ends, such as stopping collision sound or ending an event if needed.
This function is called before the actual collision processing takes place. You can use this function to affect how the collision is handled.
This function is called after the actual collision processing has been completed. You can use this function to check the result of the collision and perform actions based on that result.
⬆️ Back to Top
There are three functions used with KeyListener:
- KeyListener.isKeyPressed(keyCode): returns a boolean value indicating whether the keyCode passed in is currently being pressed.
- KeyListener.keyBeginPress(keyCode): returns a boolean value indicating whether the keyCode passed in has just been pressed.
- KeyListener.isKeyRelease(keyCode): returns a boolean value indicating whether the keyCode passed in has just been released.
The keyCode passed in is the keyCode of the GLFW library. To use them, you can do something like this:
There are 5 functions used with MouseListener:
- MouseListener.mouseButtonDown(buttonCode): returns a boolean value indicating whether the buttonCode passed in is currently being pressed.
- MouseListener.mouseBeginPress(buttonCode): returns a boolean value indicating whether the buttonCode passed in has just been pressed.
- MouseListener.isMouseRelease(buttonCode): returns a boolean value indicating whether the buttonCode passed in has just been released.
- MouseListener.getWorld(): returns the position of the mouse on the game screen.
- MouseListener.isDragging(): returns a button value indicating whether the mouse is being dragged or not.
The buttonCode here is usually one of two values: GLFW_MOUSE_BUTTON_LEFT (left mouse button) or GLFW_MOUSE_BUTTON_RIGHT (right mouse button).
⬆️ Back to Top
There are 3 type:
BodyType Static represents static objects in space. These objects do not experience any forces or velocities and do not move under the influence of other objects. This means that a Static object does not change its position or direction of movement during the simulation, unless we manually change it.
BodyType Kinematic represents objects that can move, but do not experience any external forces. You can directly change the position and velocity of a Kinematic object from the source code. Kinematic objects do not react to collision forces from other objects, but can collide and affect other objects.
This type is usually used for objects that you want to precisely control their movement, such as doors or elevators.
BodyType Dynamic represents objects that can move and are affected by forces from the environment and other objects. Dynamic objects have mass and velocity, and move under the influence of forces, such as gravity, collision forces, and external forces. This is the most common type of object used to simulate moving objects in games.
- Static: A static object that does not move and is not affected by external forces.
- Kinematic: An object that can move and actively change its position and velocity, does not experience external forces, but can collide and affect other objects.
- Dynamic: An object that can move and is affected by external forces, such as gravity, collision forces, and forces from other objects.
⬆️ Back to Top
- EditorScene is the screen where you can edit, modify, and design your game.
- GamePlayingScene: is the screen where the game is actually played.
⬆️ Back to Top
Transient variable is a type of variable whose value is created when the Game Engine is initialized. However, this value is not saved as information.
For example, in Mario games, we need to keep track of whether our character Mario is dead or not. We create a boolean variable called isDead. However, this variable is only used when we run the game, and we do not need to save it when saving the information of the Mario GameObject. Therefore, the isDead variable should be transient.
Here is an example of how to use a transient variable:
⬆️ Back to Top
If a game object is marked as serialize, it will be saved in your game project. Entities such as trees, characters, enemies, etc. need to be saved, so we set them to serialize. However, entities such as bullets, smoke, falling leaves, etc. are created during gameplay and do not need to be saved. Therefore, we set these game objects to NoSerialize so that they will be ignored when saving the project.
When you create a game object using a script, it is set to NoSerialize by default. If you want to set it to serialize, remember to call the setSerialize() function for that game object.
For game objects created on the engine interface, such as creating game objects from the assets screen, spritesheet, or prefabs, these game objects are set to Serialize by default.
⬆️ Back to Top
In Prefab window, right-click to a Prefab and choose "Create a child game object"
In Script, use Prefab.createChildFromPrefab("PrefabName")
Ex:
After create this, remember to add it to Scene by Window.getScene().addGameObjectToScene
⬆️ Back to Top
To create a new game object from the spritesheet screen, click on a sprite, then move the mouse to the EditorScene screen and place the game object at the desired position.
To create a new Spritesheet, on the Spritesheet screen, select the "Add new spritesheet" button. Then, the FileDialog screen will appear, double-click on the image you want to create a Spritesheet from and proceed.
The screen for creating a Spritesheet looks like this:
With the following information:
- Sprite width: the width of each sprite
- Sprite height: the height of each sprite
- Nums of sprites: the number of sprites
- Spacing X,Y: the spacing between each sprite.
To edit Spritesheet, right-click in Spritesheet and choose Edit like when Create
To edit Spritesheet, right-click in Spritesheet and choose Remove this spritesheet
⬆️ Back to Top
The ConsoleWindow is a screen created to support debugging.
For example, if you need to make sure that you are pressing the A key, in the update function of your script, do the following:
Add this Script to some Active Object in game. When you click A. Console will show message:
⬆️ Back to Top
Inspectors window displays information about a GameObject, such as its name, tag, and list of components. It also provides operations related to Prefabs and the ability to add a new component to the GameObject.
About the Prefab Option, the following buttons are available:
- Go to ‘prefab name’: navigates to the Prefab of the current GameObject. When you click this button, the Prefabs screen will appear, highlighting the Prefab of the current GameObject.
- Override the prefab: overrides the information, properties, and component list of the GameObject onto its Prefab. The Prefab will be updated with the information of the GameObject (except for name and position).
- Save as a new prefab: creates a new Prefab with the values of the selected GameObject.
Additionally, for Prefabs, there is an "Override all children" button, which overrides all information, properties, and component values of all GameObjects created from this Prefab.
Regarding name and tag, the name is the name of the GameObject, while the tag is used to group GameObjects under a tag for organizational purposes or other purposes. For example, in the Mario game, there are Goombas, Turtles, Fly-Turtles, etc. We can assign them all the same tag "Enemy". This makes it easier to manage and process events.
Below that is the list of components. To view the details of a component, click on the header of that component.
Finally, there is the "Add component" button. When you click this button, a popup will appear showing all available components. Click on the component that you want to add to the GameObject.
⬆️ Back to Top
Hierarchy window displays all the game objects currently in the Scene. Since the number of game objects in a Scene can be quite large, the Engine is divided into multiple tabs, each displaying a list of game objects by their tags.
If you click on a game object in the Hierarchy, the selected game object will appear in the center of the EditorScene window, and information about that game object will also be displayed in the Inspector window.
⬆️ Back to Top
In this section, you will understand the workflow of the Engine according to the following diagram:
There are a few points to note:
- When the Engine is first initialized, data is loaded, and then the start -> editorUpdate functions are executed. At this point, the EditorScene is being run.
- If the user clicks the "Play" button, the game data will be saved, and the Engine will switch to the GamePlayingScene. Then, the game data will be loaded, and the start -> update functions will be executed.
⬆️ Back to Top










































