基于C#制作一个飞机大战小游戏

news2025/6/7 23:36:50

此文主要基于C#制作一个飞机大战游戏,重温经典的同时亦可学习。

    • 实现流程
      • 1、创建项目
      • 2、界面绘制
      • 3、我方飞机
      • 4、敌方飞机
      • 5、子弹及碰撞检测

实现流程

1、创建项目

  1. 打开Visual Studio,右侧选择创建新项目。

在这里插入图片描述

  1. 搜索框输入winform,选择windows窗体应用,填写对应的保存路径点击下一步,创建成功后如下图,会有一个默认打开的Form窗体。

在这里插入图片描述
在这里插入图片描述

2、界面绘制

  1. 准备对应的素材(飞机、子弹、音效等),通过Icon以及窗体Text属性修改窗体图标以及标题显示;同时配置StartPosition属性值为CenterScreen,让窗体默认居中显示。

在这里插入图片描述

  1. 双击窗体生成窗体加载事件并定义函数对背景进行初始化。

在这里插入图片描述

使用Random产生随机数,从资源中获取图片设置为窗体背景。

在这里插入图片描述

        private const int PLANE_OFFSET = 2;       //设置每次定时器触发时图片发生偏移的速度
        
        private int pix_x = 0;
        private int pix_y = 0;         //背景图片移动起始的坐标
        int shot_y = 10;
        int blood_y = 50;
        private Image[] bgrounds;        //设置多张背景图片,每次运行程序随机产生背景图片
        int index = 0;                           //背景图片索引
        Image avatar = Resource.imgHeadSheep;     //角色头像图片
        Image boomImg = Resource.bomb4;          //爆炸效果图片
        Image shotImg = Resource.shotgun;
        Image bloodImg = Resource.bloodbox;

        bool isDropGun = false;      //是否产生shotgun的标志
        bool isDropBox = false;       //是否产生bloodbox的标志

        private void Form1_Load(object sender, EventArgs e)//窗体加载事件
        {
            InitBackground();              //初始化背景
        }

        public GameForm()
        {
            InitializeComponent();
            this.Size = new Size(420, 630);//让窗体与图片一样大
            //this.DoubleBuffered = true; //双缓冲区
        }
         ///<summary>
        /// 初始化背景,随机生成背景图片
        /// </summary>
        public void InitBackground()
        {
            bgrounds = new Image[4];
            Random rd = new Random();
            index = rd.Next(0, 4);//产生0-3的随机数,表示不同背景
            bgrounds[0] = Resource.background1;//从资源获取图片
            bgrounds[1] = Resource.background2;
            bgrounds[2] = Resource.background3;
            bgrounds[3] = Resource.background4;
        }
  1. 新建一个背景移动函数,通过定时位置让图片发生偏移,防止有空白。

在这里插入图片描述

/// <summary>
/// 背景移动函数
/// </summary>
/// <param name="e">图形对象</param>
public void BackMove(Graphics e)//通过定时位置让图片发生偏移,防止有空白
{
    e = this.CreateGraphics();
    pix_y += PLANE_OFFSET;
    if (pix_y > 630)
    {
        pix_y = 0;
    }
}
  1. 通过工具箱拖拽两个定时器到窗体上并设置定时器事件,用于定时生成血包以及强化弹药。

在这里插入图片描述
在这里插入图片描述

/// <summary>
/// 设置定时器1事件
/// </summary>
private void timer1_Tick(object sender, EventArgs e)
{
    this.Invalidate();  //使当前窗口无效,系统自动调用OnPaint()函数重绘
}

/// <summary>
/// 设置定时器2事件
/// </summary>
private void timer2_Tick(object sender, EventArgs e)
{
    Graphics g = this.CreateGraphics();
    for (int j = 0; j < Fighter.fighters.Count; j++)
    {
        if (Fighter.fighters[j].flag)
        {
            g.DrawImage(boomImg, Fighter.fighters[j].GetLoc());
            SoundPlayer music = new SoundPlayer(Resource.BOMB21);
            music.Play(); 
            Fighter.fighters.Remove(Fighter.fighters[j]);
        }
    }
}
  1. 通过Graphics类绘制游戏界面,Graphics类作用为封装一个GDI+绘图图画,效果如下。
