using System.Collections; using System.Collections.Generic; using UnityEngine; using System.Linq; /// /// Class which defines blocks around the level /// [RequireComponent(typeof(BoxCollider))] public class Block : MonoBehaviour { #region Inspector Fields [SerializeField] [Tooltip("Offset from the top of the block from which a character should stand")] private Vector3 VisualOffset = Vector3.zero; [SerializeField] [Tooltip("Can this type of block be walked on")] public bool is_Walkable = true; [Tooltip("Is this block underwater?")] public bool isWater = false; [Tooltip("Is this block at the bottom of a pit?")] public bool isPit = false; public bool isCrystals = false; public bool isRock = false; [Header("Spawn Settings")] [Tooltip("Can this block be spawned on")] public bool isSpawnable = false; [Tooltip("Direction Player is poting at when spawned")] public Direction SpawnDirection = Direction.Forward; #endregion InspectorFields #region Private Functions /// /// List of current players on this block /// public Character CurrentPlayer { get; protected set; } #endregion Private Functions #region ReadOnly Properties /// /// Blocks position in global space /// public Vector3 position { get { return transform.position; } } /// /// Position character should stand in global space /// public Vector3 VisualPosition { get { return position + VisualOffset + Vector3.up * 0.5f; } } #endregion ReadOnly Properties #region Public Functions /// /// Is a block valid to walk on /// /// Layers to check for when checking for blocks above /// public bool isWalkable(LayerMask layerMask) { //checks if there is no block above this one and that this is tagged as walkable return (is_Walkable && !isBlockAtPosition(position + Vector3.up, 1, layerMask)); } /// /// Is called when a player moves onto this block /// /// Should be implemented by a derived class /// /// Player which moved on to block ///The direction the player moved to get to this block public virtual IEnumerator OnWalkedOnByPlayer(Character player, Vector3 moveDirection) { yield return StartCoroutine(DoPush(player, moveDirection)); } /// /// Is called when a player moves off of block /// /// Should be implemented by a derived class /// /// Player which moved on to block public virtual void OnLeftByPlayer(Character player) { CurrentPlayer = null; } /// /// Called to deal with players colliding on this block /// /// Player which is moving into this block /// The direction the player moved to get to this block public virtual IEnumerator DoPush(Character newPlayer, Vector3 moveDirection) { if (CurrentPlayer == null) { CurrentPlayer = newPlayer; yield break; } Block pushBlock = GetPushLocation(moveDirection, ~CurrentPlayer.Ignore); if (pushBlock != this) { Character oldPlayer = CurrentPlayer; CurrentPlayer = newPlayer; yield return StartCoroutine(oldPlayer.MoveToBlock(pushBlock, Character.Animation.Hit, 1)); } else { Block returnBlock = GetPushLocation(-moveDirection, ~newPlayer.Ignore); if (returnBlock != this) yield return StartCoroutine(newPlayer.MoveToBlock(returnBlock, Character.Animation.Hit, 1)); } } #endregion Public Functions #region Protected Functions protected Block GetPushLocation(Vector3 pushDirection, LayerMask ignoreMask) { //setting up variables Vector3 newPosition = position + pushDirection; // position wanted Block hit; //output of block detection //if move is obstucted no where to move if (Block.isBlockAtPosition(newPosition + Vector3.up, 1, ignoreMask)) return this; //If block at Position is walkable set it to moveTo if (Block.isBlockAtPosition(newPosition, 1, ignoreMask, out hit) && hit.isWalkable(ignoreMask)) { return hit; } //else if block down one is walkable else if (Block.isBlockAtPosition(newPosition + Vector3.down, 1, ignoreMask, out hit) && hit.isWalkable(ignoreMask)) { return hit; } return this; } #endregion Protected Functions #region Editor Functions private void OnDrawGizmos() { if (!isSpawnable) return; Vector3 DrawPosition = VisualPosition + Vector3.up * 0.4f; Vector3 Perp = Quaternion.Euler(0, 90, 0) * SpawnDirection.ToVector(); DebugExtensions.DrawCube(DrawPosition, 0.4f, Color.magenta, 0); //Eyes Vector3 eyePosition = DrawPosition + SpawnDirection.ToVector() * 0.4f; DebugExtensions.DrawCube(eyePosition + Perp * 0.2f, 0.1f, Color.magenta, 0); DebugExtensions.DrawCube(eyePosition - Perp * 0.2f, 0.1f, Color.magenta, 0); //ears Vector3 earPosition = DrawPosition + SpawnDirection.ToVector() * 0.2f + Vector3.up * 0.4f; Vector3 earScale = Quaternion.LookRotation(SpawnDirection.ToVector()) * new Vector3(0.1f, 0.1f, 0.05f); DebugExtensions.DrawCube(earPosition + Perp * 0.3f, earScale, Color.magenta, 0); DebugExtensions.DrawCube(earPosition - Perp * 0.3f, earScale, Color.magenta, 0); } #endregion Editor Functions #region Static Functions /// /// Checks if there is a block at supplied position /// /// position to check at /// Scale of block. (should be 1) /// Layers to check on /// Block hit /// if a block is at position public static bool isBlockAtPosition(Vector3 position, float Scale, LayerMask layerMask, out Block hit) { //Turn scale into halfextent and shrink a bit so it doesn't hit bordering blocks Vector3 halfExtent = Vector3.one * ((Scale - 0.1f) / 2); //Get every collider which is at position Collider[] cols = Physics.OverlapBox(position, halfExtent, Quaternion.identity, layerMask); //Filter colliders for only GameObjects with an Block component Block[] blocks = cols.Where(p => p.GetComponent() != null).Select(p => p.GetComponent()).ToArray(); //Draw cube, for visuals DebugExtensions.DrawCube(position, halfExtent, Color.cyan, 1, false); //if didn't hit anyblocks return false if (blocks.Length == 0) { hit = null; return false; } else { //else get the closest block to disered position, (in case we hit mulitple blocks) hit = Utility.minBy(blocks, p => Vector3.Distance(p.transform.position, position)); return true; } } /// /// Checks if there is a block at supplied position /// /// position to check at /// Scale of block. (should be 1) /// Layers to check on /// if a block is at position public static bool isBlockAtPosition(Vector3 position, float scale, LayerMask layerMask) { //Return Overloaded function above Block hit; return (isBlockAtPosition(position, scale, layerMask, out hit)); } #endregion }