从无到有写一个C#弹球小游戏(一)

弹球游戏也算是经典小游戏之一,这次我试着自己写一个弹球小游戏。



新建项目:C#->win窗体应用程序->完成,开始写代码。


第一步先想一想这个游戏都有哪些要素:一个球,一个玩家控制的球拍,一系列砖块。他们都需要有哪些性质呢?


球:在屏幕上做直线移动,碰到别的东西反弹。

而反弹是什么呢,反弹就是改变速度方向。如果分解速度的话可以分解成x方向和y方向的速度,因为在这个游戏中球只可能碰到矩形物体,所以不考虑斜面。球只可能碰到与坐标轴平行的边界。这里说的坐标轴是win窗口上的坐标轴,向右为x轴正向,向左为y轴正向,窗口左上角是原点。

所以可以把反弹当作如果碰到与x轴平行的边界,那么与y轴平行的速度的分量变为原来的负值,如果碰到与y轴平行的边界,那么与x轴平行的速度的分量变为原来的负值。


球拍:球拍只可能随着玩家的控制做左右移动,而且碰到移动的边界的话就停止继续往前走。


砖块:砖块静止在场景中,如果被别的物体(球)碰撞就会消失。


因为C#是面向对象的,所以以上的三个东西就是三个class,而且显而易见的是他们有一些共同的性质,所以可以由一个class派生出来。

他们有什么共同的性质呢?

三个东西都得在场景中绘制出来,都可能与别的东西碰撞。

这就是一些性质:位置,边界,绘制,碰撞。


所以基类就应该有这些字段:位置信息,碰撞信息,边界信息。

这些方法:绘图,运动,是否碰撞。

位置,碰撞,边界三个东西说白了就是三个不同的矩形,只不过功能不太一样。

位置矩形确定了这个物体应该在什么地方画,碰撞矩形确定了这个物体怎么与别的东西碰撞。很多人认为这两个矩形应该是一回事,实际上在弹球这个游戏中这两个矩形确实也是一回事,但是为了类的可移植性,还是同时保留这两个信息吧。事实上,我做这个练习是为了以后写一个类似的“坦克大战”小游戏联系一下,毕竟刚开始学C#。

边界其实就是一个移动范围,确定了这个边界之后,这个物体就只能在这个边界范围内移动。也许会有人觉得所有物体共用一个边界就行了,何必每个物体储存自己的边界信息呢,这不是浪费空间吗?在这个游戏中确实也是所有物体用同一个边界信息的,可是在别的地方,比如有一个小怪,默认的状态是在城堡的门口巡逻,来来回回来来回回,这个时候这个信息就有用了,这个小怪的边界就是城堡门口的一片地方。


考虑到这三种信息都是一个矩形,所以我自己写了一个Bounds结构体来封装这个矩形,方便操作,代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace 弹球
{
    /// 
    /// 边界:为简化问题,场景中每个物体都有其矩形碰撞边界
    /// 
    public struct Bounds
    {
        /// 
        /// 左上方的顶点X坐标
        /// 
        public int Left;
        /// 
        /// 左上方的顶点Y坐标
        /// 
        public int Top;
        /// 
        /// 宽度
        /// 
        public int Right;
        /// 
        /// 高度
        /// 
        public int Bottom;

        /// 
        /// 构造函数:设置顶点位置,长宽
        /// 
        /// 
        /// 
        /// 
        /// 
        public Bounds(int iX = 0, int iY = 0, int iWidth = 0, int iHeight = 0)
        {
            Left = iX;
            Top = iY;
            Right = iX + iWidth;
            Bottom = iY + iHeight;
        }

        /// 
        /// 构造函数:根据另一边界信息创建边界
        /// 
        /// 
        public Bounds(Bounds Other)
        {
            Left = Other.Left;
            Top = Other.Top;
            Right = Other.Right;
            Bottom = Other.Bottom;
        }

        /// 
        /// 设置顶点位置,长宽,经计算得到上下左右四个值
        /// 
        /// 
        /// 
        /// 
        /// 
        public void SetBounds(int iX, int iY, int iWidth = 0, int iHeight = 0)
        {
            Left = iX;
            Top = iY;
            Right = iX + iWidth;
            Bottom = iY + iHeight;
        }

        /// 
        /// 根据另一边界设置边界
        /// 
        /// 
        public void SetBounds(Bounds Other)
        {
            Left = Other.Left;
            Top = Other.Top;
            Right = Other.Right;
            Bottom = Other.Bottom;
        }

        /// 
        /// 向右移动(参数为负时向左移动)
        /// 
        /// 
        public void MoveRight(int iDistance)
        {
            Left += iDistance;
            Right += iDistance;
        }

        /// 
        /// /// 
        /// 向下移动(参数为负时向上移动)
        /// 
        /// 
        /// 
        /// 
        public void MoveDown(int iDistance)
        {
            Top += iDistance;
            Bottom += iDistance;
        }

        /// 
        /// 向iVvector所指的方向移动,距离为iSpeed
        /// 
        /// 
        /// 
        public void Move(vector2D iVector, int iSpeed)
        {
            Left += (int)Math.Ceiling(iVector.X * iSpeed);
            Right += (int)Math.Ceiling(iVector.X * iSpeed);

            Top += (int)Math.Ceiling(iVector.Y * iSpeed);
            Bottom += (int)Math.Ceiling(iVector.Y * iSpeed);
        }
    }

    /// 
    /// 枚举值方向,有五个可能的值:None, Left, Up, Right, Down
    /// 
    enum Direction { None = 0, Left = 1, Up = 2, Right = 3, Down = 4 };

}

