Clickmania 游戏






3.13/5 (4投票s)
2005 年 3 月 31 日
3分钟阅读

57129

2331
一个简单的逻辑游戏
引言
我曾经玩过Matthias Schüssler制作的Clickomania 新一代,非常喜欢这款游戏,因此决定用C#从头开始实现它。我的版本更简单,但使用了许多相同的设计理念。我在Athlon 2000+上测试了这款游戏,运行起来非常流畅。
特点
- 动画和声音
- 保存高分(前10名)
- 撤销移动
对象模型
我使用了两个主要的类,MainForm
和Engine
。MainForm
处理事件、绘图和动画,Engine
包含游戏逻辑。我还使用了三个struct
结构体,一个用于Scores
(分数、玩家姓名、日期等),
public struct Scores
{
public int _iScores; // score
public string _sNames; // player name
public int _SbestMove; // his best move
public int _SballsLeft; // balls left after game over
public int _Stime; // time for that game
public string _Date; // the date he played (mm/dd/yyyy mm:ss)
public int _sTakeBacks; // how many times did he take back
// during the game
}
一个用于Ball
(球),
public struct Ball
{
public int _icolor; // its color
public bool _exists; // does it still exist
public bool _isDisappearing; // is it in animation status
public int _vel; // velocity of animation
public int _yvel;
}
还有一个用于Turn
(回合),其中包含关于消失的球、它们的位置和移动的列的信息,以便玩家可以撤销移动。
public struct Turn
{
public int _itColor; // Color of disappeared balls
public int [] _itposX; // Position
public int [] _itposY;
public int [] _column; // Disappeared columns
}
关于游戏逻辑
在PictureBox
上,球看起来像是移动的,但实际上它们并没有移动。球数组存储8x12个球。它们在棋盘上的位置保持不变。只有当它们应该消失时,它们才会改变颜色或将status _exist
设置为false
。
当玩家点击一个球时,它会检查该球是否属于一组颜色相同的球。这是通过一个递归函数完成的。
private int CheckNextBall(int x, int y, int color)
{
int px, nx, py, ny;
px = (x == 0 ? 0 : x - 1); //prior X
nx = (x == 7 ? 7 : x + 1); //next X
py = (y == 0 ? 0 : y - 1); //prior Y
ny = (y == 11 ? 11 : y + 1);//next Y
int ret = 1;
// the 4 "if" statements do basically the
// same, they check if the
// neighbour of the actual ball has the same color
if((_ball[px, y]._icolor == color) && (_ball[px, y]._exists) && (px != x))
{
// make it inexistant
_ball[px, y]._exists = false;
// to animate it in the main form
_ball[px, y]._isDisappearing = true;
// save color of ball( so we can take the turn back)
_turn[_turn.GetLength(0) - 1]._itColor = color;
_X[_index] = px; // and save its position (for the same reason)
_Y[_index] = y;
_index++;
ret += CheckNextBall(px, y, color);
}
if((_ball[nx, y]._icolor == color) && (_ball[nx, y]._exists) && (nx != x))
{
_ball[nx, y]._exists = false;
_ball[nx, y]._isDisappearing = true;
_turn[_turn.GetLength(0) - 1]._itColor = color;
_X[_index] = nx;
_Y[_index] = y;
_index++;
ret += CheckNextBall(nx, y, color);
}
if((_ball[x, py]._icolor == color) && (_ball[x, py]._exists) && (py != y))
{
_ball[x, py]._exists = false;
_ball[x, py]._isDisappearing = true;
_turn[_turn.GetLength(0) - 1]._itColor = color;
_X[_index] = x;
_Y[_index] = py;
_index++;
ret += CheckNextBall(x, py, color);
}
if((_ball[x, ny]._icolor == color) && (_ball[x, ny]._exists) && (ny != y))
{
_ball[x, ny]._exists = false;
_ball[x, ny]._isDisappearing = true;
_turn[_turn.GetLength(0) - 1]._itColor = color;
_X[_index] = x;
_Y[_index] = ny;
_index++;
ret += CheckNextBall(x, ny, color);
}
return ret;
}
此函数检查四个相邻球的颜色,如果它们颜色相同,则将其状态设置为消失,并检查相邻球的颜色。它还会将颜色和位置信息保存在Turn
结构体中,以便玩家可以根据需要撤销移动。
动画
这是在Mainform
类中使用动画球的位置和大小的全局变量以及pictureBox.Refresh()
方法完成的。例如,要使球出现或消失,请执行以下操作:
/// <summary>
/// Balls Disappear or appear
/// </summary>
/// <param name="appear"> if they are appearing or disappearing</param>
public void MakeBallsAppear(bool appear)
{
if(!appear)
{
PlayWav(1); // play a sound
// redraw all balls, but animate only thosse with appear state
// this occurs when balls that are clicked disappear
for(int i = 0; i < 12; i++)
{
_var = i;
Thread.Sleep(10); // sleep 10 ms between 2 frames
this.pictureBox1.Refresh();
}
_var = 0;
}
else
{
PlayWav(8);
// same thng here too, but balls reappaer when
// player takes a move back
for(int i = 11; i >= 0; i--)
{
_var = i;
Thread.Sleep(10);
this.pictureBox1.Refresh();
}
}
}
Paint
事件在上述循环中调用pictureBox.Refresh()
时绘制一帧。我们还在绘制下一帧之前等待10毫秒,这样动画就不会在速度更快的机器上显示得更快。
/// <summary>
/// here all the drawing takes place. for each
/// frame, this event is called once,
/// with a different _var, _posx or _posy
/// if an animation takes place
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
for(int i = 0; i < 8; i++)
{
for(int j = 0; j < 12; j++)
{
if(engine._ball[i, j]._exists)
{
g.DrawImage(_bmp[engine._ball[i, j]._icolor],
(i * 24) - _posx * engine._ball[i, j]._yvel,
(j * 24) + _posy * engine._ball[i, j]._vel,
24, 24);
}
if(engine._ball[i, j]._isDisappearing)
{
g.DrawImage(_bmp[engine._ball[i, j]._icolor],
(i * 24) + _var, (j * 24) + _var,
24 - (_var*2), 24 - (_var*2));
}
}
}
if((_gameover) && (!_gamewon))
{
// shadow of the text
g.DrawString("GAME OVER", new Font("Arial", 20),
System.Drawing.Brushes.Black,
new Point(10, 122));
g.DrawString("GAME OVER", new Font("Arial", 20),
System.Drawing.Brushes.LightBlue,
new Point(8, 120));
}
if(_gamewon)
{
// shadow of the text
g.DrawString("GAME WON", new Font("Arial", 20,
System.Drawing.FontStyle.Bold),
System.Drawing.Brushes.Black,
new Point(12, 122));
g.DrawString("GAME WON", new Font("Arial", 20,
System.Drawing.FontStyle.Bold),
System.Drawing.Brushes.Yellow,
new Point(10, 120));
}
}
音效
PlaySound(...)
方法来自winmm.dll,用于播放WAV文件。
[DllImport("winmm.dll")]
private static extern bool PlaySound( string lpszName,
int hModule, int dwFlags );
private void PlayWav(int play)
{
if(checkBox1.Checked)
{
string myFile = ".\\Sounds\\default.wav";
switch(play)
{
case 1:
myFile = ".\\Sounds\\BallDisappear.wav";
break;
case 2:
myFile = ".\\Sounds\\BallDown.wav";
break;
case 3:
myFile = ".\\Sounds\\lost.wav";
break;
case 4:
myFile = ".\\Sounds\\newgame.wav";
break;
case 5:
myFile = ".\\Sounds\\ColumnDis.wav";
break;
case 6:
myFile = ".\\Sounds\\ColumnAppear.wav";
break;
case 7:
myFile = ".\\Sounds\\BallUp.wav";
break;
case 8:
myFile = ".\\Sounds\\BallAppear.wav";
break;
case 9:
myFile = ".\\Sounds\\Won.wav";
break;
case 10:
myFile = ".\\Sounds\\illegal.wav";
break;
default:
break;
}
PlaySound(myFile, 0, 0x0003); // Play the sound
}
}
WAV文件必须位于包含Clickmania.exe的文件夹的Sounds子文件夹中。我使用了一些Half Life 2游戏的声音。如果要使用自己的声音,可以将它们上传到sounds文件夹并重命名。
分数
所有分数都存储在一个名为Scores.sco的二进制文件中,该文件与exe文件位于同一文件夹中。如果该文件不存在,则在运行游戏时会生成它。此文件还包含最后进入前10名的用户的姓名,因此如果他再次进入前10名,则不需要重新输入他的姓名。为了查看高分,我使用了ListView
控件。
private void PopulateListbox()
{
string name, score;
listView1.Items.Clear(); // remove items in listview
ListViewItem [] items = new ListViewItem[10];
//DateTime date;
Color textColor = new Color();
Font font;
for(int i = 0; i < 10; i++)
{
name = (i + 1).ToString() + ":" +
" " + _MF._stScores[i]._sNames;
score = _MF._stScores[i]._iScores.ToString();
if (i == 9)name = (i + 1).ToString() + ":" +
" " + _MF._stScores[i]._sNames;
textColor = _MF._stScores[i]._SballsLeft == 0 ?
System.Drawing.Color.Blue : System.Drawing.Color.Black;
textColor = (_MF._stScores[i]._sTakeBacks == 0 &&
_MF._stScores[i]._SballsLeft == 0) ?
Color.DarkRed : textColor;
font = (textColor == Color.Black) ?
new Font("Arial", 8) : new Font("Fixedsys", 8,
(_MF._stScores[i]._sTakeBacks == 0) ?
FontStyle.Italic : FontStyle.Regular);
items[i] = new ListViewItem(new string[] {name, score,
_MF._stScores[i]._Date,
_MF._stScores[i]._Stime.ToString(),
_MF._stScores[i]._SbestMove.ToString(),
_MF._stScores[i]._SballsLeft.ToString(),
_MF._stScores[i]._sTakeBacks.ToString()},
-1, textColor, Color.White, font);
listView1.Items.Add(items[i]);
}
}
根据游戏的进行方式,信息将以不同的颜色显示,例如,如果玩家没有留下任何球或没有撤销任何移动。
关于程序
这是我的第一个C#实现之一,如果我的代码在某些地方看起来很糟糕,请多包涵。
许可证
本文未附加明确的许可证,但可能在文章文本或下载文件本身中包含使用条款。如有疑问,请通过下面的讨论区联系作者。
作者可能使用的许可证列表可以在此处找到。