Sunday, September 16, 2012

Messaging System


At the start of the project I brainstormed how objects and various components would communicate with each other. Researching online one finds a number of message passing systems. I wanted something slim to fit the small scope of my game. I found a design, I thought fairly useful, by Matthew Wegner on his Blurst Technology website. Reference provided below.

It works by instantiating a singleton instance of the Communicator component. All other components make use of the Communicator's function RegisterListener, to assign a listener for a given message type. A function to call when this message type is received. 












Modifications


A number of modifications have been made to the original script. First, It is rewritten in C#, a superficial change. Second, more importantly, it does not use the Component.SendMessage function. There are debates on forums about the performance impact of this function, mainly because it uses reflection. Whether this is true or not I don't know with any certainty. I decided to avoid the function altogether since I planned on using this system in future projects. Instead I use 'events' and 'delegates' to call the appropriate function when a message is received. To transmit data a different types of message classes must be created. More on that below. Lastly, one can block and unblock certain types of messages from being transmitted to there destination. I found this to be occasionally useful.

Message Class And MessageType


Message class is the base class of all messages. Since one cannot anticipate what concrete messages will be created, the Communicator transmits all messages in the form of the base class. The message is then cast into it's concrete class, if necessary, when it is received by the receiver. The base class has one attribute which represents the type of message it is. For the base class it is of MessageType.Message. Every new type of message created must to be added to the MessageType Enum.

public class Message {
private MessageType m_Name;
public MessageType MessageType
{ get { return m_Name;} set { m_Name = value; } }
public Message()
{ m_Name = MessageType.MESSAGE; }
public Message(MessageType type)
{ m_Name = type; }
}
view raw Message.cs hosted with ❤ by GitHub
public enum MessageType
{
MESSAGE,
LEVEL_START
};
view raw MessageType.cs hosted with ❤ by GitHub

Say for example we have a MessageType to indicate a level start. Since we need to pass extra information  to the receiver we create a new class to represent this type of message and subclass to that of Message. The constructor sets what type of message this is.
MessageType = MessageType.LEVEL_START;

How Broadcasting Works




How To Use The Communicator/Message classes


The following code demonstrates how to use the message and communicator classes. Being a trivial example, both functions called are in the same script. However, this does not have to be the case, and most likely would not be, since it is useless for the calling script to call its own functions via the Communicator. To run this example, attach the Driver script to a gameobject and run the scene.

using UnityEngine;
using System.Collections;
public class Driver : MonoBehaviour {
// Specific Message Type
public class LevelStartMessage : Message
{
// Extra information not found in generic message class
private int m_LevelNum;
public int LevelNum
{
get { return m_LevelNum; }
set { m_LevelNum = value; }
}
// Param: id : Is the level number
public LevelStartMessage(int id)
{
// Set message type variable in parent class
MessageType = MessageType.LEVEL_START;
m_LevelNum = id;
}
}
private Message msg;
private LevelStartMessage levelStartMessage;
void Start ()
{
// This is a generic message type
// For generic messages we specify the message type in the constructer
// This is done because I often found a need to trigger a function, but the message
// to do this required no extra information, and thus was exactly the same as a
// generic message.
// Example:
// msg = new Message(MessageType.Pause);
// Pausing the game may require no information. We simply want to trigger the function
// to handle the pause mechanism. This way proves to be flexible, since we can
// use the same message variable and simply change the message type when needed to be
// use used as another message.
msg = new Message(MessageType.MESSAGE);
// This is a specific message type with extra attributes. Its message type is set internally
// in the constructor.
levelStartMessage = new LevelStartMessage(1);
// The nice thing is if the method listed does not exist, or has the wrong signature it results
// in a compile time error.
Communicator.Instance.RegisterListener(MessageType.MESSAGE, ActionHandleMessage);
Communicator.Instance.RegisterListener(MessageType.LEVEL_START, ActionHandleLevelStartMessage);
Communicator.Instance.Broadcast(msg);
Communicator.Instance.Broadcast(levelStartMessage);
}
// This will trigger when msg is broadcasted
public void ActionHandleMessage(Message msg)
{
Debug.Log("Generic message request received");
}
// This will trigger when levelStartMessage is broadcasted
public void ActionHandleLevelStartMessage(Message msg)
{
// Convert the generic message into a LevelStartMessage we are expecting
LevelStartMessage startMsg = msg as LevelStartMessage;
if(startMsg != null)
{
// Now we can access the specific attributes of the level start message.
Debug.Log("Level " + startMsg.LevelNum );
}
}
}
view raw Driver.cs hosted with ❤ by GitHub


