迁移 - 策略游戏





5.00/5 (18投票s)
一个VB.NET实现的城市建设游戏,包含实时策略元素。
目录
技术栈
引言
《迁移》是一款模拟策略游戏。游戏的目标是建立一个工人社区,他们执行建造新殖民地的各项任务。游戏通过鼠标操作的点选界面进行控制。玩家不能直接控制工人,而是下达命令建造建筑物、管理商品的制造和分销以及攻击对手。
有32种建筑资源和11种移民类型。当新建筑建成后,闲置的移民会被招募到专门的岗位上。例如,一旦铁匠铺建成,就会出现铁匠。有些移民担任这些角色需要特定的工具。工具匠的建筑可以制造额外的工具,并且每种工具的制造比例由玩家控制。
游戏开始时,玩家选择城堡的位置,城堡里居住着最初的定居者和储存的物资。如果建筑物和道路的布局规划不周,可能会导致交通拥堵。如果不采取对策(重新规划货物路线、建造更多仓库、更好地放置建筑物),这种单一的瓶颈可能会在整个网络中产生分布式影响,导致物资短缺,因为货物无法足够快地到达目的地。
背景
动机
多年来,我一直很欣赏Maxis的《模拟城市》和Sid Meier的《文明》等游戏。我花费了无数时间玩这些游戏并制定策略。然而,我一直想对它们进行一些“微调”。
我一直渴望拥有这些游戏的我自己版本。我只是不知道从何开始。我的业务编程背景让我无法理解如何正确编写游戏循环。我选择的语言VB中从来没有好的例子。因此,我决定将这款VB.NET游戏开源,以帮助其他人,或许能激发一些关于如何创建自己的帝国策略类游戏的想法和理解。
范围
本文的范围是游戏循环。特别是,我将讨论与VB.NET相关的游戏循环。也许在后续文章中,我将讨论细节、类以及这些类之间的相互关系。
然而,现在,我将重点讨论创建策略游戏的最大障碍。从编程的角度来看,任何游戏的核心组件都是游戏循环。游戏循环使得游戏能够平稳运行,无论用户是否有输入。
当然,在商业软件编程中,我们从来不必过多担心这个概念。在大多数情况下,我们设计“响应”用户事件的系统(例如,按钮按下,文本框输入等)。大多数传统商业软件程序响应用户输入,并且没有用户输入就不会做任何事情。例如,文字处理器在用户打字时格式化单词和文本。如果用户没有输入任何内容,文字处理器就不会做任何事情。功能可能需要很长时间才能完成,但它们都是由用户告诉程序“做”某事而启动的。另一方面,游戏无论用户输入如何,都会继续运行。
这就是游戏循环所允许的。这对我来说是一个巨大的学习曲线。希望本文能帮助你,在你进入或考虑进入游戏编程时,减轻这种学习曲线。
注意:请注意,这项工作远未完成。还有很多工作要做。
书签:我将努力更新本文,以便在修复某些错误后进行更新。
安装
步骤 1
首先,下载并解压《迁移》源代码后,您将看到以下目录布局。
步骤2(*已编辑)
接下来,您需要确保下载 *必需* 资源和另外 4 (四) 个支持文件。有 4 个压缩文件。将这些文件的内容解压到您的“Build”目录中。
步骤 3
确保您的“Build”目录中包含所有必要文件。忽略“textures.cache”文件,它将在程序启动时重新生成。它应该看起来类似于下面的图片。
步骤 4
确保您的“Resources”目录如下所示。如果不是,请务必记住您必须下载所有**必需**的资源。
步骤 5
请务必将Animations1和Animations2文件内容放入“...Build\Resources\Animations”目录中。您的“Animations”目录应如下所示。
工作原理 - 整体概览
委托(Delegates)
《迁移》利用多个委托和委托指针来完成任务。基本上,委托充当事件的处理程序。这是程序员的术语,对吧?让我举个例子:假设我正在纽约市的一家酒店办理入住手续。当我走到前台时,前台人员通常会指派其他人将我的行李搬到我的房间。行李员就是处理行李的指定委托。这在VB.NET中的委托也是如此。在《迁移》中,我们将把许多功能移交给其他类进行当前和未来的处理。
有关委托的更多信息...
多线程
在《迁移》游戏过程中,游戏将决定何时触发委托(即它们何时执行)。《迁移》使用线程来完成此任务。在每次循环中,游戏都会通过一个阈值来判断特定委托是应该立即触发还是稍后触发。一旦在循环中达到阈值,就会创建一个线程,游戏继续进行。另外请注意,这个新线程可能会触发其他线程,依此类推。
有些委托需要异步触发,有些则需要同步触发。你可能会问,两者有什么区别?当你发送电子邮件时,你通常不期望立即得到答复,而是继续做其他事情。这就像一个异步操作。一两天后,你希望能收到回复,如果很重要,你会定期查看邮箱,可能会给自己发送一个提醒来检查邮箱。另一方面,当你点击网页上的链接时,你等待响应。这就是同步操作。
有关线程的更多信息...
示例
委托和线程的示例
' Use of a Main thread to kickstart things off
' Run this thread in the background (i.e. Asynchronously)
'On the fly, anonymous Delegate ("Sub") used to call InitializeProgram
Dim thread As New System.Threading.Thread(Sub() InitializeProgram())
thread.IsBackground = True
thread.Start()
thread.Join()
初始化
一旦主线程启动,它就不会停止,直到以某种方式被“告知”停止(即系统异常、关闭、游戏停止等)。请记住,此线程在后台线程上运行,因此它将运行并允许其他系统继续运行。
此方法进一步启动新线程的过程,这些新线程也将被监视,直到它们也被“告知”停止。
' Once this Main thread is started it does not stop until its 'told' to stop somehow
Private Shared Sub InitializeProgram()
Game.Setup.Initialize()
Game.Setup.Renderer.WaitForTermination()
End Sub
...
Public Sub WaitForTermination()
Do While Not IsTerminated
Thread.Sleep(500)
Loop
End Sub
...
Class Setup
...
Public Shared Sub Initialize()
' setup renderer
Renderer = New Renderer(Language.Configuration)
AddHandler Renderer.OnRenderSprites, AddressOf Render_RenderSprites
AddHandler Renderer.OnMouseMove, AddressOf Render_MouseMove
AddHandler Renderer.OnMouseDown, AddressOf Render_MouseDown
AddHandler Renderer.OnMouseUp, AddressOf Render_MouseUp
AddHandler Renderer.OnKeyDown, AddressOf Renderer_OnKeyDown
AddHandler Renderer.OnKeyRepeat, AddressOf Renderer_OnKeyRepeat
Dim mPath As String = [Global].GetResourcePath("Animations")
AnimationLibrary.OpenFromDirectory(mPath)
InitializeGame()
End Sub
...
End Class
...
Private Shared Sub InitializeGame()
Map = New Migration.Game.Map(512, 59, New RaceConfiguration() { _
Loader.Open([Global].GetResourcePath("Configuration\Roman.Buildings.xml")) })
Renderer.AttachTerrain(Map.Terrain)
m_GUILoaderTimer = New Timer_
(AddressOf OnTimerCallback, Nothing, 1000, 1000) ' <---- Another thread
...
End Sub
游戏循环
每个游戏都包含获取用户输入、更新游戏状态、处理AI、播放音乐、音效和渲染游戏等一系列操作。这个序列主要通过《迁移》的主游戏循环(称为SimulationLoop()
)来处理。这个游戏循环是游戏的心脏。大多数更新和事件都发生在这个循环中。游戏也在此处保存到磁盘。
Private Sub SimulationLoop()
Try
Dim watch As New Stopwatch()
Dim lastElapsed As Long = 0
Dim elapsedShift As Long = 0
Dim cycleMillis As Long = 0
If System.IO.File.Exists("GameLog.s3g") Then
AddOneCycle()
...
End Sub
周期管理器
《迁移》利用“周期管理器”来处理游戏资源特定项目(即,可移动管理器、建筑物管理器等)。这些周期管理器超出了本文的范围,但可能会在以后介绍。每个周期管理器将通过其ProcessCycle()
方法处理一个周期。处理完成后,我们就可以释放已标记为释放的资源或进行任何必要的清理。这些资源将按照队列(FIFO)中的顺序进行清理安排。
...
Friend Sub AddOneCycle()
If Not IsInitialized Then
Throw New InvalidOperationException()
End If
MovableManager.CurrentCycle += 1
BuildingManager.ProcessCycle()
ResourceManager.ProcessCycle()
MovableManager.ProcessCycle()
If m_UpdateConfig Then
m_UpdateConfig = False
Configuration.Update()
End If
End Sub
...
Public Sub Synchronize(ByVal inTask As Procedure)
MovableManager.QueueWorkItem(inTask)
End Sub
...
Public Sub Synchronize(ByVal inDueTimeMillis As Long, ByVal inTask As Procedure)
MovableManager.QueueWorkItem(inDueTimeMillis, inTask)
End Sub
摘要
游戏循环比你想象的要复杂得多。游戏循环是每个游戏的心脏,没有它游戏就无法运行。但不幸的是,对于每个新的游戏程序员来说,互联网上没有关于这个主题的好的VB.NET文章。我们回顾了一个在VB.NET中为策略游戏使用委托和线程的实现。
希望本文能启发一些VB.NET程序员去着手编写下一代出色的游戏。
版权
除了所有先前的版权,我还要感谢育碧和BlueByte在图形/媒体方面的贡献。
历史
- 2013年3月20日:初始版本
- 2022年10月26日:文章更新