函数作用
DrawImage在指定位置并且按指定大小绘制指定的Image
DrawRectangle绘制由Rectangle结构指定的矩形
FillRectangle填充由一对坐标、一个宽度和一个高度指定的矩形的内部
DrawString在指定位置并目用指定的 Brush 和 Font 对象绘制指定的文本字符串
DrawImage在指定位置并且按指定大小绘制指定的Image

在这里插入图片描述

在这里插入图片描述

/// <summary>
/// 绘制游戏界面
/// </summary>
/// <param name="g"></param>
private void DrawGame(Graphics g)                    
{
    this.BackMove(g);               
    g.DrawImage(bgrounds[index], pix_x, pix_y, 420, 630);           
    g.DrawImage(bgrounds[index], pix_x, pix_y - 630, 420, 630);       //绘制背景

    g.DrawImage(avatar, 10, 10);                                            //绘制角色头像
    g.DrawRectangle(new Pen(Color.Black), new Rectangle(10, 100, 100, 10));     //绘制血条矩形
    g.FillRectangle(Brushes.Red, 10, 101, MyPlane.health, 9);                   //填充血条矩形

    g.DrawRectangle(new Pen(Color.Blue), new Rectangle(10, 120, 100, 10));
    g.FillRectangle(Brushes.Green, 11, 121, MyPlane.score, 9);
    g.DrawString("Player:摔跤猫子", new Font("宋体", 9, FontStyle.Bold), Brushes.Yellow, new Point(10, 140));      //显示玩家
    g.DrawString("Score:" + MyPlane.score, new Font("宋体", 9, FontStyle.Bold), Brushes.Yellow, new Point(10, 160));      //显示分数
}

3、我方飞机

在这里插入图片描述

  1. 创建一个MyPlane实体类,定义字段如下。

在这里插入图片描述

		public static int x = 180;
        public static int y = 530;//坐标
        public static int health = 100; //血量
        
        private const int PLANE_OFFSET = 12;//移动速度
        
        public static Image myPlaneImg=Resource.plane;//我方飞机图片
        static List<Keys> keys = new List<Keys>();//键盘键列表,用于控制飞机移动
        static Image gameOver = Resource.gameover;

        public static bool isGetGun = false;//是否得到shotgun的标志
        public static bool isGetBlood = false;//是否得到bloodbox的标志
        public static bool isGameOver = false; //游戏是否结束的标志
        public static int score = 0;      //得分
  1. 调用Graphics类的DrawImage函数显示我方飞机。

在这里插入图片描述

/// <summary>
/// 显示我方飞机
/// </summary>
/// <param name="g"></param>
public static void MyPlaneShow(Graphics g)
{
    if (health > 0)
    {
        g.DrawImage(myPlaneImg, x, y);
    }
    else if (health <= 0 || score <= 0)
    {
        isGameOver = true;
        g.DrawImage(myPlaneImg, 0, -300);
        g.DrawImage(gameOver, 10, 260);
    }
    else if (isGetBlood && health <= 90)
    {
        health += 10;
    }
}
  1. 通过Keys类定义键盘事件函数。

在这里插入图片描述

成员名称说明
KeyCode从键值提取键代码的位屏蔽
Modifiers从键值提取修饰符的位屏蔽
None没有按任何键
LButton鼠标左按钮
RButton鼠标石按钮
CancelCancel 键
MButton鼠标中按钮(三个按钮的鼠标)
XButton1第一个X鼠标按钮(五个按钮的鼠标)
XButton2第二个 X 鼠标按钮(五个按钮的鼠标)
BackBackspace 键

