22、游戏开发:从构思到实现的实用指南

游戏开发:从构思到实现的实用指南

1. 游戏构思与笔记利用

当你有创作游戏的冲动却不确定具体方向时,可以翻开你的笔记库,从中寻找有趣的点子。游戏创意笔记并非设计文档或计划,你常常能将不同笔记中的想法结合起来,许多有趣的游戏创意正是源于对几种类型或想法的组合思考。在将笔记转化为设计计划之前,我们先来了解设计计划应达成的目标。

2. 设计文档的重要性

设计文档有以下几个重要作用:
- 它能将你想制作的游戏精髓提炼成易于描述的目标。
- 把笔记整理成更正式的文档,能让你开始思考游戏的实际运作方式,而非仅仅停留在最终产品的概念上。
- 创作设计文档有助于确定哪些功能是必不可少的,哪些是锦上添花的。
- 结构良好的设计文档能将任务分解成小的模块,这样当你有时间编程时,就知道该着手做什么,并能在有限的时间内感受到完成任务的成就感。

设计文档的需求因项目规模而异,具体如下表所示:
| 工作情况 | 设计文档特点 |
| ---- | ---- |
| 为工作室工作 | 非常详细,用于让团队成员保持一致方向 |
| 小团队工作 | 简短的游戏设计文档,用于跟踪冲刺进度 |
| 独自工作 | 简单即可,给予很大自由,包含足够信息提醒自己各部分内容 |

3. 设计文档是动态的

游戏设计文档并非一成不变的,而是所谓的“活文档”,需要不断编辑以符合实际情况。下面我们来看看一个简洁、有针对性的设计文档应包含哪些内容。

4. 设计文档的标题与基本内容
  • 游戏命名 :命名游戏很困难,你选的第一个名字很可能不是最终的游戏名。可以先取一个通用的名字,之后再想一个有趣的名字。在设计文档的第一行写上临时标题。
  • 简短描述 :设计文档应从游戏的简短描述开始,例如“Flappy Dragon”可描述为“a Flappy Bird Clone”,一个通用的Roguelike游戏可描述为“a Dungeon Crawler with procedurally generated levels, monsters of increasing difficulty, and turn - based movement”。关键是要简洁,类似于营销中的电梯演讲,方便向朋友简要介绍你正在做的游戏。
  • 故事 :并非每个游戏都有故事,像“Flappy Bird”没人知道它为何拼命向东飞,对于这类有趣的小游戏,有无故事并不重要。如果你的游戏有故事,就包含故事的概要;否则可跳过这部分。
  • 基本游戏循环 :游戏通常有“设计循环”,描述主角的行动及其对世界的影响。例如“Flappy Dragon”的循环很简单:龙向东飞,避开障碍物。回合制地下城探索游戏的循环如下:
    1. 到达随机生成的地下城关卡。
    2. 探索地下城。
    3. 遭遇怪物,选择战斗或逃跑。
    4. 沿途拾取物品。
    5. 找到关卡出口,然后从步骤1重复。

你的设计可能有多个循环,这没问题,但可能意味着你正在制作一个大型或复杂的游戏。例如实时策略游戏有以下几个循环:
1. 定位资源并安排工人收集。
2. 建造基地,考虑资源使用、单位创建和防御。
3. 组建军队。
4. 定位敌方据点并安排军队摧毁。

下面是一个简单的游戏循环流程图:

graph LR
    A[开始] --> B[到达地下城关卡]
    B --> C[探索地下城]
    C --> D{遭遇怪物}
    D -- 战斗 --> E[战斗]
    D -- 逃跑 --> C
    E --> F[拾取物品]
    F --> G[找到出口]
    G --> B
5. 最小可行产品(MVP)

构思游戏的想法很容易,但将其精简到核心要素却很难。能实现基本设计目标的最小程序就是最小可行产品(MVP)。MVP是你的初始目标,构建MVP的各个模块,完成后你就有了一个可玩的游戏。它可能不是成品,但值得你自豪,也可以分享给朋友获取反馈。很多时候,只有尝试了才知道一个想法是否可行。专注于MVP能避免你花费大量时间去完善一个听起来有趣但实际行不通的想法。

6. 拓展目标

