• # Learning How to write a 2D UFO game using the Orx Portable Game Engine - Part 4

General and Gameplay Programming

# Creating Pickup Objects

This is part 4 of a series on creating a game with the Orx Portable Game Engine. Part 1 is here, and part 3 is here.

In our game, the player will be required to collect objects scattered around the playfield with the ufo.

When the ufo collides with one, the object will disappear, giving the impression that it has been picked up.

Begin by creating a config section for the graphic, and then the pickup object:

[PickupGraphic]
Texture = pickup.png
Pivot   = center

[PickupObject]
Graphic = PickupGraphic

The graphic will use the image pickup.png which is located in the project's data/object folder.

It will also be pivoted in the center which will be handy for a rotation effect later.

Finally, the pickup object uses the pickup graphic. Nice and easy.

Our game will have eight pickup objects. We need a simple way to have eight of these objects in various places.

We will employ a nice trick to handle this. We will make an empty object, called PickupObjects which will hold eight copies of the pickup object as child objects.

That way, wherever the parent is moved, the children move with it.

[PickupObjects]
ChildList = PickupObject1 # PickupObject2 # PickupObject3 # PickupObject4 # PickupObject5 # PickupObject6 # PickupObject7 # PickupObject8
Position  = (-400, -300, -0.1)

This object will have no graphic. That's ok. It can still act like any other object.

Notice the position. It is being positioned in the top left hand corner of the screen. All of the child objects PickupObject1 to PickupObject8 will be positioned relative to the parent in the top left corner.

Now to create the actual children. We'll use the inheritance trick again, and just use PickupObject as a template:

[PickupObject1@PickupObject]
Position = (370, 70, -0.1)

[PickupObject2@PickupObject]
Position = (210, 140, -0.1)

[PickupObject3@PickupObject]
Position = (115, 295, -0.1)

[PickupObject4@PickupObject]
Position = (215, 445, -0.1)

[PickupObject5@PickupObject]
Position = (400, 510, -0.1)

[PickupObject6@PickupObject]
Position = (550, 420, -0.1)

[PickupObject7@PickupObject]
Position = (660, 290, -0.1)

[PickupObject8@PickupObject]
Position = (550, 150, -0.1)

Each of the PickupObject* objects uses the properties defined in PickupObject. And the only difference between them are their Position properties.

The last thing to do is to create an instance of PickupObjects in code in the Init() function:

orxObject_CreateFromConfig("PickupObjects");

Compile and Run.

Eight pickup objects should appear on screen. Looking good.

It would look good if the pickups rotated slowly on screen, just to make them more interesting. This is very easy to achieve in Orx using FX.

FX can also be defined in config.

FX allows you to affect an object's position, colour, rotation, scaling, etc, even sound can be affected by FX.

Change the PickupObject by adding a FXList property:

[PickupObject]
Graphic = PickupGraphic
FXList  = SlowRotateFX

Clearly being an FXList you can have many types of FX placed on an object at the same time. We will only have one.

An FX is a collection of FX Slots. FX Slots are the actual effects themselves. Confused? Let's work through it. First, the FX:

[SlowRotateFX]
SlotList = SlowRotateFXSlot
Loop     = true

This simply means, use some effect called SlowRotateFXSlot, and when it is done, do it again in a loop.

Next the slot (or effect):

[SlowRotateFXSlot]
Type       = rotation
StartTime  = 0
EndTime    = 10
Curve      = linear
StartValue = 0
EndValue   = 360

That's a few properties. First, the Type, which is a rotation FX.

The total time for the FX is 10 seconds, which comes from the StartTime and EndTime properties.

The Curve type is linear so that the values changes are done so in a strict and even manner.

And the values which the curve uses over the 10 second period starts from 0 and climbs to 360.

Re-run and notice the pickups now turning slowly for 10 seconds and then repeating.

# Picking up the collectable objects

Time to make the ufo collide with the pickups. In order for this to work (just like for the walls) the pickups need a body.

And the body needs to be set to collide with a ufo and vice versa.

First a body for the pickup template:

[PickupObject]
Graphic   = PickupGraphic
FXList    = SlowRotateFX
Body      = PickupBody

Then the body section itself:

[PickupBody]
Dynamic  = false
PartList = PickupPart

Just like the wall, the pickups are not dynamic. We don't want them bouncing and traveling around as a result of being hit by the ufo. They are static and need to stay in place if they are hit.

Next to define the PickupPart:

[PickupPart]
Type      = sphere
Solid     = false
SelfFlags = pickup
CheckMask = ufo

The pickup is sort of roundish, so we're going with a spherical type.

