动手小游戏_飞机大战激活成功教程版

动手小游戏_飞机大战激活成功教程版一、关于飞机大战要说微信中最火爆的小游戏是哪款,可能既不是精心打造的3D大作,也不是《植物大战僵尸2》,而是微信5.0刚开启时的《飞机大战》。就是这样一款铅笔手绘风格的简单到不能再简单的“打飞机”

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

一、关于飞机大战

  要说微信中最火爆的小游戏是哪款,可能既不是精心打造的3D大作,也不是《植物大战僵尸2》,而是微信5.0刚开启时的《飞机大战》。

动手小游戏_飞机大战激活成功教程版

  就是这样一款铅笔手绘风格的简单到不能再简单的“打飞机”游戏,让国内的微信用户一次又一次地尝试,并表示似乎又找回了童年时玩电子游戏的那份单纯的快乐。至于游戏的玩法都不用加以介绍,就是简单的“打飞机”。

二、关于游戏设计

2.1 总结游戏印象

  (1)一个玩家飞机,多个电脑飞机

  ①动手小游戏_飞机大战激活成功教程版  ②动手小游戏_飞机大战激活成功教程版  ③动手小游戏_飞机大战激活成功教程版  ④动手小游戏_飞机大战激活成功教程版

  (2)玩家飞机可以发射子弹,电脑飞机也可以发射子弹

  ①动手小游戏_飞机大战激活成功教程版  ②动手小游戏_飞机大战激活成功教程版

  (3)玩家和电脑飞机被击中后有爆炸效果,并且有一定几率出现大型飞机

  ①动手小游戏_飞机大战激活成功教程版  ②动手小游戏_飞机大战激活成功教程版

2.2 总结设计思路

  (1)万物皆对象

  在整个游戏中,我们看到的所有内容,我们都可以理解为游戏对象(GameObject),每一个游戏对象,都由一个单独的类来创建;在游戏中主要有三类游戏对象:一是飞机,二是子弹,三是背景;其中,飞机又分为玩家飞机和电脑飞机,子弹又分为玩家子弹和电脑子弹。于是,我们可以对飞机进行抽象形成一个抽象父类:PlaneBase,然后分别创建两个子类:PlanePlayer和PlaneEnemy;然后对子弹进行抽象形成一个抽象类:BulletBase,然后分别创建两个子类:BulletPlayer和BulletEnemy。但是,我们发现这些游戏对象都有一些共同的属性和方法,例如X,Y轴坐标,长度和宽度,以及绘制(Draw())和移动(Move())的方法,这时我们可以设计一个抽象类,形成了GameObject类:将共有的东西封装起来,减少开发时的冗余代码,提高程序的可扩展性,符合面向对象设计的思路:

动手小游戏_飞机大战激活成功教程版

动手小游戏_飞机大战激活成功教程版

  (2)计划生育好

  在整个游戏中,我们的玩家飞机对象只有一个,也就是说在内存中只需要存一份即可。这时,我们想到了伟大的计划生育政策,于是我们想到了使用单例模式。借助单例模式,可以保证只生成一个玩家飞机的实例,即为程序提供一个全局访问点,避免重复创建浪费不必要的内存。当然,除了玩家飞机外,我们的电脑飞机集合、子弹集合等集合对象实例也保证只有一份存储,降低游戏开销;

动手小游戏_飞机大战激活成功教程版

  (3)对象的运动

  在整个游戏过程中,玩家可以通过键盘上下左右键控制玩家飞机的上下左右运动,而飞机的运动本质上还是改变游戏对象的X轴和Y轴的坐标,然后一直不间断地在窗体上重绘游戏对象。相比玩家飞机的移动,电脑飞机的移动则完全是通过程序中设置的随机函数控制左右方向移动的,而玩家飞机发出的子弹执行的运动则是从下到上,而电脑飞机发出的子弹执行的运动则是从上到下。

xy

  (4)设计流程图

动手小游戏_飞机大战激活成功教程版

三、关键代码实现 

3.1 客户端开发

  (1)设计GameObject类:封装所有游戏对象的公有属性