这里先用熟悉的WASD键来控制我方飞机移动。

在这里插入图片描述

/// <summary>
/// 显示我方飞机
/// </summary>
/// <param name="g"></param>
public static void MyPlaneShow(Graphics g)
{
    if (health > 0)
    {
        g.DrawImage(myPlaneImg, x, y);
    }
    else if (health <= 0 || score <= 0)
    {
        isGameOver = true;
        g.DrawImage(myPlaneImg, 0, -300);
        g.DrawImage(gameOver, 10, 260);
    }
    else if (isGetBlood && health <= 90)
    {
        health += 10;
    }
}

/// <summary>
/// 松开键盘键
/// </summary>
/// <param name="key"></param>
public static void Keyup(Keys key)
{
    keys.Remove(key);
}

/// <summary>
/// 按下键盘键
/// </summary>
/// <param name="key"></param>
public static void Keydown(Keys key)
{
    if (!keys.Contains(key))
    {
        keys.Add(key);
    }
}

/// <summary>
/// 判断按键是否被按下
/// </summary>
/// <param name="key"></param>
/// <returns>是则返回true  不是则返回false</returns>
public static bool IsKeyDown(Keys key)
{
    return keys.Contains(key);
}

/// <summary>
/// 用键盘控制我方飞机移动
/// </summary>
public static void MyPlaneMove()
{
    if(isGameOver)
    {
        return;
    }
    if (IsKeyDown(Keys.A))
    {
        myPlaneImg = Resource.planeLeft;
        if (x < 5)
            x = 5;
        x -= PLANE_OFFSET;
    }
    if (IsKeyDown(Keys.D))
    {
        myPlaneImg = Resource.planeRight;
        if (x > 370)
            x = 370;
        x += PLANE_OFFSET;
    }
    if (IsKeyDown(Keys.W))
    {
        if (y < 5)
            y = 5;
        y -= PLANE_OFFSET;
    }
    if (IsKeyDown(Keys.S))
    {
        if (y > 530)
            y = 530;
        y += PLANE_OFFSET;
    }
}

4、敌方飞机

在这里插入图片描述

  1. 创建一个Fighter实体类,定义字段如下。

在这里插入图片描述

Image redImg;
Image greenImg;
Image yellowImg;
public Image fighterImg;//敌机图片
private const int FIGHTER_OFFSET = 4;//敌机图片移动速度
public int _x = 0;
public int _y = 0;//敌机图片移动起始的坐标
public static List<Fighter> fighters = new List<Fighter>();//敌机对象列表
private int fi;//敌机图片索引
List<Image> imgList = new List<Image>();//敌机图片列表
public bool flag = false;//碰撞的标志
  1. 通过Random产生随机数,用于定义随机出现的敌机x以及y点坐标。

在这里插入图片描述
在这里插入图片描述

/// <summary>
/// 随机产生敌机
/// </summary>
public static void ProduceFighter()
{
    Random rad = new Random();
    if (rad.Next(18) == 0)
    {
        Fighter f = new Fighter(rad.Next(0, 350), rad.Next(0, 3));
        fighters.Add(f);
    }
}

public Fighter(int x,int i)
{
	   _x = x;//横坐标
	   fi = i;
	   redImg = Resource.fighterRed;
	   greenImg = Resource.fighterGreen;
	   yellowImg = Resource.fighterYellow;
	   switch (fi)
	   {
	       case 0:
	           fighterImg = redImg;break;
	       case 1:
	           fighterImg = greenImg;break;
	       case 2:
	           fighterImg = yellowImg;break;
	       default:
	           break;
	   }
	   imgList.Add(redImg);
	   imgList.Add(greenImg);
	   imgList.Add(yellowImg);
}
  1. 通过Graphics绘制敌机图片。

在这里插入图片描述

/// <summary>
/// 出现敌机
/// </summary>
public void FighterShow(Graphics g)
{
   g.DrawImage(fighterImg,_x,_y);
}