It is not solid. We want the ufo to able to pass through it when it collides. It should not influence the ufo's travel at all.

The pickup is given a label of pickup and will only collide with an object with a label of ufo.

The ufo must reciprocate this arrangement (just like a good date) by adding pickup to its list of bodypart check masks:

[UfoBodyPart]
Type      = sphere
Solid     = true
SelfFlags = ufo
Friction  = 1.2
CheckMask = wall # pickup

This is a static bodypart, and we have specified collision actions to occur if the ufo collides with a pickup. But it's a little difficult to test this right now. However you can turn on the debug again to check the body parts:

[Physics]
Gravity   = (0, 0, 0)
ShowDebug = true

Re-run to see the body parts.

Switch off again:

[Physics]
Gravity   = (0, 0, 0)
ShowDebug = false

To cause a code event to occur when the ufo hits a pickup, we need something new: a physics hander. The hander will run a function of our choosing whenever two objects collide.

We can test for these two objects to see if they are the ones we are interested in, and run some code if they are.

First, add the physics hander to the end of the Init() function:

orxClock_Register(orxClock_FindFirst(orx2F(-1.0f), orxCLOCK_TYPE_CORE), Update,
orxNULL, orxMODULE_ID_MAIN, orxCLOCK_PRIORITY_NORMAL);
orxEvent_AddHandler(orxEVENT_TYPE_PHYSICS, PhysicsEventHandler);

This will create a physics handler, and should any physics event occur, (like two objects colliding) then a function called PhysicsEventHandler will be executed.

Our new function will start as:

orxSTATUS orxFASTCALL PhysicsEventHandler(const orxEVENT *_pstEvent)
{

orxOBJECT *pstRecipientObject, *pstSenderObject;

/* Gets colliding objects */
pstRecipientObject = orxOBJECT(_pstEvent->hRecipient);
pstSenderObject = orxOBJECT(_pstEvent->hSender);

const orxSTRING recipientName = orxObject_GetName(pstRecipientObject);
const orxSTRING senderName = orxObject_GetName(pstSenderObject);

orxLOG("Object %s has collided with %s", senderName, recipientName);

return orxSTATUS_SUCCESS;
}
}

Every handler function passes an orxEVENT object in. This structure contains a lot of information about the event.

The eID is tested to ensure that the type of physics event that has occurred is a orxPHYSICS_EVENT_CONTACT_ADD which indicates when objects collide.

If true, then two orxOBJECT variables are declared, then set from the orxEVENT structure. They are passed in as the hSender and hRecipient objects.

Next, two orxSTRINGs are declared and are set by getting the names of the objects using the orxObject_GetName function. The name that is returned is the section name from the config.

Potential candidates are: UfoObject, BackgroundObject, and PickupObject1 to PickupObject8.

The names are then sent to the console.

Finally, the function returns orxSTATUS_SUCCESS which is required by an event function.

Compile and run.

If you drive the ufo into a pickup or the edge of the playfield, a message will display on the console. So we know that all is working.

Next is to add code to remove a pickup from the playfield if the ufo collides with it. Usually we could compare the name of one object to another and perform the action.

In this case, however, the pickups are named different things: PickupObject1, PickupObject2, PickupObject3… up to PickupObject8.

So we will need to actually just check if the name contains “PickupObject” which will match well for any of them.

In fact, we don't need to test for the “other” object in the pair of colliding objects. Ufo is a dynamic object and everything else on screen is static. So if anything collides with PickupObject*, it has to be the ufo. Therefore, we won't need to test for that.

First, remove the orxLOG line. We don't need that anymore.

Change the function to become:

orxSTATUS orxFASTCALL PhysicsEventHandler(const orxEVENT *_pstEvent)
{

orxOBJECT *pstRecipientObject, *pstSenderObject;

/* Gets colliding objects */
pstRecipientObject = orxOBJECT(_pstEvent->hRecipient);
pstSenderObject = orxOBJECT(_pstEvent->hSender);

const orxSTRING recipientName = orxObject_GetName(pstRecipientObject);
const orxSTRING senderName = orxObject_GetName(pstSenderObject);

if (orxString_SearchString(recipientName, "PickupObject") != orxNULL) {
}

if (orxString_SearchString(senderName, "PickupObject") != orxNULL) {
}
}

return orxSTATUS_SUCCESS;
}

You can see the new code additions after the object names.

If an object name contains the word “PickupObject”, then the ufo must have collided with it. Therefore, we need to kill it off. The safest way to do this is by setting the object's lifetime to 0.

This will ensure the object is removed instantly and deleted by Orx in a safe manner.

Notice that the test is performed twice. Once, if the pickup object is the sender, and again if the object is the recipient.

