Sign in to follow this  

tutorial - inter object communication

This topic is 4869 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Communication between objects tutorial - by Jason Lough - using MSVC++ 6.0 - normal_toes@hotmail.com - Aug 16, 2004 Have you ever wanted to create objects in a class that can communicate with each other? This tutorial covers communication between multiple objects in the same class. This can be a very useful technique in game programming, as it lets you think in real world terms. This tutorial assumes you know how to use pointers and classes. If you cant make your own classes and dont know how to use the -> operator, then you will probably be lost here. This tutorial also assumes you know how to make a basic window, although I do include code that does this in the following sections. The problem : You are modeling (in software) a (very) simple car. This car has multiple parts (or objects) : windows, wheels, and an engine. You want the wheels to report to the engine their RPM. This will require communication between objects. Here is the heirarchy of the classes : CCar --------------- | | | CEngine CWheels CWindow... etc First, the code to get a basic window up. This is what is in main.cpp :
///////////////////////////////////main.cpp//////////////////////////////////////////////////////////////////
#include <windows.h>
#include <stdio.h> //for sprintf

HWND ghWnd = 0;
HINSTANCE ghInst = 0;
HDC gdc = 0;

#include "Car.h"
CCar *pCar = 0;

#include "Engine.h"
class CEngine;

LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	int result;

	switch(msg)
	{
	case WM_CREATE:
		break;
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	case WM_PAINT:
		ValidateRect(ghWnd, NULL);
		break;

	case WM_LBUTTONDOWN:
		result = MessageBox(ghWnd, "whatever", "Caption",  MB_OK);
		break;
	case WM_RBUTTONDOWN:
		result = MessageBox(ghWnd, "Right Mouse Key Pressed", "Caption",  MB_OK);
		break;
	
	case WM_KEYDOWN:
		switch (wParam)	
		{
		case VK_ESCAPE:
			DestroyWindow(ghWnd);
			break;
	
		default:		
			char keypressed[256] = {0};
			sprintf(keypressed, "%i", wParam);
			result = MessageBox(ghWnd, keypressed, "key pressed", MB_OK);
			break;
		
		}

	}
	return DefWindowProc(hWnd, msg, wParam, lParam);
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, INT)
{
	ghInst = hInstance;

	WNDCLASSEX wc;
	wc.cbSize =		sizeof(WNDCLASSEX);
	wc.style =		CS_CLASSDC, //CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
	wc.lpfnWndProc =	WndProc; //where to pass messages to (defined below)
	wc.cbClsExtra =		0;
	wc.cbWndExtra =		0;
	wc.hInstance =		hInstance;
	wc.hIcon =		NULL, //LoadIcon(NULL, IDI_APPLICATION);
	wc.hCursor =		(HCURSOR)LoadCursor(NULL, IDC_ARROW); //can be NULL
	wc.hbrBackground =	(HBRUSH)GetStockObject(WHITE_BRUSH); //color of bkgrnd, can be NULL
	wc.lpszMenuName =	NULL;
	wc.lpszClassName =	"WinApp";//classname
	wc.hIconSm =		NULL;

	RegisterClassEx(&wc);

	ghWnd = CreateWindowEx(NULL,
				"WinApp", //classname (defined above)
				NULL, //window caption
				WS_OVERLAPPEDWINDOW,
				0, //x pos of top left corner of window
				0, //y pos of top left corner of window
				400, //width of window
				400, //height of window
				NULL,
				NULL,
				hInstance,
				NULL);

	ShowWindow(ghWnd, SW_SHOWDEFAULT);
	UpdateWindow(ghWnd);

	pCar = new CCar;	
	pCar->pEngine->Go();

	MSG msg;
	while(1)
	{
		if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
			if(msg.message == WM_QUIT)
				break;
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		else
		{

		}
	}

	UnregisterClass("WinApp", hInstance);

	return 0;
}


