Oct 23, 2021Kuina-chan

This is the tutorial 3 to learn the basic features of Kuin Programming Language for exe execution environment. This time, let's make a simple mini-game.

1Create A Mini-game

Now let's write a simple program. This is a mini-game where you have to jump to dodge incoming enemies.
Insert the code for the "main And A draw Control" from the snippet feature introduced in the previous section. It is shown in Figure 1-1.
  1. var wndMain: wnd@Wnd
  2. var drawMain: wnd@Draw
  4. func main()
  5.   do @wndMain :: wnd@makeWnd(null, %aspect, 1600, 900, "Title")
  6.   do @drawMain :: wnd@makeDraw(@wndMain, 0, 0, 1600, 900, %scale, %scale, false)
  8.   while(wnd@act())
  9.     do draw@render(60)
  10.   end while
  11. end func
Figure 1-1: kui_jump1.kn
Roughly, the program is similar to the window creation we did in the previous section, with the window being created on line 5.
In line 6, the wnd@makeDraw function is used to create a draw control. The draw control is a control that can draw advanced graphics.
The draw@render function in line 9 transfers the drawing result to the screen. If a value of 60 is passed, it will wait for 60FPS (60 draws per second).
What you draw in the draw control is drawn in an invisible area called the back buffer. Therefore, to transfer it to the visible screen, you need to call the draw@render function at the end of the process. By drawing to the back buffer once, the status during the drawing process does not appear on the screen, preventing the screen from flickering.
When run, it will look like Figure 1-2.
Kui-jump (1)
Figure 1-2: Kui-jump (1)
The screen is completely black because nothing has been drawn yet.

2Defining a Coordinate Class

This time, I will define a class to handle the coordinates. A class is a new type created by lumping together a number of variables and functions.
Add the program shown in Figure 2-1.
  1. class Pos()
  2.   +var x: float
  3.   +var y: float
  4.   +var veloX: float
  5.   +var veloY: float
  6. end class
  8. var player: @Pos
  9. var enemy: @Pos
  10. var wndMain: wnd@Wnd
  11. var drawMain: wnd@Draw
  13. func main()
  14.   do @wndMain :: wnd@makeWnd(null, %aspect, 1600, 900, "Title")
  15.   do @drawMain :: wnd@makeDraw(@wndMain, 0, 0, 1600, 900, %scale, %scale, false)
  17.   while(wnd@act())
  18.     do draw@render(60)
  19.   end while
  20. end func
Figure 2-1: kui_jump2.kn
In lines 8-9, I have created two variables player and enemy of type @Pos, which I have defined in lines 1-6. The part between "class Class Name()" and "end class" is the class, which is a new type with some variables and functions.
In this program, I have four variables x, y, veloX, and veloY in the class. The type of each variable, float, is a type that can contain decimals.

3Show Player And Enemy

Now let's show the players and the enemy. Assign the coordinates to the variables of the player @player and the enemy @enemy, and draw based on the coordinate information (Figure 3-1).
  1. class Pos()
  2.   +var x: float
  3.   +var y: float
  4.   +var veloX: float
  5.   +var veloY: float
  6. end class
  8. var player: @Pos
  9. var enemy: @Pos
  10. var wndMain: wnd@Wnd
  11. var drawMain: wnd@Draw
  13. func main()
  14.   do @wndMain :: wnd@makeWnd(null, %aspect, 1600, 900, "Title")
  15.   do @drawMain :: wnd@makeDraw(@wndMain, 0, 0, 1600, 900, %scale, %scale, false)
  17.   do @player :: #@Pos
  18.   do @player.x :: 400.0
  19.   do @player.y :: 800.0
  20.   do @enemy :: #@Pos
  21.   do @enemy.x :: 1500.0
  22.   do @enemy.y :: 800.0
  23.   do @enemy.veloX :: -10.0
  25.   while(wnd@act())
  26.     do draw2d@circle(@player.x, @player.y, 50.0, 50.0, 0xFFFFFF00)
  27.     do draw2d@circle(@enemy.x, @enemy.y, 40.0, 40.0, 0xFFFF0000)
  28.     do draw@render(60)
  29.   end while
  30. end func
