学习中014
学习中014
说一个基础知识吧:MonoBehaviour的构造函数由unity引擎自己调用,什么时候调用,调用几次都不知道的~所以任何关于游戏的逻辑(初始化逻辑)都应该写在Awake或者Start上!
GroundProperties
GameController
点击弹窗口的时候 加个变量存一下不就完了嘛
继承方式,代码更规范
父类
using System;
using UnityEngine;
namespace Production
{
public abstract class ProductionBuilding : MonoBehaviour
{
// 生产产品ID
public int productId = -1;
// 生产周期
public int productPeriod = 7;
// 生产数量
public int productCount = 5;
// 维护费用
public int costMoney = 5;
// 生长周期
public int growPeriod = -1;
// 是否已长大
protected bool IsGrown;
// 是否需要生长(growPeriod > 0)
protected bool IsNeedGrow;
// 已经过去的天数
protected int PassingDays;
public int GrownRemainingDays()
{
// 成熟剩余天数
return growPeriod - PassingDays - 1;
}
public int ProductRemainingDays()
{
// 生产剩余天数
return productPeriod - PassingDays - 1;
}
protected virtual void Awake()
{
WorldTimer.DoThisDay += NewDay;
IsNeedGrow = growPeriod > 0;
}
public bool IsGrowing()
{
// 正在生长:如果生长周期 > 0,且没有长大(默认),且过去的天数不大于生长周期
// 否则,返回没有在生长了
return growPeriod > 0 && !IsGrown && PassingDays <= growPeriod;
}
// 每个子类需要重写每天的过法
protected abstract void NewDay();
protected virtual void OnDestroy()
{
WorldTimer.DoThisDay -= NewDay;
}
}
}
农场类
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Production
{
public class Farm : ProductionBuilding
{
// 到达阶段2的天数
public int stage2 = 2;
// 到达阶段3的天数
public int stage3 = 3;
// 当前阶段
private int _currentStage = 1;
// 当前显示的阶段
private int _showingStage = 1;
private readonly Dictionary<int, GameObject> _stages = new Dictionary<int, GameObject>();
protected override void Awake()
{
base.Awake();
for (var i = 1; i <= 3; i++)
{
// 加载不同阶段
_stages[i] = transform.Find(i.ToString()).gameObject;
if (i >= 2)
{
_stages[i].SetActive(false);
}
}
}
private void Update()
{
// 如果当前显示场景等于当前场景,不处理
if (_showingStage == _currentStage) return;
// 否则,当前显示场景隐藏,显示当前场景
_stages[_showingStage].SetActive(false);
_stages[_currentStage].SetActive(true);
// 当前显示场景等于当前场景
_showingStage = _currentStage;
}
protected override void NewDay()
{
// 农场无条件直接过一天
PassingDays += 1;
if (IsNeedGrow && !IsGrown)
{
// 如果需要生长且没长大,处理成长逻辑
if (PassingDays < growPeriod) return;
IsGrown = true;
PassingDays = 0;
}
else
{
// 如果已经长大了,处理生产逻辑
// 注意这里当前阶段,需要生长的直接跳过第一阶段(如:苹果树)
_currentStage = PassingDays < stage2 && !IsNeedGrow ? 1 : PassingDays < stage3 ? 2 : 3;
if (PassingDays < productPeriod) return;
InstanceManager.Instance.knapsack.AddGoods(productId, productCount);
_currentStage = 1;
PassingDays = 0;
}
}
}
}
牧场类
using System;
namespace Production
{
public class Pasture : ProductionBuilding
{
// 消耗的产品ID
public int consumeProductId;
// 消耗的数量
public int consumeCount;
// 食物是否足够,用于UI展示
public bool NoFood { get; private set; } = true;
protected override void NewDay()
{
// 减少食物,食物不足不算一天
var consumeSuccess = InstanceManager.Instance.knapsack.AddGoods(consumeProductId,consumeCount * -1);
NoFood = !consumeSuccess;
if (NoFood) return;
// 成长和生产
PassingDays += 1;
if (IsNeedGrow && !IsGrown)
{
if (PassingDays < growPeriod) return;
IsGrown = true;
PassingDays = 0;
}
else
{
if (PassingDays < productPeriod) return;
InstanceManager.Instance.knapsack.AddGoods(productId, productCount);
PassingDays = 0;
}
}
}
}
换个思路,点击按钮时不要侵入UI逻辑,UI组件自己找该显示什么
using System;
using Architecture;
using Class;
using Game;
using UnityEngine;
using UnityEngine.UI;
namespace UI
{
public class FarmInfoWin : Window<FarmInfoWin>
{
public Text titleText;
public Text productNameText;
public Image productIconImage;
public Text outputText;
public Text timerText;
public Button destroyBtn;
private Building _curShowingBuilding;
private Farm _curShowingFarm;
protected override void Awake()
{
base.Awake();
destroyBtn.onClick.AddListener(DestroyFarm);
WorldTimer.DoThisDay += UpdateTimerText;
}
public override void Show()
{
base.Show();
UpdateInfo();
}
private void UpdateInfo()
{
// 获取点击时的建筑物
var curGround = GameController.Instance.CurrentSelectGroundProperties;
_curShowingBuilding = Dict.BuildingDict[curGround.BuildingId];
_curShowingFarm = curGround.transform.Find("Building")
.GetComponent<Farm>();
var curGoods = Dict.GoodsDict[_curShowingFarm.productId];
titleText.text = _curShowingBuilding.Name;
productNameText.text = curGoods.Name;
productIconImage.sprite = Resources.Load<Sprite>("Icon/" + curGoods.ResName);
var prefix = _curShowingFarm.growPeriod > 0 ? _curShowingFarm.growPeriod + "天成熟,然后" : "";
outputText.text = prefix + "每" + _curShowingFarm.productPeriod + "天产出" + _curShowingFarm.count + "个" + curGoods.Name;
UpdateTimerText();
}
private void UpdateTimerText()
{
timerText.text = _curShowingFarm.IsGrowing()
? "未成熟,距离成熟还有" + (_curShowingFarm.growPeriod - _curShowingFarm.PassingDays) + "天"
: "距离收获还有" + (_curShowingFarm.productPeriod - _curShowingFarm.PassingDays) + "天";
}
private void DestroyFarm()
{
Hide();
// TODO 拆除建筑,改变GroundProperties的State
}
}
}
有点乱,个人觉得更好的实现
using System;
using System.Collections.Generic;
using Game;
using UnityEngine;
namespace Architecture
{
public class Farm : MonoBehaviour
{
public int productId = -1;
public int productPeriod = 7;
public int count = 5;
public int cost = 5;
public int growPeriod = -1;
public int stage2 = 2;
public int stage3 = 3;
private int _currentStage = 1;
private int _showingStage = 1;
private bool _isGrown;
private bool _isNeedGrow;
private int _passingDays;
private readonly Dictionary<int, GameObject> _stages = new Dictionary<int, GameObject>();
private void Awake()
{
WorldTimer.Instance.DoThisDay += NewDay;
for (var i = 1; i <= 3; i++)
{
_stages[i] = transform.Find(i.ToString()).gameObject;
if (i >= 2)
{
_stages[i].SetActive(false);
}
}
_isNeedGrow = growPeriod > 0;
}
private void Update()
{
if (_showingStage == _currentStage) return;
_stages[_showingStage].SetActive(false);
_stages[_currentStage].SetActive(true);
_showingStage = _currentStage;
}
private void NewDay()
{
_passingDays += 1;
if (growPeriod > 0 && !_isGrown)
{
if (_passingDays <= growPeriod) return;
_isGrown = true;
_passingDays = 0;
}
else
{
_currentStage = _passingDays < stage2 && !_isNeedGrow ? 1 : _passingDays < stage3 ? 2 : 3;
if (_passingDays < productPeriod) return;
_passingDays = 0;
Knapsack.Instance.AddGoods(productId, count);
}
}
private void OnDestroy()
{
WorldTimer.Instance.DoThisDay -= NewDay;
}
}
}
更好的实现方法
using UnityEngine;
public class CameraMove : MonoBehaviour
{
private const float MoveSpeed = 0.15f;
private static readonly Vector3 MinPos = new Vector3(-14, 14, -12);
private static readonly Vector3 MaxPos = new Vector3(11, 14, 15);
private static readonly Vector3 InitPos = new Vector3(0, 14, 0);
private void Start()
{
transform.position = InitPos;
}
private void Update()
{
var trans = transform;
var position = trans.position;
var x = Input.GetAxis("Horizontal");
var z = Input.GetAxis("Vertical");
var newX = position.x + x * MoveSpeed;
var newZ = position.z + z * MoveSpeed;
newX = Mathf.Clamp(newX, MinPos.x, MaxPos.x);
newZ = Mathf.Clamp(newZ, MinPos.z, MaxPos.z);
trans.position = new Vector3(newX, position.y, newZ);
}
}
判断指定的碰撞对象 要修改对象的标签
https://blog.csdn.net/Joyeishappy/article/details/96469090
//窗口1 Form1
注册监听事件
public delegate void ListenerHandler();
public event ListenerHandler Listener=null;
public void DoSomeThing() {
if (Listener!=null)//确定事件已被订阅,也就是已被注册
{
Listener();//触发事件
}
}
//窗口2 Form2
Form1 f1=new Form1();
f1. Listener+=new ListenerHandler(noteMe);//订阅(注册)窗口1的Listener事件
//事件处理方法
private void noteMe(){
//定义窗口1的Listener事触发后执行的动作
}
//执行
f1. DoSomeThing();//执行
//触发事件是个主动的过程,没有什么监听,就像你执行一个方法一样
txt记得另存为UTF-8编码
摄像头设置移动速度过快的时候有个问题, 那就是摄像头的 `Transform.position` 移动的时候出现误差.
比如最大值设定为 `15` 的时候, 移动速度过快直接移动到角落会出现 `15.0001` 之类的值, 这时候就会卡死没办法移动, 所以这里需要判断移动位置的极限值.
using UnityEngine;
/// <summary>
/// 摄像头移动
/// </summary>
public class CameraMove : MonoBehaviour{
public Camera target;
public float moveSpeed = 100f;
public Vector2 mixPos = new Vector2(-15, -15);// 最小距离
public Vector2 maxPos = new Vector2(15 , 15);// 最大距离
/// <summary>
/// 程序初始化方法
/// </summary>
void Awake() {
if (target == null) target = Camera.main;
}
void Update() {
float x = Input.GetAxis("Horizontal");
float z = Input.GetAxis("Vertical");
// 获取当前的摄像头位置
var pos = target.transform.position;
// 判断目前的是否达到了最大速度
if (pos.x < mixPos.x || pos.z < mixPos.y) x = 0 ;
if (pos.x > maxPos.x || pos.z > maxPos.y) z = 0 ;
Debug.Log(pos);
// 利用开始进行对象移动
target.transform.position = new Vector3(
pos.x + x * moveSpeed ,
pos.y,
pos.z + z * moveSpeed
);
// 为了防止出现 15.0001 这种情况需要直接判断最小值和最大值
pos = target.transform.position;// 再次获取位置准备校验距离
if (pos.x < mixPos.x) pos.x = mixPos.x;
if (pos.z < mixPos.y) pos.z = mixPos.y;
if (pos.x > maxPos.x) pos.x = maxPos.x;
if (pos.z > maxPos.y) pos.z = maxPos.y;
// 检查完成重置位置
target.transform.position = pos;
}
}
最后点击 UI 的时候会出现射线被锁定到 UI 上面的问题, 也就是点击 Ground 的时候弹出窗口点击购买无法更新玩家的价格, 这里先采用比较简单的遮挡 UI 方法后续看看是否有其他方法处理:
using UnityEngine;
using UnityEngine.EventSystems;// 引入 Unity 事件系统
// <summary>
/// 游戏控制器类
/// </summary>
public class GameController : MonoBehaviour{
/// <summary>
/// 更新方法
/// </summary>
void Update(){
// EventSystem.current.IsPointerOverGameObject 用来判断目前鼠标是不是锁定在 UI 层上
// 实际上判断鼠标是否在放置在含有 Canvas 组件对象上( 创建 UI 时候自动附加在 UI 的根部件 )
if (!EventSystem.current.IsPointerOverGameObject()) {
// 这样就能成功锁定到射线指定的 Ground 对象
_ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(_ray , out _hit , rayDistance)) {
if (_hit.collider.CompareTag("Ground")) {
activeGroundProp = _hit.collider.GetComponent<GroundProperties>();
}
}
}
}
}
这里要对游戏要素进行拆分, 需要细分游戏的要素( 农场游戏来说 ):
地块需要包含以下的内容:
类型, 用于区分是否可以被购买, 是否可以进行建造, 是否可以移除
价格, 用于土地是否可以被玩家购买下来
玩家需要包含以下的信息:
身上的拥有多少金币, 是否可以进行购买/建造操作
界面需要包含以下信息:
点击土地的时候, 是弹出还是显示对应窗口
判断是购买弹出窗口界面, 还是建造/升级窗口界面
对于类型可以利用数值来处理类型, 也可以通过 `enum` 枚举来定义选择类型, 按照使用情况来进行选择即可.
GroundProperties 脚本内容:
using UnityEngine;
/// <summary>
/// 土地属性类型
/// </summary>
public class GroundProperties : MonoBehaviour{
/// <summary>
/// 土地的通用类型标识( 公开的类型对象 )
/// 可以通过 GroundProperties.StatusFlags.Empty 直接获取
/// </summary>
public enum StatusFlags {
Empty, // 空地
Payed, // 已购买
Build, // 已经建造
}
/// <summary>
/// 土地当前状态
/// </summary>
public StatusFlags Status { get; private set; }
/// <summary>
/// 土地的价格
/// </summary>
public readonly int Price = 200;
}
GameController 脚本内容:
using UnityEngine;
/// <summary>
/// 游戏控制器类
/// </summary>
public class GameController : MonoBehaviour{
// 其他代码略
void Update(){
// 获取土地状态, 并判断土地状态
switch (activeGroundProp.Status) {
case GroundProperties.StatusFlags.Empty:
Debug.Log("弹出购买土地");
break;
case GroundProperties.StatusFlags.Payed:
Debug.Log("弹出购买土地");
break;
case GroundProperties.StatusFlags.Build:
Debug.Log("弹出建造土地");
break;
}
}
}
Unity 之中的委托应用十分广泛, 这里假设背包类对象编写, 一般背包道具更新会设置以下事件:
记录背包道具变动
如果界面是通用金币, 则需要更新界面金币数量
如果涉及敌人也需要通知并且针对的类型, 需要去通知敌人, 玩家已经获得某个道具
using System;
using System.Collections;
using UnityEngine;
/// <summary>
/// 背包对象类
/// </summary>
public class Bag : MonoBehaviour{
// 假设目前的背包对象列表
private ArrayList items = default;
// 注意委托默认不对外则为 private, 如果要手动对外绑定则直接声明 public
// 这里默认返回 bool 是为了考虑到如果背包满了需要弹出提示错误
public delegate bool ItemChange(int val);
// 声明背包的委托事件[ 对外开放, 可以被其他脚本绑定 ]
public ItemChange changeItem;
// 初始化方法
private void Awake() {
changeItem += AddItem;// 追加添加道具委托方法
changeItem += UpdateUI;// 追加获取道具的之后更新界面
changeItem += NotifyMessage;// 追加推送全局通知道具消息[ 可以同步推送给敌人/好友 ]
}
// 对外方法, 用于唤醒所有的道具获取的更新事件
public void UpdateItem(int val) {
if (changeItem.Invoke(val)) {
Debug.Log("获取成功");
} else {
Debug.Log("获取失败");
};
}
// 追加道具[ 默认背包对象脚本本体成员,名为 `Bag` ]
private bool AddItem(int val) {
items.Add(val);
return true;
}
// 以下为假设情况
// ===================================================
// 更新界面获取道具[ 假设为界面脚本的类成员, 名为 `GUI` ]
private bool UpdateUI(int val) {
Debug.Log("获取道具 = " + val);
return true;
}
// 弹出内部消息提示[ 假设为弹出消息框架提示用户, 名为 `Message` ]
private bool NotifyMessage(int val) {
Debug.Log("敌对对象注意, 玩家XXX获取道具 = " + val);
return true;
}
}
添加监听事件
Button.onClick.AddListener(delegate{函数名();});
移除监听事件
Button.onClick.RemoveAllListeners();
RayTest