unity 实现吸血鬼幸存者的随机奖励
设置奖励的数据类型
//
// Auto Generated Code By excel2json
// https://neil3d.gitee.io/coding/excel2json.html
// 1. 每个 Sheet 形成一个 Struct 定义, Sheet 的名称作为 Struct 的名称
// 2. 表格约定:第一行是变量名称,第二行是变量类型
// Generate From C:\Users\wasi\Desktop\Reward.xlsx.xlsx
public class Reward
{
public int RewardID; // id值
public string RewardName; // 名字
public string RewardType; // 类型
public int RewardValue; // 值
public string Descript; // 描述
public string BG; // 背景图
public string Icon; // 图标
public string AddObjcetName; // 增加物体
public int MaxCount; // 最多出现次数
public float CumulativeProbability; // 出现概率
public string Level; // 奖励品质
}
// End of Auto Generated Code
奖励接口
// 定义奖励接口
using UnityEngine;
using System;
public interface IRewardable
{
string Name { get; }
Reward Reward { get; set; }
/// <summary>
/// 异步返回奖励信息的方法
/// </summary>
/// <param name="callback">加载完成后的回调,返回加载的奖励信息</param>
void ReturnRewardInfo(Action<RewardInfo> callback);
/// <summary>
/// 授予奖励的方法
/// </summary>
void GetReward();
}
public class RewardInfo
{
public string name; // 奖励名称
public Sprite bgSprite; // 背景图片
public Sprite headSprite; // 头像图片
public string description; // 描述
public int currentConut;
public int maxCount;
}
奖励父类
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using System;
public class Rewardable : IRewardable
{
public Reward Reward { get; set; }
public string Name => GetType().Name;
public RewardInfo info;
public Rewardable(Reward reward)
{
Debug.Log("初始化数据"+Name);
Reward = reward;
info = new RewardInfo();
info.name = Reward.RewardName;
info.description = Reward.Descript;
info.maxCount = Reward.MaxCount;
info.currentConut=0;
}
public void ReturnRewardInfo(Action<RewardInfo> callback)
{
// 异步加载背景图
AsyncOperationHandle<Sprite> bgHandle = Addressables.LoadAssetAsync<Sprite>(Reward.BG);
bgHandle.Completed += (op) =>
{
if (op.Status == AsyncOperationStatus.Succeeded)
{
info.bgSprite = op.Result;
}
else
{
Debug.LogError($"Failed to load sprite with key {Reward.BG}");
}
// 异步加载图标
AsyncOperationHandle<Sprite> iconHandle = Addressables.LoadAssetAsync<Sprite>(Reward.Icon);
iconHandle.Completed += (opIcon) =>
{
if (opIcon.Status == AsyncOperationStatus.Succeeded)
{
info.headSprite = opIcon.Result;
}
else
{
Debug.LogError($"Failed to load sprite with key {Reward.Icon}");
}
// 调用回调,返回加载完成的 RewardInfo
callback?.Invoke(info);
};
};
}
public virtual void GetReward()
{
// 实现领取奖励的逻辑
}
public bool CanDraw()//是否能抽取
{
return info.currentConut < info.maxCount; // 检查是否可以抽取
}
}
奖励系统制作成持久性单列
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PersistentSingleton<T> : MonoBehaviour where T : Component
{
public static T instance { get; private set; }
protected virtual void Awake()
{
if (instance == null)
{
instance=this as T;
DontDestroyOnLoad(gameObject);
}
else if (instance != this)
{
Destroy(gameObject);
}
}
}
奖励管理器和奖励工厂 用Newtonsoft来解析数据
using Newtonsoft.Json;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
public class RewardManager :PersistentSingleton<RewardManager>
{
//全部的奖励
private List<Reward> rewardDataList = new List<Reward>();
private List<Rewardable> rewardList = new List<Rewardable>();
//拥有的奖励
private List<Rewardable> owerRawardList = new List<Rewardable>();
void Start()
{
LoadJson();
foreach (string name in RewardFactory.GetRewardNames())
{
// Debug.Log("奖励名字"+ name);
}
}
private void LoadJson()
{
// 使用 Addressables 异步加载 JSON 文件
Addressables.LoadAssetAsync<TextAsset>("Data/Reward.json").Completed += OnJsonLoaded;
}
private void OnJsonLoaded(AsyncOperationHandle<TextAsset> handle)
{
if (handle.Status == AsyncOperationStatus.Succeeded)
{
// JSON 文件加载成功
TextAsset jsonAsset = handle.Result;
string json = jsonAsset.text;
rewardDataList = JsonConvert.DeserializeObject<List<Reward>>(json);
foreach (var reward in rewardDataList)
{
GrantReward(reward.RewardID);
}
// 在这里处理 JSON 数据
//暂时获得全部奖励
owerRawardList=rewardList;
//每次被重新生成了
Debug.Log("重新刷新奖励-------------------------");
}
else
{
Debug.LogError("Failed to load JSON: " + handle);
}
}
private void GrantReward(int rewardID)
{
var data = rewardDataList.Find(r => r.RewardID == rewardID);
Rewardable rewardable = RewardFactory.GetReward(data.RewardType, data);
if (!rewardList.Contains(rewardable))
{
rewardList.Add(rewardable);
}
}
public Rewardable[] GetOwerRewardable(int number)
{
Rewardable[] newRewards=new Rewardable[number];
HashSet<int> rewardIdList = new HashSet<int>();//记录已经抽过的奖励
int i = 0;
while (i<number)
{
float randomValue =UnityEngine.Random.Range(0f, 1f);
Rewardable reward = owerRawardList[UnityEngine.Random.Range(0, owerRawardList.Count)];
if (reward.Reward.CumulativeProbability>randomValue&&reward.CanDraw())
{
if (!rewardIdList.Contains(reward.Reward.RewardID))
{
rewardIdList.Add(reward.Reward.RewardID);
newRewards[i] = reward;
i++;
}
}
}
return newRewards;
}
}
public static class RewardFactory
{
private static Dictionary<string, Type> rewardByName;
private static bool isInit => rewardByName != null;
private static void Init()
{
if (isInit) return;
// Debug.Log("初始化奖励脚本");
//获得脚本中所有的 IRewardable 类 同时 要是类 和不是接口 而且要是 Rewardable 子类
var rewardTypes = Assembly.GetAssembly(typeof(IRewardable)).GetTypes()
.Where(myType => myType.IsClass && !myType.IsInterface && myType.IsSubclassOf(typeof(Rewardable)));
rewardByName = new Dictionary<string, Type>();
foreach (var rewardType in rewardTypes)
{
//运用反射创建类 添加到字典里
var tempEffect = Activator.CreateInstance(rewardType, new Reward()) as IRewardable;
rewardByName.Add(tempEffect.Name, rewardType);
}
}
public static Rewardable GetReward(string rewardType, Reward reward)
{
Init();
if (rewardByName.ContainsKey(rewardType))
{
Type type = rewardByName[rewardType];
try
{
//反射创建实例
var rewardInstance = Activator.CreateInstance(type, reward) as Rewardable;
return rewardInstance;
}
catch (Exception ex)
{
Debug.LogError($"无法创建奖励实例 {rewardType}: {ex.Message}");
}
}
else
{
Debug.LogWarning($"未找到奖励类型 {rewardType}");
}
return null;
}
public static IEnumerable<string> GetRewardNames()
{
// Debug.Log("获取所有奖励名称");
Init();
return rewardByName.Keys;
}
}
选取奖励制作成按钮
using UnityEngine;
using UnityEngine.UI;
using System;
using System.Collections;
public class RewardButton : MonoBehaviour
{
[SerializeField] private Image BG;
[SerializeField] private Image headImage;
[SerializeField] private Text nameText;
[SerializeField] private Text descriptText;
[SerializeField] private GameObject BackGround;
private Button button;
private bool isOnclik;
private Action action;
private void Awake()
{
button = GetComponent<Button>();
}
private void Start()
{
button.onClick.AddListener(() =>
{
OnButtonOnClick();
});
}
public void Init(IRewardable rewardable)
{
// 异步加载并显示奖励信息
rewardable.ReturnRewardInfo((info) =>
{
if (info != null)
{
BackGround.SetActive(false);
BG.sprite = info.bgSprite;
headImage.sprite = info.headSprite;
nameText.text = info.name;
descriptText.text = info.description;
// 添加按钮点击事件
action+=()=>rewardable.GetReward();
}
else
{
Debug.LogError("Failed to load reward info");
}
});
}
public void ButtonAddAction(Action action)
{
this.action += action;
}
void OnButtonOnClick()
{
if (isOnclik)
{
action?.Invoke();
}
StartCoroutine(DoubleOnClikCoroutine());
}
IEnumerator DoubleOnClikCoroutine()
{
isOnclik=true;
yield return new WaitForSeconds(0.5f);
isOnclik = false;
}
}
奖励界面
using DG.Tweening;
using System;
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class RewardUI : MonoBehaviour
{
[SerializeField] Canvas canvas;
[SerializeField] RewardButton rewardButton;
public Transform cardParent;
public Transform cardStartPosT;
public int cardCount = 10;
public float cardWidth = 1f;
public float dealDuration = 1f;
public float delayBetweenCards = 0.2f;
private void OnEnable()
{
WaveUI.OnRewardEvent+=onRewardEvent;
}
private void OnDisable()
{
WaveUI.OnRewardEvent-=onRewardEvent;
}
private void onRewardEvent()
{
Debug.Log("调用奖励");
CreateCard();
}
void CreateCard()
{
canvas.enabled=true;
foreach (Transform item in cardParent)
{
Destroy(item.gameObject);
}
IRewardable[] rewardables = RewardManager.instance.GetOwerRewardable(cardCount);
int cardNumber =cardCount;
float totalWidth = cardNumber * cardWidth;
float startingX = cardParent.position.x - totalWidth / 2 + cardWidth / 2;
for (int i = 0; i < cardNumber; i++)
{
var card = Instantiate(rewardButton);
// Position the card
float cardX = startingX + i * cardWidth;
card.transform.position = cardStartPosT.position;
card.transform.SetParent(cardParent);
card.transform.localScale = Vector3.one;
DealCard(card.gameObject, i * delayBetweenCards, new Vector3(cardX, cardParent.position.y, cardParent.position.z), rewardables[i]);
}
}
private void DealCard(GameObject card, float delay, Vector3 targetPos, IRewardable rewardable)
{
// Store the initial rotation
Quaternion initialRotation = card.transform.rotation;
// Create rotate animation
var sequence = DOTween.Sequence();
sequence.AppendInterval(delay);
// Move card to target position
sequence.Append(card.transform.DOMove(targetPos, 0.1f));
// Rotate card to half way over dealDuration / 2
sequence.Append(card.transform.DORotate(new Vector3(0, 90, 0), dealDuration / 2));
// Add a function callback here to do something when the card is half way turned
// For example, change the card's sprite to show its face
sequence.AppendCallback(() => HalfWayThere(card, rewardable));
// Rotate card to fully turned over dealDuration / 2
sequence.Append(card.transform.DORotate(initialRotation.eulerAngles, dealDuration / 2));
// Add a callback for completion after the animation finishes
// sequence.OnComplete(() => ResetReawardPlane());
}
private void HalfWayThere(GameObject card, IRewardable rewardable)
{
card.GetComponent<RewardButton>().Init(rewardable);
card.GetComponent<RewardButton>().ButtonAddAction(
() =>
{
canvas.enabled=false;
GameManager.GameState=GameState.UI;
}
);
}
//private void Update()
//{
// if (Input.GetKeyDown(KeyCode.Escape))
// {
// Debug.Log("生成奖励");
// // CreateReward();
// CreateCard();
// }
//}
}
具体的奖励类
比如我增加枪支
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
public class AddWeaponReward : Rewardable
{
public AddWeaponReward(Reward reward) : base(reward)
{
}
public override void GetReward()
{
info.currentConut++;//计入使用次数
AsyncOperationHandle<GameObject> bgHandle = Addressables.LoadAssetAsync<GameObject>(Reward.AddObjcetName);
bgHandle.Completed += (op) =>
{
if (op.Status == AsyncOperationStatus.Succeeded)
{
GameObject weapon=op.Result;
GameObject.FindAnyObjectByType<PlayerWeapon>().AddGun(weapon);
}
else
{
Debug.LogError($"Failed to load sprite with key {Reward.BG}");
}
};
Debug.Log("增加武器");
}
}
我的excel配置表,我用excel2json 转换为json数据
保存在 data下面,这里用Addressable标记