Boat Attack使用了Universal RP的许多新图形功能,可以用于探索 Universal RP 的使用方式和技巧。
您最多选择25个主题 主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 

374 行
12 KiB

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.SceneManagement;
using BoatAttack.UI;
using UnityEngine.Playables;
using UnityEngine.Rendering.Universal;
using Random = UnityEngine.Random;
namespace BoatAttack
{
public class RaceManager : MonoBehaviour
{
#region Enums
[Serializable]
public enum GameType
{
Singleplayer = 0,
LocalMultiplayer = 1,
Multiplayer = 2,
Spectator = 3,
Benchmark = 4
}
[Serializable]
public enum RaceType
{
Race,
PointToPoint,
TimeTrial
}
[Serializable]
public class Race
{
//Race options
public GameType game;
public RaceType type;
public int boatCount = 4; // currently hardcoded to 4
//Level options
public string level;
public int laps = 3;
public bool reversed;
//Competitors
public List<BoatData> boats;
}
#endregion
public static RaceManager Instance;
[NonSerialized] public static bool RaceStarted;
[NonSerialized] public static Race RaceData;
public Race demoRaceData = new Race();
[NonSerialized] public static float RaceTime;
private readonly Dictionary<int, float> _boatTimes = new Dictionary<int, float>();
public static Action<bool> raceStarted;
[Header("Assets")] public AssetReference[] boats;
public AssetReference raceUiPrefab;
public AssetReference raceUiTouchPrefab;
public static void BoatFinished(int player)
{
switch (RaceData.game)
{
case GameType.Singleplayer:
if (player == 0)
{
var raceUi = RaceData.boats[0].Boat.RaceUi;
raceUi.MatchEnd();
ReplayCamera.Instance.EnableSpectatorMode();
}
break;
case GameType.LocalMultiplayer:
break;
case GameType.Multiplayer:
break;
case GameType.Spectator:
break;
case GameType.Benchmark:
break;
default:
throw new ArgumentOutOfRangeException();
}
}
private void Awake()
{
if(Debug.isDebugBuild)
Debug.Log("RaceManager Loaded");
Instance = this;
}
private void Reset()
{
RaceStarted = false;
RaceData.boats.Clear();
RaceTime = 0f;
_boatTimes.Clear();
raceStarted = null;
}
public static void Setup(Scene scene, LoadSceneMode mode)
{
Instance.StartCoroutine(SetupRace());
}
public static IEnumerator SetupRace()
{
if(RaceData == null) RaceData = Instance.demoRaceData; // make sure we have the data, otherwise default to demo data
while (WaypointGroup.Instance == null) // TODO need to re-write whole game loading/race setup logic as it is dirty
{
yield return null;
}
WaypointGroup.Instance.Setup(RaceData.reversed); // setup waypoints
yield return Instance.StartCoroutine(CreateBoats()); // spawn boats;
switch (RaceData.game)
{
case GameType.Singleplayer:
yield return Instance.StartCoroutine(CreatePlayerUi(0));
SetupCamera(0); // setup camera for player 1
break;
case GameType.LocalMultiplayer:
break;
case GameType.Multiplayer:
break;
case GameType.Spectator:
ReplayCamera.Instance.EnableSpectatorMode();
break;
case GameType.Benchmark:
break;
default:
throw new ArgumentOutOfRangeException();
}
Instance.StartCoroutine(BeginRace());
}
public static void SetGameType(GameType gameType)
{
RaceData = new Race {game = gameType,
boats = new List<BoatData>(),
boatCount = 4,
laps = 3,
type = RaceType.Race
};
Debug.Log($"Game type set to:{RaceData.game}");
switch (RaceData.game)
{
case GameType.Singleplayer:
var b = new BoatData();
b.human = true; // single player is human
RaceData.boats.Add(b); // add player boat
GenerateRandomBoats(RaceData.boatCount - 1); // add random AI
break;
case GameType.Spectator:
GenerateRandomBoats(RaceData.boatCount);
break;
case GameType.LocalMultiplayer:
Debug.LogError("Not Implemented");
break;
case GameType.Multiplayer:
Debug.LogError("Not Implemented");
break;
case GameType.Benchmark:
Debug.LogError("Not Implemented");
break;
default:
throw new ArgumentOutOfRangeException();
}
}
public static void SetLevel(int levelIndex)
{
RaceData.level = ConstantData.GetLevelName(levelIndex);
Debug.Log($"Level set to:{levelIndex} with path:{RaceData.level}");
}
/// <summary>
/// Triggered to begin the race
/// </summary>
/// <returns></returns>
private static IEnumerator BeginRace()
{
var introCams = GameObject.FindWithTag("introCameras");
introCams.TryGetComponent<PlayableDirector>(out var introDirector);
if (introDirector)
{
while (introDirector.state == PlayState.Playing)
{
yield return null;
}
introCams.SetActive(false);
}
yield return new WaitForSeconds(3f); // countdown 3..2..1..
RaceStarted = true;
raceStarted?.Invoke(RaceStarted);
SceneManager.sceneLoaded -= Setup;
}
/// <summary>
/// Triggered when the race has finished
/// </summary>
private static void EndRace()
{
RaceStarted = false;
switch (RaceData.game)
{
case GameType.Spectator:
UnloadRace();
break;
case GameType.Singleplayer:
SetupCamera(0, true);
break;
case GameType.LocalMultiplayer:
break;
case GameType.Multiplayer:
break;
case GameType.Benchmark:
break;
default:
throw new ArgumentOutOfRangeException();
}
}
private void LateUpdate()
{
if (!RaceStarted) return;
int finished = RaceData.boatCount;
for (var i = 0; i < RaceData.boats.Count; i++)
{
var boat = RaceData.boats[i].Boat;
if (boat.MatchComplete)
{
_boatTimes[i] = Mathf.Infinity; // completed the race so no need to update
--finished;
}
else
{
_boatTimes[i] = boat.LapPercentage + boat.LapCount;
}
}
if(RaceStarted && finished == 0)
EndRace();
var mySortedList = _boatTimes.OrderBy(d => d.Value).ToList();
var place = RaceData.boatCount;
foreach (var boat in mySortedList.Select(index => RaceData.boats[index.Key].Boat).Where(boat => !boat.MatchComplete))
{
boat.Place = place;
place--;
}
RaceTime += Time.deltaTime;
}
#region Utilities
public static void LoadGame()
{
AppSettings.LoadScene(RaceData.level);
SceneManager.sceneLoaded += Setup;
}
public static void UnloadRace()
{
Debug.LogWarning("Unloading Race");
if(Instance.raceUiPrefab != null && Instance.raceUiPrefab.IsValid())
{
Instance.raceUiPrefab.ReleaseAsset();
}
Instance.Reset();
AppSettings.LoadScene(0, LoadSceneMode.Single);
}
public static void SetHull(int player, int hull) => RaceData.boats[player].boatPrefab = Instance.boats[hull];
private static IEnumerator CreateBoats()
{
for (int i = 0; i < RaceData.boats.Count; i++)
{
var boat = RaceData.boats[i]; // boat to setup
// Load prefab
var startingPosition = WaypointGroup.Instance.StartingPositions[i];
AsyncOperationHandle<GameObject> boatLoading = Addressables.InstantiateAsync(boat.boatPrefab, startingPosition.GetColumn(3),
Quaternion.LookRotation(startingPosition.GetColumn(2)));
yield return boatLoading; // wait for boat asset to load
boatLoading.Result.name = boat.boatName; // set the name of the boat
boatLoading.Result.TryGetComponent<Boat>(out var boatController);
boat.SetController(boatLoading.Result, boatController);
boatController.Setup(i + 1, boat.human, boat.livery);
Instance._boatTimes.Add(i, 0f);
}
}
private static void GenerateRandomBoats(int count, bool ai = true)
{
for (var i = 0; i < count; i++)
{
var boat = new BoatData();
Random.InitState(ConstantData.SeedNow+i);
boat.boatName = ConstantData.AiNames[Random.Range(0, ConstantData.AiNames.Length)];
BoatLivery livery = new BoatLivery
{
primaryColor = ConstantData.GetRandomPaletteColor,
trimColor = ConstantData.GetRandomPaletteColor
};
boat.livery = livery;
boat.boatPrefab = Instance.boats[Random.Range(0, Instance.boats.Length)];
if (ai)
boat.human = false;
RaceData.boats.Add(boat);
}
}
private static IEnumerator CreatePlayerUi(int player)
{
var touch = Input.touchSupported && Input.multiTouchEnabled &&
(Application.platform == RuntimePlatform.Android ||
Application.platform == RuntimePlatform.IPhonePlayer);
var uiAsset = touch ? Instance.raceUiTouchPrefab : Instance.raceUiPrefab;
var uiLoading = uiAsset.InstantiateAsync();
yield return uiLoading;
if (uiLoading.Result.TryGetComponent(out RaceUI uiComponent))
{
var boatData = RaceData.boats[player];
boatData.Boat.RaceUi = uiComponent;
uiComponent.Setup(player);
}
}
private static void SetupCamera(int player, bool remove = false)
{
// Setup race camera
if(remove)
AppSettings.MainCamera.cullingMask &= ~(1 << LayerMask.NameToLayer($"Player{player + 1}")); // TODO - this needs more work for when adding splitscreen.
else
AppSettings.MainCamera.cullingMask |= 1 << LayerMask.NameToLayer($"Player{player + 1}"); // TODO - this needs more work for when adding splitscreen.
}
public static int GetLapCount()
{
if (RaceData != null && RaceData.type == RaceType.Race)
{
return RaceData.laps;
}
return -1;
}
#endregion
}
}