public void fMove()
{
    _y += FIGHTER_OFFSET;
}

/// <summary>
/// 敌机移动函数
/// </summary>
public static void FighterMove(Graphics g)//通过定时位置让图片发生偏移
{
    for (int i = 0; i < fighters.Count; i++)
    {
        fighters[i].FighterShow(g);
        fighters[i].fMove();
        if (fighters[i]._y > 650)
        {
            fighters.Remove(fighters[i]);
        }
    }
}

5、子弹及碰撞检测

在这里插入图片描述

  1. 创建一个MyBullet实体类,定义字段如下。

在这里插入图片描述

private int x;//子弹横坐标
private int y;//子弹纵坐标
private const int BULLET_OFFSET = 18;//移动速度
public int Angle;//子弹角度
private Image bulImg;//定义子弹图片
private const double PI = Math.PI;
public static List<MyBullet> mybulList = new List<MyBullet>();//子弹对象集合

static Bitmap bm = new Bitmap(Resource.bomb4);//爆炸图片
public bool isHit = false;//碰撞的标志
  1. 通过按键盘J键来产生我方子弹

在这里插入图片描述

  /// <summary>
  /// 通过按键盘J键来产生我方子弹
  /// </summary>
  public static void ProduceMybul()
  {
      if (!MyPlane.isGameOver && MyPlane.IsKeyDown(Keys.J))
      {
          mybulList.Add(new MyBullet(MyPlane.x + 13, MyPlane.y - 10, 0));
          if (MyPlane.isGetGun)
          {
              mybulList.Add(new MyBullet(MyPlane.x + 13, MyPlane.y - 8, 60));
              mybulList.Add(new MyBullet(MyPlane.x + 7, MyPlane.y - 8, 30));
              mybulList.Add(new MyBullet(MyPlane.x + 30, MyPlane.y - 12, 120));
              mybulList.Add(new MyBullet(MyPlane.x, MyPlane.y - 7, 150));
          }
      }
  }
  1. 定义敌机碰撞检测方法

在这里插入图片描述