The Communicator

This is the current version of the Communicator class used in the Cube Logic demo.



using UnityEngine;
using System.Collections;
/*******************************************************************************
* Singleton Class:
* All gameobjects register with 'Communicator' Enabling them receive messages
* from other objects and send messages to other objects.
* One object will handle all internal game messages ex updating player life,
* death ect... and external messages communication with the game structure ex
* menu toggling, game start, end, ect....
******************************************************************************/
public class Communicator : MonoBehaviour {
private static Communicator m_Instance;
private Hashtable m_Listeners;
private Hashtable m_BlockedMessages;
public delegate void EventHandler(Message msg);
private static event EventHandler handleMsgEvent;
public static Communicator Instance
{
get
{
if (m_Instance == null)
{
m_Instance = new GameObject("Communicator").AddComponent<Communicator>();
}
return m_Instance;
}
}
/*******************************************************************************
* Do: Associates eventhandler with a string message name
* How: Hashes eventhandler via messagename as key if same key exists then multi
* aggregate the eventhandler.
*
*******************************************************************************/
public void RegisterListener(MessageType name, EventHandler msgHandler)
{
bool found = m_Listeners.ContainsKey(name);
if(found)
{
handleMsgEvent = m_Listeners[name] as EventHandler;
handleMsgEvent+= msgHandler;
m_Listeners[name] = handleMsgEvent;
}
else if(!found)
{
m_Listeners.Add(name, msgHandler);
}
}
/*******************************************************************************
* Do: Unregister specfied handler.
* How: Get handler from hashtable via message name as key. Remove handler.
* Prereq: Handler must exist;
*******************************************************************************/
public void UnregisterListener(MessageType name, EventHandler msgHandler)
{
bool found = m_Listeners.ContainsKey(name);
if(found)
{
handleMsgEvent = m_Listeners[name] as EventHandler;
handleMsgEvent-= msgHandler;
m_Listeners[name] = handleMsgEvent;
}
}
/*******************************************************************************
* Do: Blocks certain types of messages from being transmitted.
* How: If a message type is not blocked it adds it to the block table
* Prereq: Must not already be blocked
*******************************************************************************/
public void BlockIncomingMessages(MessageType blockedMsgType)
{
if( !m_BlockedMessages.ContainsKey(blockedMsgType))
{
m_BlockedMessages.Add(blockedMsgType, false);
}
}
/*******************************************************************************
* Do: Unblocks certain types of messages so can be transmitted.
* How: If a message type is blocked, it is removed from the block table
* Prereq: Message type must have been previously blocked
*******************************************************************************/
public void UnblockIncomingMessages(MessageType unblockedMsgType)
{
if(m_BlockedMessages.ContainsKey(unblockedMsgType))
{
m_BlockedMessages.Remove(unblockedMsgType);
}
}
/*******************************************************************************
* Do: Mulitcast event to all listeners
* How: Get listener from hashtable via message name as key. Multicast event.
*******************************************************************************/
public void Broadcast(Message msg)
{
bool found = m_Listeners.ContainsKey(msg.MessageType);
bool isBlocked = m_BlockedMessages.ContainsKey(msg.MessageType);
if(found & !isBlocked)
{
handleMsgEvent = m_Listeners[msg.MessageType] as EventHandler;
handleMsgEvent(msg);
}
}
// Initialize this component
void Awake ()
{
enabled = false;
m_Listeners = new Hashtable();
m_BlockedMessages = new Hashtable();
}
// Cleanup
public void OnApplicationQuit ()
{
m_Listeners.Clear();
m_BlockedMessages.Clear();
m_Listeners = null;
m_BlockedMessages = null;
handleMsgEvent = null;
m_Instance = null;
}
}
view raw Communicator.cs hosted with ❤ by GitHub

References
Original messaging system design found at:

Saturday, September 15, 2012

Game Structure Blueprint

The Design

As mentioned in the previous post I wanted to come up with a blueprint to handle the setup/running of the game as well as managing the different game states.


The design is pretty straightforward. Game component begins by initializing a singleton Communicator instance to handle all communications among objects. 


Next, the Scene Director is initialized which directs scenes to transition from one to another. Each scene handles loading/unloading its own assets at the request of the scene director's enter scene and exit scene functions when transitioning between scenes. By default Scene Director initializes the Splash Scene as its first scene. 


