I don’t now why you’d have to do all that…
-
interface methods are intended to be public, the object will have the method publicly available regardless of if it’s typed as itself, or typed as the interface
-
the interface should define the general idea of being interacted with, the concrete classes will implement the how they’re interacted with
You should have very minimal type casting.
So for example here’s a very simple example of what you might do (I’m writing this all right here as psuedo-code, it may contain bugs, it’s intended for example):
So lets say we have a plant in the area. And the Player can pick up a watering can and go water the plant. Lets suppose we use a system where we check for overlapping trigger colliders on a special “Interactable” layer. We might do something like this:
First we have our basic interfaces to describe this all:
/*
* Represents an actor that can act, or be acted upon.
*/
public interface IActor
{
//we imply that all actors are components
Transform transform { get; }
GameObject gameObject { get; }
}
/*
* Represents a way in which an IActor can be acted upon.
*/
public interface IInteraction
{
//IActor actor - the actor performing doing the interaction
//IActor passive - an optional actor getting interacted with
bool Interact(IActor actor, IActor passive);
}
And lets get some basic actors.
SimpleActor don’t do much accept just act as a simple handle.
But PlayerActor can activate interactions on inputs ‘Action’ and ‘Drop’.
//just represents a simple actor
public class SimpleActor : MonoBehaviour, IActor
{
}
/*
* Represents the player...
*/
public class PlayerActor : MonoBehaviour, IActor
{
public List<IInteraction> Actions;
public List<IInteraction> Drops;
void Update()
{
if(Input.GetButtonDown("Action"))
{
foreach(var action in Actions)
{
if(action.Interact(this, null)) return;
}
}
if (Input.GetButtonDown("Drop"))
{
foreach(var drop in Drops)
{
if(drop.Interact(this, null)) return;
}
}
}
}
Now how about an interaction for that player to perform, a ‘pickup’ interaction maybe. You’d stick this in the array of ‘Actions’ on PlayerActor. Lets not forget though since it’s for picking things up we need that proximity/trigger overlap script I mentioned:
public class ActorHand : MonoBehaviour, IInteraction
{
public IActor Actor; //the actor that owns this interaction
public IHoldable HeldItem;
public ProximityInteractionController proximityInteractionController; //the proximity controller for picking things up
public bool Interact(IActor actor, IActor passive)
{
if(this.HeldItem != null)
{
return this.HeldItem.Activate(actor, passive);
}
else
{
//interact with what is nearby... you may want to sort by distance or something, I just randomly go at it
foreach(var other in proximityInteractionController.NearActors)
{
var holdable = other.gameObject.GetComponent<IHoldable>();
if(holdable != null)
{
if(holdable.Interact(this.Actor, passive))
{
//play some anims? signal a UnityEvent?
this.HeldItem = holdable;
return true;
}
}
}
}
return false;
}
}
public class ProximityInteractionController : MonoBehaviour
{
public readonly HashSet<IActor> NearActors;
private void OnTriggerEnter(Collider other)
{
var actor = other.GetComponent<IActor>();
if(actor != null) NearActors.Add(actor);
}
private void OnTriggerExit(Collider other)
{
var actor = other.GetComponent<IActor>();
if(actor != null) NearActors.Remove(actor);
}
}
And well we’re going to need something that can be picked up and activated:
/*
* Represents something that can be held.
*
* Note - we dont' necessarily have to implement the 'held' and 'actor' together. I just did for clarity.
*/
public class SimpleHoldable : MonoBehaviour, IHoldable
{
public IActor Actor; //the actor that owns this
public IInteraction OnActivateInteraction; //an interaction that occurs when it's activated
private IActor _heldBy;
public bool Interact(IActor actor, IActor passive)
{
if(_heldBy != null) return false; //already held
//play some anims? signal a UnityEvent?
_heldBy = actor;
return true;
}
public bool Activate(IActor actor, IActor passive)
{
if(this.OnActivateInteraction != null) return this.OnActivateInteraction.Interact(actor, passive);
}
public void Drop()
{
_heldBy = null;
//play some anims? signal a UnityEvent?
}
}
public interface IHoldable : IInteraction
{
bool Activate(IActor actor, IActor passive);
void Drop();
}
And how about a way to drop it. This would get stuck in the player’s ‘Drop’ list:
public class DropInteraction : MonoBehaviour, IInteraction
{
public IActor Actor; //the actor that owns this interaction
public ActorHand Hand; //the hand items are placed in
public bool Interact(IActor actor, IActor passive)
{
if(this.Hand.HeldItem != null)
{
//play some anims? signal a UnityEvent?
this.Hand.HeldItem.Drop();
return true;
}
return false;
}
}
And what does that SimpleHoldable do when ‘OnActivateInteraction’ is called? How about if it’s a watering can, you attach the ‘WateringInteraction’ to it:
public class WateringInteraction : MonoBehaviour, IInteraction
{
public bool Interact(IActor actor, IActor passive)
{
bool result = false;
var proximity = actor.gameObject.GetComponent<ProximityInteractionController>();
if(proximity != null && proximity.NearActors.Count > 0)
{
foreach(var other in proximity.NearActors)
{
var waterable = other.gameObject.GetComponent<IWaterable>();
if(waterable != null && waterable.Interact(actor, passive)) result = true;
}
}
return result;
}
}
public interface IWaterable : IInteraction
{
}
And lastly you just got to have something that can be watered:
public class WaterPlantInteraction : MonoBehaviour, IWaterable
{
public bool Interact(IActor actor, IActor passive)
{
//play some animations or something? adjust some stats?
}
}