确定MVP后,列出你想要的其他功能作为拓展目标,并按重要性排序,因为你可能无法实现所有目标。理想情况下,选择符合整体设计且能分小模块添加的目标。一次或两次会话就能添加一个功能,比想着“6周后可能完成这个功能”更有动力。

7. 避免过度劳累

如果你有过软件开发经验,可能经历过让人疲惫不堪的项目。如果一个目标给你带来压力,就从设计中剔除它,尤其是你作为独自的业余开发者时。如果你自己都讨厌制作游戏,玩家很可能也会讨厌玩这个游戏。

8. 精简功能

如果你想完成游戏,就要果断一些。把不在最小可行产品范围内的功能都列为拓展目标。开始享受游戏开发后,你会想出很多点子,有些是好点子,有些则不然。把它们记录下来,但在将其作为新任务添加到游戏开发文档之前,问问自己“我真的需要这个吗?”除了核心机制,很多功能可能并非必需,只是锦上添花。如果觉得能实现这个锦上添花的功能,就添加到拓展目标;否则,记在笔记本里,也许能在2.0版本或其他游戏中使用。

9. 冲刺与动力

有了顶层设计文档后,要将其转化为计划,把想法分解成小模块。例如,如果游戏涉及玩家在地下城徘徊,制作一个基本地图并实现移动就是一个合适的模块;如果是回合制游戏,设置回合结构就是一个好的冲刺任务。

在规划冲刺任务时,要考虑依赖关系,比如在有地图、玩家和可战斗的对象之前,无法实现战斗功能;玩家有生命值统计之前,血瓶就没有用处;巨大的热核爆炸效果很酷,但需要有目标、使用理由和表示巨大伤害的方式。

保持冲刺任务简短也很重要,一个短的冲刺任务(最好能在一两次会话内完成)能让你有明显的进度感,你可以玩新功能并说“这是我做的”。避免采用旧的“瀑布式”模式,即先写完所有代码再集成,几个月都看不到有意义的进展,这样很容易让你灰心丧气并失去对项目的兴趣。如果发现某些代码行不通,不要害怕扔掉,但要把它保存起来,也许以后会有用。

10. 设计的长期好处

保留笔记能在你想写代码时提供丰富的想法,有助于避免创作瓶颈。拿一个想法,快速做个设计,拼凑出一个原型,即使很糟糕也没关系,重要的是你尝试了并从中学习。随着开发能力的提升,你扔掉的和完成的项目都会积累经验,让你更清楚什么可行什么不可行,还能积累可复用的代码。

11. 现实评估自己

在游戏开发论坛或Discord服务器上,你会发现每个人都有宏大的想法,但有经验的开发者会建议新手从简单的游戏开始,比如“Pong”。你确实需要从小项目做起,逐步积累经验。先从简单的项目开始,等有足够经验和团队时,再去尝试大型项目。

12. 快速验证想法

设计新游戏时,可能会缺少一个关键元素:趣味性。但只有尝试了才知道,你可以创建一个简陋的原型,展示游戏的基本循环,给信任的朋友试玩。如果核心想法不错,他们玩得开心就能说明问题;如果不行,把它记在笔记里,以后也许能作为小游戏融入其他游戏,最坏的情况是你知道这个想法不可行,以后不再尝试。

13. 不要为截止日期焦虑

如果你把游戏开发当作爱好,不要过分纠结于截止日期。一个“有趣”的项目因为截止日期让你痛苦,那就失去了意义。如果使用项目管理应用,给自己留足够的时间,错过截止日期就重新安排。

14. 总结

结合记录笔记和简短设计文档模板,你就有了规划小型游戏项目所需的一切。不需要花大量时间写长篇大论的设计文档,但开始时做一个简单的大纲能帮助你完成项目。

15. Rust 代码速查表

