Featured image of post 麦田物语开发日记(五)

麦田物语开发日记(五)

2D俯视角游戏麦田物语的制作全过程

时间系统

时间系统的代码逻辑

思路

该部分主要通过两个重要的机制来完成,每秒的阈值每个时间单位的进制来组合实现,每秒阈值通过使用Unity自带的Times.deltaTime的累加实现,如果超过该阈值,则进一秒,因此当阈值越低,游戏内的时间流动越快。

而且游戏中时间流逝的进制在设置好以后通过每秒的递增来通过if语句体现

  • 设置时间的阈值和单位进制
1
2
3
4
// 放入settings.cs中便于修改
/*时间控制*/
public const float GameTimeThreshold = 0.1f; // 每秒的时间长度,数值越小时间越快
public const int GameSecondHold = 59, GameMinuteHold = 59, GameHourHold = 23, GameDayHold = 10, GameMonthHold = 12, GameSeasonHold = 3;     // 时间的进制
  • 设置季节枚举(略过)
  • 设置时间的增长

利用deltaTime机制,当计时器超过游戏中每秒的刻度,就增加游戏秒一次,并调用计时函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// TimeManager.cs中的方法
private void Update()
    {
        if (!_gameClockPause)
        {
            tikTime += Time.deltaTime;
            if (tikTime >= Settings.GameTimeThreshold)
            {
                tikTime -= Settings.GameTimeThreshold;
                UpdateGameTime();                
            }
        }

    }
  • 计时增长函数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// TimeManager.cs中的方法
/*时间单位*/
private int _gameSeconds, _gameMinutes, _gameHours, _gameDays, _gameMonth, _gameYears;
/*时间的计数遍历*/
private Season _currentSeason = Season.Spring;
private int _monthInSeason = 3;     // 每个季度的月份
private bool _gameClockPause;
private float tikTime;      // 计时器

// 每次新开游戏的初始化
private void newGameTime()
{
    tikTime = 0f;
    _gameClockPause = false;
    _gameSeconds = 0;
    _gameMinutes = 0;
    _gameHours = 0;
    _gameDays = 1;
    _gameMonth = 1;
    _gameYears = 1;
    _currentSeason = Season.Spring;
}

	/// <summary>
    /// 更新游戏时间, 游戏世界的每一秒都要调用一次的函数,
    /// </summary>
    private void UpdateGameTime()
    {
        _gameSeconds++;
        // 如果累计时间超过了时间的进制单位,则进位
        if (_gameSeconds > Settings.GameSecondHold)
        {
            _gameMinutes++;
            _gameSeconds = 0;
            if (_gameMinutes > Settings.GameMinuteHold)
            {
                _gameHours++;
                _gameMinutes = 0;
                if (_gameHours > Settings.GameHourHold)
                {
                    _gameDays++;
                    _gameHours = 0;
                    if (_gameDays > Settings.GameDayHold)
                    {
                        _gameMonth++;
                        _gameDays = 1;
                        _monthInSeason--;

                        if (_gameMonth > Settings.GameMonthHold)
                            _gameMonth = 1;

                        if (_monthInSeason == 0)
                        {
                            _monthInSeason = 3;
                            int seasonNumber = (int)_currentSeason;
                            seasonNumber++;

                            if (seasonNumber > Settings.GameSecondHold)
                            {
                                seasonNumber = 0;
                                _gameYears++;
                                _gameYears %= 9999;
                            }
                        }
                    }
                }
            }
        }
        //
        // Debug.Log("now Seconds is :" +  _gameSeconds + ":: Minutes is " + _gameMinutes);
    }

在该函数中,由于每秒刻度为0.1f,也就是说将现实一秒分为了10份,每一份是游戏中的一秒,那么现实世界每6秒相当于游戏内一分钟

构建时间UI

在UI的大Canvas下构建一个基础的panel用于构造时间框体,然后不断的在内部增加图片层次。

  • 为了实现只显示图片一部分,因此要利用遮罩效果