/// <summary>
/// 敌机碰撞检测方法
/// </summary>
 public static void IsHitEnemy(Graphics g)
{
    Rectangle myPlaneRect = new Rectangle(MyPlane.x, MyPlane.y, MyPlane.myPlaneImg.Width, MyPlane.myPlaneImg.Height);    //包住myplane的Rectangle

    //g.DrawRectangle(new Pen(Color.Red), myPlaneRect);
    for(int i = 0; i < mybulList.Count; i++)
      for (int j = 0; j < Fighter.fighters.Count; j++)
      { 
          Rectangle mybulRect = new Rectangle(mybulList[i].x, mybulList[i].y, 8, 10);
          Rectangle fighterRect = new Rectangle(Fighter.fighters[j]._x, Fighter.fighters[j]._y, 65, 45);
          //g.DrawRectangle(new Pen(Color.Black), fighterRect);
          if (mybulRect.IntersectsWith(fighterRect))   //我方子弹击中敌机,敌机爆炸
          {
             mybulList.Remove(mybulList[i]);
             Fighter.fighters[j].flag = true;
             if (MyPlane.score < 100)
             {
                 MyPlane.score += 1;
             }
          }
          else if (myPlaneRect.IntersectsWith(fighterRect))  //我方飞机撞上敌机,敌机爆炸
          {
              Fighter.fighters[j].flag = true;
              if (MyPlane.score < 100)
              {
                  MyPlane.score += 1;
              }
          }
     }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/334587.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

线程的生命周期和状态中方法的详解

点个关注&#xff0c;必回关 一、线程的生命周期和状态图&#xff1a; 图一&#xff1a; 图二 二、线程状态 1&#xff1a;新建 2&#xff1a;就绪&#xff08;拿到执行权开始运行&#xff09; 3&#xff1a;运行 4&#xff1a;阻塞 5&#xff1a;死亡 三、线程生命周期…

CUDA中的动态并行

CUDA的动态并行 文章目录CUDA的动态并行1. 介绍1.1. 简述警告&#xff1a;与父块的子内核显式同步&#xff08;即在设备代码中使用 cudaDeviceSynchronize()&#xff09;在 CUDA 11.6 中已弃用&#xff0c;并计划在未来的 CUDA 版本中删除。1.2. 术语2. 执行环境和内存模型2.1.…

ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程

ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程 我家里的MAC没这个问题。这个是在windows上发生的。 起因很简单我用ASP.NET CORE 3.1 MVC做个项目做登录将数据从VIEW post到Controller上结果意外的报了错误。 各种百度都说…

不要再问Spring是如何解决循环依赖了

1、什么是循环依赖&#xff1f; 循环依赖主要来次三个方面&#xff0c;第一种A相互依赖&#xff0c;第二种是 A依赖B&#xff0c;B依赖A&#xff0c;第三种是A依赖B&#xff0c;B依赖C&#xff0c;C依赖A。 总结一句话就是对象之间形成环形依赖。 代码如下&#xff1a; 第一…

FPGA时序约束与分析 (1)--- 约束工具及相关命令的使用

注意&#xff1a; 时序约束辅助工具或者相关的TCL命令&#xff0c;都必须在 open synthesis design / open implemention design 后才能有效运行。 1、时序约束辅助工具 2、查看相关时序信息 3、一般的时序约束顺序 1、 时序约束辅助工具&#xff08;1&#xff09;时序约束编辑…

蓝桥杯刷题练习Python(笔记)

# a1 A # a2 A B A # a3 ABA C ABA # a4 ABACABADABACABAnum int(input(N < 26:)) # 26 个字母 str "ABCDEFGHIJKLMNOPQRSTUVWXYZ"if num 1:a A # str[0] else:a Afor i in range(1, num, 1): # b str[i]c a b aa c# print(str[i])# print(a) …

【React】从 0 开始学 React —— 实现井字棋小游戏

目录1 React 简介2 实现井字棋小游戏2.1 初始化2.2 props2.3 setState2.4 状态提升2.5 副本2.6 简化组件2.7 key2.8 小结3 核心概念1 React 简介 React 是一个声明式&#xff0c;高效且灵活的用于构建用户界面的 JavaScript 库 官网提供了两种学习思路 如果你喜欢边做边学&am…

Vue3 组件之间的通信

组件之间的通信 经过前面几章的阅读&#xff0c;相信开发者已经可以搭建一个基础的 Vue 3 项目了&#xff01; 但实际业务开发过程中&#xff0c;还会遇到一些组件之间的通信问题&#xff0c;父子组件通信、兄弟组件通信、爷孙组件通信&#xff0c;还有一些全局通信的场景。 …

【GPLT 二阶题目集】L2-043 龙龙送外卖

参考地址&#xff1a;AcWing 4474. 龙龙送外卖&#xff08;杂题选讲&#xff09; 作者&#xff1a;yxc 感谢y总&#xff01; 龙龙是“饱了呀”外卖软件的注册骑手&#xff0c;负责送帕特小区的外卖。帕特小区的构造非常特别&#xff0c;都是双向道路且没有构成环 —— 你可以…

Spring Cloud Alibaba Sentinel 控制台

简介 Sentinel 控制台是流量控制、熔断降级规则统一配置和管理的入口&#xff0c;它为用户提供了机器自发现、簇点链路自发现、监控、规则配置等功能。在 Sentinel 控制台上&#xff0c;我们可以配置规则并实时查看流量控制效果。 下载 有以下两种方式获取Sentinel控制台 下…

进阶技术:Linux Arm32是如何调用C Main的

前言&#xff1a; Linux x64通过qemu的rdinit方式调用的C Main&#xff0c;实际上是通过load_elf_binary(加载和解析elf)和start_thread(设置Ip和sp)&#xff0c;用缺页异常来调用。关于这点可以看这篇文章&#xff1a;点击查看。那么Arm32里面是如何调用C Main的呢&#xff1f…

Android 进阶——Framework 核心之Binder 相关预备理论(一)

文章大纲引言一、进程的内存空间和进程隔离二、Linux 系统内存的用户空间和内核空间1、用户空间&#xff08;User Space&#xff09;2、内核空间&#xff08;Kernel Space&#xff09;三、Linux IPC 原理1、内核态和用户态2、IPC 步骤四、内核模块和驱动五、Binder1、Binder IP…

优思学院|從《狂飙》高启强爱看的《孙子兵法》到六西格玛项目管理

近期最受人瞩目的&#xff0c;无疑是电视剧《狂飙》中出类拔萃的反派高启强。而在剧中&#xff0c;指引高启强走向顶峰的&#xff0c;正是那部著名的军事经典——《孙子兵法》。 在剧中&#xff0c;高启强在一次村庄改造项目上遇到了困难&#xff0c;但他仍保持冷静&#xff0…

QT开发安卓程序初识

Qt | Qt For Android、Qt5.14.2安卓开发环境搭建详细步骤 测试结果如下&#xff1a;

AcWing 840. 模拟散列表

题目描述 餐前小菜&#xff1a; 在讨论本题目之前先看一个简单的问题&#xff1a;给出 NNN 个正整数 (a1,a2,...,an)(a_1,a_2,...,a_n)(a1​,a2​,...,an​)&#xff0c;再给出 MMM 个正整数 (x1,x2,...,xm)(x_1,x_2,...,x_m)(x1​,x2​,...,xm​)&#xff0c;问这 MMM 个数中…

冷知识|鹤顶红还能用来修长城?

大家好&#xff0c;我是建模助手。 在上篇浅浅地蹭了波热点之后&#xff0c;我灵机一动&#xff0c;倒不如也搞一搞建筑方面的冷知识&#xff1f;冷热搭配&#xff0c;事半功倍... 问问大家&#xff0c;如果谈起古建筑&#xff0c;关键词都有什么&#xff1f;是庄严、震撼、壮…

DHCP实验及配置

DHCP实验配置基于接口拓扑图配置基于全局拓扑图配置基于接口 拓扑图 配置 [Huawei]dhcp enable//在全局下使能DHCP服务 [Huawei]interface GigabitEthernet0/0/0//进入接口 [Huawei-GigabitEthernet0/0/0] ip address 192.168.1.1 255.255.255.0 //配置接口地址 [Huawei-Giga…

什么是QoS?QoS是如何工作的?QoS的实验配置如何进行?

QoS&#xff08;Quality of Service&#xff09;是服务质量的简称。对于网络业务来说&#xff0c;服务质量包括哪些方面呢&#xff1f; 从传统意义上来讲&#xff0c;无非就是传输的带宽、传送的时延、数据的丢包率等&#xff0c;而提高服务质量无非也就是保证传输的带宽&…

人工智能:分享五个目前最火的ChatGPT开源项目

❤️作者主页&#xff1a;IT技术分享社区 ❤️作者简介&#xff1a;大家好,我是IT技术分享社区的博主&#xff0c;从事C#、Java开发九年&#xff0c;对数据库、C#、Java、前端、运维、电脑技巧等经验丰富。 ❤️荣誉&#xff1a; CSDN博客专家、数据库优质创作者&#x1f3c6;&…

Nacos简介(一)

目录 一、概览 二、注册中心基本概念 1) 什么是注册中心&#xff1f; 2) 如果没有注册中心&#xff1f;会怎样 3) 注册中心主要有三种角色&#xff1a; 4) 服务注册中心的作用 5&#xff09;CAP 理论 6&#xff09;CP和AP的选择 三、什么是 Nacos&#xff1f; 四、Nac…