type
status
date
slug
summary
tags
category
icon
password
学习资料来自《世嘉新人培训教材—游戏开发》[日]平山尚 著 罗水东 译

🎮1.1 第一个游戏:实例代码分析

👉 简单介绍

 
这本书的第一章主要由三个部分构成:(注:和原书分类不一致,是根据个人理解分类)
  1. 实例代码的分析(结构和功能)
  1. 添加游戏开发中的重要功能:读取场景数据
  1. C++知识点
这篇文章主要讲解 1.实例代码的分析(结构与功能),会展示不同代码块的源码,并分别分析它们的主要功能和逻辑,提出需要注意的点。
 
(其中下划线和带星号的部分是我没有理解全面的地方,连同最后的疑难问题,我会尝试在下一个同主题文章中解答)

🦝实例代码分析(结构和功能)

🦈关于游戏程序的最简单总结:

游戏程序:
  1. 获取输入
  1. 将输入更新到游戏中
  1. 显示结果
简单代码:

《箱子搬运工》小游戏代码分析

这里分析一下《箱子搬运工》小游戏代码的大致结构
💥代码由《世嘉新人培训教材—游戏开发》一书提供,可以自行使用(包括商用),作者和笔者不承担任何责任,使用时请尊重他人劳动成果,请不要损毁世嘉和笔者的名誉。
 

一)include头部文件

二)场景数据常量

注意点:
  • 为什么不一开始用0代表空间,1代表玩家:先用容易理解的字符串进行赋值,游戏开始后再将其转化为其他形式,这样做更简单。
  • 因为这里的值后续都不需要修改,所以使用全局变量const,可避免很多bug
  • 全局变量可以在程序中的任何地方被访问,所以命名时可以以g开头,方便识别
 

三)枚举类型

注意点:
  • 枚举类型数组的作用:保存场景中的所有状态(在一个容量等于“宽*高”的枚举类型数组中)
  • 另一种存放数据的方法:将是否是目的地的信息*存放在另一个数组中,或者通过位运算*将两种信息保存在同一个数组里
 

四)函数原型(声明需要使用的函数)

五)main函数(游戏主逻辑,会调用上述函数)

注意点:
  • 枚举类型是一种用于列举的类型,所以可以通过new生成,也可以用作参数和返回值*
  • 枚举类型内部的变量只能通过枚举类型来赋值,如果直接赋值会导致编译错误
  • 逻辑上Object应该是一个二维数组,但是这里使用了一维数组的创建方式,原因是二维数组无法通过new动态创建,会丧失灵活性。
  • 养成及时释放空间的习惯,注意是delete[]而非delete
  • 释放空间后最好将指针赋值为0,很大程度上避免指针相关的bug
  • 注意主循环中函数的顺序:draw() → checkClear() → getInput → update()
  • getInput之前需要先检测是否通关,避免响应输入之前已经通关了却没有被检测出来
 

六)初始化场景

注意点:
  • 初始化部分的代码主要逻辑是:逐个读取字符并将其转换为Object类型。将stageData里的字符串转化为Object类型写入state数组中。
  • 最后的if语句是为了忽略非法输入。最好的方法是通过场景数据自行计算出宽度和高度值,我们可以封装一个函数来实现这个功能*
 

七)绘制

注意点:
  • draw()的功能和initialize()正好相反,是把Object数组里的内容转化成字符,再通过cout输出。这部分在一些游戏中也是类似的,只不过是把程序内的数据转化为图像渲染出来。
  • 这里的思路是,创建一个char array,并且数组中字符的顺序对应Object数组的内元素的顺序,这样在for循环中,o的int值就代表了与之对应的字符的index,font[o]可以直接打印出我们想要的字符。另一种方法是在for循环中用switch进行处理:
 

八)更新

Update部分往往是游戏的核心,所以代码也比较长,我会逐步讲解。
🦁参数
  • 一般来说,用单个的字母作为参数变量名不是很好的习惯。但是如果该变量所在的代码很短,使用频率也很高,单个字母不容易和其他变量搞混,那只要在一开始加上详细的注释,就可以使用单个字母来命名。
 
🐯输入
 
🐱检测玩家位置
注意点:
  • 有方法可以不需要检索玩家位置,而且运行起来更快:将玩家位置保存在某个变量中。
  • 书中提到的方法:创建一个Satge类等,并在其中设置一个成员变量,用于查找结束时保存玩家的位置,那么就可以在保持代码简洁的同时保证处理效率。* 但是这个方法可能也会导致错误,即通过查找获取的玩家位置和保存在变量中的玩家位置可能会不一样(*是在何种情况中呢?)所以在不影响速度的情况下可以每次都检查一遍玩家位置。
  • 代码中的
    • 将玩家的情况分为了两类,显得不够简洁,可以封装在isPlayer()这样的函数中或者单独将“是否是目的地”的信息保存到另外的变量中(*可以之后尝试)
 
🦊移动
注意点:
  • 命名习惯中:dx中的d是“difference”,tx中的t是“temporary”
  • 这里最重要的就是搞清楚人物移动有哪几种具体情况,这里有两大情况玩家可以移动:
    • A.Target Position是空白或者目的地:
      判断tp是空白还是目的地,然后改变tp和p的Object值
      B.Target Position是箱子,并且沿该方向的下一个网格是空白或者目的地:
      判断tp有没有箱子和tp2是不是空白或者目的地,然后逐步改变tp2,tp,p的位置
  • 如果将“是否在目的地”的信息储存在别处,代码会更加简洁(*之后可尝试)
  • 作者省略了tp为墙壁的代码,但是为了让代码更容易理解应当加上
  • 注意检测推动的有效性
 

九)通关检测

注意点:
  • 如果场景数据出错,导致目的地的数量比箱子还多,也依然会判定通过。所以初始化时必须仔细检查场景数据。
 

🐃总结归纳

回顾一下这160行代码的主要结构:
  1. 包含头文件 #inclue ..
  1. 枚举类型 enum xx {};
  1. 数据场景常量
  1. 函数声明
  1. 游戏主逻辑(main函数):初始化initialize() loop: draw()→checkClear()→getInput→update()
  1. 初始化函数initialize():逐个读取字符并将其转换为Object类型
  1. 绘制函数draw():是把Object数组里的内容转化成字符,再通过cout输出
  1. 更新函数update():输入,检测玩家位置,移动
  1. 通关检测函数

🐄待解答问题

(下一篇本主题文章的时候会尝试解答,如果找到正确答案会将问题划去)
  1. 如何调试来看看到枚举类型的名字?
  1. main函数中,为什么最后只释放了枚举类型的指针?stageData为什么不需要释放?

下一章提要

💡
添加游戏开发中的重要功能:读取场景数据
游戏开发学习笔记01 - [1.1 实例代码的分析游戏开发学习笔记01 - [1.1 实例代码的分析
Loading...