该方法是先设置一个主要显示内容的图片,然后将需要被显示的图像设置为该图像的子物体,然后对父物体使用Mask组件。而具体需要显示的内容则通过旋转得到

  • 为了让所有图片都在同一部分,以设置每个时钟块,因此先定好整个物体的位置(用Image本身来定位),然后将image删除,将定位好的物体作为父物体,然后设置每个子物体的位置

  • 为了让btn的图片按键准确,需要对该精灵图片设置的advanced - > Read/Write进行勾选,这样才能保证不会点击图片空白处

连接时间代码和UI

**思路:**在时间的UI代码中绑定一个UI变化函数,并在时间流逝代码中,每次分钟和小时变化的时候,调用这个事件,从而引起UI的变化;而图标等则通过代码进行变化。

步骤

  • 构造UI代码,并建立对应的UI部件

  • 定义UI变化的事件,并将事件与UI变化代码绑定

  • 在时间变化函数中呼叫事件

  • 构造UI代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// 在TimeUI.cs中定义使用的所有组件,并绑定信息
public RectTransform dayNightImage;
public RectTransform timeBlockParent;
public Image seasonImg;
public TextMeshProUGUI dateText;
public TextMeshProUGUI timeText;

public Sprite[] seasonSprites;
private List<GameObject> _timeBlocks = new List<GameObject>();

private void Awake()
{
    /* 为时间块绑定子物体 */
    for (int i = 0; i < timeBlockParent.childCount; i++)
    {
        _timeBlocks.Add(timeBlockParent.GetChild(i).gameObject);
        timeBlockParent.GetChild(i).gameObject.SetActive(false);
    }
}
  • 对时间调用事件进行设置
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// EventHandler.cs
/* 每分钟更新的事件 */
public static event Action<int, int> GameMinuteEvent;

public static void CallGameMinuteEvent(int minute, int hour)
{
    GameMinuteEvent?.Invoke(minute, hour);
}

/* 小时和日期更新的事件 */
public static event Action<int, int, int, int, Season> GameDateEvent;

public static void CallGameDateEvent(int hour, int day, int month, int year, Season season)
{
    GameDateEvent?.Invoke(hour, day, month, year, season);
}

上述函数将在TimeManager.cs的每分钟更新和每小时更新中进行使用,并且需要保证每次游戏一开始就需要更新,且由于事件是在OnEnable中绑定的,因此一开始的调用只能在Start中

  • 更新UI
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// 在TimeUI中定义UI变化的函数
private void OnGameDateEvent(int hour, int day, int month, int year, Season season)
    {
        dateText.text = year.ToString("0000") + "年" + month.ToString("00") + "月" +
                        day.ToString("00") + "日";
        seasonImg.sprite = seasonSprites[(int)season];

        SwitchHourBlockImage(hour);
        DayNightImageRotate(hour);
    }

    private void OnGameMinuteEvent(int min ,int hour)
    {
        timeText.text = hour.ToString("00") + ":" + min.ToString("00");
    }

    /// <summary>
    /// 对时间块进行处理,每四个小时亮一块
    /// </summary>
    /// <param name="hour"></param>
    private void SwitchHourBlockImage(int hour)
    {
        var index = hour / 4;
        
        for (int i = 0; i < _timeBlocks.Count; i++)
        {
            if (i < index + 1)
            {
                _timeBlocks[i].SetActive(true);
            }
            else
            {
                _timeBlocks[i].SetActive(false);
            }
        }
        
    }

    /// <summary>
    /// 对时间图片进行旋转
    /// </summary>
    /// <param name="hour"></param>
    private void DayNightImageRotate(int hour)
    {
        var targetRotate = hour * 15 - 90;
        dayNightImage.DORotate(new Vector3(0, 0, targetRotate), 1f);
    }

dayNightImage.DORotateDC.Tweening的函数,用于对图片进行旋转,其第一个参数是旋转的目标位置

Built with Hugo
Theme Stack designed by Jimmy