Therefore we need to check and handle both.

Compile and run.

Move the ufo over the pickups and they should disappear nicely.

We'll leave it there for the moment. In the final, Part 5, we'll cover adding sounds, a score, and winning the game.

Report Article

## User Feedback

There are no comments to display.

## Create an account

Register a new account

• 11
• 13
• 88
• 11
• 10
• ### Similar Content

• By Josheir
I have rewritten this function a few times now and I am having trouble getting every other row to have the triangles face the reverse direction.  The image enclosed demonstrates the faulty behavior which includes only one row facing the reversed direction.
I am also confused by how many indexes I should have for the g_vertex_buffer_data_land variable, could someone show me a breakdown like : 18 vertices times 2 sets  times 8 columns times 8 depth.
In another post fleabay mentioned I was not setting a VAO in core profile, however It seems to be.
here is the code:

float* getVertices(void) { //using defines int incol = _colus; int depth = _depth; int i = 0; float scaleit = .5; float tempdepth = 0; int startindexat = 0; int counter = 0; int secondcounter = 0; //for (; (tempdepth+1) <= (depth);) //don't forget to change this back! for (int q=0;q<3;q++) { //odd rows for (int col = 0; (col+1) <= (incol ); col++) { GLfloat matrix1[3][3] = { {(col + 1),0,(tempdepth)},{ (col),0,(tempdepth)}, {(col),0,(tempdepth + 1) } }; // //vertex 1 g_vertex_buffer_data_land[startindexat + 0 + counter] = matrix1[0][0] * scaleit; g_vertex_buffer_data_land[startindexat + 1 + counter] = matrix1[0][1] * scaleit; g_vertex_buffer_data_land[startindexat + 2 + counter] = matrix1[0][2] * scaleit; //vertex 2 g_vertex_buffer_data_land[startindexat + 3 + counter] = matrix1[1][0] * scaleit; g_vertex_buffer_data_land[startindexat + 4 + counter] = matrix1[1][1] * scaleit; g_vertex_buffer_data_land[startindexat + 5 + counter] = matrix1[1][2] * scaleit; g_vertex_buffer_data_land[startindexat + 6 + counter] = matrix1[2][0] * scaleit; g_vertex_buffer_data_land[startindexat + 7 + counter] = matrix1[2][1] * scaleit; g_vertex_buffer_data_land[startindexat + 8 + counter] = matrix1[2][2] * scaleit; int matrix2[3][3] = { { (col + 1),0,(tempdepth + 1)},{ (col + 1),0,(tempdepth)}, {(col),0,(tempdepth + 1) } }; g_vertex_buffer_data_land[startindexat + 9 + counter] = matrix2[0][0] * scaleit; g_vertex_buffer_data_land[startindexat + 10 + counter] = matrix2[0][1] * scaleit; g_vertex_buffer_data_land[startindexat + 11 + counter] = matrix2[0][2] * scaleit; g_vertex_buffer_data_land[startindexat + 12 + counter] = matrix2[1][0] * scaleit; g_vertex_buffer_data_land[startindexat + 13 + counter] = matrix2[1][1] * scaleit; g_vertex_buffer_data_land[startindexat + 14 + counter] = matrix2[1][2] * scaleit; g_vertex_buffer_data_land[startindexat + 15 + counter] = matrix2[2][0] * scaleit; g_vertex_buffer_data_land[startindexat + 16 + counter] = matrix2[2][1] * scaleit; g_vertex_buffer_data_land[startindexat + 17 + counter] = matrix2[2][2] * scaleit; counter = counter + 18; }//end col startindexat = 17 + counter+ 1; for (int col2 = 0; (col2+1) <= (incol); col2++) { //first triangle : even rows GLfloat matrix3[3][3] = { {(col2 + 1) ,0,(tempdepth + 2)} , {(col2 + 1),0,(tempdepth + 1)}, {(col2),0,(tempdepth +1)} }; // //vertex 1 g_vertex_buffer_data_land[(startindexat + secondcounter)] = matrix3[0][0] * scaleit; g_vertex_buffer_data_land[(startindexat + 1 + secondcounter)] = matrix3[0][1] * scaleit; g_vertex_buffer_data_land[(startindexat + 2 + secondcounter)] = matrix3[0][2] * scaleit; //vertex 2 g_vertex_buffer_data_land[(startindexat + 3 + secondcounter)] = matrix3[1][0] * scaleit; g_vertex_buffer_data_land[(startindexat + 4 + secondcounter)] = matrix3[1][1] * scaleit; g_vertex_buffer_data_land[(startindexat + 5 + secondcounter)] = matrix3[1][2] * scaleit; g_vertex_buffer_data_land[(startindexat + 6 + secondcounter)] = matrix3[2][0] * scaleit; g_vertex_buffer_data_land[(startindexat + 7 + secondcounter)] = matrix3[2][1] * scaleit; g_vertex_buffer_data_land[(startindexat + 8 + secondcounter)] = matrix3[2][2] * scaleit; // even (2) int matrix4[3][3] = { {(col2 + 1),0,(tempdepth+ 2)},{ (col2),0,(tempdepth+ 1)}, {(col2),0,(tempdepth + 2) } }; g_vertex_buffer_data_land[(startindexat+9 + secondcounter)] = matrix4[0][0] * scaleit; g_vertex_buffer_data_land[(startindexat+10 + secondcounter)] = matrix4[0][1] * scaleit; g_vertex_buffer_data_land[(startindexat+11 + secondcounter)] = matrix4[0][2] * scaleit; g_vertex_buffer_data_land[(startindexat+12 + secondcounter)] = matrix4[1][0] * scaleit; g_vertex_buffer_data_land[(startindexat+13 + secondcounter)] = matrix4[1][1] * scaleit; g_vertex_buffer_data_land[(startindexat+14 + secondcounter)] = matrix4[1][2] * scaleit; g_vertex_buffer_data_land[(startindexat+15 + secondcounter)] = matrix4[2][0] * scaleit; g_vertex_buffer_data_land[(startindexat+16 + secondcounter)] = matrix4[2][1] * scaleit; g_vertex_buffer_data_land[(startindexat+17 + secondcounter)] = matrix4[2][2] * scaleit; //one column of 4 triangles //(three vetices per triangle) secondcounter = secondcounter + 18; } startindexat = 17 + secondcounter + 1; tempdepth = tempdepth - 1; } return gvertices; }
I am hoping someone might have the experience to help me solve this problem.  Or, what could I check and do I need to show more repo code?

Thank you,
Josheir

• Hiya! i'm Jason, And i want to reach everyone here who's excited about making games! And as the Title says, I'm looking for either GMS2 Programmers Or Godot Programmers.
I want to invite you to my Game Creation Of Imagistory, A Beautiful 2D RPG game with Plot twists, Corky Characters and an amazing story.
Here is some spoliers:
Long ago, A mystical Comet Flew throughout the universe.
Legends say that one day this comet will create a brand-new galaxy.
A little scientist named Brown tried to fly to space to see the comet in action.
His Friends, the Wizards, went with him to see the explosion.
Until…
A Vortex Pulled them into a strange portal. They all scattered in different locations.
What will Brown Do?

And Here is Some Art:
I'm Ciao Gelato #7986 on discord and my email is is imagistory2500@gmail.com, if you want to contact me there.

• I'm looking for a open source PBR rendering engine that I can use.  Basic requirements are Windows (C/C++) and free to use type license.

The first two hits I get on Google are:

Filament

LuxCoreRender
https://luxcorerender.org/

Does anybody have any experience using any of these, or do you recommend something else that's better?
Thanks.
Pluses: Active development, easy to compile, zero dependencies.
• By Sneikyz
Hello,
I'm an amateur digital artist looking for a beginner-level project. I'd like to be a part of a team that wants to learn to create games. It might be a simple idle, mini, simulation game or a visual novel.
I'm still a beginner at backgrounds and I usually draw females but I'm willing to learn.
Time invested to the game creation might differ every week mostly because of my work and will be discussed separately.

Here's my DA : https://www.deviantart.com/sneikyz
• By Josheir
This is a follow up to a previous post.  MrHallows had asked me to post the project, so I am going to with a new fresh thread so that I can get the most needed help.
I have put the class in the main .cpp to simplify for your debugging purposes.  My error is :
I tried adding : #define GLFW_INCLUDE_NONE, and tried adding this as a preprocessor definitions too. I also tried to change the #ifdef - #endif, except I just couldn't get it working. The code repository URL is :
https://github.com/Joshei/GolfProjectRepo/tree/combine_sources/GOLFPROJ

The branch is : combine_sources
The Commit ID is: a4eaf31
glad1.cpp was also in my project, I removed it to try to solve this problem.

Here is the description of the problem at hand:
Except for glcolor3f and glRasterPos2i(10,10); the code works without glew.h.  When glew is added there is only a runtime error (that is shown above.)

I could really use some exact help.  You know like, "remove the include for gl.h on lines 50, 65, and 80.  Then delete the code at line 80 that states..."

I hope that this is not to much to ask for, I really want to win at OpenGL.  If I can't get help I could use a much larger file to display the test values or maybe it's possible to write to an open file and view the written data as it's outputted.