////////////////////////////////////////////////////////////////////////////////////////////////////////
Notice what it #include and defines near the top. This may lead you to believe there is a Car.h file somewhere. Here it is : //////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// // Car.h: interface for the CCar class. // ////////////////////////////////////////////////////////////////////// #if !defined(AFX_CAR_H__F59BCE1C_0EA5_4519_B9CA_E34CC294486D__INCLUDED_) #define AFX_CAR_H__F59BCE1C_0EA5_4519_B9CA_E34CC294486D__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 class CEngine; //these are called incomplete class declarations class CWheel; //this makes the compiler aware of them at compile time //kinda one of the keys to this tutorial :) class CCar { public: CCar(); //this is a constructor. its automatically called when an object of this type is created virtual ~CCar(); //this is a destructor, automatically called when the object is destroyed CEngine *pEngine; CWheel *pWheel; }; #endif // !defined(AFX_CAR_H__F59BCE1C_0EA5_4519_B9CA_E34CC294486D__INCLUDED_) /////////////////////////////////////////////////////////////////////// Fairly simple. You may notice I use class wizard to help make the classes. This is just my preference, as it makes life a little easier by doing all the pragma once and basic class setup stuff for me. If you dont know how to use this, just go to insert, then new class. Its self xplanatory from there on. It ends up making you a .h and .cpp file for your new class. BTW, the destructor is virtual for the reason that a derived class may not use the destructor properly unless its virtual, just in case thats new for someone. see the part where it says class CEngine; class CWheel; What is this? These are called incomplete class declarations. You must declare them if youre going to use them. Since CCar uses an instance of CEngine, it has to declare it there. "But theres no include Car.h!!" you may say. That is taken care of in the Car.cpp file, shown next : ///////////////////////car.cpp///////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// // Car.cpp: implementation of the CCar class. ////////////////////////////////////////////////////////////////////// #include "Engine.h" #include "Wheel.h" #include "Car.h" ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// CCar::CCar() { pEngine = new CEngine(this); pWheel = new CWheel(this); } CCar::~CCar() { delete pEngine; delete pWheel; } ////////////////////////////////////////////////////////////////////// Once again, I kept it as bare bones simple as I could. Here are the #inlcude statements you were wondering about. Also, the constructor and destructor take care of actually creating and destroying the objects for you. Nice of them, huh? :) Now you dont ever have to worry about creating and destroying them, all you do is make a car object, and it does it for you. Now, you must realize by now that the car is the topmost object, and the other components (wheel, engine, etc...) are parts of the car, or sub-objects. Think of it as "a car has a window, a car has a engine, etc..." The trick is to be able to pass info between sub objects, quickly, easily, and efficiently. This is done by creating a pointer in each sub object that points to the car object, and the car object has a pointer to each of the sub objects. Check out the definition of CWheel : ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// // Wheel.h: interface for the CWheel class. ////////////////////////////////////////////////////////////////////// #if !defined(AFX_WHEEL_H__CD8D84EE_A62B_4231_872A_742A668EDDDC__INCLUDED_) #define AFX_WHEEL_H__CD8D84EE_A62B_4231_872A_742A668EDDDC__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 class CCar; //another incomplete class declaration class CWheel { public: CWheel(CCar *pcar); //constructor gettin a pointer to the car object virtual ~CWheel(); void SetRPM(float newrpm); float GetRPM(); private: CCar *pCar; float RPM; }; #endif // !defined(AFX_WHEEL_H__CD8D84EE_A62B_4231_872A_742A668EDDDC__INCLUDED_) ////////////////////////////////////////////////////////////////////// See the definition of CCar? See the constructor takes a pointer to the CCar? What do you think happens in the constructor in the cpp file? Something like pCar = pcar;? (Note the capital and lowercase Cc there) This is how the subobject (the wheel in this case) can access the top object! pWheel->pCar would get you access to any public entities in the CCar object. Since pEngine represents the CEngine class, and its public, you can send and recieve info to or from any other objects connected like this. Now for wheel.cpp : ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// // Wheel.cpp: implementation of the CWheel class. ////////////////////////////////////////////////////////////////////// #include "Wheel.h" ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// CWheel::CWheel(CCar *pcar) { pCar = pcar; } CWheel::~CWheel() { } void CWheel::SetRPM(float rpm) { RPM = rpm; } float CWheel::GetRPM() { return RPM; } ////////////////////////////////////////////////////////////////////// To test all this out, look at main.cpp. The line pCar->pEngine->Go(); This causes Go() in CEngine to execute. Which has float x = pCar->pWheel->GetRPM(); That is an example of how to pass data between objects. CEngine accessed pCar, which accessed pWheels GetRPM function, which returned a float. [Edited by - nosajghoul on August 16, 2004 12:04:50 PM]

Share this post


Link to post
Share on other sites
Hello Jason,

Some points :

1) it is a rather bad idea to use that kind of object communication. It adds a lot of dependencies in your code base - which is not a good idea when designing an application. Suppose that you want to use the code you have developped in another project (the CWheel). Then you'll have to add CCar to that project as well. And CEngine... And all you wanted to do was implementing a bike (yes, bikes have wheels too :)). To solve the problem of interobject communication, one simple and good solution is to use the observer pattern (see here for an explaination). Using the terminology of the later, your CWheel is you subject, and your CEngine is your observer.

2) concerning your tutorial work, you may use the (source) tags to show source code - or at least use the (code) tag, because as you can see, the forums destroyed the indentation you used. ANother thing : why do you show the window creation since you do not use it at all in the remaining of your code ? Do not add complexity to a tutor, unless the complexity itself is the subject of the tutor. You explaination works even if you are using a DOS box to print the result of the program :)

HTH

Share this post


Link to post
Share on other sites
Emmanuel Deloget :

Ya, thats a good way to do it. Im sure theres alotta better ways to do this, Im still a noob. :)

This was my 1st tutorial ever, and its existence is due to the fact that I couldnt find this info anywhere for a long time, and when I finally did, it was like a eureka moment, lol. I hope it helps someone.

peace,
Jason

Share this post


Link to post
Share on other sites

This topic is 4869 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this