Making a simple 2D game in Chimera - Wantcha/Chimera GitHub Wiki
How to work with Chimera
Despite its still relatively primitive stage, Chimera is a fully functional game engine, capable of building and exporting simple games. I will show what the general workflow is when creating a game in Chimera by showing you the steps I took when creating the example project included in the source code, Super Cool Fish Game (with bombs), which definitely didn't have me rewrite a third of the engine because it made realize how broken the code was. It's a simple 2D game, where you control a fish by moving the mouse on the screen and have to dodge bombs coming towards you at an increasingly faster rate for 60 seconds. You can also dash to escape tight spots.
1. Setting up the project
When opening Chimera, you will be prompted to create a new project or open an existing one. We're gonna select New Project and select an empty folder where our project will be set up.

After that, we are now free to use Chimera and are directed into an empty scene. We will go to File -> Save Scene as.... We will go into our assets folder and save our scene with the name "MainScene.chimera".
2. Creating Entities and attaching textures
Let's create some objects. Right-click into the Hierarchy tab and goto Create->Sprite. Holy scrap, we have a white square!

I am going to click on the square and look at its Components in the Properties window. There, I am going to click on the square next to the Texture section, navigate to where in the assets folder I put my background image, click on it and press Select. Lo and behold, we have an image in the scene! I am going to go ahead and rename the entity in the properties to "Background" and try holding Alt and dragging left-click in the scene to orbit around.
Next, I am going to create a Camera entity in the scene, so we can actually have things rendering in the game because right now our Game view is completely dark. After creating the camera, we drag it back on the Z-axis to about 8, so it can actually see the image and we set its Projection Type to Orthographic (since we are making a 2D game).
After that, we'll resize the background to fit the entire space of the camera and move it back on the Z-axis to about -10, so it renders behind everything else. We'll go on and also add sprite entities for the fish and the bomb and space them a bit in the scene.

3. Our first script
We are going to go into the assets folder and create a new folder called "Scripts". In the scripts folder, we're going to create a new text document and call it "Bomb.lua" (make sure the extension is also changed, so the file isn't "Bomb.lua.txt"). We want to make the bomb move to the right of the screen at a constant speed.
At the top of the script, we'll make a variable local speed and default it at 3.0 (it's a good practice to make all variables local, unless they specifically need to be global).
Below, we'll declare the Update function, which will be run once every frame. Don't forget the ts (delta time/timestep) parameter. Inside of it, we'll take the Transform component of the current entity and make it equal to a new Vector3, where the X position has the value speed * ts (we multiply this value by ts to make sure the bomb will move at a constant speed, regardless of framerate; if you want more information on this, google "delta time in games"). Now the bomb will always move towards the right side of the screen.
We also want the bomb to destroy itself once it passes the edge of the screen since we don't need it anymore, so let's check that scenario. At the top of the script, I'll declare a cameraEntity variable and leave it empty. Below, I'll declare the Init function, which will be executed once at the start of the entity's creation. There, I'll use the GetEntityByName function to get the "Camera" entity.

In Update, I'll get the Camera Component of that entity and store it in a camera variable, declared within the function. What we want is the X position in world space of the left edge of the screen. I am going to use Camera Component's ScreenToWorld function to convert the Vector2(0, 0) (which is the bottom-left corner of the screen in window space) and store its X position in another variable for clarity, since we only need that component of the vector.
After that, we simply compare our current X position to the value we've just converted and if it's less than that, it means we passed the edge and we can destroy the current entity.

I've also gone and added a rotationSpeed variable that we can use to make the bombs also rotate when moving. We can go ahead and save this, go in the editor and attach a Lua Script Component to the bomb and specify this script from the Properties, similar to how we did for the textures. Now, if we hit Play at the top of the screen to enter Play Mode, we should see the bomb slowly moving to the right and disappearing (make sure the bomb is placed within the Game view bounds before). Exiting Play Mode will revert all changes made during this mode, so we don't have to worry about creating the bomb again.
4. Creating Entity Wraps and making bombs spawn
Now we want to create a system that spawns bombs at the right side of the screen using a time interval. Let's create a new script and call it "BombSpawner.lua". In the scene, we'll create an empty entity, call it "GameManager" and attach this script to it. Next, I'll take the Bomb entity and attach a Circle Collider 2D Component to it and edit it in the Properties to fit the shape of the bomb and also check the "Is Sensor" property (I'll explain why in a bit). It won't do much now, but we'll set it up for later. Next, I'll drag and drop the Bomb from the Scene Hierarchy to the Project Manager tab. This will create the entity wrap "Bomb.wrap" that we can use in script or in the editor to create more of this predefined entity.
Just like in the last script, we'll hold a variable for our Camera entity and initialize it in the Init() function. Next, we'll create a new function and call it "SpawnBomb()". In SpawnBomb(), I'll spawn the Wrap using the SpawnWrap() function and make sure I insert the correct path as the argument ( Paths need to be relative to the assets folder and start with a backslash. Also, because we are working on windows and the source codes works with the operating system's weird formatting, we need to type a double backslash for each backslash, so if we want "\Bomb.wrap" we have to write "\\Bomb.wrap" ). We'll retain the value returned by this function in a variable so we can have a reference to the entity that we've just spawned. Using the Camera Component, we'll get the world position of the right side of the screen using once again the ScreenToWorld function. We'll then set the X position of the newly spawned to the converted value add a small offset so it spawns outside the screen and not right on the edge. I'll also make the Z coordinate to be -0.5, just so the bombs render behind the fish.

