热带音乐. Scratch 对 C#.





5.00/5 (7投票s)
如果您是尝试使用 Scratch 向孩子介绍编程的程序员,那么这篇文章就是为您准备的。比较 Scratch 和 C# 中的一个简单游戏。
引言
任何家里有学龄儿童的人都听说过 Scratch——面向儿童的视觉编程语言。该语言由 MIT Media Lab 开发,旨在鼓励年轻人发展计算机技能。Scratch 网站拥有超过 64,000,000 名注册用户。其中许多人长大后会加入 CodeProject。我有自己的孙子孙女,我从 MIT 网站下载了 Scratch 应用程序。我购买了 Jon Woodcock 的《Scratch 编码游戏》一书。和我的孙子一起,我们尝试了一些游戏。其中一个游戏是“热带音乐”。我根据自己的喜好修改了它。然后我决定用 C# 重写它。我的目标是展示从块式视觉语言到通用专业计算机语言的区别。本文重点介绍了使用 C# 类型语言编写额外的开发工作。
热带音乐游戏
热带音乐是一款记忆游戏。有四个鼓。每个鼓都有不同的音调。程序播放随机的鼓(或音调)序列。玩家必须重复该序列。游戏从一个音调的序列开始,到七个音调结束。如果玩家成功,将播放下一个序列。如果玩家犯了错误,游戏将结束。每次正确响应都会为分数增加一分。从一个到七个音调的七场比赛,满分是 28 分。
Scratch 程序
我版本的 Scratch 程序 TropicalTunes.sb3 附在此文章中。程序代码稍后在文章中显示。要观看 Scratch 版本游戏的运行情况,请访问 Scratch MIT 网站,选择创建并在您的网页浏览器中运行它。或者访问 Scratch 下载网站并将 Scratch 应用安装到您的计算机上。在这两种情况下,请点击文件和从您的计算机加载,然后导航到 TropicalTunes.sb3 文件。
如果您觉得记住七个音符很难,就像我一样,请按向上箭头,音符列表将显示出来。按下向下箭头将隐藏列表。只是不要告诉任何人。
给黑客的提示。.sb3 文件是一个 .zip 文件。如果您将扩展名更改为 .zip,您可以看到包含的文件。文件的名称是十六进制数字的随机序列。文件列表由资源文件和一个 project.json 文件组成。资源文件的扩展名是您所期望的。如果您双击其中任何一个,您将看到或听到资源内容。对于 .svg 文件,您需要矢量图形编辑器,例如 Inkscape。如果您用 Visual Studio 打开 project.json 文件,然后转到编辑、高级和格式化文档,您就可以阅读它。
用 C# 开发热带音乐
C# 开发和 Scratch 开发之间的主要区别在于图像和音频资源的可用性。Scratch 环境提供了许多背景图像(称为舞台)、精灵图像和音频剪辑的选择。要使用 C# 开发类似的游戏,您需要自己创建或获取这些图像和音频剪辑。图像通常是 .jpg、.bmp 或 .png 文件,音频剪辑是 .wav 文件。有时,以矢量格式 .svg 创建图像很方便。但是,最终,图像必须导出为 .png 文件。
Scratch 术语中的背景图像或舞台
对于背景图像,我想要一个宽高比为 16:9、分辨率为 2560 x 1440 的图像。如果您转到 Google 图片并键入“tropical Paradise 2560 by 1440”,您会找到许多图像文件可供选择。我的选择是这张来自Pinterest 网站的图片。
鼓
对于鼓,程序需要八张图片。四个正常尺寸的鼓,每个都有不同的颜色方案,以及四个匹配的较大鼓。当播放一个鼓时,程序会用较大的鼓替换正常尺寸的鼓。我发现查找一个矢量图形鼓很方便,它可以轻松地被矢量图形编辑器修改以生成所有八个鼓。矢量图形图像具有 .svg 扩展名。我的矢量图形编辑器是 Inkscape。有关更多信息,请访问 inkscape 网站。我在 FREE*SVG 网站上找到了合适的鼓。对于每个鼓,我保存了修改后的矢量图形图像,并导出了一个 .png 文件作为资源文件。矢量图形文件已附带,尽管运行程序不需要它们。
原始鼓 | ![]() |
正常鼓 | ![]() |
活动鼓 | ![]() |
音频剪辑资源
与图像不同,音频样本在互联网上不易获得。我未能为本项目找到合适的 .wav 文件。Scratch 应用程序有一个添加扩展选项。其中一个选择是音乐演奏乐器和鼓。我编写了一个四行程序来演奏四个鼓音。我从Audacity 网站下载了 Audacity,一个免费的音频编辑程序。当 Scratch 播放四个音符时,Audacity 正在录制声音。接下来,我使用 Audacity 将声音剪辑分成四个单独的 .wav 文件。每个音符一个。
Audacity 四个鼓音
将图像和音频文件嵌入到可执行文件中
一旦您拥有了图像文件和音频文件资源,您就可以将它们嵌入到可执行文件中。
- 将资源文件加载到项目的Resources目录中
- 在解决方案资源管理器中,右键单击项目并选择属性。
- 选择资源。
- 在第一个选项卡中,选择图像。
- 在第二个选项卡添加资源中,选择添加现有文件。
- 选择所有图像文件。
- 回到第一个选项卡并选择音频。
- 对音频文件重复前两个步骤。
- 在解决方案资源管理器中打开Resources文件夹。您的所有资源文件都应该在那里。
- 选择Resources文件夹中的所有文件。
- 右键单击选定内容并单击属性。
- 将构建操作更改为嵌入式资源。
- 要从 C# 程序中访问资源,请键入:
Properies.Resources.<resource-name>
。例如,Bitmap BGImageFile = Properties.Resources.Background;
- 图像资源将返回为
Bitmap
类。音频资源将返回为Stream
类。
调整应用程序框架的大小
当热带音乐应用程序启动时,它将占用计算机屏幕的 90%。用户可以通过拖动边缘或角落来调整框架的大小。用户可以单击全屏图标或最大化按钮。在所有情况下,背景图像都将以正确的纵横比显示。
显示图像以适应屏幕
此方法显示背景图像,使其纵横比正确以适应框架的客户区大小。这是一个两步过程。第一步,我们创建一个背景和四个正常尺寸鼓的内存图像。第二步,我们将组合图像绘制到屏幕上。使用两步的原因是为了避免在 DPI 高于 96 dpi 的笔记本电脑上运行程序时出现 DPI AWARENESS 问题。我们使用内存图像在大鼓显示后恢复图像。我们在大鼓上绘制正常尺寸的鼓及其周围的区域。
// Draw background image
internal void DrawBackground()
{
// assume BG image width is equal to client size width
BGImageWidth = Parent.ClientSize.Width;
// calculate BG image height to preserve aspect ratio
BGImageHeight = BGImageFile.Height * BGImageWidth / BGImageFile.Width;
// height is too big. We need to recalculate and find image width
if(BGImageHeight > Parent.ClientSize.Height)
{
BGImageHeight = Parent.ClientSize.Height;
BGImageWidth = BGImageFile.Width * BGImageHeight / BGImageFile.Height;
}
// image position within the client area of the frame
BGPosX = (Parent.ClientSize.Width - BGImageWidth) / 2;
BGPosY = (Parent.ClientSize.Height - BGImageHeight) / 2;
// release previous background image memory
if(BGMemoryImage != null) BGMemoryImage.Dispose();
// create background memory image
BGMemoryImage = new Bitmap(BGImageWidth, BGImageHeight);
// create graphics object to draw the background image
Graphics ImageGraph = Graphics.FromImage(BGMemoryImage);
// paint a scaled version of the background image to fit the area
ImageGraph.DrawImage(BGImageFile, 0, 0, BGImageWidth, BGImageHeight);
// paint the four normal sized drums
for(int Index = 0; Index < 4; Index++) Parent.DrumArray[Index].DrawNormalDrum(ImageGraph);
// dispose the image graph object
ImageGraph.Dispose();
// draw the background including drums on the computer monitor
Graphics ScreenGraph = Parent.CreateGraphics();
ScreenGraph.FillRectangle(Brushes.LightGray, 0, 0,
Parent.ClientSize.Width, Parent.ClientSize.Height);
ScreenGraph.DrawImageUnscaled(BGMemoryImage, BGPosX, BGPosY);
ScreenGraph.Dispose();
return;
}
// Draw normal drum image over the background memory image
internal void DrawNormalDrum(Graphics ImageGraph)
{
// shortcut for background width and height
BGPosX = Parent.BGImage.BGPosX;
BGPosY = Parent.BGImage.BGPosY;
// shortcut for background width and height
int BGImageWidth = Parent.BGImage.BGImageWidth;
int BGImageHeight = Parent.BGImage.BGImageHeight;
// displayed drum width and height
NormalDrumWidth = (int) Math.Floor(NormalDrumScale * BGImageWidth);
NormalDrumHeight = NormalDrumWidth * NormalImage.Height / NormalImage.Width;
LargeDrumWidth = (int) Math.Floor(LargeDrumScale * BGImageWidth);
LargeDrumHeight = LargeDrumWidth * LargeImage.Height / LargeImage.Width;
// drum center position on memory image
DrumPosX = (int) (RelPosX * BGImageWidth);
DrumPosY = (int) (RelPosY * BGImageHeight);
// normal drum rectangle relative to memory image
NormalRect = new Rectangle(DrumPosX - NormalDrumWidth / 2,
DrumPosY - NormalDrumHeight / 2, NormalDrumWidth, NormalDrumHeight);
// large drum rectangle
LargeRect = new Rectangle(DrumPosX - LargeDrumWidth / 2,
DrumPosY - LargeDrumHeight / 2, LargeDrumWidth, LargeDrumHeight);
// draw normal sized drum in memory image
ImageGraph.DrawImage(NormalImage, NormalRect);
return;
}
Scratch 程序游戏逻辑
热带音乐程序总体上分为两个部分:整体控制(背景)和鼓控制。由于有四个鼓,总共有五个独立的执行路径。查看代码,很容易看到任何软件程序的主要构建块:赋值语句、条件语句、循环和控制语句。
C# 程序游戏逻辑
C# 程序的核心,游戏控制逻辑,是一个由游戏状态逻辑控制的循环。有六种状态。
GameState.Off
- 关闭状态。程序正在等待用户按下开始(绿色)图标。GameState.Init
- 游戏初始化。重置分数并将游戏编号设置为第 1 场游戏。GameState.StartSequence
- 为一场游戏开始音符序列。GameState.PlayNote
- 向玩家播放单个随机鼓或音符。游戏逻辑方法退出循环并等待音符播放结束。GameState.PlayNoteDone
- 在音符播放后,控制会返回到此状态。程序将继续播放序列中的下一个音符(返回到GameState.PlayNote
)。如果这是序列中的最后一个音符,程序将等待用户重复完整序列。GameState.UserNote
- 在用户播放每个鼓或音符后,控制将返回到此状态。如果用户 10 秒内未单击任何鼓,游戏将停止。如果敲击了正确的鼓,程序将等待序列中的下一个鼓。如果是序列中的最后一个鼓,我们将返回到GameState.StartSequence
。如果是最后一个序列,即第七场游戏,则为完美得分。如果用户未敲击正确的鼓,则游戏结束。
每个状态都以 continue
或 return
结尾。以 continue
结尾的块表示到下一个状态的过渡是即时的。以 return
结尾的块表示程序将在计时器事件或鼠标单击事件后返回到游戏逻辑。
/// Game loop
/// This is the main method that controls the game
private void GameLoop()
{
// switch based on game state
for(;;) switch(State)
{
// game is off. waiting for click on go icon
case GameState.Off:
default:
ActiveDrum = 0;
NoteTimer.Enabled = false;
TimeoutTimer.Enabled = false;
return;
// start a full game
case GameState.Init:
Score = 0;
ScoreArea.DrawScore(Score);
GameNo = 1;
State = GameState.StartSequence;
continue;
// start one sequence of notes
case GameState.StartSequence:
Array.Clear(DrumSequence, 0, MaxGames);
NoteNo = 0;
State = GameState.PlayNote;
continue;
// play one note
case GameState.PlayNote:
// random drum number 1 to 4
SetActiveDrumNo();
// save drum number in drum sequence array
DrumSequence[NoteNo] = ActiveDrum;
// display in list area
if(ShowList) ListArea.DrawList();
// play drum sound
DrumArray[ActiveDrum - 1].PlayDrum();
// next state after play drum is done
State = GameState.PlayNoteDone;
NoteTimer.Enabled = true;
return;
// play of single note is done
case GameState.PlayNoteDone:
// update note number and test for end of sequence
if(++NoteNo < GameNo)
{
// not end of the sequence. play another note in the sequence
State = GameState.PlayNote;
continue;
}
// wait for user to play back the sequence of notes
ActiveDrum = 0;
NoteNo = 0;
State = GameState.UserNote;
TimeoutTimer.Enabled = true;
return;
// user played one note
case GameState.UserNote:
// reset wait timeout
TimeoutTimer.Enabled = false;
// timeout. user did not play a note within timeout time
if(ActiveDrum == 0)
{
MessageArea.DisplayMessage("Timeout");
State = GameState.Off;
continue;
}
// user played the correct note
if(ActiveDrum == DrumSequence[NoteNo])
{
// update score
ScoreArea.DrawScore(++Score);
// reset active drum
ActiveDrum = 0;
// update note number and test for end of notes sequence
if(++NoteNo < GameNo)
{
// there are more notes in the sequence
TimeoutTimer.Enabled = true;
return;
}
// update game number and test for end of games
if(++GameNo <= MaxGames)
{
// after delay continue with next game
State = GameState.StartSequence;
NoteTimer.Enabled = true;
return;
}
// end of game with perfect score
PerfectSound.Play();
MessageArea.DisplayMessage("Perfect Score");
GameNo = 0;
State = GameState.Off;
return;
}
// stop the game, user made an error
GameOverSound.Play();
MessageArea.DisplayMessage("Wrong note");
ActiveDrum = 0;
State = GameState.Off;
return;
}
}
结论
我毫不怀疑 Scratch 是向任何人(无论老少)介绍计算机编程的绝佳工具。它直观地展示了基本的编程构建块。它是一个很好的教育工具。Scratch 语言在构建简单的游戏方面非常有效。用 C# 代码开发相同的游戏要复杂得多。但对我来说,这很有趣。很明显,Scratch 语言在超越简单游戏方面存在局限性。
历史
- 2021 年 1 月 27 日:版本 1.0 原始版本