以下是一些常见的Rust代码用法:
- 变量赋值
rust let n = 5; // 赋值5给n,n的类型自动推导 let n : i32 = 5; // 赋值5给n,n是i32类型 let n; // 创建一个名为n的占位变量,之后可赋值一次 let mut n = 5; // 赋值5给n,n是可变的,之后可改变 let n = i == 5; // 赋值表达式i==5的结果(true或false)给n,n的类型自动推导
- 结构定义
rust struct S { x:i32 } // 创建一个包含i32类型字段x的结构,通过s.x访问 struct S (i32); // 创建一个包含i32的元组结构,通过s.0访问 struct S; // 创建一个单元结构,会在代码中被优化掉
- 枚举定义
rust enum E { A, B } // 定义一个有A和B两个选项的枚举类型 enum E { A(i32), B } // 定义一个有A和B的枚举类型,A包含一个i32
- 控制流
rust while x { ... } // 当x为true时执行代码块 loop { break; } // 循环执行代码块,直到遇到break for i in 0..4 {...} // 循环执行代码块,i取值为0, 1, 2, 3(范围是排他的) for i in 0..=4 {...} // 循环执行代码块,i取值为0, 1, 2, 3, 4(范围是包含的) for i in iter {...} // 对迭代器的每个成员执行代码块 iter.for_each(|n|...) // 对迭代器的每个元素执行闭包 if x {...} else {...} // 如果x为true,执行第一个代码块;否则执行第二个
- 函数与闭包
rust fn my_func() {...} // 声明一个无参数、无返回类型的函数 fn my_func(i:i32) {..} // 声明一个有i(i32类型参数)的函数 fn n2(n:i32) -> i32 { n*2 } // 声明一个接受i32参数并返回n*2的函数 || { ... } // 创建一个无参数的闭包 || 3 // 创建一个无参数返回3的闭包 |a| a*3 // 创建一个接受参数a并返回a*3的闭包
- 匹配枚举
rust match e { MyEnum::A => do_it(), // 当e为A时,调用do_it() MyEnum::B(n) => do_it(n), // 提取成员变量n _ => do_something_else() // 其他情况执行do_something_else() }
- 可选变量与结果变量处理
rust option.unwrap() // 解包可选变量,为空时崩溃 option.expect(”Fail”) // 解包可选变量,为空时带消息崩溃 option.unwrap_or(3) // 解包可选变量,为空时用3替代 if let Some(option) = option { ... } // 提取可选变量内容 result.unwrap() // 解包结果变量,为错误时崩溃 result.expect(”Fail”) // 解包结果变量,为错误时带消息崩溃 result.unwrap_or(3) // 解包结果变量,为错误时用3替代 if let Ok(result) = result { ... } // 提取结果变量 function_that_might_fail()? // 函数返回Result时用?简写解包
- 元组与解构
rust let i = (1, 2); // 赋值1和2给元组i的成员0和1 let (i, j) = (1, 2); // 从元组(1, 2)解构出i和j i.0 // 访问元组i的第一个成员
- 模块与导入
rust mod m; // 引用模块m,查找m.rs或m/mod.rs文件 mod m { ... } // 内联声明模块m,在作用域内可用m::x use m::*; // 导入模块m的所有成员到当前作用域 use m::a; // 导入模块m的a到当前作用域
- 迭代器链
rust iter // 迭代器,可来自集合或返回迭代器的函数 .for_each(|n| ... ) // 对迭代器所有成员执行闭包 .collect::<T>() // 将迭代器收集到类型T的新集合中 .count() // 计算迭代器成员数量 .filter(|n| ...) // 过滤迭代器,只保留闭包返回true的条目 .filter_map(|n| ...) // 过滤迭代器,返回闭包返回Some(x)的第一个条目,返回None则忽略 .find(|n| ...) // 在迭代器中查找条目,无匹配返回None .fold(|acc, x| ...) // 对迭代器所有条目累积到acc .map(|n| ...) // 将迭代器成员转换为闭包结果 .max() // 查找迭代器中的最大值(仅限数字条目) .max_by(|n| ...) // 根据闭包确定迭代器中的最大值 .min() // 查找迭代器中的最小值(仅限数字条目) .min_by(|n| ...) // 根据闭包确定迭代器中的最小值 .nth(n) // 返回迭代器第n个位置的条目 .product() // 计算迭代器所有元素的乘积(仅限数字条目) .rev() // 反转迭代器顺序 .skip(n) // 跳过迭代器的前n个条目 .sum() // 计算迭代器所有条目的和(仅限数字条目) .zip(other_it) // 与另一个迭代器合并,按A, B, A, B模式排列合并条目

游戏开发:从构思到实现的实用指南

16. 变量赋值的深入理解

在Rust中,变量赋值有着不同的方式和特点。除了前面提到的基本赋值方式,我们还可以进一步理解其区别。例如,使用 let n = 5; 时,Rust会自动推导变量 n 的类型,这在简单场景下非常方便。而 let n : i32 = 5; 则明确指定了 n 的类型为 i32 ,这种方式在需要明确类型的情况下很有用,比如在进行一些特定的数值计算时。

创建占位变量 let n; 后,后续只能对其进行一次赋值,这有助于保证变量的使用逻辑清晰。而 let mut n = 5; 声明的可变变量 n ,可以在后续代码中改变其值,这在需要动态更新变量值的场景中很常见,比如在游戏中记录玩家的得分。

17. 结构体与枚举的应用场景

结构体和枚举是Rust中非常重要的类型定义方式。结构体 struct S { x:i32 } 可以用来组织相关的数据,例如在游戏中可以用结构体表示角色的属性,如生命值、攻击力等。通过 s.x 的方式可以方便地访问结构体中的字段。

元组结构体 struct S (i32); 则适用于只包含一个或几个简单数据项的情况,通过 s.0 访问其中的数据。而单元结构体 struct S; 通常会在代码中被优化掉,可能用于一些特殊的标记场景。

枚举类型 enum E { A, B } enum E { A(i32), B } 可以用来表示具有多种状态或选项的情况。在游戏中,枚举可以用来表示角色的状态,如站立、行走、攻击等。通过匹配枚举值,可以执行不同的代码逻辑,如下表所示:
| 枚举类型 | 应用场景 |
| ---- | ---- |
| enum E { A, B } | 简单的状态选择,如开关状态 |
| enum E { A(i32), B } | 带有额外数据的状态选择,如不同类型的事件并附带参数 |

18. 控制流的灵活运用

Rust的控制流语句为程序的执行提供了丰富的选择。 while 循环 while x { ... } 适用于需要在某个条件为真时持续执行代码的场景,比如在游戏中检测玩家是否按下某个按键。 loop 循环 loop { break; } 则可以实现无限循环,直到遇到 break 语句才会退出,常用于需要不断更新游戏画面的场景。

for 循环有多种形式, for i in 0..4 {...} for i in 0..=4 {...} 分别用于排他和包含范围的循环,在遍历数组或执行固定次数的操作时很有用。 for i in iter {...} iter.for_each(|n|...) 则用于对迭代器的元素进行操作,迭代器可以来自集合或其他函数返回。

if 语句 if x {...} else {...} 用于根据条件执行不同的代码块,在游戏中可以根据玩家的操作或游戏状态进行不同的处理。

下面是一个简单的控制流流程图:

graph LR
    A[开始] --> B{条件判断}
    B -- 真 --> C[执行代码块1]
    B -- 假 --> D[执行代码块2]
    C --> E[结束]
    D --> E
19. 函数与闭包的优势

函数和闭包是Rust中实现代码复用和模块化的重要工具。函数 fn my_func() {...} fn my_func(i:i32) {..} fn n2(n:i32) -> i32 { n*2 } 可以将不同的功能封装起来,提高代码的可读性和可维护性。例如,在游戏中可以将角色的移动、攻击等功能封装成不同的函数。

闭包 || { ... } || 3 |a| a*3 则更加灵活,可以捕获周围环境中的变量,并且可以作为参数传递给其他函数。在游戏中,闭包可以用于事件处理,比如当玩家点击某个按钮时执行特定的操作。

20. 匹配枚举的实际应用

匹配枚举是Rust中处理不同状态的强大方式。通过 match e { ... } 语句,可以根据枚举值执行不同的代码逻辑。例如,在游戏中处理角色的不同状态:

enum CharacterState {
    Standing,
    Walking,
    Attacking
}

fn handle_state(state: CharacterState) {
    match state {
        CharacterState::Standing => println!("角色站立"),
        CharacterState::Walking => println!("角色行走"),
        CharacterState::Attacking => println!("角色攻击")
    }
}

在这个例子中,根据角色的不同状态,会输出不同的信息。

21. 可选变量与结果变量的处理技巧

可选变量和结果变量在Rust中用于处理可能为空或出错的情况。 option.unwrap() option.expect(”Fail”) option.unwrap_or(3) 用于解包可选变量,当可选变量为空时, unwrap() 会崩溃, expect() 会带消息崩溃,而 unwrap_or() 会使用默认值替代。

if let Some(option) = option { ... } 则可以安全地提取可选变量的内容,避免程序崩溃。对于结果变量, result.unwrap() result.expect(”Fail”) result.unwrap_or(3) 有类似的作用, function_that_might_fail()? 则是一种简洁的解包方式,当函数返回错误时会直接返回。

22. 元组与解构的便捷性

元组 let i = (1, 2); let (i, j) = (1, 2); 可以将多个值组合在一起,并且可以通过解构的方式方便地获取其中的值。在游戏中,元组可以用于表示坐标、速度等多个相关的值。通过 i.0 可以访问元组的第一个成员,这种方式简洁明了。

23. 模块与导入的组织代码

模块和导入可以帮助我们组织代码,提高代码的可维护性。 mod m; 用于引用模块,会查找 m.rs m/mod.rs 文件。 mod m { ... } 则可以内联声明模块,在当前作用域内可以通过 m::x 的方式访问模块中的内容。

use m::*; use m::a; 分别用于导入模块的所有成员和特定成员到当前作用域,这样可以避免每次使用模块中的内容都要写完整的路径。

24. 迭代器链的强大功能

迭代器链是Rust中处理集合数据的强大工具。通过 iter 获取迭代器后,可以使用一系列的方法对其进行操作。 iter.for_each(|n| ... ) 用于对迭代器的每个元素执行闭包, iter.collect::<T>() 可以将迭代器的元素收集到一个新的集合中。

iter.filter(|n| ...) iter.filter_map(|n| ...) 用于过滤迭代器的元素, iter.find(|n| ...) 可以查找满足条件的元素。 iter.fold(|acc, x| ...) 可以对迭代器的元素进行累积操作, iter.map(|n| ...) 可以将迭代器的元素进行转换。

下面是一个迭代器链的使用示例:

let numbers = vec![1, 2, 3, 4, 5];
let sum_of_even = numbers.iter()
    .filter(|&n| n % 2 == 0)
    .map(|n| n * 2)
    .sum();
println!("偶数的两倍之和: {}", sum_of_even);

在这个示例中,我们对一个整数向量进行了过滤、转换和求和操作。

25. 总结与展望

通过以上对游戏开发从构思到实现的各个方面的介绍,以及Rust代码的详细讲解,我们可以看到,游戏开发需要综合运用各种知识和技巧。从游戏的构思、设计文档的编写,到代码的实现和优化,每一个环节都至关重要。

在未来的游戏开发中,我们可以不断尝试新的创意和技术,结合Rust的强大功能,开发出更加优秀的游戏作品。同时,要不断总结经验,提高自己的开发能力,避免重复犯错,让游戏开发的过程更加高效和愉快。

希望这些内容能为你在游戏开发的道路上提供一些帮助,祝你在游戏开发中取得成功!

【博士论文复现】【阻抗建模、验证扫频法】光伏并网逆变器扫频与稳定性分析(包含锁相环电流环)(Simulink仿真实现)内容概要:本文档是一份关于“光伏并网逆变器扫频与稳定性分析”的Simulink仿真实现资源,重点复现博士论文中的阻抗建模与扫频法验证过程,涵盖锁相环和电流环等关键控制环节。通过构建详细的逆变器模型,采用小信号扰动方法进行频域扫描,获取系统输出阻抗特性,并结合奈奎斯特稳定判据分析并网系统的稳定性,帮助深入理解光伏发电系统在弱电网条件下的动态行为与失稳机理。; 适合人群:具备电力电子、自动控制理论基础,熟悉Simulink仿真环境,从事新能源发电、微电网或电力系统稳定性研究的研究生、科研人员及工程技术人员。; 使用场景及目标:①掌握光伏并网逆变器的阻抗建模方法;②学习基于扫频法的系统稳定性分析流程;③复现高水平学术论文中的关键技术环节,支撑科研项目或学位论文工作;④为实际工程中并网逆变器的稳定性问题提供仿真分析手段。; 阅读建议:建议读者结合相关理论教材与原始论文,逐步运行并调试提供的Simulink模型,重点关注锁相环与电流控制器参数对系统阻抗特性的影响,通过改变电网强度等条件观察系统稳定性变化,深化对阻抗分析法的理解与应用能力。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值