We'll go ahead and declare 2 variables at the top of the script, time which will be the time in secomds since the last bomb spawned, and timeToSpawnNextBomb, which will be the time in seconds which should be waited until the next bomb spawns. In Update, I'll increment time by ts, so it increases based on real time. We then go and check if time has surpassed timeToSpawnNextBomb. If it has, we reset time to 0 and call the SpawnBomb() function. We also want bombs to spawn faster and faster the more time passes, so we'll check if timeToSpawnNextBomb is above a certain threshold (so we make sure it doesn't become too small) and if it is, we decrease it by a small given value.

4. Making the fish move and dash
We'll take the Fish entity and attach a Circle Collider 2D Component on it as well as a Rigid Body 2D Component (we need at least one of 2 colliding entities to have rigid body 2D in order for the collision to register properly). We'll take the collider and adjust it properly and set the gravity scale of the Rigid Body 2D to 0 since we don't want the fish to be falling.
Let's create a new script and call it "MoveTowardsMouse.lua". I'll declare a speed variable at the top of the scrip and default it to 2.8, as well as declare a variable for the cameraEntity (for later) and dashSpeed, dashDuration, dashTime, dashCoolDown and an isDashing defaulted to false. I set up some default values for those dash parameters and we'll take care of them in a bit.

We want the fish to move towards the mouse position on the screen. We get the mousePosition using Input.GetMousePosition() and store into 2 variables and convert them into world space using the ScreenToWorld function. We also convert the (0, 0) and (cameraWidth, cameraHeight) Vector2's, to get the world space bounds of the game, so we can use them later to restrict the movement of the player within the screen.
Then, we check if the distance between the mouse cursor and the player is higher than a given threshold. This is just so we don't move the fish at all times, because otherwise, we may get jittery and glitchy movement if the 2 positions are the same. If this condition is true, we use the Vec3.RotateTowards utility function which gives us the angles in radians between 2 vectors. We add that value to the current rotation, and now the fish is always looking at the mouse! To also make it move, we just add the forward vector of the fish multiplied by the speed and ts to the current position and now the fish will always move in the direction it is facing.

For the dash, let's explain what the variables we declared mean. dashTime is the time passed since the last dash, dashSpeed is the speed of the dash, dashDuration is the duration in seconds of the "dashing" state, dashCoolDown is the time in seconds that needs to be waited until the next available dash and isDashing is just a boolean showing if the player is in the "dashing state". Let's start implementing this.
We'll start by incrementing dashTime by ts, so it increases by the actual time. Next, let's check if the user presses the Space key and if the dashTime has surpassed the dashCoolDown. If both are true, then we initialize the "dashing state", by marking isDashing as true and resetting dashTime to 0.
Below, we'll go ahead and write the logic for the "dash state". If isDashing is marked as true, we'll increment our Transform position by the Forward direction multiplied by the dashSpeed times ts. In this IF statement, we'll also check if the dashTime has grown bigger than the dashDuration (we are repurposing dashTime to count the amount of seconds since the player started dashing) and if so, we'll reset dashTime to 0 and isDashing to false. We'll also encapsulate the previously written move logic into an ELSE statement, so we only run that part of the code when we are NOT dashing.

We also want to limit the position of the player within the bounds of the game. Now that we have the bounds of the screen, we can easily do that by checking if the position of the player exceeds those bounds and override the values if necessary.

Attach this script to the character and play around to see how it feels!
5. It's the final countdown (and how to implement random distribution)
The goal of the game is to survive 60 seconds, but right now we have no way of telling the time. We'll make another script called "Countdown.lua" and attach it to the Game Manager. In it, we'll declare a totalTime variable and default it to 60 and a timeLeft variable which we will default to totalTime (you will see why we need 2 of them in part 7). We're also going to declare 2 bool variables, hasWon and hasLost, default them to false, and not mark them as local. We need to leave this variables as global, because we are going to access them later on from other scripts.
In the Update function, we'll check if both hasWon and hasLost are false, since we don't want to keep counting if the game is over. In the IF statement, we'll decrement timeLeft by ts so the value goes down with time and then check if timeLeft is less than or equal to 0. If it is, it means we have successfully survived 60 seconds and we can mark hasWon as true.