Figure 3-1: kui_jump3.kn
In lines 17 to 23, I set the values for the variables @player and @enemy.
In lines 17 and 20, "#@Pos" is assigned, and by writing "#Class Name", the entity of that class is created. Variables of class types must have their instances created before they are used.
By writing ".x" as in line 18, the variable x held by the class variable can be read and written. This x is a variable that the @Pos class has, which was defined in the 2nd line.
In this program, I will put the X and Y coordinate values in .x and .y respectively, and the horizontal and vertical velocities in .veloX and .veloY respectively.
In lines 26 and 27, using the draw2d@circle function, I am drawing the player and enemies in the form of a circle based on their location information.
The arguments in parentheses of the draw2d@circle function are the X coordinate of the center of the circle, the Y coordinate of the center of the circle, the radius X, the radius Y, and the color, respectively. Colors are specified in hexadecimal and written (in additive color mixing) as "0xOpaquenessRedGreenBlue". For example, this "0xFFFFFF00" represents yellow, and "0xFFFF0000" represents red.
When run, it will look like Figure 3-2.
Kui-jump (2)
Figure 3-2: Kui-jump (2)

4Enemy And Player Movement

From here on, only the part between "while(wnd@act())" and "end while" will be rewritten, so only that part of the program will be excerpted and posted. Add the enemy movement process to it (Figure 4-1).
  1.   while(wnd@act())
  2.     do @enemy.x :+ @enemy.veloX
  3.     if(@enemy.x < -40.0)
  4.       do @enemy.x :: 1640.0
  5.       do @enemy.veloX :: -lib@rndFloat(6.0, 50.0)
  6.     end if
  7.     do draw2d@circle(@player.x, @player.y, 50.0, 50.0, 0xFFFFFF00)
  8.     do draw2d@circle(@enemy.x, @enemy.y, 40.0, 40.0, 0xFFFF0000)
  9.     do draw@render(60)
  10.   end while
Figure 4-1: kui_jump4.kn
In the 2nd line I have added velocity X @enemy.veloX to position X @enemy.x. The term "@enemy.x :+ @enemy.veloX" is shorthand for "@enemy.x :: @enemy.x + @enemy.veloX", and the sum of @enemy.x and @enemy.veloX is assigned to @enemy.x. By repeating this process, the enemy will gradually move to the side.
Lines 3 to 6 are the process to return the enemy to the right edge of the screen when it has gone to the left edge. When you write "if(value)", if the value is true, the line between "end if" will be executed in order, and then the process will proceed to the next line, and if the value is false, the process will skip to "end if" and proceed to the next line.
In other words, the third line, "if(@enemy.x < -40.0)" will perform the process sandwiched between "end if" when the enemy's X coordinate becomes less than the screen's leftmost coordinate -40.0. In the intervening area, the enemy's X coordinate @enemy.x is moved to the right edge of the screen 1640.0 in line 4, and the velocity @enemy.veloX is changed to a random number in the range of -6.0 to -50.0 in line 5.
lib@rndFloat is a function that returns a random fractional number, with the arguments in parentheses being minimum random number and maximum random number in that order.
If you run this program, you will see that the enemy will come at the player again and again, changing speed.

5Player Jumping

Now, the next step is to make it possible for the player to jump to avoid enemies (Figure 5-1).
  1.   while(wnd@act())
  2.     do @enemy.x :+ @enemy.veloX
  3.     if(@enemy.x < -40.0)
  4.       do @enemy.x :: 1640.0
  5.       do @enemy.veloX :: -lib@rndFloat(6.0, 50.0)
  6.     end if
  7.     do @player.veloY :+ 1.0
  8.     do @player.y :+ @player.veloY
  9.     if(@player.y > 800.0)
  10.       do @player.y :: 800.0
  11.       do @player.veloY :: 0.0
  12.     end if
  13.     if(input@pad(0, %a) = 1 & @player.y = 800.0)
  14.       do @player.veloY :: -20.0
  15.     end if
  16.     do draw2d@circle(@player.x, @player.y, 50.0, 50.0, 0xFFFFFF00)
  17.     do draw2d@circle(@enemy.x, @enemy.y, 40.0, 40.0, 0xFFFF0000)
  18.     do draw@render(60)
  19.   end while