动手小游戏_飞机大战激活成功教程版
动手小游戏_飞机大战激活成功教程版

 /// <summary> /// 抽象类:游戏对象基类 /// </summary> public abstract class GameObject { public int X { get; set; } public int Y { get; set; } public int Width { get; set; } public int Height { get; set; } public int Speed { get; set; } public int Life { get; set; } public Direction Dir { get; set; } public GameObject(int x, int y, int width, int height, int speed, int life, Direction dir) { this.X = x; this.Y = y; this.Width = width; this.Height = height; this.Speed = speed; this.Life = life; this.Dir = dir; } public GameObject(int x, int y) { this.X = x; this.Y = y; } // 实例方法:返回所在矩形区域用于碰撞检测 public Rectangle GetRectangle() { return new Rectangle(this.X, this.Y, this.Width, this.Height); } // 抽象方法:游戏对象的绘制各不相同 public abstract void Draw(Graphics g); // 虚方法:游戏对象的移动各不相同 public virtual void Move() { // 根据指定的移动方向进行移动 switch (Dir) { case Direction.Up: this.Y -= this.Speed; break; case Direction.Down: this.Y += this.Speed; break; case Direction.Left: this.X -= this.Speed; break; case Direction.Right: this.X += this.Speed; break; } // 移动之后判断是否超出了边界 if (this.X <= 0) { this.X = 0; } if (this.X >= 380) { this.X = 380; } if (this.Y <= 0) { this.Y = 0; } if (this.Y >= 670) { this.Y = 670; } } }

View Code

  一切皆对象,这里封装了游戏对象:飞机、子弹以及其他游戏对象共有的属性,以及两个抽象方法,让对象们(飞机?子弹?爆炸效果?等)自己去实现。

  (2)设计SingleObject类:保证游戏中的类都只有一个实例

动手小游戏_飞机大战激活成功教程版
动手小游戏_飞机大战激活成功教程版

/// <summary> /// 单例模式类 /// </summary> public class SingleObject { private SingleObject() { } private static SingleObject singleInstance = null; public static SingleObject GetInstance() { if (singleInstance == null) { singleInstance = new SingleObject(); } return singleInstance; } #region 单一实例对象列表 // 1.游戏背景单一实例 public GameBackground Background { get; set; } // 2.游戏标题单一实例 public GameTitle Title { get; set; } // 3.玩家飞机单一实例 public PlanePlayer Player { get; set; } // 4.玩家飞机子弹集合单一实例 public List<BulletPlayer> PlayerBulletList { get; set; } // 5.敌人飞机集合单一实例 public List<PlaneEnemy> EnemyList { get; set; } // 6.敌人飞机子弹集合单一实例 public List<BulletEnemy> EnemyBulletList { get; set; } // 7.玩家飞机爆炸效果单一实例 public List<BoomPlayer> PlayerBoomList { get; set; } // 8.敌人飞机爆炸效果单一实例 public List<BoomEnemy> EnemyBoomList { get; set; } #endregion // 为游戏屏幕增加一个游戏对象 public void AddGameObject(GameObject go) { if (go is GameBackground) { this.Background = go as GameBackground; } if (go is GameTitle) { this.Title = go as GameTitle; } if (go is PlanePlayer) { this.Player = go as PlanePlayer; } if (go is BulletPlayer) { if(this.PlayerBulletList == null) { this.PlayerBulletList = new List<BulletPlayer>(); } this.PlayerBulletList.Add(go as BulletPlayer); } if (go is PlaneEnemy) { if (this.EnemyList == null) { this.EnemyList = new List<PlaneEnemy>(); } this.EnemyList.Add(go as PlaneEnemy); } if(go is BulletEnemy) { if (this.EnemyBulletList == null) { this.EnemyBulletList = new List<BulletEnemy>(); } this.EnemyBulletList.Add(go as BulletEnemy); } if (go is BoomPlayer) { if (this.PlayerBoomList == null) { this.PlayerBoomList = new List<BoomPlayer>(); } this.PlayerBoomList.Add(go as BoomPlayer); } if (go is BoomEnemy) { if (this.EnemyBoomList == null) { this.EnemyBoomList = new List<BoomEnemy>(); } this.EnemyBoomList.Add(go as BoomEnemy); } } // 移除指定的游戏对象 public void RemoveGameObject(GameObject go) { if (go is GameTitle) { this.Title = null; } if (go is BulletPlayer) { this.PlayerBulletList.Remove(go as BulletPlayer); } if (go is PlaneEnemy) { this.EnemyList.Remove(go as PlaneEnemy); } if (go is BulletEnemy) { this.EnemyBulletList.Remove(go as BulletEnemy); } if (go is BoomPlayer) { this.PlayerBoomList.Remove(go as BoomPlayer); } if (go is BoomEnemy) { this.EnemyBoomList.Remove(go as BoomEnemy); } } // 为游戏屏幕绘制游戏背景对象 public void DrawFirstBackground(Graphics g) { if (Background != null) { Background.Draw(g); } if (Title != null) { Title.Draw(g); } if (Player != null) { Player.Draw(g); } } // 为游戏屏幕绘制所有游戏对象 public void DrawGameObjects(Graphics g) { if (Background != null) { Background.Draw(g); } if (Player != null) { Player.Draw(g); } if (PlayerBulletList != null) { for (int i = 0; i < PlayerBulletList.Count; i++) { PlayerBulletList[i].Draw(g); } } if (EnemyList != null) { for (int i = 0; i < EnemyList.Count; i++) { EnemyList[i].Draw(g); } } if(EnemyBulletList != null) { for (int i = 0; i < EnemyBulletList.Count; i++) { EnemyBulletList[i].Draw(g); } } if (PlayerBoomList != null) { for (int i = 0; i < PlayerBoomList.Count; i++) { PlayerBoomList[i].Draw(g); } } if (EnemyBoomList != null) { for (int i = 0; i < EnemyBoomList.Count; i++) { EnemyBoomList[i].Draw(g); } } } // 玩家得分 public int Score { get; set; } }