Splash Scene, as with all scenes, loads and initializes its own assets, game objects,  components, and registers listeners with the Communicator to listen for certain messages. The first object is the splash menu, from which the user can start a selected level or quit the application. Next is the Splash Input, which enables the user to interact with the 3D splash menu  using the mouse. Lastly, the Splash Level Preview allows the user to scroll through a list of currently available levels to view them. Once the start button is pressed, the currently selected level number is passed to the scene director along with a message to end the splash scene. Scene Director calls the splash cleanup function, which unloads the assets and deregisters any listeners withe the Communicator. The director then sends a message to the Level Scene to Initialize the given level. 


Upon initialization the level loads all assets, and configures the columns with their exposures. Exposure is the amount influence a column has on  other columns. Exposures are configured in a separate file to prevent hard coding them in the game per level. 

Control flow reaches the Level scene now, which has a number of objects. A level menu system from which the user can return to the splash scene or reset the current level. Column Manager which is responsible for managing all data related to the columns which include, tracking the selected and effected columns, moving columns correctly based on exposures, validating each move, resetting invalid moves, and changing column textures. The user continues to play the game until they complete it or quit.

Assuming the user quits the level,  a message is sent back to the scene director indicating level termination. Scene director calls the level component's cleanup function which unloads all assets and removes all of its registered listeners from the Communicator, preventing it from sending or receiving any messages. The scene is then destroyed, and Scene Director transitions to the Splash Screen.

The user can continue this loop, however assume the user presses the application quit button. A message is sent back to the director to cleanup the splash scene. The director then sends a message to the game component, which cleans up the Communicator  and Scene Director objects before exiting the application.

Friday, September 14, 2012

DEMO - CUBE LOGIC

Well, this blog was marked as spam, by the automated spam detection system, after one post. I do manage to impress myself occasionally. Anyhow, the mishap has been cleared up and I'll start by posting some work on my first demo idea. 


Project Goal


My attempt was to make a puzzle game, that had a very small scope in terms of it's gameplay. An experiment to figure out how to go about structuring basic game code as far as transitioning between scenes, loading/unloading assets, handling menu systems, handling how components interact with each other, how the user interacts with the objects. Basically, creating a template that could be applied, with minimal tweaking, to my future projects.


Game Idea

On every level the player is faced with a group of rectangular columns, each with a number representing it's position, zero, one, two or three.  The point is to level all the columns down to zero. However, some columns  effect other ones by a certain factor. For example, pulling column 'A' upwards may effect column 'B',  pushing it downwards by two positions. The move is only valid when all the effected columns are within the range of positions zero through three (inclusive). After every move the column visually updates to its current position. The puzzle is to figure out which combination of movements are needed to result in all the columns moving down to position zero.


The Result


The game runs exactly how I imagined it. However, while forcing a few reluctant people to play the game, under threat of pain, there were certain things I noticed. 

One, after the first few minutes no one paid attention to the number position on any column. Instead, the puzzles were solved by randomly moving different columns to see what the effect would be. Solving the puzzle basically by getting a feel for how the columns interacted. This makes the numbering system almost irrelevant. The only benefit is knowing when a column is zero or three, to mark them as limits, and even in that there is only slight utility.

Two, how much one column effects another column is completely unpredictable. The reason being, no rules govern it. It is simply how I made it to be on any given level. One way to rectify this would be to think of the positions of each column as weight. Moving a column would transfer its weight evenly onto the effected columns. Example if a player moves a column by two positions, and that effects two other columns. Then each of the effected columns will be moved by a magnitude of one.This gives some predictability to the interactions. However, this seems like a minor point because of the first issue. Namely that it is more fun to move the objects around and get a feel for the interactions rather than work through it. 

Three, it's not obvious what direction the effected columns will move. Some move in parallel to the original movement, some move in opposition.  It is color coded but difficult to differentiate. I am partial to black and white design, so I left it as is. Easily rectifiable using different colors though.






Download

An eight level demo is available in my Dropbox, for Windows users. I'll post a mac version up if I'm not lazy about it.  Apologies, I'll export my future projects for the web browser to avoid a download.


Controls

The only keyboard input is to bring up the pause menu screen up while playing.  To do so press the  'A' key.


Thursday, September 13, 2012

Well, this is my first blog post ever. I started this to keep track and showcase my work with Unity. Complete ideas and half ideas, along with an occasional ramble, will make there way here with code samples that may be of benefit to some, including myself if offered alternative solutions. Indirectly so many blogs and forums have helped me to solve problems that I thought I should add my own to the universe. I can already tell, while writing this first entry, consistency is going to be an issue. Hello world.