这个游戏不用多介绍 是以前最早的一种经典游戏,可谓家喻户晓。
下面我用C#4.0来描写,在一定程度做出这个游戏。当然不足之处还有很多,有些地方我暂时还无能为力,好了,闲话不多说,让我们入题吧。
1,建一个winform项目,
2,现在保留Form1.cs窗口,做游戏窗口,
3,我们要采用GDI+的图形API来对游戏进行实时绘制,
Form1.cs当中将包含一些与游戏相关的内容,下面分别介绍,
1,最简单的绘制引擎
void gc_UpdateGamePicture(Cell[,] obj) {
try {
for (int i = 0 ; i < GameContainer.Width ; i++) {
for (int j =2 ; j < GameContainer.Height ; j++) {
this.csb.Color = GameHelper.ColorMapping(obj[i,j].ftype);
this.g.FillRectangle(this.csb,i * cellSize.Width,(j-1) * cellSize.Height,cellSize.Width,cellSize.Height);
this.g.DrawRectangle(this.p,i * cellSize.Width,(j-1) * cellSize.Height,cellSize.Width,cellSize.Height);
}
}
} catch { }
}
//
这里面有个参数叫做Cell这是一个自定义结构,这个二维Cell[,]数组,是对游戏画面的一个数据映射,它的结构如下
public struct Cell {
public BrickType ftype;
public int x,y;
public bool fIsFixed;
//
public Cell copy(int x,int y) {
Cell c = default(Cell);
c = new Cell();
c.x = this.x + x;
c.y = this.y + y;
c.ftype = this.ftype;
c.fIsFixed = this.fIsFixed;
return c;
}
internal void move(int x,int y) {
if (this.x + x >= 0 && this.x + x < GameContainer.Width && this.y + y >= 0 && this.y + y < GameContainer.Height) {
this.x += x; this.y += y;
}
}
}
,这个绘制引擎方法很简单没有必要过多深说,主要就是你用Form1.CreateGraphics()把Graphics创建出来,让它可以在窗口上绘制,另外注意一下,如何把Cell数据转换成为绘制到窗口上的不同色块,这里我把窗口按,12*22进行了单元划分,当然Cell[,]的大小是,12*24多出的2行是为了在生成方块时有一个预备,以避开数组越界,让砖块可以从最顶部开始往下落, OKey,在Form1里面 除了这个以外 就是另一个 重要的相关方法了,这就是用户操作,让我们来看下代码:
protected override void OnKeyDown(KeyEventArgs e) {
switch (e.KeyCode) {
case Keys.Space:
//this.gc.CutFloors += 29;//use test time
break;
case Keys.Down:
this.gc.CurrentBrick.OnToBottom();
//MessageBox.Show("down");
break;
case Keys.Up:
this.gc.CurrentBrick.Eddy();
//MessageBox.Show("up");
break;
case Keys.Left:
this.gc.CurrentBrick.MoveLeft();
//MessageBox.Show("left");
break;
case Keys.Right:
this.gc.CurrentBrick.MoveRight();
//MessageBox.Show("right");
break;
default:
break;
}
base.OnKeyDown(e);
}
代码很直观这点我做得不错,大家知到这个游戏操作很简单,向左移,向右移,旋转,和落下,大约4种最基本的用户操作,在这里都写上了
this.gc.CurrentBrick.XXX这是什么玩意? 好的,这是这个游戏项目中的业务层部分,在面象对象的程序风格里,我们总是少不了什么?少不了“对象模型”对吧,那么这些什么gc,什么CurrentBrick这些东西,都是这个项目的对象模型中的具体实例,好的接下来让我为大家介绍下,这个项目的对象模型吧,除了,刚刚我们看到的那个Cell以外,我们还需要很多其它东西,
我们需要:
1,GameContainer//this.gc ,这个表示的是游戏容器,是整个对象模型顶级容器。
2, Brick//this.gc.CurrentBrick, 这个表示的是游戏砖块
3,一个enum用以表明砖块的类型,代码如下:
public enum BrickType : byte {
N = 0, I, L, nL, Z, nZ, Q, T
}
4,一个用以表明砖块状态的enum
public enum BrickState : byte {
n, e, w, s
}
5,做一个helper类,我将之取名为GameHelper它将提供一些额外的必要功能。
对象模型中的对象就这些吧。
接下来,我们来分别看看这些对象的定义
1,GameContainer,
这个类里面需要包含一些东西,
-1,Cell[,]所以色块,我们知到色块将构造出砖块也构造出那些固定下面的游戏画面背景部分。
-2, 一个游戏运行引擎,//里面还有代码冗余,我没有移除。那个Interlocked的存在并没有解决我想要解决的问题,暂时让它在那吧反正也不影响什么。
public void Run() {
this.Reset();
GameHelper.GameBegineSound();
#if !test
this.t = Task.Factory.StartNew(o => {
while (!this.CheckGameOver()) {
this.CurrentBrick.Reset();
while (!this.CurrentBrick.IsFixed) {
if (Interlocked.Read(ref threadmark) == 1) {
break;
}
GameHelper.Wait(this.CutFloors);
this.CurrentBrick.MoveDown();
}
}
},null);
#else
ThreadPool.QueueUserWorkItem(o => {
while (!this.CheckGameOver()) {
this.CurrentBrick.Reset();
while (!this.CurrentBrick.IsFixed&&Interlocked.Read(ref threadmark) == 0) {
GameHelper.Wait(this.CurFloors);
this.CurrentBrick.MoveDown();
}
}
});
#endif
}
-3,一个Brick对象,
-3,实现Brick.Exist事件,游戏将会产生砖块的存在性检查,
bool CurrentBrick_Exist(Cell[] arg) {
bool b = true;
foreach (var item in arg) {
if (item.x >= 0 && item.x < Width && item.y >= 0 && item.y < Height && !this.data[item.x,item.y].fIsFixed) continue;
b = false;
break;
}
return b;
}
-4,实现Brick.ToBottom事件请求,因为ToBottom并不适合在Brick中实现,
void CurrentBrick_ToBottom(Cell[] obj) {
Interlocked.Increment(ref threadmark);
while (true) {
for (int i = 0 ; i < Brick.Len ; i++) {
if (obj[i].y == Height - 1 || this.data[obj[i].x,obj[i].y + 1].fIsFixed) {
foreach (var item in obj) {
this.data[item.x,item.y].fIsFixed = true;
this.data[item.x,item.y].ftype = item.ftype;
}
var x = this.Sort(new int[] { obj[0].y,obj[1].y,obj[2].y,obj[3].y });
foreach (var item in x) {
this.CheckFloor(item);
}
this.CurrentBrick.IsFixed = true;
GameHelper.BrickFixedSound();
Interlocked.Decrement(ref threadmark);
return;
}
}
for (int i = 0 ; i < Brick.Len ; i++) {
obj[i].move(0,1);
}
}
}
-5,减层操作,就是游戏规则中规定的,当砖块累积满一行而没有任何空单元块时所要采取的动作,即将它从游戏画面中先移除,
void CheckFloor(int num) {
for (int i = 0 ; i < Width ; i++) {
if (this.data[i,num].ftype == BrickType.N) return;
}
for (int j = num ; j > 2 ; j--) {
for (int i = 0 ; i < Width ; i++) {
this.data[i,j].ftype = this.data[i,j - 1].ftype;
this.data[i,j].fIsFixed = this.data[i,j - 1].fIsFixed;
}
}
this.CutFloors++;
GameHelper.CutFloorSound();
this.OnNotify(this.CutFloors.ToString() + " 层");
}
-6,向Form1绘制UI,发送事件,以启动开头我讲到过的那个绘制引擎,向UI绘制实时的游戏画面。
void CurrentBrick_Update(Cell[] brickData) {
foreach (var item in this.previousBrickData) {
if (!this.data[item.x,item.y].fIsFixed)
this.data[item.x,item.y].ftype = BrickType.N;
}
BrickType bt = brickData[0].ftype;
for (int i = 0 ; i < Brick.Len ; i++) {
this.data[brickData[i].x,brickData[i].y].ftype = bt;
if (brickData[i].y == GameContainer.Height - 1 || brickData[i].y + 1 < Height && this.data[brickData[i].x,brickData[i].y + 1].fIsFixed) {
Interlocked.Increment(ref GameContainer.threadmark);
for (int j = 0 ; j < Brick.Len ; j++) {
this.data[brickData[j].x,brickData[j].y].fIsFixed = true;
this.data[brickData[j].x,brickData[j].y].ftype = bt;
}
var x = this.Sort(new int[] { brickData[0].y,brickData[1].y,brickData[2].y,brickData[3].y });
foreach (var item in x) {
this.CheckFloor(item);
}
GameHelper.BrickFixedSound();
this.CurrentBrick.IsFixed = true;
Interlocked.Decrement(ref GameContainer.threadmark);
break;
}
}
brickData.CopyTo(this.previousBrickData,0);
this.OnUpdateGamePicture(this.data);
}
-7,检查游戏是否结束
private bool CheckGameOver() {
bool b = default(bool);
for (int i = 0 ; i < Width ; i++) {
if (this.data[i,2].fIsFixed) {
b = true;
this.OnGameOver();
break;
}
}
return b;
}
//---
2,Brick
-1,我们需要一个,Cell[]来代码砖块,你分析下就会发现每个砖块都是由4个单元块构成的,所以这个Cell.Length=4;
-2,需要一个 用于操作的 旋转不变块,Cell c;这个概念很业务化,需要对业务有比较深的了解你才会构造出这一概念来。
-3,左移
public void MoveLeft() {
this.c.move(-1,0);
var v2 = this.Build(this.CurrentState);
if (this.OnExist(v2)) {
v2.CopyTo(this.data,0);
this.OnUpdate();
} else {
this.c.move(1,0);
}
}
-4,右移
public void MoveRight() {
this.c.move(1,0);
var v2 = this.Build(this.CurrentState);
if (this.OnExist(v2)) {
v2.CopyTo(this.data,0);
this.OnUpdate();
} else {
this.c.move(-1,0);
}
}
-5,下移
public void MoveDown() {
this.c.move(0,1);
var v2 = this.Build(this.CurrentState);
if (this.OnExist(v2)) {
v2.CopyTo(this.data,0);
this.OnUpdate();
} else {
this.c.move(0,-1);
this.OnUpdate();
this.IsFixed = true;
}
}
-6,旋转
public void Eddy() {
BrickState temp = this.CurrentState;
var x = (byte)this.CurrentState;
x++;
var y = x % 4;
this.CurrentState = (BrickState)y;
var v2 = this.Build(this.CurrentState);
if (this.OnExist(v2)) {
v2.CopyTo(this.data,0);
this.OnUpdate();
} else {
this.CurrentState = temp;
}
}
-7,落下
internal void OnToBottom() {
if (this.ToBottom != null)
this.ToBottom.Invoke(this.data);
}
-8,每次一个砖落下固定以后,需要的重置方法
public void Reset() {
Random r = new Random();
BrickType bt = (BrickType)r.Next(1,8);
BrickState bs = (BrickState)r.Next(0,4);
this.CurrentState = bs;
this.IsFixed = false;
this.c = new Cell();
this.c.x = 3; this.c.y = 2;
this.c.ftype = bt;
//this.data=this.Build(this.CurrentState);
}
-9,如果有人要问你一个项目中存不存在业务重点,或核心,那么我的回答是存在,有些东西,很费力,而且承担着某些关键的实现,比如下面这个东西,它就是Brick中的逻辑低层,你看看上面那些代码写得相对幽雅,为什么?因为做了逻辑分层,处理,设计上有比较好的重构方案,来看下这个所谓的业务重点代码是什么玩意儿?
最后给出游戏效果图
总结:
我们通过游戏开发,了解了 对象模型,和逻辑分层等概念,也看到要写好一个游戏的不容易,的确越到后来,越细小的BUG,越难,越高精尖
越费时费力。这个游戏,到目前来说,存在的BUG还太多,我这里暂不着介绍,等下篇blog吧,游戏开发,体现程序的艺术与价值没问题,让我们一起乐在其中吧,虽然这是一个费时费力的活儿。