View Code

  这里借助单例模式,保证玩家飞机对象只有一个存储,电脑飞机集合也只有一个,而具体的电脑飞机对象则分别在单例类中的集合中进行Add和Remove。

  (3)设计CollisionDetect方法:不停地进行碰撞检测

  ①Rectangle的IntersectsWith方法

Line

  在游戏界面中,任何一个游戏对象我们都可以视为一个矩形区域(Rectangle类实例),它的坐标是X轴和Y轴,它还有长度和宽度,可以轻松地确定一个它所在的矩形区域。那么,我们可以通过Rectangle的IntersectsWith方法确定两个Rectangle是否存在重叠,如果有重叠,此方法将返回 true;否则将返回 false。那么,在飞机大战中主要是判断两种情况:一是玩家或电脑飞机发射的子弹是否击中了对方?二是玩家是否撞到了敌人飞机?

  ②在定时器事件中定期执行碰撞检测方法

动手小游戏_飞机大战激活成功教程版
动手小游戏_飞机大战激活成功教程版

 // 碰撞检测方法 public void CollisionDetect() { #region 1.判断玩家的子弹是否打到了敌人飞机身上 for (int i = 0; i < PlayerBulletList.Count; i++) { for (int j = 0; j < EnemyList.Count; j++) { if(PlayerBulletList[i].GetRectangle().IntersectsWith(EnemyList[j].GetRectangle())) { // 1.敌人的生命值减少 EnemyList[j].Life -= PlayerBulletList[i].Power; // 2.生命值减少后判断敌人是否死亡  EnemyList[j].IsOver(); // 3.玩家子弹打到了敌人身上后将玩家子弹销毁  PlayerBulletList.Remove(PlayerBulletList[i]); break; } } } #endregion #region 2.判断敌人的子弹是否打到了玩家飞机身上 for (int i = 0; i < EnemyBulletList.Count; i++) { if(EnemyBulletList[i].GetRectangle().IntersectsWith(Player.GetRectangle())) { // 使玩家发生一次爆炸但不阵亡  Player.IsOver(); break; } } #endregion #region 3.判断敌人飞机是否和玩家飞机相撞 for (int i = 0; i < EnemyList.Count; i++) { if (EnemyList[i].GetRectangle().IntersectsWith(Player.GetRectangle())) { EnemyList[i].Life = 0; EnemyList[i].IsOver(); break; } } #endregion }

View Code

3.2 服务端开发

  (1)创建监听玩家连接的Socket,不停地监听玩家的游戏连接请求

动手小游戏_飞机大战激活成功教程版
动手小游戏_飞机大战激活成功教程版

 private void btnBeginListen_Click(object sender, EventArgs e) { if (isEndService) { SetTxtReadOnly(); if (socketWatch == null) { // 创建Socket->绑定IP与端口->设置监听队列的长度->开启监听连接 socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); socketWatch.Bind(new IPEndPoint(IPAddress.Parse(txtIPAddress.Text), int.Parse(txtPort.Text))); socketWatch.Listen(10); threadWatch = new Thread(ListenClientConnect); threadWatch.IsBackground = true; threadWatch.Start(socketWatch); } isEndService = false; this.btnStartGame.Enabled = true; ShowMessage("^_^:飞机大战服务器端启动服务成功,正在等待玩家进入游戏..."); } else { MessageBox.Show("服务已启动,请不要重复启动服务!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning); } } private void ListenClientConnect(object obj) { Socket serverSocket = obj as Socket; while (!isEndService) { Socket proxSocket = null; try { // 注意:Accept方法会阻断当前所在的线程 proxSocket = serverSocket.Accept(); dictClients.Add(proxSocket.RemoteEndPoint.ToString(), proxSocket); ShowMessage("*_*:玩家<" + proxSocket.RemoteEndPoint.ToString() + ">连接上了,请准备开始游戏。"); playerCount++; ThreadPool.QueueUserWorkItem(new WaitCallback(ReceiveData), proxSocket); } catch (SocketException ex) { ShowMessage("#_#:异常【" + ex.Message + ""); // 让方法结束,终结当前监听客户端数据的异步线程 return; } catch (Exception ex) { ShowMessage("#_#:异常【" + ex.Message + ""); // 让方法结束,终结当前监听客户端数据的异步线程 return; } } }

View Code

  在.NET中进行网络编程,一般都会涉及到Socket,其过程大概会经历如下图所示的流程:

动手小游戏_飞机大战激活成功教程版

PS:Socket非常类似于电话插座,以一个电话网为例:电话的通话双方相当于相互通信的2个程序,电话号码就是IP地址。任何用户在通话之前,首先要占有一部电话机,相当于申请一个Socket;同时要知道对方的号码,相当于对方有一个固定的Socket。然后向对方拨号呼叫,相当于发出连接请求。对方假如在场并空闲,拿起电话话筒,双方就可以正式通话,相当于连接成功。双方通话的过程,是一方向电话机发出信号和对方从电话机接收信号的过程,相当于向Socket发送数据和从Socket接收数据。通话结束后,一方挂起电话机相当于关闭socket,撤消连接。

  (2)使用线程池ThreadPool新开线程,不停地接收玩家发送的信息

  ThreadPool.QueueUserWorkItem(new WaitCallback(ReceiveData), proxSocket);

  在监听线程中使用了线程池,开启了一个新的线程来接收客户端发送过来的数据,那么这个ReceiveData方法如何实现的:

动手小游戏_飞机大战激活成功教程版
动手小游戏_飞机大战激活成功教程版

 private void ReceiveData(object obj) { Socket proxSocket = obj as Socket; byte[] data = new byte[1024 * 1024]; int length = 0; while (!isEndService) { try { length = proxSocket.Receive(data); } catch (SocketException ex) { ShowMessage("#_#:异常【" + ex.Message + ""); StopConnection(proxSocket); // 让方法结束,终结当前接收客户端数据的异步线程 return; } catch (Exception ex) { ShowMessage("#_#:异常【" + ex.Message + ""); StopConnection(proxSocket); // 让方法结束,终结当前接收客户端数据的异步线程 return; } if (length <= 0) { ShowMessage("*_*:玩家<" + proxSocket.RemoteEndPoint.ToString() + ">退出了游戏"); StopConnection(proxSocket); if (playerCount > 0) { playerCount--; } // 让方法结束,终结当前接收客户端数据的异步线程 return; } else { // 接受客户端发送过来的消息 string playerScore = Encoding.UTF8.GetString(data, 0, length); dictScores.Add(proxSocket.RemoteEndPoint.ToString(), Convert.ToInt32(playerScore)); if (dictScores.Count > 0 && dictScores.Count == playerCount) { ComparePlayerScores(); } } } }

View Code

  (3)当所有玩家都发送完游戏分数,服务器端对所有分数进行排序并发送最终名次

动手小游戏_飞机大战激活成功教程版
动手小游戏_飞机大战激活成功教程版

 private void ComparePlayerScores() { List<KeyValuePair<string, int>> scoreList = dictScores.OrderByDescending(s => s.Value).ToList(); for (int i = 0; i < scoreList.Count; i++) { string result = string.Format("您本次的成绩是第{0}名,分数为{1}分", i + 1, scoreList[i].Value); byte[] bytes = Encoding.UTF8.GetBytes(result); byte[] data = new byte[bytes.Length + 1]; data[0] = 2; Buffer.BlockCopy(bytes, 0, data, 1, bytes.Length); dictClients[scoreList[i].Key].Send(data, 0, data.Length, SocketFlags.None); } }

View Code

  在服务端有一个键值对集合专门存储玩家对应分数,然后对其按分数进行降序排序,排序后再遍历集合一一向玩家发送名次信息;

四、个人开发小结

4.1 服务端开启服务

动手小游戏_飞机大战激活成功教程版

  服务器端主要开启监听玩家连接请求的服务,当几个处在同一局域网的玩家连接后,服务端管理员点击“开始游戏”则客户端会启动游戏。

4.2 客户端开始游戏

动手小游戏_飞机大战激活成功教程版

  在客户端中,玩家飞机可以通过不停地发射子弹向不同类型的电脑飞机来获取得分,但是如果被敌人飞机的子弹击中分数也会被扣去一部分。

4.3 服务端计算成绩客户端显示

  动手小游戏_飞机大战激活成功教程版动手小游戏_飞机大战激活成功教程版

  当两个玩家连接游戏服务端后,便开始了“打飞机”的战斗,当指定时间后游戏结束,显示各自的游戏名次和分数。

  当然,还有很多核心的内容没有实现。希望有兴趣的童鞋可以去继续完善实现,这里提供一个我的飞机大战实现仅供参考,谢谢!

参考资料

  赵剑宇,《C#开发太空大战》:http://open.itcast.cn/net/3-106.html

附件下载

  MyPlaneGame:https://github.com/EdisonChou/The-Fighting-of-Planes

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/167284.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • kali安装步骤

    kali安装步骤kali镜像下载地址:http://mirrors.ustc.edu.cn/kali-images/1.    新建虚拟机 -选择自定义选择虚拟机硬件兼容性(默认我的是12.0)选择下一步选择稍后安装操作系统-下一步选择linux内核3.x 64位-下一步修改虚拟机名称为kali2.0   路径为我自己在G创建的kali文件夹处理器 1核我本机物理内存不大够了所以设置512M-…

  • python基础(8)python中is和==的区别详解

    python基础(8)python中is和==的区别详解前置知识点当我们创建一个对象时,我们要知道它内部干了些什么1.创建了一个随机id,开辟了一片内存地址2.自动声明了这个对象的类型type3.给这个对象赋值value小例子a=1pri

  • SD卡与MMC卡的区别

    SD卡与MMC卡的区别本文译至:http://home.impress.co.jp/magazine/dosvpr/q-a/0108/qa0108_2.htm

  • Deep Java Library_java atomicinteger

    Deep Java Library_java atomicintegerjava有NativeMemoryTracking帮助我们查看jvm带来的内存分配问题,这个只能看jvm带来的,如果是jni的调用申请的内存,那这个工具是没有用的。那大家可能疑惑了,那这个工具也没想象的那么有用,java各种分区,堆的,非堆的,还有直接内存的值jmx都有,想排查是否是jvm带来的似乎也可以做到。那NativeMemoryTracking的作用是什么呢?对比现有的工具查看内存数据的工…

    2022年10月23日
  • ZBrush中必须记住的常用快捷键

    ZBrush中必须记住的常用快捷键ZBrush是一款数字雕刻和绘画软件,它以强大的功能和直观的工作流程彻底改变了整个三维雕刻行业。强大的功能离不开便捷的操作,为此ZBrush提供了一系列常用操作快捷键,熟练掌握这些快捷键

  • ingress什么意思_k8s kong

    ingress什么意思_k8s kongk8sIngress介绍Http代理Https代理Ingress介绍我们已经知道,Service对集群之外暴露服务的主要方式有两种:NodePort和LoadBalancer,但是这两种方式,都有一定的缺点:NodePort方式的缺点是会占用很多集群机器的端口,那么当集群服务变多的时候,这个缺点就愈发明显。LoadBalancer的缺点是每个Service都需要一个LB,浪费,麻烦,并且需要kubernetes之外的设备的支持。基于这种现状,kubernetes提供了Ingress资源对象,I

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号