因为后main会涉及到物体移动的问题,而一个矩形移动至少会涉及到两个值的改变,比如左右移动至少会改变left和right边界,所以封装好移动的几个函数方便使用。


方向的枚举值有五个,但是在这个程序中只会用到三个(表示拍子的移动方向),代码的冗余是挺令人不爽,但是考虑到”坦克大战“中坦克的移动方向会有五个,所以也不难忍受。


上面有一个vector2D的结构体,这其实就是矢量,因为球的速度是矢量,包括x方向的和y方向的速度,所以我写了这么一个结构体如下:

    /// 
    /// 二维向量,用来表示平面上的方向,只有方向,没有大小
    /// 
    public struct vector2D
    {
        /// 
        /// 横向的值
        /// 
        private double x;

        /// 
        /// 获得横向的值
        /// 
        public double X
        {
            get { return x; }
        }

        /// 
        /// 将横向值取反
        /// 
        public void NegateX()
        {
            x = -x;
        }

        /// 
        /// 纵向的值
        /// 
        private double y;

        /// 
        /// 将纵向值取反
        /// 
        public void NegateY()
        {
            y = -y;
        }

        /// 
        /// 获得纵向的值
        /// 
        public double Y
        {
            get { return y; }
        }


        /// 
        /// 构造函数:向量为单位长度,参数只代表方向
        /// 
        /// 
        /// 
        public vector2D(double iX, double iY)
        {
            double length = Math.Sqrt(iX*iX + iY*iY);
            x = iX/length;
            y = iY/length;
        }

        /// 
        /// 注意:向量为单位长度
        /// 
        /// 
        /// 
        public void setValue(double iX, double iY)
        {
            double length = Math.Sqrt(iX * iX + iY * iY);
            x = iX / length;
            y = iY / length;
        }
    }

经过上面的铺垫,那三个类的父类代码终于出来了,如下:

using System;
using System.Drawing;

namespace 弹球
{
    /// 
    /// 放置在场景中所有物体的父类,包含位置边界、碰撞边界和移动边界等基本信息。
    /// 
    abstract class Actor
    {
        public Actor()
        { }

        /// 
        /// 位置信息:绘图时的信息
        /// 
        public Bounds PositionBounds;

        /// 
        /// 碰撞信息:物体与其他物体发生碰撞时的信息
        /// 
        public Bounds CollisionBounds;

        /// 
        /// 移动边界信息:物体无论如何移动都不会移出此范围
        /// 
        public Bounds MoveBounds;

        /// 
        /// 构造函数:设置Actor的位置
        /// 
        /// 
        /// 
        public Actor(int iX, int iY)
        {
            PositionBounds.SetBounds(iX, iY);
            CollisionBounds.SetBounds(iX, iY);
        }

        /// 
        /// Actor的绘图函数,每个Actor有自己的绘图函数,当要绘制它的时候调用这个函数
        /// 
        /// 
        abstract public void Draw(Graphics g);

        abstract public void Update();

        /// 
        /// 向右移动(参数为负时向左移动)
        /// 
        /// 
        public void MoveRight(int distance)
        {
            PositionBounds.MoveRight(distance);
            CollisionBounds.MoveRight(distance);
        }

        /// 
        /// 向下移动(参数为负时向上移动)
        /// 
        /// 
        public void MoveDown(int distance)
        {
            PositionBounds.MoveDown(distance);
            CollisionBounds.MoveDown(distance);
        }

        /// 
        /// 检测是否与另一物体碰撞:
        /// 
        /// 
        /// 
        public Boolean IsCollisionDirectionWith(Actor Other)
        {
            return Actor.IsCollision(this, Other);
        }

        /// 
        /// 静态方法:检测两个Actor是否相碰撞,如果碰撞返回true
        /// 
        /// 
        /// 
        /// 
        public static Boolean IsCollision(Actor a, Actor b)
        {
            if (a.CollisionBounds.Right < b.CollisionBounds.Left ||
                a.CollisionBounds.Bottom < b.CollisionBounds.Top ||
                b.CollisionBounds.Right < a.CollisionBounds.Left ||
                b.CollisionBounds.Bottom < a.CollisionBounds.Top)
            {
                return false;
            }
            else
            {
                return true;
            }
        }
    }
}

你可能感兴趣的:(C#)