Creating a simple game scene from scratch.

For the needs of this tutorial we will start from a full scene exported from a DCC package (3ds Max in this case) as FBX.

Note: This tutorial does not cover the project creation and assets conversion, please refer to the corresponding documentation chapter if you wish to learn about these subjects.

  1. Open the Simple Ball Game tutorial project ('Tutorials/01 - Simple Ball Game/Simple Ball Tutorials.ngp'),
  2. Open the game scene ('01 - Simple Ball Game/game.nms').

This the scene you should see on your screen:


The game scene.

You can test this iteration of the game right away by selecting 'Preview...' in the Scene menu (default shortcut: F5).

While minimal this scene already puts a number of advanced concepts to play, namely: realtime shadows, physics, collision response and scripted controls. Since all rendering aspects were setup prior to the scene import by the artist let's directly move to...

1. Physics Setup


There are four gameplay elements here:

  • The game area,
  • The two rackets (one for the player and one for the computer AI),
  • The ball.

Despite its appearance this simple scene exercices the three different physic modes available. Since we do not want to program everything by hand, the ball motion and collision response is fully handled by the physics engine. The gameplay area only needs static collision response while the rackets need dynamic collision response with script controlled motion.


The Physics property panel.
  • In the Scene Explorer select the Ball object and expand the Physics panel in the property view:
    The physics mode is set to "Dynamic", meaning that the physics engine is responsible for controlling both the motion and the collision response of the ball.
  • Now select the Player object:
    The physics mode is set to "Kinematic", this is to inform the physics engine that we will move this otherwise static collision item.
  • Finally select the Mur object:
    The physics mode is set to "Static", meaning a static collision object.
    Note: If a static object is moved its collision will still take place in its original position, that why we have to set the rackets to kinematic.

There, that's all there is to the physics setup for the scene. But another very important aspect of the physics simulation must be configured: collisions.

2. Collision Setup


Reminder: The game.nms scene has this setup already done if you do not wish to spend time on it.

If you preview the scene at this point nothing much happens, the ball falls in space ignoring the boundaries of the game area and everything else remains still. In order for the game objects to interact we need to define one or more collision shapes for the game elements.

  • Select the Ball object: In the Physics panel find the Collision Shape group.

    The collision shape group.

    Click on the Add... button and select Sphere to create a new sphere collision shape for the ball item.
    You can then edit this particular shape properties in the fields below the collision shape list but since everything is okay as default, leave it that way for now.

  • Select a racket: Create a box collision shape of size 2x1x0.4m.
  • Select the other racket: Repeat the same operation.
    Note: Or you can copy/paste the racket you just made, rename it and move it to the correct position instead.
  • Select the game mur object: Create 4 box collision shapes. For the left and right shapes the size is 0.5x0.5x15m, the top and bottom one 17x0.5x0.5m. Position for the left box {-8.25,0.25,0}, the right box {8.25,0.25,0}, the top box {0,0.25,7.25} and the bottom box {0,0.25,-7.25}.
    Note: You can use the viewport transformation gizmo (move, rotate, scale) by toggling on the Edit in View button, right below the collision shape list.

Previewing the scene now won't make much difference since there is no ground collision for the game area.
This will be addressed using a script later on.

Since the ball motion is completely controlled by the physics engine and we want perfect rebound off the surfaces it collides with (perfect elasticity where the speed does not change, and without friction) we need to change some attributes of the collision shapes we just created:

  • Select the mur object: Select all of its collision shape (click on the first shape then on the last while maintaining the shift key down) and change their static and dynamic friction to 0 and their restitution to 1.
  • Select the rackets: Set their collision shape static and dynamic friction to 1 and their restitution to 1.
  • Select the ball: Set its collision shape restitution to 1.

The physics and collision setup are complete for the scene, in fact they are complete for the whole project.

3. Script Setup



1. Scripting the Ball


Let's start by writing the script for the ball object. The ball should, among other things:

  1. Stay in the game area,
  2. Collide with the area walls and the rackets,
  3. Stay in the XZ plane (ie. no vertical motion in 3d space, the scene is seen from top toward bottom),
  4. It should never travel parallel to the X axis (or else it would never reach the rackets).

Constraints 1 and 2 are already handled for us by the physics engine. In order to enforce the remaining constraint, let's add a script to the ball. The easiest way to do so is by using the New Script Wizard:

  • Select the ball object: In the property view, unfold the Script panel.

    The item script property panel.

    Click on the Add... button and select New... in the drop-down menu.

  • The New Script Wizard is already filled with most of the informations it requires based on the current item selection and should look like this.


    The new script wizard.

    Assuming this is what you see, press OK to create the script. The editor opens the new script right away for edition. The wizard has created a default item script with the basic declarations for the class you specified. It consists of a class declaration with two methods OnSetup() and OnUpdate.