Figure 5-1: kui_jump5.kn
In line 7 I have added a downward acceleration of 1.0 to the player's velocity Y @player.veloY. This is the equivalent of gravity.
In line 8 I have added the velocity Y @player.veloY to the position Y @player.y. At this stage, gravity is applied to the player, and the player falls downward more and more.
Lines 9 to 12 are the process of grounding the player so that it does not penetrate below the ground. If the player is below the ground (800.0), the position Y is corrected to the ground position 800.0 and the speed Y is reset to 0.0.
Lines 13 to 15 are the jump process. "input@pad(0, %a)" will return 0 if the A button on the gamepad is not pressed, 1 the moment it is pressed, and 2 or more if it is kept pressed. The arguments in parentheses for the input@pad function are, in order, gamepad number and gamepad button.
The "&" in line 13 corresponds to "and" in English, and is true if both the left and right values are true, and false otherwise. In other words, in line 13, if the player is grounded (@player.y = 800.0) at the moment the A button on the gamepad is pressed, then in line 14, the velocity Y @player.veloY is set to -20.0 to make the player move in an upward throwing jump.
The A button on the gamepad is assigned to the Z key and space key on the keyboard by default, so the game can be controlled with the keyboard as well.
When run, it will look like Figure 5-2.
Kui-jump (3)
Figure 5-2: Kui-jump (3)


Finally, I will write the collision detection (Figure 5-3).
  1. while(wnd@act())
  2.   if(lib@dist(@enemy.x, @enemy.y, @player.x, @player.y) < 50.0 + 40.0)
  3.     do draw2d@rect(0.0, 0.0, 1600.0, 900.0, 0xFFCC3333)
  4.     do draw2d@circle(@player.x, @player.y, 50.0, 50.0, 0xFFFFFF00)
  5.     do draw2d@circle(@enemy.x, @enemy.y, 40.0, 40.0, 0xFFFF0000)
  6.     do draw@render(0)
  7.     do lib@sleep(800)
  8.     do @wndMain.close()
  9.   end if
  10.   do @enemy.x :+ @enemy.veloX
  11.   if(@enemy.x < -40.0)
  12.     do @enemy.x :: 1640.0
  13.     do @enemy.veloX :: -lib@rndFloat(6.0, 50.0)
  14.   end if
  15.   do @player.veloY :+ 1.0
  16.   do @player.y :+ @player.veloY
  17.   if(@player.y > 800.0)
  18.     do @player.y :: 800.0
  19.     do @player.veloY :: 0.0
  20.   end if
  21.   if(input@pad(0, %a) = 1 & @player.y = 800.0)
  22.     do @player.veloY :: -20.0
  23.   end if
  24.   do draw2d@circle(@player.x, @player.y, 50.0, 50.0, 0xFFFFFF00)
  25.   do draw2d@circle(@enemy.x, @enemy.y, 40.0, 40.0, 0xFFFF0000)
  26.   do draw@render(60)
  27. end while
Figure 5-3: kui_jump6.kn
The lib@dist function in the 2nd line is a function to calculate the distance between two points. Calculate the distance between the enemy and the player, and if it is less than the sum of the radii of the enemy and the player, then they are too close to collide. The arguments in parentheses of the lib@dist function are, in order, X-coordinate of the first point, Y-coordinate of the first point, X-coordinate of the second point, and Y-coordinate of the second point, and the function returns the value of the distance between the two points.
Lines 3 to 8 are for processing when a collision occurs. The background is filled in red with the draw2d@rect function in line 3, the player and the enemy are drawn in lines 4 to 6, and after waiting 800 milliseconds (0.8 seconds) with the lib@sleep function in line 7, the window is closed and terminated with the @wndMain.close method in line 8.
It would be more helpful to bring up the game over screen and return to the title screen.
The arguments in parentheses of the draw2d@rect function are, in order, left coordinate, top coordinate, width, height, and color. Since the size of the draw control was 1600x900, the 3rd line "do draw2d@rect(0.0, 0.0, 1600.0, 900.0, 0xFFCC3333)" will fill the entire screen.
When run, it will look like Figure 5-4.
Kui-jump (4)
Figure 5-4: Kui-jump (4)
That's it, the mini-game is complete.
The game I made this time turned out to be simple looking, but you can get a full-fledged look by just using images to draw the characters instead of circles. Next time, I will create an action game using images.