practice mode integrated to the onboarding tutorial
This commit is contained in:
@@ -169,11 +169,7 @@ public class GameCanvas : MonoBehaviour
|
||||
CupidLobby.instance.Cancel();
|
||||
LoginManager.ClearMatchYourTeam();
|
||||
|
||||
if (NetworkManager.singleton != null && NetworkClient.active)
|
||||
{
|
||||
NetManager.MarkIntentionalClientDisconnect();
|
||||
NetworkManager.singleton.StopClient();
|
||||
}
|
||||
NetManager.StopNetworkingForSceneChange();
|
||||
|
||||
LevelLoadManager.LoadLevel("MainMenu");
|
||||
}
|
||||
|
||||
@@ -608,7 +608,28 @@ public class GameManager : NetworkBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
bool turnTimerPaused;
|
||||
|
||||
public void SetTurnTimerPaused(bool paused)
|
||||
{
|
||||
if (!isServer)
|
||||
{
|
||||
CmdSetTurnTimerPaused(paused);
|
||||
return;
|
||||
}
|
||||
turnTimerPaused = paused;
|
||||
}
|
||||
|
||||
[Command(requiresAuthority = false)]
|
||||
void CmdSetTurnTimerPaused(bool paused)
|
||||
{
|
||||
turnTimerPaused = paused;
|
||||
}
|
||||
|
||||
void HandleTurnTimer(){
|
||||
if (turnTimerPaused)
|
||||
return;
|
||||
|
||||
if(turnTimerCounter < turnTimer){
|
||||
if(!isMoving && !switchingTeams && gameStarted){
|
||||
turnTimerCounter += Time.deltaTime;
|
||||
@@ -846,7 +867,7 @@ public class GameManager : NetworkBehaviour
|
||||
|
||||
public void StopClient(){
|
||||
try{
|
||||
NetworkManager.singleton.StopClient();
|
||||
NetManager.StopNetworkingForSceneChange();
|
||||
}catch(Exception e){
|
||||
Logger.Log("Error stopping client: " + e.Message);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ using System.Collections;
|
||||
using Mirror;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Networking;
|
||||
using UnityEngine.UI;
|
||||
using DG.Tweening;
|
||||
|
||||
public class GameTutorialManager : MonoBehaviour
|
||||
{
|
||||
@@ -15,6 +17,17 @@ public class GameTutorialManager : MonoBehaviour
|
||||
|
||||
public AiBotController aiController;
|
||||
|
||||
[Header("UI")]
|
||||
public CanvasGroup intro_shoot;
|
||||
public CanvasGroup intro_goal;
|
||||
public float introFadeDuration = 0.3f;
|
||||
|
||||
Button _introShootButton;
|
||||
Button _introGoalButton;
|
||||
bool _shootIntroDismissed;
|
||||
bool _goalIntroDismissed;
|
||||
bool _introVisible;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
tutorialModeEnabled = false;
|
||||
@@ -25,25 +38,171 @@ public class GameTutorialManager : MonoBehaviour
|
||||
tutorialModeEnabled = true;
|
||||
|
||||
tutorialModeTrigger = false;
|
||||
|
||||
if (!tutorialModeEnabled)
|
||||
{
|
||||
HideIntroImmediate(intro_shoot);
|
||||
HideIntroImmediate(intro_goal);
|
||||
}
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
GameManager.OnTeamChanged += OnTeamChanged;
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
GameManager.OnTeamChanged -= OnTeamChanged;
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
if (tutorialModeEnabled)
|
||||
tutorialModeEnabled = false;
|
||||
|
||||
if (intro_shoot != null)
|
||||
DOTween.Kill(intro_shoot);
|
||||
if (intro_goal != null)
|
||||
DOTween.Kill(intro_goal);
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
if (tutorialModeEnabled)
|
||||
{
|
||||
NetManager.StopNetworkingForSceneChange();
|
||||
|
||||
NetworkManager.singleton.networkAddress = "localhost";
|
||||
NetworkManager.singleton.StartHost();
|
||||
|
||||
WireIntroButtons();
|
||||
HideIntroImmediate(intro_goal);
|
||||
HideIntroImmediate(intro_shoot);
|
||||
|
||||
StartCoroutine(SetupAiWhenReady());
|
||||
StartCoroutine(ShowShootIntroWhenReady());
|
||||
}
|
||||
}
|
||||
|
||||
void WireIntroButtons()
|
||||
{
|
||||
if (intro_shoot != null)
|
||||
_introShootButton = intro_shoot.GetComponentInChildren<Button>(true);
|
||||
if (intro_goal != null)
|
||||
_introGoalButton = intro_goal.GetComponentInChildren<Button>(true);
|
||||
|
||||
if (_introShootButton != null)
|
||||
_introShootButton.onClick.AddListener(OnShootIntroDismissed);
|
||||
if (_introGoalButton != null)
|
||||
_introGoalButton.onClick.AddListener(OnGoalIntroDismissed);
|
||||
}
|
||||
|
||||
IEnumerator ShowShootIntroWhenReady()
|
||||
{
|
||||
while (GameManager.instance == null || !GameManager.instance.gameStarted)
|
||||
yield return null;
|
||||
|
||||
while (NetPlayer.localPlayer == null)
|
||||
yield return null;
|
||||
|
||||
ShowIntro(intro_shoot);
|
||||
}
|
||||
|
||||
void OnTeamChanged(Team team)
|
||||
{
|
||||
if (!tutorialModeEnabled || _introVisible)
|
||||
return;
|
||||
if (team != GetPlayerTeam())
|
||||
return;
|
||||
if (!_shootIntroDismissed || _goalIntroDismissed)
|
||||
return;
|
||||
|
||||
ShowIntro(intro_goal);
|
||||
}
|
||||
|
||||
void OnShootIntroDismissed()
|
||||
{
|
||||
if (_shootIntroDismissed)
|
||||
return;
|
||||
|
||||
_shootIntroDismissed = true;
|
||||
DismissIntro(intro_shoot);
|
||||
}
|
||||
|
||||
void OnGoalIntroDismissed()
|
||||
{
|
||||
if (_goalIntroDismissed)
|
||||
return;
|
||||
|
||||
_goalIntroDismissed = true;
|
||||
DismissIntro(intro_goal);
|
||||
}
|
||||
|
||||
void ShowIntro(CanvasGroup group)
|
||||
{
|
||||
if (group == null)
|
||||
return;
|
||||
|
||||
EnsureTutorialCanvasVisible(group);
|
||||
DOTween.Kill(group);
|
||||
|
||||
group.gameObject.SetActive(true);
|
||||
group.alpha = 1f;
|
||||
group.interactable = true;
|
||||
group.blocksRaycasts = true;
|
||||
|
||||
_introVisible = true;
|
||||
if (GameManager.instance != null)
|
||||
GameManager.instance.SetTurnTimerPaused(true);
|
||||
}
|
||||
|
||||
void DismissIntro(CanvasGroup group)
|
||||
{
|
||||
if (group == null)
|
||||
return;
|
||||
|
||||
group.interactable = false;
|
||||
group.blocksRaycasts = false;
|
||||
|
||||
DOTween.Kill(group);
|
||||
group.DOFade(0f, introFadeDuration)
|
||||
.SetEase(Ease.InOutSine)
|
||||
.OnComplete(() =>
|
||||
{
|
||||
group.gameObject.SetActive(false);
|
||||
_introVisible = false;
|
||||
if (GameManager.instance != null)
|
||||
GameManager.instance.SetTurnTimerPaused(false);
|
||||
});
|
||||
}
|
||||
|
||||
static void HideIntroImmediate(CanvasGroup group)
|
||||
{
|
||||
if (group == null)
|
||||
return;
|
||||
|
||||
DOTween.Kill(group);
|
||||
group.alpha = 0f;
|
||||
group.interactable = false;
|
||||
group.blocksRaycasts = false;
|
||||
group.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
static void EnsureTutorialCanvasVisible(CanvasGroup group)
|
||||
{
|
||||
Transform root = group.transform.root;
|
||||
if (root.localScale == Vector3.zero)
|
||||
root.localScale = Vector3.one;
|
||||
}
|
||||
|
||||
static Team GetPlayerTeam()
|
||||
{
|
||||
if (NetPlayer.localPlayer != null)
|
||||
return NetPlayer.localPlayer.myTeam;
|
||||
return GameManager.MyTeam;
|
||||
}
|
||||
|
||||
/// <summary>Call from the main menu before loading the game scene.</summary>
|
||||
public static void PrepareLaunch(MatchmadePlayer myPlayer, MatchmadePlayer cpuPlayer, Team myTeam = Team.Red)
|
||||
{
|
||||
|
||||
@@ -29,6 +29,35 @@ public class NetManager : NetworkManager
|
||||
{
|
||||
if (Transport.active != null)
|
||||
Transport.active.OnClientError -= OnTransportClientError;
|
||||
|
||||
if (NetworkServer.active || NetworkClient.active)
|
||||
StopNetworkingForSceneChange();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fully tears down Mirror before loading another scene. Required for tutorial host mode;
|
||||
/// <see cref="NetworkManager.StopClient"/> alone leaves the server running.
|
||||
/// </summary>
|
||||
public static void StopNetworkingForSceneChange()
|
||||
{
|
||||
MarkIntentionalClientDisconnect();
|
||||
|
||||
NetworkManager manager = singleton != null ? singleton : NetworkManager.singleton;
|
||||
if (manager != null)
|
||||
{
|
||||
if (NetworkServer.active && NetworkClient.active)
|
||||
manager.StopHost();
|
||||
else if (NetworkClient.active)
|
||||
manager.StopClient();
|
||||
else if (NetworkServer.active)
|
||||
manager.StopServer();
|
||||
return;
|
||||
}
|
||||
|
||||
if (NetworkClient.active)
|
||||
NetworkClient.Shutdown();
|
||||
if (NetworkServer.active)
|
||||
NetworkServer.Shutdown();
|
||||
}
|
||||
|
||||
static void OnTransportClientError(TransportError error, string reason)
|
||||
|
||||
@@ -31,6 +31,10 @@ public class TutorialManagerMainMenu : MonoBehaviour
|
||||
|
||||
[Header("Screen 6")]
|
||||
public GameObject screen6;
|
||||
public Button btnNext6;
|
||||
|
||||
[Header("Screen 7")]
|
||||
public GameObject screen7;
|
||||
public Button btnFinish;
|
||||
|
||||
void Start()
|
||||
@@ -38,6 +42,7 @@ public class TutorialManagerMainMenu : MonoBehaviour
|
||||
btnNext1.onClick.AddListener(OnNext1);
|
||||
btnSkip.onClick.AddListener(OnSkip);
|
||||
btnNext2.onClick.AddListener(OnNext2);
|
||||
btnNext6.onClick.AddListener(OnNext6);
|
||||
|
||||
btnFinish.onClick.AddListener(OnStartTutorialMatch);
|
||||
|
||||
@@ -77,6 +82,13 @@ public class TutorialManagerMainMenu : MonoBehaviour
|
||||
curScreen = 3;
|
||||
}
|
||||
|
||||
void OnNext6(){
|
||||
screen6.SetActive(false);
|
||||
screen7.SetActive(true);
|
||||
|
||||
curScreen = 7;
|
||||
}
|
||||
|
||||
void OnNext3(){
|
||||
screen3.SetActive(false);
|
||||
screen4.SetActive(true);
|
||||
|
||||
@@ -185,19 +185,22 @@ public class LevelLoadManager : MonoBehaviour
|
||||
|
||||
|
||||
public void Leave(string levelName = "MainMenu"){
|
||||
NetManager.MarkIntentionalClientDisconnect();
|
||||
GameManager.instance.StopClient();
|
||||
NetManager.StopNetworkingForSceneChange();
|
||||
|
||||
StartCoroutine(CoroutineWaitTillDisconnection(levelName));
|
||||
}
|
||||
|
||||
IEnumerator CoroutineWaitTillDisconnection(string levelName){
|
||||
Show(); //Load the UI until the level is loaded
|
||||
while(NetworkClient.isConnected){
|
||||
const float timeoutSeconds = 5f;
|
||||
float elapsed = 0f;
|
||||
while ((NetworkClient.isConnected || NetworkClient.active || NetworkServer.active) && elapsed < timeoutSeconds)
|
||||
{
|
||||
elapsed += Time.unscaledDeltaTime;
|
||||
yield return null;
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(1f); //Just to make sure
|
||||
yield return new WaitForSeconds(0.25f);
|
||||
|
||||
LoadLevel(levelName);
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ using UnityEngine.Rendering;
|
||||
using UnityEngine.UI;
|
||||
|
||||
/// <summary>
|
||||
/// Inverted stencil mask (see materialForRendering). For raycasts, assign
|
||||
/// <see cref="raycastPassThroughRects"/> to rects that match each visual "hole"
|
||||
/// — clicks there won’t hit this graphic and pass to UI behind.
|
||||
/// Inverted stencil mask (see materialForRendering). Raycast options live on
|
||||
/// <see cref="CutoutMaskUISettings"/> on the same GameObject (Unity's Image
|
||||
/// inspector does not show fields on Image subclasses).
|
||||
///
|
||||
/// Parent <see cref="Mask"/> also implements <see cref="ICanvasRaycastFilter"/> and only
|
||||
/// allows points inside its RectTransform. A fullscreen child under a small mask would
|
||||
@@ -15,13 +15,10 @@ using UnityEngine.UI;
|
||||
/// ignoreMasks) so hit testing matches the fullscreen Image; rendering still uses stencil
|
||||
/// because <see cref="MaskableGraphic.maskable"/> stays true.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(CutoutMaskUISettings))]
|
||||
public class CutoutMaskUI : Image
|
||||
{
|
||||
[Tooltip("Rects matching cutout/hole areas. Clicks inside are not consumed by this Image. Leave empty to use the parent Mask’s RectTransform.")]
|
||||
[SerializeField] RectTransform[] raycastPassThroughRects;
|
||||
|
||||
[Tooltip("The Image on the same object as Mask is usually the cutout shape and still receives raycasts. When this is on, that graphic’s Raycast Target is turned off while this component is enabled so the hole reaches UI behind the overlay.")]
|
||||
[SerializeField] bool disableRaycastOnParentMaskGraphic = true;
|
||||
CutoutMaskUISettings settings;
|
||||
|
||||
Mask cachedParentMask;
|
||||
Graphic cachedMaskGraphic;
|
||||
@@ -33,8 +30,10 @@ public class CutoutMaskUI : Image
|
||||
return Raycast(sp, eventCamera, ignoreMasks: true);
|
||||
}
|
||||
|
||||
public override Material materialForRendering{
|
||||
get{
|
||||
public override Material materialForRendering
|
||||
{
|
||||
get
|
||||
{
|
||||
Material material = new Material(base.materialForRendering);
|
||||
material.SetInt("_StencilComp", (int)CompareFunction.NotEqual);
|
||||
return material;
|
||||
@@ -43,8 +42,10 @@ public class CutoutMaskUI : Image
|
||||
|
||||
protected override void OnEnable()
|
||||
{
|
||||
settings = GetComponent<CutoutMaskUISettings>();
|
||||
base.OnEnable();
|
||||
if (!disableRaycastOnParentMaskGraphic)
|
||||
|
||||
if (settings == null || !settings.disableRaycastOnParentMaskGraphic)
|
||||
return;
|
||||
|
||||
cachedParentMask = GetComponentInParent<Mask>();
|
||||
@@ -76,18 +77,19 @@ public class CutoutMaskUI : Image
|
||||
public override bool IsRaycastLocationValid(Vector2 screenPoint, Camera eventCamera)
|
||||
{
|
||||
if (IsInsidePassThroughHole(screenPoint, eventCamera))
|
||||
return false;
|
||||
return settings != null && settings.blockRaycastThroughCutout;
|
||||
|
||||
return base.IsRaycastLocationValid(screenPoint, eventCamera);
|
||||
}
|
||||
|
||||
bool IsInsidePassThroughHole(Vector2 screenPoint, Camera eventCamera)
|
||||
{
|
||||
if (raycastPassThroughRects != null)
|
||||
RectTransform[] passThroughRects = settings != null ? settings.raycastPassThroughRects : null;
|
||||
if (passThroughRects != null)
|
||||
{
|
||||
for (int i = 0; i < raycastPassThroughRects.Length; i++)
|
||||
for (int i = 0; i < passThroughRects.Length; i++)
|
||||
{
|
||||
RectTransform rect = raycastPassThroughRects[i];
|
||||
RectTransform rect = passThroughRects[i];
|
||||
if (rect != null && RectTransformUtility.RectangleContainsScreenPoint(rect, screenPoint, eventCamera))
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
using UnityEngine;
|
||||
|
||||
[DisallowMultipleComponent]
|
||||
[RequireComponent(typeof(CutoutMaskUI))]
|
||||
public class CutoutMaskUISettings : MonoBehaviour
|
||||
{
|
||||
[Tooltip("Rects matching cutout/hole areas. Clicks inside are not consumed by this Image. Leave empty to use the parent Mask's RectTransform.")]
|
||||
public RectTransform[] raycastPassThroughRects;
|
||||
|
||||
[Tooltip("The Image on the same object as Mask is usually the cutout shape and still receives raycasts. When this is on, that graphic's Raycast Target is turned off while this component is enabled so the hole reaches UI behind the overlay.")]
|
||||
public bool disableRaycastOnParentMaskGraphic = true;
|
||||
|
||||
[Tooltip("When enabled, clicks inside cutout/hole areas are consumed by this Image instead of passing through to UI behind.")]
|
||||
public bool blockRaycastThroughCutout;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 29b95265513fc314995c59cb90745214
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 05aa9e67ef3520047a8c20a6f96165d8
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user