The third constraint (stay in the XZ plane) is easily satisfied by filtering out the Y component on all physics calculation for the ball item. The ItemPhysicSetLinearFactor(Item, Vector) function allow us to do just that, so in the OnSetup(Item) member of the Ball class let's add:

function    OnSetup(item)
{
        // Restrict linear motion to the XZ plane.
        ItemPhysicSetLinearFactor(item, Vector(1, 0, 1))
}

Note: The OnSetup(Item) function of the Ball class takes a single parameter of type Item. This parameter is passed by the framework to your script when an item utilizing this class is setup. In this case when the Ball object of the scene will be setup it will passed to this function as a parameter. This way you may assign this script to several items and have it affect each of them separately. This is an important concept to understand, the script you write is not directly bound to a single item.

The last constraint (no motion purely on the X axis) is done in the OnUpdate function. By retrieving the item linear velocity, making sure that its Z component is not null and setting the result back as the current item linear velocity.

function    OnPhysicStep(item, dt)
{
        // Get the item linear velocity.
        local   v = ItemGetLinearVelocity(item)
 
        // Forbid purely horizontal motion.
        if (v.z >= 0 && v.z < 1.0)               v.z = 10.0
        if (v.z <= 0 && v.z > -1.0) v.z = -10.0
 
        // Clamp and set the final velocity.
        ItemSetLinearVelocity(item, v.ClampMagnitude(8))
}

You will notice the velocity vector magnitude (ie. length) is limited to 8 units so that the ball does not reach an unplayable speed.

2. Scripting the Computer AI


In the same way the ball script was created, create a script for the Computer object. Like the ball script, only the OnSetup and OnUpdate functions are needed at this point.

This very simple AI will be unbeatable as it will constantly perfectly align itself with ball position on the X axis. To do so, we first need to retrieve the ball item from the AI script by querying the scene for it. There are different ways to get access to the scene from scripts:

  1. Being in a script assigned to the scene we are interested in (this is not the case),
  2. Having access to a valid item known to belong to the scene we are interested in (this is the case),
  3. Through the globally defined g_scene variable.

Of these three possibilities only the last two apply here. Using g_scene is fairly safe except for the most corner cases. The other option would simply require calling ItemGetScene(Item) and passing it the racket item (which is sent to us by the framework on the racket item update).

So, let's just write the OnSetup function as:

function       OnSetup(item)
{
        // Query the scene for the ball item.
        ball_item = SceneFindItem(g_scene, "Ball")
}

In order to be able to access the ball_item variable from any class member we must declare it in the class scope:

class  Computer
{
        ball_item       =  0
 
        // ...
}

All is left to do is to align the racket item to the ball item on each frame update. This is done in the OnUpdate function:

function    OnUpdate(item)
{
        // Get the ball item and this item position.
        local           ball_position = ItemGetPosition(ball_item),
                        self_position = ItemGetPosition(item)
 
        // Align item with the ball on the X axis.
        self_position.x = pos.x
 
        // Make sure the racket stays in the game area.
        if (self_position.x < -7) self_position.x = -7
        if (self_position.x > 7)       self_position.x = 7
 
        // Set this item position back.
        ItemSetPosition(item, self_position)
}

Remember: The script you write is not directly connected to the item you write it for. The framework passes the current item as a parameter of the function it calls so that your script may work with any item you assign it to. Therefore you should not use explicit references to an item such as returned by SceneFindItem unless you really need to access that specific item.

3. Scripting the Player


The only remaining thing to do at this point is to script the player racket. You should know by able to understand most of the player script by yourself so here it is in all its glory:

class  Player
{
        function        OnUpdate(item)
        {
                KeyboardUpdate();
 
                // Get item position.
                local   position = ItemGetPosition(item)
 
                // Test the left and right arrow keyboard keys and move the racket accordingly.
                if (KeyboardSeekFunction(DeviceKeyPress, KeyLeftArrow))
                        position.x -= Mtr(16) * g_dt_frame
                if (KeyboardSeekFunction(DeviceKeyPress, KeyRightArrow))
                        position.x += Mtr(16) * g_dt_frame
 
                // Keep the racket in the game area.
                if (position.x < -7)      position.x = -7
                if (position.x > 7)    position.x = 7
 
                // Set the update item position back.
                ItemSetPosition(item, position)
        }
}

The only thing worth noticing is the usage of the global g_dt_frame. This variable contains the last update duration in seconds, so if you are running at a constant 60 images per second, its value will be 1 / 60. By multiplying the amount of meters the racket moves by this value we make the motion speed independent of the display refresh rate. If the display refresh is twice as fast, g_dt_frame value is halved so that in the same time span two updates will move by the same amount as a single update.

4. Conclusion


By previewing the scene you can now see that the game is starting to take shape. Of course with an unbeatable AI it is not much fun but we are going to address that issue in the next part of this tutorial.