練手WPF(四)——貪吃蛇小游戲的簡易實現(下)


八、生成新的單節蛇身
我們這里先說說游戲小原理好了,游戲運行后,通過計時器事件不斷生成新的單節蛇身類SnakeNode,添加到List中的0位置,原來的蛇頭變成了第二節。該節新蛇頭的坐標通過蛇頭前進方向Direct進行判斷,即如果原蛇頭往左運行,則新蛇頭在原蛇頭位置的左一格生成,其他方向以此類推。最后將該節添加到SnakeNodes列表中,並將相應圖形添加到游戲區。

private void GenNewSnakeNode()
{
    SnakeNode snakeNode = null;
    switch (Direct)
    {
        case Direction.UP:
            snakeNode = new SnakeNode(new Point(SnakeNodes[SnakeHead]._pos.X,
                SnakeNodes[SnakeHead]._pos.Y - 1));
            break;

        case Direction.DOWN:
            snakeNode = new SnakeNode(new Point(SnakeNodes[SnakeHead]._pos.X,
                SnakeNodes[SnakeHead]._pos.Y + 1));
            break;

        case Direction.LEFT:
            snakeNode = new SnakeNode(new Point(SnakeNodes[SnakeHead]._pos.X - 1,
                SnakeNodes[SnakeHead]._pos.Y));
            break;

        case Direction.RIGHT:
            snakeNode = new SnakeNode(new Point(SnakeNodes[SnakeHead]._pos.X + 1,
                SnakeNodes[SnakeHead]._pos.Y));
            break;
    }

    if (snakeNode != null)
    {
        SnakeNodes.Insert(0, snakeNode);
        myCanvas.Children.Add(SnakeNodes[0]._rect);
    }
}

 

九、隨機水果位置
注意該新位置不得與蛇身任何一節重疊,如果重疊則另外生成新位置,直到成功為止。

private Point SetFruitToRandomPos()
{
    bool flag = true;
    Point pos = new Point();
    while (flag)
    {
        flag = false;
        pos = new Point(rnd.Next(0, CellWidth), rnd.Next(0, CellHeight));
        foreach (var node in SnakeNodes)
        {
            if (pos.X == node._pos.X && pos.Y == node._pos.Y)
            {
                flag = true;
                break;
            }
        }
    }

    return pos;
}

 

十、碰撞檢測(蛇頭與水果)
因為只有蛇頭會最先觸碰到水果,所以只需判斷蛇頭坐標與水果坐標是否相同即可。如果撞到水果,則將水果隨機生成新位置;如果沒有碰到,則刪除蛇尾一節(因為之后會通過計時器會生成新一節蛇頭,這樣就會保存蛇身長度,感覺位置前進了一格)。

private void CheckCollision()
{
    if (SnakeNodes[SnakeHead]._pos.X == fruit._pos.X && SnakeNodes[SnakeHead]._pos.Y == fruit._pos.Y)
    {
        fruit.SetPostion(SetFruitToRandomPos());
    }
    else
    {
        if (myCanvas.Children.Contains(SnakeNodes[SnakeNodes.Count - 1]._rect))
            myCanvas.Children.Remove(SnakeNodes[SnakeNodes.Count - 1]._rect);

        SnakeNodes.RemoveAt(SnakeNodes.Count - 1);
    }
}

 

十一、判斷是否游戲結束
很簡單,看看蛇頭是否觸碰游戲區四邊或蛇身的任何一節,如果是則返回true。
注意蛇身循環是從1開始的,如果從0開始你知道會出現什么情況。

        private bool IsGameOver()
        {
            if (SnakeNodes[SnakeHead]._pos.X == -1 || SnakeNodes[SnakeHead]._pos.X == CellWidth
                || SnakeNodes[SnakeHead]._pos.Y == -1 || SnakeNodes[SnakeHead]._pos.Y == CellHeight)
            {
                return true;
            }

            foreach (var node in SnakeNodes)
            {
                if (node == SnakeNodes[SnakeHead])
                    continue;

                if (node._pos.X == SnakeNodes[SnakeHead]._pos.X && node._pos.Y == SnakeNodes[SnakeHead]._pos.Y)
                {
                    return true;
                }
            }

            return false;
        }

 

