1
votes

I am building a simple top down rouge-like game using unity. I have development experience, but am a beginner to both unity framework and c#. Is there a clean and graceful way of handling player object interactions with other various types of objects in the scene?

For example, the scene would be full of walls, enemies and other objects that block the path and the player can interact with by attempting to move in their direction. Standing near a chest and moving in it's direction would not move the player, but open the chest. If moving into a wall, it would damage it, etc.

At the moment i'm using raycast2d to get hold of the object blocking the path, but can't find a solution of how to interact with it without having to check what type of object it is (is a Wall; is a Chest; etc..). I made an interface which all interactable objects implement with a method interact(), but different types of objects require different information. A wall would require the amount of damage it should take (depends on player stats which are in the player class), chest would take none. Therefore it's difficult for all of them to implement the same interface. Having the wall ask the player what's his damage is also poor programming practice.

I've found a similar question on this posted a while ago on stackowerflow. It suggests using an observer pattern, but I can't imagine every single object on the scene being subscribed to player movement event and after each move checking weather it was hit or not.

Is there a standard solution to this kind of interaction? One which would be loosely coupled, clean and follow good programming practices?

1
Look for layers and colliders. - aybe
how would they help exactly? I might not understand layers and colliders fully. I know they're used to specify that objects on the same layer can collide. But both chest and wall would be on the same blocking layer as the player, what I want to do is have them react to the interaction differently without having to check if it's a wall or anything else. - Eimantas Šapoka

1 Answers

1
votes

If I were in your situation, I would add an empty GameObject with a Collider2D to the object (wall, chest, etc) and toggle the box for isTrigger. (Note that this will require that the object is derived from Monobehaviour and that might not always be desireable.)

From there I would use some combination of the specialized functions OnTriggerStay, OnTriggerEnter and OnTriggerExit :

//in Chest.cs
void OnTriggerEnter() //http://docs.unity3d.com/ScriptReference/Collider.OnTriggerEnter.html
{
    OpenChest();
} 

//in Wall.cs
void OnTriggerStay() //http://docs.unity3d.com/ScriptReference/Collider.OnTriggerStay.html
{
    DamageWall();
}

From here, you have two choices to make sure certain gameObjects do not trigger the wrong collider. One way uses code:

//in Chest.cs
void OnTriggerEnter(Collider other)
{
    //notice this won't be very efficient
    if(other.gameObject.GetComponent<PlayerScript>() != null) OpenChest();
    //if(other.name == "The Player") OpenChest(); //probably less efficient at runtime
}

The other solution uses layers and is more efficient. To add layers to Unity, go through edit -> project settings -> tags and layers (the tabs on the top left) and add some layer names (e.g. Wall, Chest, Player, Enemy). Next find the triggers (and preferrably the parents of the triggers) and label the object as a part of that layer in the Inspector by switching from the Default layer to the desired label (e.g. Wall).

From here, you can use another trick, selective collisions between layers by going through edit -> project settings -> physics and looking at the Layer Collision Matrix. If two objects need to collide, leave the checkbox filled; if they should never collide, uncheck the box; if they should collide situationally either split the layer into two or more or add code that determines whether or not to collide.

Another tip: if a player should only chop down a tree when holding a chainsaw, you can make it so holding onto a chainsaw creates a special trigger for destroying trees.

edit: additional gotchas:

1) two triggers cannot collide (one must be a trigger and the other must be a collider)
2) triggers can move (they do not need to be stationary)
3) if one collider-trigger pair seems illogical, switching which object is the trigger and which is the collider might make the code simpler.

edit2: removing coupling:

OnTriggerStay(Collider other) will be called for each instance and would work well if all monsters and players dealt the same damage, but that's clearly not the case. If each instance deals a different amount of damage you can do the following:

void OnCollisionEnter(Collision other)
{
    addSelfDPS(other.gameObject.GetComponent<WallDamager>().getDPS());
}
void OnCollisionExit(Collision other)
{
    addSelfDPS(-1*other.gameObject.GetComponent<WallDamager>().getDPS());
}

if you use this method, however, there will be issues when objects are spawned and destroyed inside of a trigger, so you might need to address that in any constructors (Awake/Start) or destructors (OnDestroy).