一. 游戏界面
首先,按照惯例,编辑mainwindow.xaml,先将游戏界面制作好。非常简单:
(1)主游戏区依然使用我们熟悉的canvas控件,大小为640×480像素,设定每小格子为20px,所以横坚坐标的格子数为32×24。见源代码的最后位置。
(2)定位控件我们使用dockpanel,方便放置主菜单。
(3)将按键事件previewkeydown放在window内。

<window x:class="moonsnake.mainwindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:moonsnake"
        mc:ignorable="d"
        previewkeydown="mycanvas_previewkeydown"
        title="moon snake game" height="540" width="660" windowstartuplocation="centerscreen" resizemode="canminimize">
    <dockpanel>
        <menu dockpanel.dock="top">
            <menuitem header="文件">
                <menuitem name="menufile_newgame" header="新游戏" click="menufile_newgame_click" />
                <separator/>
                <menuitem name="menufile_exit" header="退出" click="menufile_exit_click" />
            </menuitem>
            <menuitem header="控制">
                <menuitem name="menucontrol_pause" header="暂停" click="menucontrol_pause_click" />
            </menuitem>
            <menuitem header="帮助">
                <menuitem name="menuhelp_about" header="关于..." click="menuhelp_about_click" />
            </menuitem>
        </menu>
        <canvas x:name="mycanvas" height="480" width="640" background="#222222" focusable="true"
                    previewkeydown="mycanvas_previewkeydown" />

    </dockpanel>
</window>

 

二、添加水果fruit类
因为我们不打算使用任何图片,所以为了简单起见,就只使用红色的实心圆代表水果好了。
看下面的代码:功能简单,主要通过两个属性指定水果的位置和图形。

public class fruit
{
    public point _pos { get; set; }
    public ellipse _ellipse { get; set; }
    public canvas _canvas { get; set; }

    public fruit(point point, canvas canvas)
    {
        _pos = point;
        _canvas = canvas;

        _ellipse = new ellipse
        {
            width = 20,
            height = 20,
            fill = brushes.red
        };

        _ellipse.setvalue(canvas.leftproperty, _pos.x * 20);
        _ellipse.setvalue(canvas.topproperty, _pos.y * 20);
        _canvas.children.add(_ellipse);
    }

    public void setpostion(point pos)
    {
        _pos = pos;

        _ellipse.setvalue(canvas.leftproperty, _pos.x * 20);
        _ellipse.setvalue(canvas.topproperty, _pos.y * 20);
    }
}

 

三、添加单节蛇身snakenode类
每个snakenode代表蛇身的一节,之后我们会通过list<snakenode>列表代表整条蛇。
看代码就知道了,与水果类非常相似,甚至比它更简单,构造函数没有传递canvas参数,因为我们打算在主程序实现添加图形到主游戏区的功能,只要指定它的位置和形状即可,形状则使用了有边线的矩形代替。

public class snakenode
{
    public point _pos { get; set; }
    public rectangle _rect { get; set; }

    public snakenode(point point)
    {
        _pos = point;

        _rect = new rectangle
        {
            width = 20,
            height = 20,
            stroke = new solidcolorbrush(colors.dodgerblue),
            strokethickness = 3,
            fill = brushes.skyblue
        };

        _rect.setvalue(canvas.leftproperty, _pos.x * 20);
        _rect.setvalue(canvas.topproperty, _pos.y * 20);
    }
}

 

四、定义四个常量和两个枚举
看注释:

const int cellsize = 20;                // 小格子大小
const int snakehead = 0;                // 蛇头位置(永远位于列表0)
const int cellwidth = 640 / cellsize;    // 游戏区横格数
const int cellheight = 480 / cellsize;    // 游戏区纵格数

// 蛇身前进方向
enum direction
{
    up,
    down,
    left,
    right
}
direction direct = direction.up;

// 游戏状态
enum gamestate
{
    none,
    gameing,
    pause,
    stop
}
gamestate currgamestate = gamestate.none;

 

五、很少的几个字段变量

list<snakenode> snakenodes = new list<snakenode>();        // 蛇身列表
fruit fruit;                                            // 水果
random rnd = new random((int)datetime.now.ticks);        // 随机数
system.windows.threading.dispatchertimer timer = new system.windows.threading.dispatchertimer();    // 计时器

 

六、画游戏区暗格线
主要使用path控件,通过循环每隔20px画一根横线和纵线。

private void drawgrid()
{
    path gridpath = new path();
    gridpath.stroke = new solidcolorbrush(color.fromargb(255, 50, 50, 50));
    gridpath.strokethickness = 1;

    stringbuilder data = new stringbuilder();

    for (int x = 0; x < 640; x += cellsize)
    {
        data.append($"m{x},0 l{x},480 ");
    }

    for (int y = 0; y<480; y += cellsize)
    {
        data.append($"m0,{y} l640,{y} ");
    }

    gridpath.data = geometry.parse(data.tostring());
    mycanvas.children.add(gridpath);
}

 

七、我是构造方法
这里画底线和设置计时器。

public mainwindow()
{
    initializecomponent();

    drawgrid();

    timer.interval = new timespan(0, 0, 0, 0, 260);
    timer.tick += timer_tick;            
}

可先注释掉最后一行,运行看看游戏界面了。