Right now we have a bit of a mishap in our game. All the bombs are being spawned at the same Y value, which isn't very interesting or challenging. What we need is for them spawn at random Y positions. Let's go back into our Bomb.lua script.
Chimera doesn't have built-in random functions at the moment, so we'll have to resort to using Lua's random functions, which are a bit... tricky to set up. In order to set up the Random Number Generator in a chunk of code, we have to use these 2 lines of code.

Lua also doesn't have a built-in function for generating random float values between 2 given numbers, so we'll have to make our own. Just copy and paste this function into your code if you don't want to do too much research into how Lua RNG works.

What we want is to randomize the Y value of the bomb once it is spawned in the scene. First, we need to get the minimum and maximum values that we will randomize between. The minimum value will be the bottom of the screen and the maximum will be the top, so we have to use the ScreenToWorld function once again! After we get the values, we'll use the RandomFloat function and assign its result in the Transform position.

6. Checking for collisions
We want the game to end if the player touches a bomb, so we need to check for collisions. When we mark a collider as "Sensor", we're telling it we don't any actual physical interaction between that collider and the environment, but we still want it to register the collisions. We marked the Bombs as Sensors because we don't want to risk having them collide with each other, and we don't need them to push the player around. Because of that, we're going to use the SensorEnter2D(otherCollider) function, which will be called every time the current entity starts colliding with a Sensor, giving us information about the other collider we've made contact with. We're going to add this function to the MoveTowardsMouse.lua script.
At the top of the script, I'll declare the game manager entity variable and initialize it properly in the Init() function with the GetEntityByName. Then, in the SensorEnter2D function, we want to get the "Countdown" script from the game manager, so we'll use the GetLuaScriptComponent(1) function (since it's the second script on the entity, we put index 1 as the parameter) and store it in a countDown variable. Then, getting global variables or functions from it is just a matter of doing countDown.variableName, or countDown.ExecuteFunction(). We'll check this to see if both hasWon and hasLost are false and if they are, we can mark hasLost as true.

Using this logic, I've also gone ahead and encapsulated all the movement code of the fish and bombs and the bomb spawner timer in IF statements that check whether the hasLost variable is false.

7. Adding menus and UI
We'd like to have a bit of an interface telling the user how much time they have left. We're going to add a new Sprite and reshape it as a rectangle, bring it to the front so it renders in front of everything, and parent it to the Camera. I'll rename it to TimeLeftBar. I'll do the same for a sprite with some text on it I made in Photoshop and place it right above the time bar.
In Countdown.lua, I'll retain the timebar entity in a variable called progressBar. Then, in Update I'll set the X scale of the progressBar to the default scale multiplied by the division between the timeLeft and totalTime. This will shrink the bar more and more as time passes, so we have a visual indication of the time left.

In the scene, we'll go ahead and create a new sprite and add my Game Over texture to it to make a panel. we'll also parent it to the camera and disable it (untick the checkbox next to the name in the Properties tab). In the MoveTowardsMouse.lua script, we'll make a variable for gameOverPanel and store get the panel by name in it, then, in the SensorEnter2D function, we'll enable it when the game is marked as lost.

8. Making the game more interesting
I feel like the game is a bit too easy and predictable right now, so let's work on that. Let's make the size and the speed of the bombs variable. To the Bomb script!
I declared a minSpeed and a maxSpeed that I used to initialize the speed value inside the Init using the RandomFloat function. I did the same thing for the size, but I just hardcoded some values because I am lazy. I also made sure to also increase the radius of the circle collider, because the size of the collider won't automatically scale with the object.

I also would like the bombs to spawn more towards the vertical position of the player to make the game even more difficult. I got a reference to the Character entity and took its Y position. I won't go in-depth and explain the math behind this formula, but this basically moves the bomb a bit closer to the Y position of the Player, while still making it feel a bit random.


9. Setting up the scenes, finishing up, and exporting the game
Let's make the main and victory menus. I'll go File -> New Scene... and then save it as "MainMenu.chimera". We'll add a camera and a sprite with my main menu art. Then, I'll go into Project -> Scene Manager and press on Add Current Scene. The Scene Manager holds all the scenes available in the game and the first one from the list will be the one being open by default when you start the actual game, once you export it. We'll then go back to the Main Scene and add it to that list and repeat the process for another scene, which we'll call "Win.chimera" with its own texture and camera.

Next, I am going to go into the Countdown script and make the game able to switch levels. If the countdown reaches 0 and we win, we use the SceneManager.LoadScene function to load the scene with index 2 (the win scene) and I am also going to add an ELSE statement that checks if the user pressed the Enter key and reloads the main game scene if that is the case. This so that when we lose the game, we can press Enter to restart and try again.

I am also going to make a MainMenu.lua script and attach it to a random object in the Main Menu scene that simply checks for input and loads the scene index 1 if that input is received.
The game should be done now! You can go in the Main Menu scene and play it out. To export it into its own game, we can go to Project -> Build and select a different directory where the game should be exported to. After that, just go to that directory and execute the .exe.
That's it! We made a game in Chimera!