任务系统一半多,商城筛选功能,一些细节和免费包

This commit is contained in:
FloatGaming
2026-03-15 04:27:21 +08:00
parent 416190b23e
commit ec42f2e34b
344 changed files with 32225 additions and 6481 deletions
@@ -0,0 +1,65 @@
public static class DailyTaskEventHub
{
public static void ReportLogin()
{
DailyTaskService.EnsureInstance().ReportEvent(new DailyTaskEventData
{
taskType = userTasksPool.TaskType.Login,
amount = 1f
});
}
public static void ReportPlaySong()
{
DailyTaskService.EnsureInstance().ReportEvent(new DailyTaskEventData
{
taskType = userTasksPool.TaskType.PlaySong,
amount = 1f
});
}
public static void ReportFullCombo()
{
DailyTaskService.EnsureInstance().ReportEvent(new DailyTaskEventData
{
taskType = userTasksPool.TaskType.FullCombo,
amount = 1f
});
}
public static void ReportTotalScore(float totalScore)
{
DailyTaskService.EnsureInstance().ReportEvent(new DailyTaskEventData
{
taskType = userTasksPool.TaskType.TotalScore,
amount = totalScore
});
}
public static void ReportUseItem(int amount)
{
DailyTaskService.EnsureInstance().ReportEvent(new DailyTaskEventData
{
taskType = userTasksPool.TaskType.UseItem,
amount = amount
});
}
public static void ReportSpendCoins(int amount)
{
DailyTaskService.EnsureInstance().ReportEvent(new DailyTaskEventData
{
taskType = userTasksPool.TaskType.SpendCoins,
amount = amount
});
}
public static void ReportGameDuration(float durationSeconds)
{
DailyTaskService.EnsureInstance().ReportEvent(new DailyTaskEventData
{
taskType = userTasksPool.TaskType.GameDuration,
amount = durationSeconds
});
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 14861f485792abe459d3724786b183f8
@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
[Serializable]
public class DailyTaskRuntimeEntry
{
public string taskID;
public float progress;
public bool isCompleted;
public bool isClaimed;
}
[Serializable]
public class DailyTaskSaveData
{
public string dateKey;
public int refreshUsedCount;
public List<DailyTaskRuntimeEntry> activeTasks = new List<DailyTaskRuntimeEntry>();
public List<DailyTaskAccumulatedProgress> accumulatedProgress = new List<DailyTaskAccumulatedProgress>();
}
public struct DailyTaskEventData
{
public userTasksPool.TaskType taskType;
public float amount;
}
[Serializable]
public class DailyTaskAccumulatedProgress
{
public int taskType;
public float progress;
}
public sealed class DailyTaskViewData
{
public userTasksPool.TaskDefinition definition;
public DailyTaskRuntimeEntry runtimeEntry;
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 03c6abfd01940964aa67c37ecf92af16
@@ -0,0 +1,49 @@
using System;
using System.IO;
using UnityEngine;
public static class DailyTaskSaveService
{
private static string SaveFilePath
{
get { return Path.Combine(Application.persistentDataPath, "daily_tasks.json"); }
}
public static DailyTaskSaveData Load()
{
if (!File.Exists(SaveFilePath))
{
return new DailyTaskSaveData();
}
try
{
var json = File.ReadAllText(SaveFilePath);
var data = JsonUtility.FromJson<DailyTaskSaveData>(json);
return data ?? new DailyTaskSaveData();
}
catch (Exception ex)
{
Debug.LogWarning($"[DailyTaskSaveService] Load failed: {ex.Message}");
return new DailyTaskSaveData();
}
}
public static void Save(DailyTaskSaveData data)
{
if (data == null)
{
return;
}
try
{
var json = JsonUtility.ToJson(data, true);
File.WriteAllText(SaveFilePath, json);
}
catch (Exception ex)
{
Debug.LogWarning($"[DailyTaskSaveService] Save failed: {ex.Message}");
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: abd7cb33db74f5f4096c58a5416f9b53
@@ -0,0 +1,665 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.SceneManagement;
public sealed class DailyTaskService : MonoBehaviour
{
public static DailyTaskService Instance { get; private set; }
public event Action OnTasksChanged;
private readonly Queue<DailyTaskEventData> pendingEvents = new Queue<DailyTaskEventData>();
private readonly Dictionary<string, userTasksPool.TaskDefinition> definitionById = new Dictionary<string, userTasksPool.TaskDefinition>(StringComparer.Ordinal);
private DailyTaskSaveData saveData;
private userTasksPool configuredTaskPool;
private int configuredTaskMaxSize = 5;
private int configuredRefreshMaxTimes = 3;
private bool initialized;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void Bootstrap()
{
EnsureInstance();
}
public static DailyTaskService EnsureInstance()
{
if (Instance != null)
{
return Instance;
}
var host = new GameObject("__daily_task_service");
host.hideFlags = HideFlags.HideInHierarchy | HideFlags.HideInInspector;
DontDestroyOnLoad(host);
Instance = host.AddComponent<DailyTaskService>();
return Instance;
}
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
SceneManager.sceneLoaded -= OnSceneLoaded;
SceneManager.sceneLoaded += OnSceneLoaded;
InitializeStorageIfNeeded();
}
private void OnDestroy()
{
if (Instance == this)
{
SceneManager.sceneLoaded -= OnSceneLoaded;
}
}
public void Configure(userTasksPool taskPool, int taskMaxSize, int refreshMaxTimes)
{
configuredTaskPool = taskPool;
configuredTaskMaxSize = Mathf.Max(1, taskMaxSize);
configuredRefreshMaxTimes = Mathf.Max(0, refreshMaxTimes);
RebuildDefinitionIndex();
EnsureTodayTasks();
FlushPendingEvents();
NotifyTasksChanged();
}
public IReadOnlyList<DailyTaskViewData> GetActiveTaskViews()
{
EnsureTodayTasks();
var result = new List<DailyTaskViewData>();
if (saveData == null || saveData.activeTasks == null)
{
return result;
}
for (int i = 0; i < saveData.activeTasks.Count; i++)
{
var runtimeEntry = saveData.activeTasks[i];
if (runtimeEntry == null || string.IsNullOrEmpty(runtimeEntry.taskID))
{
continue;
}
userTasksPool.TaskDefinition definition;
if (!definitionById.TryGetValue(runtimeEntry.taskID, out definition))
{
continue;
}
result.Add(new DailyTaskViewData
{
definition = definition,
runtimeEntry = runtimeEntry
});
}
return result;
}
public void ReportEvent(DailyTaskEventData eventData)
{
InitializeStorageIfNeeded();
if (!IsConfigured())
{
pendingEvents.Enqueue(eventData);
return;
}
EnsureTodayTasks();
float accumulatedIncrement = GetAccumulatedIncrement(eventData);
if (accumulatedIncrement <= 0f)
{
return;
}
float accumulatedValue = AddAccumulatedProgress(eventData.taskType, accumulatedIncrement);
bool changed = false;
for (int i = 0; i < saveData.activeTasks.Count; i++)
{
var runtimeEntry = saveData.activeTasks[i];
if (runtimeEntry == null || runtimeEntry.isClaimed)
{
continue;
}
userTasksPool.TaskDefinition definition;
if (!definitionById.TryGetValue(runtimeEntry.taskID, out definition))
{
continue;
}
if (!IsEventMatch(definition, eventData))
{
continue;
}
float previousProgress = runtimeEntry.progress;
bool previousCompleted = runtimeEntry.isCompleted;
runtimeEntry.progress = GetInitialProgress(definition, accumulatedValue);
runtimeEntry.isCompleted = runtimeEntry.progress >= Mathf.Max(0.0001f, definition.targetValue);
if (!Mathf.Approximately(previousProgress, runtimeEntry.progress) || previousCompleted != runtimeEntry.isCompleted)
{
changed = true;
}
}
if (changed)
{
Save();
NotifyTasksChanged();
}
}
public bool TryRefreshTasks()
{
if (!IsConfigured())
{
return false;
}
EnsureTodayTasks();
if (saveData.refreshUsedCount >= configuredRefreshMaxTimes)
{
return false;
}
saveData.refreshUsedCount += 1;
GenerateDailyTasks();
Save();
NotifyTasksChanged();
return true;
}
public int GetRemainingRefreshCount()
{
EnsureTodayTasks();
return Mathf.Max(0, configuredRefreshMaxTimes - (saveData != null ? saveData.refreshUsedCount : 0));
}
public bool TryClaimReward(string taskID, Player_SO playerData, out string failureMessage)
{
failureMessage = string.Empty;
if (string.IsNullOrWhiteSpace(taskID))
{
failureMessage = "任务参数无效";
return false;
}
EnsureTodayTasks();
DailyTaskRuntimeEntry runtimeEntry;
userTasksPool.TaskDefinition definition;
if (!TryGetTaskPair(taskID, out runtimeEntry, out definition))
{
failureMessage = "未找到任务";
return false;
}
if (!runtimeEntry.isCompleted)
{
failureMessage = "任务尚未完成";
return false;
}
if (runtimeEntry.isClaimed)
{
failureMessage = "奖励已领取";
return false;
}
if (!TryGrantReward(definition, playerData, out failureMessage))
{
return false;
}
runtimeEntry.isClaimed = true;
Save();
NotifyTasksChanged();
return true;
}
public int ClaimAllAvailableRewards(Player_SO playerData, out string failureMessage)
{
failureMessage = string.Empty;
EnsureTodayTasks();
int claimedCount = 0;
var taskIds = new List<string>();
var views = GetActiveTaskViews();
for (int i = 0; i < views.Count; i++)
{
var runtimeEntry = views[i].runtimeEntry;
if (runtimeEntry == null || !runtimeEntry.isCompleted || runtimeEntry.isClaimed)
{
continue;
}
taskIds.Add(runtimeEntry.taskID);
}
for (int i = 0; i < taskIds.Count; i++)
{
string claimError;
if (!TryClaimReward(taskIds[i], playerData, out claimError))
{
if (claimedCount <= 0)
{
failureMessage = claimError;
}
continue;
}
claimedCount += 1;
}
if (claimedCount <= 0 && string.IsNullOrEmpty(failureMessage))
{
failureMessage = "暂无可领取奖励";
}
return claimedCount;
}
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
if (string.Equals(scene.name, "UI_UI", StringComparison.OrdinalIgnoreCase))
{
ReportEvent(new DailyTaskEventData
{
taskType = userTasksPool.TaskType.Login,
amount = 1f
});
}
}
private void InitializeStorageIfNeeded()
{
if (initialized)
{
return;
}
saveData = DailyTaskSaveService.Load() ?? new DailyTaskSaveData();
initialized = true;
}
private void EnsureTodayTasks()
{
InitializeStorageIfNeeded();
if (!IsConfigured())
{
return;
}
string todayKey = GetTodayKey();
if (saveData == null)
{
saveData = new DailyTaskSaveData();
}
bool needRegenerate = !string.Equals(saveData.dateKey, todayKey, StringComparison.Ordinal)
|| saveData.activeTasks == null
|| saveData.activeTasks.Count == 0;
if (!needRegenerate)
{
EnsureAccumulatedProgressCompatibility();
return;
}
saveData.dateKey = todayKey;
saveData.refreshUsedCount = 0;
saveData.accumulatedProgress = new List<DailyTaskAccumulatedProgress>();
GenerateDailyTasks();
Save();
}
private void GenerateDailyTasks()
{
if (saveData == null)
{
saveData = new DailyTaskSaveData();
}
saveData.activeTasks = new List<DailyTaskRuntimeEntry>();
var candidates = configuredTaskPool.tasks
.Where(t => t != null && t.addToTaskPool && !string.IsNullOrWhiteSpace(t.taskID) && t.selectionWeight > 0)
.ToList();
if (candidates.Count == 0)
{
return;
}
int pickCount = Mathf.Min(configuredTaskMaxSize, candidates.Count);
var random = new System.Random(unchecked(GetTodayKey().GetHashCode() + saveData.refreshUsedCount * 397));
var selectedDefinitions = new List<userTasksPool.TaskDefinition>();
while (selectedDefinitions.Count < pickCount && candidates.Count > 0)
{
int totalWeight = candidates.Sum(t => Mathf.Max(1, t.selectionWeight));
int roll = random.Next(0, totalWeight);
int cumulative = 0;
int selectedIndex = 0;
for (int i = 0; i < candidates.Count; i++)
{
cumulative += Mathf.Max(1, candidates[i].selectionWeight);
if (roll < cumulative)
{
selectedIndex = i;
break;
}
}
selectedDefinitions.Add(candidates[selectedIndex]);
candidates.RemoveAt(selectedIndex);
}
for (int i = 0; i < selectedDefinitions.Count; i++)
{
var definition = selectedDefinitions[i];
float currentProgress = GetInitialProgress(definition, GetAccumulatedProgress(definition.taskType));
saveData.activeTasks.Add(new DailyTaskRuntimeEntry
{
taskID = definition.taskID,
progress = currentProgress,
isCompleted = currentProgress >= Mathf.Max(0.0001f, definition.targetValue),
isClaimed = false
});
}
}
private bool IsConfigured()
{
return configuredTaskPool != null && definitionById.Count > 0;
}
private void RebuildDefinitionIndex()
{
definitionById.Clear();
if (configuredTaskPool == null || configuredTaskPool.tasks == null)
{
return;
}
for (int i = 0; i < configuredTaskPool.tasks.Count; i++)
{
var definition = configuredTaskPool.tasks[i];
if (definition == null || string.IsNullOrWhiteSpace(definition.taskID))
{
continue;
}
if (!definitionById.ContainsKey(definition.taskID))
{
definitionById.Add(definition.taskID, definition);
}
}
}
private void EnsureAccumulatedProgressCompatibility()
{
if (saveData == null)
{
return;
}
if (saveData.accumulatedProgress == null)
{
saveData.accumulatedProgress = new List<DailyTaskAccumulatedProgress>();
}
if (saveData.accumulatedProgress.Count > 0 || saveData.activeTasks == null || saveData.activeTasks.Count == 0)
{
return;
}
bool changed = false;
for (int i = 0; i < saveData.activeTasks.Count; i++)
{
var runtimeEntry = saveData.activeTasks[i];
if (runtimeEntry == null || string.IsNullOrWhiteSpace(runtimeEntry.taskID))
{
continue;
}
userTasksPool.TaskDefinition definition;
if (!definitionById.TryGetValue(runtimeEntry.taskID, out definition))
{
continue;
}
AddAccumulatedProgress(definition.taskType, Mathf.Max(0f, runtimeEntry.progress));
changed = true;
}
if (changed)
{
Save();
}
}
private void FlushPendingEvents()
{
if (!IsConfigured())
{
return;
}
while (pendingEvents.Count > 0)
{
ReportEvent(pendingEvents.Dequeue());
}
}
private static bool IsEventMatch(userTasksPool.TaskDefinition definition, DailyTaskEventData eventData)
{
return definition != null && definition.taskType == eventData.taskType;
}
private float AddAccumulatedProgress(userTasksPool.TaskType taskType, float amount)
{
if (saveData == null)
{
saveData = new DailyTaskSaveData();
}
if (saveData.accumulatedProgress == null)
{
saveData.accumulatedProgress = new List<DailyTaskAccumulatedProgress>();
}
int taskTypeValue = (int)taskType;
for (int i = 0; i < saveData.accumulatedProgress.Count; i++)
{
var entry = saveData.accumulatedProgress[i];
if (entry == null || entry.taskType != taskTypeValue)
{
continue;
}
entry.progress = Mathf.Max(0f, entry.progress + amount);
return entry.progress;
}
var newEntry = new DailyTaskAccumulatedProgress
{
taskType = taskTypeValue,
progress = Mathf.Max(0f, amount)
};
saveData.accumulatedProgress.Add(newEntry);
return newEntry.progress;
}
private float GetAccumulatedProgress(userTasksPool.TaskType taskType)
{
if (saveData == null || saveData.accumulatedProgress == null)
{
return 0f;
}
int taskTypeValue = (int)taskType;
for (int i = 0; i < saveData.accumulatedProgress.Count; i++)
{
var entry = saveData.accumulatedProgress[i];
if (entry == null || entry.taskType != taskTypeValue)
{
continue;
}
return Mathf.Max(0f, entry.progress);
}
return 0f;
}
private static float GetInitialProgress(userTasksPool.TaskDefinition definition, float accumulatedValue)
{
if (definition == null)
{
return 0f;
}
return Mathf.Min(Mathf.Max(0f, accumulatedValue), Mathf.Max(0.0001f, definition.targetValue));
}
private bool TryGetTaskPair(string taskID, out DailyTaskRuntimeEntry runtimeEntry, out userTasksPool.TaskDefinition definition)
{
runtimeEntry = null;
definition = null;
if (saveData == null || saveData.activeTasks == null)
{
return false;
}
for (int i = 0; i < saveData.activeTasks.Count; i++)
{
var candidate = saveData.activeTasks[i];
if (candidate == null || !string.Equals(candidate.taskID, taskID, StringComparison.Ordinal))
{
continue;
}
userTasksPool.TaskDefinition matchedDefinition;
if (!definitionById.TryGetValue(candidate.taskID, out matchedDefinition))
{
return false;
}
runtimeEntry = candidate;
definition = matchedDefinition;
return true;
}
return false;
}
private static bool TryGrantReward(userTasksPool.TaskDefinition definition, Player_SO playerData, out string failureMessage)
{
failureMessage = string.Empty;
if (definition == null)
{
failureMessage = "任务定义无效";
return false;
}
if (definition.rewardAmount <= 0)
{
failureMessage = "奖励数量无效";
return false;
}
switch (definition.rewardType)
{
case userTasksPool.RewardType.coins:
{
var wallet = PlayerEconomyLedger.EnsureInstance();
if (playerData != null)
{
wallet.AttachPlayerData(playerData);
}
wallet.AddCoins(definition.rewardAmount);
return true;
}
case userTasksPool.RewardType.expBottle:
{
var bottleLedger = ExpBottleLedger.EnsureInstance();
if (playerData != null)
{
bottleLedger.AttachPlayerData(playerData);
}
bottleLedger.Add(definition.rewardExpBottleKind, definition.rewardAmount);
return true;
}
case userTasksPool.RewardType.dushMaterial:
{
var materialLedger = DushMaterialLedger.EnsureInstance();
if (playerData != null)
{
materialLedger.AttachPlayerData(playerData);
}
materialLedger.Add(definition.rewardDushMaterialKind, definition.rewardAmount);
return true;
}
default:
failureMessage = "暂不支持该奖励类型";
return false;
}
}
private static float GetAccumulatedIncrement(DailyTaskEventData eventData)
{
switch (eventData.taskType)
{
case userTasksPool.TaskType.TotalScore:
case userTasksPool.TaskType.SpendCoins:
case userTasksPool.TaskType.GameDuration:
return Mathf.Max(0f, eventData.amount);
case userTasksPool.TaskType.Login:
case userTasksPool.TaskType.PlaySong:
case userTasksPool.TaskType.FullCombo:
case userTasksPool.TaskType.UseItem:
return Mathf.Max(1f, eventData.amount);
default:
return Mathf.Max(0f, eventData.amount);
}
}
private static string GetTodayKey()
{
return DateTime.Now.ToString("yyyy-MM-dd");
}
private void Save()
{
DailyTaskSaveService.Save(saveData);
}
private void NotifyTasksChanged()
{
if (OnTasksChanged != null)
{
OnTasksChanged();
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 41ce491ff1399f340bd82a554499a880
+303 -4
View File
@@ -1,7 +1,6 @@
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Generic;
using Bansonic;
using UnityEngine;
using UnityEngine.UI;
public class dailyTaskManager : MonoBehaviour
@@ -9,16 +8,21 @@ public class dailyTaskManager : MonoBehaviour
[Header("任务池so")]
public userTasksPool taskPool;
[Header("player data")]
public Player_SO playerData;
[Header("今日进度")]
public Slider todayProgressSlider;
public Text todayProgressPercent;
public Text todayProgressText;
public Text notasksanymore;
[Header("一键领取")]
public Button oneKeyRewardBtn;
[Header("刷新任务")]
public Button refreshTaskBtn;
[Header("任务配置")]
[Tooltip("任务列表最大容量")]
public int taskMaxSize = 5;
@@ -28,4 +32,299 @@ public class dailyTaskManager : MonoBehaviour
[Header("prefabs")]
public GameObject dailyTaskItemPrefab;
public Transform dailyTaskItemParent;
[Header("reward sprites")]
public Sprite coinRewardSprite;
private readonly List<GameObject> spawnedTaskItems = new List<GameObject>();
private readonly Dictionary<int, Sprite> rewardSpriteByItemId = new Dictionary<int, Sprite>();
private void Start()
{
CacheRewardSpritesFromStoreItems();
var taskService = DailyTaskService.EnsureInstance();
taskService.Configure(taskPool, taskMaxSize, refreshMaxTimes);
taskService.OnTasksChanged += RefreshTaskUi;
if (oneKeyRewardBtn != null)
{
oneKeyRewardBtn.onClick.RemoveAllListeners();
oneKeyRewardBtn.onClick.AddListener(OnOneKeyRewardClicked);
}
if (refreshTaskBtn != null)
{
refreshTaskBtn.onClick.RemoveAllListeners();
refreshTaskBtn.onClick.AddListener(OnRefreshTaskClicked);
}
RefreshTaskUi();
}
private void OnDestroy()
{
if (DailyTaskService.Instance != null)
{
DailyTaskService.Instance.OnTasksChanged -= RefreshTaskUi;
}
}
private void RefreshTaskUi()
{
ClearSpawnedItems();
var tasks = DailyTaskService.EnsureInstance().GetActiveTaskViews();
if (notasksanymore != null)
{
notasksanymore.gameObject.SetActive(tasks.Count == 0);
notasksanymore.text = tasks.Count == 0 ? "今日暂无任务" : string.Empty;
}
int completedCount = 0;
for (int i = 0; i < tasks.Count; i++)
{
var taskView = tasks[i];
if (taskView == null || taskView.definition == null || taskView.runtimeEntry == null)
{
continue;
}
if (taskView.runtimeEntry.isCompleted)
{
completedCount += 1;
}
SpawnTaskItem(i, taskView);
}
UpdateTodayProgress(tasks.Count, completedCount);
RefreshOneKeyRewardButtonState(tasks);
RefreshTaskButtonState();
}
private void SpawnTaskItem(int index, DailyTaskViewData taskView)
{
if (dailyTaskItemPrefab == null || dailyTaskItemParent == null)
{
return;
}
var instance = Instantiate(dailyTaskItemPrefab, dailyTaskItemParent);
spawnedTaskItems.Add(instance);
var controller = instance.GetComponent<taskPrefabController>();
if (controller == null)
{
return;
}
controller.Setup(taskView, index + 1, ResolveRewardSprite(taskView.definition), () => OnSingleRewardClicked(taskView.runtimeEntry.taskID));
}
private void UpdateTodayProgress(int totalTaskCount, int completedCount)
{
float progress = totalTaskCount <= 0 ? 0f : (float)completedCount / totalTaskCount;
if (todayProgressSlider != null)
{
todayProgressSlider.value = progress;
}
if (todayProgressPercent != null)
{
todayProgressPercent.text = (progress * 100f).ToString("F0") + "%";
}
if (todayProgressText != null)
{
todayProgressText.text = completedCount + "/" + totalTaskCount;
}
}
private void ClearSpawnedItems()
{
for (int i = spawnedTaskItems.Count - 1; i >= 0; i--)
{
if (spawnedTaskItems[i] != null)
{
Destroy(spawnedTaskItems[i]);
}
}
spawnedTaskItems.Clear();
}
private void OnSingleRewardClicked(string taskID)
{
string failureMessage;
if (!DailyTaskService.EnsureInstance().TryClaimReward(taskID, playerData, out failureMessage))
{
if (!string.IsNullOrEmpty(failureMessage))
{
gNotice.warning.display(failureMessage);
}
return;
}
RefreshTaskUi();
}
private void OnOneKeyRewardClicked()
{
string failureMessage;
int claimedCount = DailyTaskService.EnsureInstance().ClaimAllAvailableRewards(playerData, out failureMessage);
if (claimedCount <= 0)
{
if (!string.IsNullOrEmpty(failureMessage))
{
gNotice.warning.display(failureMessage);
}
return;
}
RefreshTaskUi();
}
private void RefreshOneKeyRewardButtonState(IReadOnlyList<DailyTaskViewData> tasks)
{
if (oneKeyRewardBtn == null)
{
return;
}
bool hasClaimableReward = false;
for (int i = 0; i < tasks.Count; i++)
{
var taskView = tasks[i];
if (taskView == null || taskView.runtimeEntry == null)
{
continue;
}
if (taskView.runtimeEntry.isCompleted && !taskView.runtimeEntry.isClaimed)
{
hasClaimableReward = true;
break;
}
}
oneKeyRewardBtn.interactable = hasClaimableReward;
}
private void OnRefreshTaskClicked()
{
if (taskPool == null)
{
gNotice.warning.display("未配置任务池");
return;
}
var taskService = DailyTaskService.EnsureInstance();
if (!taskService.TryRefreshTasks())
{
if (taskService.GetRemainingRefreshCount() <= 0)
{
gNotice.warning.display("已达到今日刷新上限");
}
else
{
gNotice.warning.display("刷新失败");
}
RefreshTaskButtonState();
return;
}
RefreshTaskUi();
}
private void RefreshTaskButtonState()
{
if (refreshTaskBtn == null)
{
return;
}
refreshTaskBtn.interactable = taskPool != null && DailyTaskService.EnsureInstance().GetRemainingRefreshCount() > 0;
}
private Sprite ResolveRewardSprite(userTasksPool.TaskDefinition definition)
{
if (definition == null)
{
return null;
}
switch (definition.rewardType)
{
case userTasksPool.RewardType.coins:
return coinRewardSprite;
case userTasksPool.RewardType.expBottle:
return GetRewardSpriteByItemId(GetExpBottleRewardItemId(definition.rewardExpBottleKind));
case userTasksPool.RewardType.dushMaterial:
return GetRewardSpriteByItemId(GetDushMaterialRewardItemId(definition.rewardDushMaterialKind));
default:
return null;
}
}
private void CacheRewardSpritesFromStoreItems()
{
rewardSpriteByItemId.Clear();
var rewardStoreItems = Resources.LoadAll<storeItemSO>("so/storeSO");
for (int i = 0; i < rewardStoreItems.Length; i++)
{
var itemSO = rewardStoreItems[i];
if (itemSO == null || itemSO.itemIcon == null)
{
continue;
}
rewardSpriteByItemId[itemSO.itemID] = itemSO.itemIcon;
}
}
private Sprite GetRewardSpriteByItemId(int itemId)
{
if (itemId <= 0)
{
return null;
}
Sprite sprite;
return rewardSpriteByItemId.TryGetValue(itemId, out sprite) ? sprite : null;
}
private static int GetExpBottleRewardItemId(ExpBottleKind kind)
{
switch (kind)
{
case ExpBottleKind.Common: return 78001;
case ExpBottleKind.Medium: return 78002;
case ExpBottleKind.Superior: return 78003;
case ExpBottleKind.Supreme: return 78004;
case ExpBottleKind.Extraordinary: return 78005;
case ExpBottleKind.Celestial: return 78006;
case ExpBottleKind.RainAll: return 78011;
case ExpBottleKind.AdvancedRainAll: return 78012;
case ExpBottleKind.SuperRainAll: return 78013;
default: return 0;
}
}
private static int GetDushMaterialRewardItemId(DushMaterialKind kind)
{
switch (kind)
{
case DushMaterialKind.Material78021: return 78021;
case DushMaterialKind.Material78022: return 78022;
case DushMaterialKind.Material78023: return 78023;
case DushMaterialKind.Material78024: return 78024;
default: return 0;
}
}
}
@@ -14,13 +14,16 @@ MonoBehaviour:
m_EditorClassIdentifier:
tasks:
- taskID: 10101
description: "\u767B\u5F55\u6E38\u620F"
description: "\u767B\u5F55\u6E38\u620Fv50"
taskType: 0
refreshFrequency: 1
targetValue: 1
rewardType: 0
rewardAmount: 0
rewardAmount: 50
rewardExpBottleKind: 0
rewardDushMaterialKind: 0
addToTaskPool: 1
selectionWeight: 1
- taskID: 10102
description: "\u5B8C\u62103\u9996\u6B4C\u66F2"
taskType: 1
@@ -28,28 +31,62 @@ MonoBehaviour:
targetValue: 3
rewardType: 0
rewardAmount: 0
rewardExpBottleKind: 0
rewardDushMaterialKind: 0
addToTaskPool: 1
selectionWeight: 1
- taskID: 10103
description: "\u5B8C\u6210\u4E00\u6B2199\u8FDE\u51FB"
description: "\u8FBE\u62101\u6B21Full Combo"
taskType: 2
refreshFrequency: 1
targetValue: 99
targetValue: 1
rewardType: 0
rewardAmount: 0
rewardExpBottleKind: 0
rewardDushMaterialKind: 0
addToTaskPool: 1
selectionWeight: 1
- taskID: 10104
description: "\u8FBE\u5230\u4E00\u6B21\u603B\u52061000000"
description: "\u7D2F\u8BA1\u83B7\u5F971000000\u5206"
taskType: 3
refreshFrequency: 1
targetValue: 1000000
rewardType: 0
rewardAmount: 0
rewardType: 1
rewardAmount: 4
rewardExpBottleKind: 0
rewardDushMaterialKind: 0
addToTaskPool: 1
selectionWeight: 1
- taskID: 10105
description: "\u4F7F\u7528\u4E00\u6B21\u9644\u9B54\u4E4B\u74F6"
description: "\u4F7F\u7528\u4EFB\u610F\u7C7B\u578B\u7ECF\u9A8C\u74F6\u4E00\u6B21"
taskType: 5
refreshFrequency: 1
targetValue: 1
rewardType: 0
rewardAmount: 0
addToTaskPool: 0
rewardExpBottleKind: 0
rewardDushMaterialKind: 0
addToTaskPool: 1
selectionWeight: 1
- taskID: 10106
description: "\u82B1\u8D391000\u91D1\u5E01"
taskType: 7
refreshFrequency: 1
targetValue: 1000
rewardType: 0
rewardAmount: 0
rewardExpBottleKind: 0
rewardDushMaterialKind: 0
addToTaskPool: 1
selectionWeight: 1
- taskID: 10107
description: "\u6E38\u620F\u65F6\u957F\u8FBE\u523030\u5206\u949F"
taskType: 8
refreshFrequency: 1
targetValue: 1800
rewardType: 0
rewardAmount: 0
rewardExpBottleKind: 0
rewardDushMaterialKind: 0
addToTaskPool: 1
selectionWeight: 1
@@ -1,5 +1,5 @@
using System.Collections.Generic;
using UnityEngine;
using System.Collections.Generic;
[CreateAssetMenu(fileName = "NewUserTasksPool", menuName = "DailyTask/UserTasksPool")]
public class userTasksPool : ScriptableObject
@@ -10,7 +10,6 @@ public class userTasksPool : ScriptableObject
[System.Serializable]
public class TaskDefinition
{
[Tooltip("Unique identifier for the task")]
public string taskID;
@@ -22,20 +21,28 @@ public class userTasksPool : ScriptableObject
public TaskType taskType;
[Tooltip("How often the task refreshes")]
public RefreshFrequency refreshFrequency;
public RefreshFrequency refreshFrequency = RefreshFrequency.DailyReset;
[Tooltip("Target value to complete the task (e.g., 100 combo, 3 songs)")]
public float targetValue;
[Tooltip("Target value to complete the task")]
public float targetValue = 1f;
[Tooltip("Type of reward given upon completion")]
public RewardType rewardType;
[Tooltip("Quantity of the reward")]
public int rewardAmount;
public int rewardAmount = 1;
[Tooltip("是否加入任务池")]
[Tooltip("Used when rewardType is expBottle")]
public ExpBottleKind rewardExpBottleKind = ExpBottleKind.Common;
[Tooltip("Used when rewardType is dushMaterial")]
public DushMaterialKind rewardDushMaterialKind = DushMaterialKind.Material78021;
[Tooltip("Whether this task can be included in the random daily task pool")]
public bool addToTaskPool = true;
[Tooltip("Weighted random selection weight")]
public int selectionWeight = 1;
}
public enum TaskType
@@ -46,21 +53,22 @@ public class userTasksPool : ScriptableObject
TotalScore,
WatchStory,
UseItem,
ShareGame
ShareGame,
SpendCoins,
GameDuration
}
public enum RewardType
{
player_money,
player_material,
bottleOfEXP,
playerEXP
coins,
expBottle,
dushMaterial
}
public enum RefreshFrequency
{
OnLogin,
DailyReset,
Never
OnLogin,
DailyReset,
Never
}
}
+117 -27
View File
@@ -245,8 +245,8 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 14.409, y: -0.56745}
m_SizeDelta: {x: 29.618, y: 15.665}
m_AnchoredPosition: {x: 12.13, y: 0}
m_SizeDelta: {x: 34.1858, y: 20}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &602272132256342015
CanvasRenderer:
@@ -278,12 +278,12 @@ MonoBehaviour:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: dfa10de3e2846524089b5073175cd151, type: 3}
m_FontSize: 14
m_FontSize: 18
m_FontStyle: 0
m_BestFit: 0
m_MinSize: 10
m_MinSize: 1
m_MaxSize: 40
m_Alignment: 0
m_Alignment: 3
m_AlignByGeometry: 0
m_RichText: 1
m_HorizontalOverflow: 0
@@ -325,7 +325,7 @@ RectTransform:
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: -19.4, y: 0.6}
m_SizeDelta: {x: 14, y: 18}
m_SizeDelta: {x: 18, y: 18}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &5147910286390164955
CanvasRenderer:
@@ -355,7 +355,7 @@ MonoBehaviour:
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 4453427214848945594, guid: be41ed26869bfcf46b4c6b3acf9ec09e, type: 3}
m_Sprite: {fileID: 21300000, guid: 30f736af053d2094e9406aa272c0725b, type: 3}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
@@ -402,8 +402,8 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 171.90002, y: 1.1999512}
m_SizeDelta: {x: 110, y: 47}
m_AnchoredPosition: {x: 171.90002, y: -0.3146}
m_SizeDelta: {x: 110, y: 50.0289}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &5599503168244631389
CanvasRenderer:
@@ -468,7 +468,7 @@ MonoBehaviour:
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
m_DisabledColor: {r: 0.6509434, g: 0.6509434, b: 0.6509434, a: 1}
m_ColorMultiplier: 1
m_FadeDuration: 0.1
m_SpriteState:
@@ -498,6 +498,7 @@ GameObject:
- component: {fileID: 1888209457113168138}
- component: {fileID: 4060125231613820467}
- component: {fileID: 5034613503719026902}
- component: {fileID: 3223954077874542836}
m_Layer: 5
m_Name: progress
m_TagString: Untagged
@@ -516,13 +517,14 @@ RectTransform:
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Children:
- {fileID: 8144451642442379881}
m_Father: {fileID: 5109224055821985532}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 348, y: 55}
m_AnchoredPosition: {x: 6.5, y: 0}
m_SizeDelta: {x: 317, y: 30}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &4060125231613820467
CanvasRenderer:
@@ -545,23 +547,111 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 0.74294424, g: 1, b: 0.6556604, a: 1}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 21300000, guid: 4dcbef834d5273c41aee29d861169fd9, type: 3}
m_Sprite: {fileID: 21300000, guid: ad4148593b05d0f47980774815c325fe, type: 3}
m_Type: 3
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 0
m_FillAmount: 0
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!114 &3223954077874542836
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4811890131364998009}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 31a19414c41e5ae4aae2af33fee712f6, type: 3}
m_Name:
m_EditorClassIdentifier:
m_ShowMaskGraphic: 1
--- !u!1 &4969152878334586126
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 8144451642442379881}
- component: {fileID: 8489452282027704886}
- component: {fileID: 5843470283112251827}
m_Layer: 5
m_Name: Image
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &8144451642442379881
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4969152878334586126}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1888209457113168138}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 13.8749, y: 0.000059128}
m_SizeDelta: {x: 371.4091, y: 30}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &8489452282027704886
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4969152878334586126}
m_CullTransparentMesh: 1
--- !u!114 &5843470283112251827
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4969152878334586126}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 21300000, guid: f47d8a39555c75d4aa3ca75114e70bc4, type: 3}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1.83
--- !u!1 &6006229956337094046
GameObject:
m_ObjectHideFlags: 0
@@ -746,8 +836,8 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: -4.121, y: 1.7968}
m_SizeDelta: {x: 255.338, y: 28.4246}
m_AnchoredPosition: {x: -9.9906, y: 0}
m_SizeDelta: {x: 267.0772, y: 30}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &3518498349447220834
CanvasRenderer:
@@ -779,7 +869,7 @@ MonoBehaviour:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: bc54bd51ea8b84448ba1b65311872862, type: 3}
m_FontSize: 14
m_FontSize: 15
m_FontStyle: 0
m_BestFit: 0
m_MinSize: 1
@@ -863,7 +953,7 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0.375, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: -74.6, y: 17}
m_AnchoredPosition: {x: -76.8, y: 12.584}
m_SizeDelta: {x: 200, y: 50}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &2318309606455167046
@@ -921,8 +1011,8 @@ MonoBehaviour:
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_fontSize: 28
m_fontSizeBase: 28
m_fontSize: 20
m_fontSizeBase: 20
m_fontWeight: 400
m_enableAutoSizing: 0
m_fontSizeMin: 18
@@ -999,8 +1089,8 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 348, y: 55}
m_AnchoredPosition: {x: 6.5, y: 0}
m_SizeDelta: {x: 317, y: 30}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &8024881274196398679
CanvasRenderer:
@@ -1030,8 +1120,8 @@ MonoBehaviour:
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 21300000, guid: 4dcbef834d5273c41aee29d861169fd9, type: 3}
m_Type: 0
m_Sprite: {fileID: 0}
m_Type: 1
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
@@ -1039,4 +1129,4 @@ MonoBehaviour:
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
m_PixelsPerUnitMultiplier: 0.7
@@ -1,6 +1,7 @@
using UnityEngine;
using TMPro;
using UnityEngine.UI;
using UnityEngine.Events;
public class taskPrefabController : MonoBehaviour
{
@@ -13,4 +14,53 @@ public class taskPrefabController : MonoBehaviour
public Button rewardButton;
public Image rewardImg;
public Text rewardAmmount;
public void Setup(DailyTaskViewData taskView, int displayIndex, Sprite rewardSprite, UnityAction onRewardClicked)
{
if (taskView == null || taskView.definition == null || taskView.runtimeEntry == null)
{
return;
}
if (taskNumber != null)
{
taskNumber.text = displayIndex.ToString("00");
}
if (taskText != null)
{
taskText.text = taskView.definition.description;
}
if (taskProgress_fillAmount_img != null)
{
float targetValue = Mathf.Max(0.0001f, taskView.definition.targetValue);
float clampedProgress = Mathf.Clamp(taskView.runtimeEntry.progress, 0f, targetValue);
taskProgress_fillAmount_img.fillAmount = clampedProgress / targetValue;
}
if (rewardImg != null)
{
rewardImg.sprite = rewardSprite;
rewardImg.enabled = rewardSprite != null;
}
if (rewardAmmount != null)
{
rewardAmmount.text = taskView.definition.rewardAmount.ToString();
}
if (rewardButton == null)
{
return;
}
rewardButton.onClick.RemoveAllListeners();
if (onRewardClicked != null)
{
rewardButton.onClick.AddListener(onRewardClicked);
}
rewardButton.interactable = taskView.runtimeEntry.isCompleted && !taskView.runtimeEntry.isClaimed;
}
}