十三、刪除游戲區的所有蛇身節
這個會在重新開始游戲時使用到:

private void RemoveSnakeNodeAll()
{
    for (int i = 0; i < SnakeNodes.Count; i++)
    {
        if (myCanvas.Children.Contains(SnakeNodes[i]._rect))
        {
            myCanvas.Children.Remove(SnakeNodes[i]._rect);
        }
    }
}

 

順便再來一發刪除游戲區的水果:

private void RemoveFruit()
{
    if (fruit == null) 
    {
        return;
    }

    if (myCanvas.Children.Contains(fruit._ellipse))
    {
        myCanvas.Children.Remove(fruit._ellipse);
    }
}

 

十四、按鍵操作
通過方向鍵,設置Direct值即可。

private void MyCanvas_PreviewKeyDown(object sender, KeyEventArgs e)
{
    switch (e.Key)
    {
        case Key.Left:
            if (Direct != Direction.RIGHT)
            {
                Direct = Direction.LEFT;
            }
            break;

        case Key.Right:
            if (Direct != Direction.LEFT)
            {
                Direct = Direction.RIGHT;
            }
            break;

        case Key.Up:
            if (Direct != Direction.DOWN)
            {
                Direct = Direction.UP;
            }
            break;

        case Key.Down:
            if (Direct != Direction.UP)
            {
                Direct = Direction.DOWN;
            }
            break;

        case Key.Escape:
            Application.Current.Shutdown();
            break;

        case Key.Space:
            if (CurrGameState == GameState.NONE)
                return;

            if (CurrGameState == GameState.PAUSE)
            {
                CurrGameState = GameState.GAMEING;
                timer.Start();
                MenuControl_Pause.Header = "暫停";
            }
            else if (CurrGameState == GameState.GAMEING)
            {
                CurrGameState = GameState.PAUSE;
                timer.Stop();
                MenuControl_Pause.Header = "繼續";
            }
            break;
    }
}

 

十四、定義開始游戲方法
先刪除界面中的蛇身和水果(保留底部暗格),隨機位置生成水果和三節蛇身。

private void StartGame()
{
    RemoveSnakeNodeAll();
    RemoveFruit();

    int startX = rnd.Next(5, CellWidth - 6);
    int startY = rnd.Next(5, CellHeight - 6);
    Direct = Direction.RIGHT;

    fruit = new Fruit(SetFruitToRandomPos(), myCanvas);

    SnakeNodes = new List<SnakeNode>();
    SnakeNodes.Add(new SnakeNode(new Point(startX, startY)));
    GenNewSnakeNode();
    GenNewSnakeNode();
}

 

十五、開始游戲菜單

調用StartGame()方法,並設定計時器開始。

private void MenuFile_NewGame_Click(object sender, RoutedEventArgs e)
{
    StartGame();
    timer.Start();
    CurrGameState = GameState.GAMEING;
    MenuControl_Pause.Header = "暫停";
}

 

十六、暫停與繼續
直接上代碼,沒有要說的了。

private void MenuControl_Pause_Click(object sender, RoutedEventArgs e)
{
    if (CurrGameState == GameState.GAMEING)
    {
        CurrGameState = GameState.PAUSE;
        timer.Stop();
        MenuControl_Pause.Header = "繼續";
    }
    else if (CurrGameState == GameState.PAUSE)
    {
        CurrGameState = GameState.GAMEING;
        timer.Start();
        MenuControl_Pause.Header = "暫停";
    }
}

 

十七、退出游戲

private void MenuFile_Exit_Click(object sender, RoutedEventArgs e)
{
    Application.Current.Shutdown();
}

 

十八、其他菜單功能
自己實現好了

private void MenuHelp_About_Click(object sender, RoutedEventArgs e)
{
}

 

[2019.12.23]更新:添加計時器事件代碼(我不知道這個方法上次沒有貼出來)

十九、計時器事件

/// <summary>
/// 計時器事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Timer_Tick(object sender, EventArgs e)
{
    CheckCollision();
    GenNewSnakeNode();

    if (IsGameOver())
    {
        CurrGameState = GameState.STOP;
        timer.Stop();
        MessageBox.Show("游戲結束", "警告", MessageBoxButton.OK);

        return;
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM