/**
* Copyright 2019 The Knights Of Unity, created by Pawel Stolarczyk
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using DemoGame.Scripts.Gameplay.Cards;
using DemoGame.Scripts.Gameplay.NetworkCommunication;
using DemoGame.Scripts.Gameplay.NetworkCommunication.MatchStates;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace DemoGame.Scripts.Gameplay.Hands
{
///
/// UI element responsible for displaying card in hand.
/// Can be drag around and dropped back in hand or on the battlefield to send message to host.
///
public class CardGrabber : MonoBehaviour, IPointerDownHandler, IPointerUpHandler
{
#region Fields
///
/// Image displaying card's sprite.
///
[SerializeField] private Image _cardImage = null;
///
/// Textfield displaying the level of a card.
///
[SerializeField] private Text _cardLevel = null;
///
/// Textfield displaying the card cost.
///
[SerializeField] private Text _cost = null;
///
/// Card drag speed.
/// Every frame this object will reduce the distance to
/// by this value.
///
[SerializeField] private float _dragSpeed = 0.4f;
///
/// Layer used to cast ray from camera.
/// This will help determine where user want to drop their card.
///
[SerializeField] private LayerMask _rayPlaneMask = new LayerMask();
///
/// If true, this object is currently being dragged.
///
private bool _isDragged;
///
/// If true, this card grabber is currently hovered over .
///
private bool _isVisualized;
///
/// Object responsible for showing where dragged card will drop.
///
private DropVisualizer _visualizer;
///
/// If true, this object has already been played.
/// Awaiting play acceptance or rejection.
///
private bool _isPlayed;
///
/// Set on drag begin.
/// Determines how far is the mousse cursor from transform's position.
///
private Vector2 _dragOffset;
///
/// Position where this object is trying to move to.
///
private Vector2 _targetPosition;
///
/// Slot in users hand. Cards that are not currently grabbed will try
/// to move to slot's position.
///
private Transform _slot;
///
/// Used to adjust real mouse position on a scaled canvas.
///
private CanvasScaler _canvasScaler;
///
/// Reference to user's hand region. Dropping card in this region
/// returns the card to it's assigned hand slot.
///
private RectTransform _handRegion;
///
/// Reference to the region of the battlefield where this card can be played.
///
private RectTransform _dropRegion;
#endregion
#region Properties
///
/// Returns the underlying card.
///
public Card Card { get; private set; }
///
/// Slot in users hand. Cards that are not currently grabbed will try
/// to move to slot's position.
///
public Transform Slot { get; private set; }
#endregion
#region Events
///
/// Invoked whenever user clicks on this object.
///
public event Action OnDragStarted;
///
/// Invoked whenever user releases this object with pointer over their hand region
/// or over any region other than .
///
public event Action OnCardReturned;
///
/// Invoked whenever user releases this object with pointer over .
///
public event Action OnCardPlayed;
#endregion
#region Mono
///
/// Adjusts this object's position.
///
private void Update()
{
if (_isDragged == true)
{
_targetPosition = (Vector2)Input.mousePosition + _dragOffset;
_targetPosition.x = Mathf.Clamp(_targetPosition.x, 0, Screen.width);
_targetPosition.y = Mathf.Clamp(_targetPosition.y, 0, Screen.height);
}
transform.position = Vector2.Lerp(transform.position, _targetPosition, _dragSpeed * Time.deltaTime);
HandleVisualization();
}
#endregion
#region Methods
///
/// Starts card drag.
///
public void OnPointerDown(PointerEventData eventData)
{
if (_isPlayed == true)
{
// Card was already played
return;
}
_isDragged = true;
_dragOffset = transform.position - Input.mousePosition;
OnDragStarted?.Invoke(this);
}
///
/// Ends card drag.
///
public void OnPointerUp(PointerEventData eventData)
{
if (_isDragged == true)
{
_isDragged = false;
Vector2 dropPosition = eventData.position;
// Clamp the position to screen rect
dropPosition.x = Mathf.Clamp(dropPosition.x, 0, Screen.width);
dropPosition.y = Mathf.Clamp(dropPosition.y, 0, Screen.height);
OnDropped(dropPosition);
}
}
///
/// Creates visualizer to help showing where grabbed card will be dropped.
///
private void HandleVisualization()
{
if (_isDragged == true && OverAssignedRegion(transform.position) == true)
{
if (_isVisualized == false)
{
_visualizer = Instantiate(Card.GetCardInfo().VisualizerPrefab);
_visualizer.ShowVisualizer(this, MatchCommunicationManager.Instance.IsHost);
}
_isVisualized = true;
_visualizer.UpdatePosition(Card.GetCardInfo().DropRegion, _rayPlaneMask);
}
else if (_isPlayed == false)
{
if (_isVisualized == true)
{
_visualizer.HideVisualizer(this);
_visualizer = null;
}
_isVisualized = false;
}
}
///
/// Returns true if this card grabber hovers over .
///
public bool OverAssignedRegion(Vector2 position)
{
if (IsOverRegion(_handRegion, position) == true)
{
// Card hovered over hand; can't be played here
return false;
}
else if (_dropRegion != null)
{
// Check if card is over _dropRegion
if (IsOverRegion(_dropRegion, position) == true)
{
// Card over drop region
return true;
}
else
{
// Card outside drop region
return false;
}
}
else
{
// _dropRegion is null, card can be played anywhere
return true;
}
}
///
/// Determines whether card was dropped in and should be played
/// or returned to hand.
///
private void OnDropped(Vector2 position)
{
bool isOverDropRegion = OverAssignedRegion(position);
if (isOverDropRegion == true)
{
_isPlayed = true;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out RaycastHit hit, Mathf.Infinity, _rayPlaneMask))
{
Debug.Log("Point: " + hit.point);
OnCardPlayed?.Invoke(this, hit.point);
}
else
{
Debug.LogError("Raycast didn't hit anything; is ground plane active?");
ReturnToHand();
OnCardReturned?.Invoke(this);
}
}
else
{
ReturnToHand();
OnCardReturned?.Invoke(this);
}
}
///
/// Returns true if is inside .
///
public bool IsOverRegion(RectTransform rect, Vector2 point)
{
// Getting rect of drop region
Rect dropRect = rect.rect;
dropRect.position += (Vector2)rect.position;
dropRect.size *= _canvasScaler.transform.localScale.y;
if (dropRect.Contains(point) == true)
{
// Card over drop region
return true;
}
else
{
return false;
}
}
///
/// Cancels the play.
/// Card is returned to hand.
///
public void CancelPlay()
{
ReturnToHand();
_isPlayed = false;
}
///
/// Sets assigned slot as the target of this object to move to.
///
public void ReturnToHand()
{
_targetPosition = _slot.position;
}
///
/// Initializes card grabber.
/// Sets the UI and position.
///
public void Initialize(Card card, Transform slot, RectTransform handRegion, RectTransform dropRegion, CanvasScaler canvasScaler)
{
this.Card = card;
this.Slot = slot;
this._handRegion = handRegion;
this._dropRegion = dropRegion;
_cardImage.sprite = card.GetCardInfo().Sprite;
_cost.text = card.GetCardInfo().Cost.ToString();
_cardLevel.text = "lvl " + card.level.ToString();
_slot = slot;
_canvasScaler = canvasScaler;
ReturnToHand();
}
///
/// Invoked when card play was allowed.
///
public void Resolve(MatchMessageCardPlayed message)
{
if (_isVisualized == true)
{
_visualizer.HideVisualizer(this);
_isVisualized = false;
}
}
#endregion
}
}