精华内容
下载资源
问答
  • 用 construct 2 制作简易弹幕游戏

    千次阅读 2017-11-20 06:05:55
    用 construct 2 制作简易弹幕游戏1 打开construct2 加入背景 3 建立新的图层4 在新的图层里加入素材超人 弹幕4 加入鼠标5 给超人和弹幕设置动作 超人的 弹幕的 6 加入文字框 7 编写代码完成啦!!!

    用 construct 2 制作简易弹幕游戏

    1 打开construct

    这里写图片描述

    2 加入背景

    这里写图片描述
    这里写图片描述
    这里写图片描述

    3 建立新的图层

    这里写图片描述

    4 在新的图层里加入素材

    超人
    这里写图片描述
    弹幕

    这里写图片描述

    这里写图片描述

    4 加入鼠标

    这里写图片描述

    5 给超人和弹幕设置动作

    这里写图片描述
    超人的
    这里写图片描述
    弹幕的
    这里写图片描述

    6 加入文字框

    这里写图片描述
    这里写图片描述

    7 编写代码

    这里写图片描述

    完成啦!!!

    这里写图片描述
    这里写图片描述

    展开全文
  • 弹幕射击小游戏

    热门讨论 2012-12-16 22:45:29
    模仿东方永夜抄,开发的C++弹幕游戏,包括素材、源码
  • gmk弹幕游戏测试

    2014-04-29 21:43:33
    gmk制作的一个弹幕测试类游戏,里面实现了基本的直线弹幕,长期存在的光线弹幕,和跟踪弹幕,和自动锁定弹幕
  • 以Ten Seconds为主题创作的弹幕游戏 使用Unity创建的弹幕游戏 设置了14个关卡 4个不同的章节
  • 我最近开坑了一个类似东方project的弹幕游戏制作,由于之前没有经验(也就是说我是个小白),遇到弹幕碰撞时产生如下问题: 我有敌人的ArrayList<Enemy> 也有玩家的弹幕...
  • 首先感谢我们组的两位大佬,认真负责又很有耐心地指导我们,跟着他们学到了不少东西。 先设计好初稿,然后再分析实现步骤,比如列出数学物理公式,最后就是编码实现阶段了。下面是我的代码: BulletCenterDance: ...
    首先感谢我们组的两位大佬,认真负责又很有耐心地指导我们,跟着他们学到了不少东西。

    先设计好初稿,然后再分析实现步骤,比如列出数学物理公式,最后就是编码实现阶段了。下面是我的代码:

    BulletCenterDance:

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class BulletCenterDance : BulletTypeB {
    
        public Vector2 Valocity;
        
        // Update is called once per frame
        void Update () {
            transform.Translate(Valocity * Time.deltaTime);
        }
    }
    

    RevolveCircle:

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class RevolveCircle : SpellCard {
    
        public RevolveCircle() : base(new Circle(), new Vector2(-1.5f, 2.4f), 78f, 15000, "想起「旋转圆舞」 ")
        {
        }
    }
    
    class Circle : State<Boss>{
        float time = 2f;
        float timeC = 4f;
        float t = -4f;
        GameObject Revolve;
        public override void Enter(Boss owner)
        {
            Revolve  = ((Satori)owner).RevolveCircleResources.GetComponent<RevolveCircleResource>().RevolveCircle;
            owner.transform.position = new Vector3(-1.53f, 0, 0);
        }
        public override void Execute(Boss owner)
        {
            timeC -= Time.deltaTime;
            time -= Time.deltaTime;
            t += Time.deltaTime;
            if (time < 0) {
                time += 0.1f;
                Vector2 StartLo;
                if (GameManager.player != null) {
                    StartLo = GameManager.player.transform.position - owner.transform.position;
                } else {
                    StartLo = new Vector2 (0, 1);
                }
                StartLo.Normalize ();
                for (int i = -1; i <= 1; i++) {
                    float routeAngle = 30 * Mathf.Deg2Rad * i;
                    Vector2 valocity = new Vector2(StartLo.x*Mathf.Cos(routeAngle)-StartLo.y*Mathf.Sin(routeAngle),StartLo.x*Mathf.Sin(routeAngle)+StartLo.y*Mathf.Cos(routeAngle));
                    valocity *= 3.0f;
                    BulletCenterDance c = GameObject.Instantiate (((Satori)owner).RevolveCircleResources.GetComponent<RevolveCircleResource> ().RevolveCircle, owner.transform.position, Quaternion.identity).GetComponent<BulletCenterDance> ();
                    c.Valocity = valocity;
                    GameObject.Destroy (c.gameObject, 5f);
                }
    
    
    
            }
            if (timeC<0) {
                timeC += 0.3f;
                for (int i = 0; i < 4; i++) {
                    Vector2 StartLocation = new Vector2 (Mathf.Cos (t), Mathf.Sin (t));
                    Vector2 valocity = new Vector2 (2f * Mathf.Cos (2f * t + i * Mathf.PI / 2), 2f * Mathf.Sin (2f * t + i * Mathf.PI / 2));
                    BulletCenterDance r = GameObject.Instantiate (((Satori)owner).RevolveCircleResources.GetComponent<RevolveCircleResource> ().BulletTriangle, owner.transform.position + (Vector3)StartLocation, Quaternion.identity).GetComponent<BulletCenterDance> ();
                    r.Valocity = valocity;
                    GameObject.Destroy (r.gameObject, 10f);
                }
                for (int i = 0; i < 4; i++) {
                    Vector2 StartLocation = new Vector2 (-Mathf.Cos (t), Mathf.Sin (t));
                    Vector2 valocity = new Vector2 (-2f * Mathf.Cos (2f * t + i * Mathf.PI / 2), 2f * Mathf.Sin (2f * t + i * Mathf.PI / 2));
                    BulletCenterDance r = GameObject.Instantiate (((Satori)owner).RevolveCircleResources.GetComponent<RevolveCircleResource> ().BulletTriangle, owner.transform.position + (Vector3)StartLocation, Quaternion.identity).GetComponent<BulletCenterDance> ();
                    r.Valocity = valocity;
                    GameObject.Destroy (r.gameObject, 10f);
                }
            }
        }
                
    
    
    }
    
    
    

    RevolveCircleResources:

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class RevolveCircleResource : MonoBehaviour {
    
        public GameObject RevolveCircle; //
        public GameObject BulletTriangle; //
        // Use this for initialization
        void Start () {
            
        }
        
        // Update is called once per frame
        void Update () {
            
        }
    }
    
    

    转载于:https://www.cnblogs.com/1997Ff/p/7498400.html

    展开全文
  • 如果只有这些子弹,那看...GameBarrage弹幕类,用于创建一些酷炫叼的弹幕~~其实核心也就是通过一些数学公式,然后批量AddBullet而已~~ 先来看看类型的定义 public enum BarrageType { Line, RotateAndShoot,

    如果只有这些子弹,那看起来必然是很一般的,想想看,只有几颗子弹,孤零零的从下面跑到上面。。。。。

    GameBarrage弹幕类,用于创建一些酷炫叼的弹幕~~其实核心也就是通过一些数学公式,然后批量AddBullet而已~~

    先来看看类型的定义

    public enum BarrageType
        {
            Line,
            RotateAndShoot,
            CrossLine,
            Circle
        }


    初步就这四种,其余的弹幕类型都可以通过这几种组合出来

    然后就是弹幕的具体实现

     

    public class GameBarrage
        {
            private GameScene mScene;
    
            public GameBarrage(GameScene scene)
            {
                this.mScene = scene;
            }
    
            /// <summary>
            /// 圆形弹幕
            /// </summary>
            /// <param name="isOwn">是否为玩家子弹</param>
            /// <param name="type">子弹类型</param>
            /// <param name="color">子弹颜色</param>
            /// <param name="position">中心点位置</param>
            /// <param name="speed">子弹移动速度</param>
            /// <param name="accel">子弹移动加速度</param>
            /// <param name="interval">每颗子弹的角度间隔</param>
            /// <param name="delay">延迟发射时间</param>
            public void CreateCircle(bool isOwn, int type, int color, Vector2 position, float speed, float accel, int interval, float delay)
            {
                int angle = 0;
    
                while (angle < 360)
                {
                	mScene.AddBullet(new GameBullet(isOwn, position, Helper.GetSpeedWithAngle(angle, speed), Helper.GetSpeedWithAngle(angle, accel), type, color, delay));
                    angle += interval;
                }
            }
    
            /// <summary>
            /// 扇形弹幕
            /// </summary>
            /// <param name="isOwn">是否为玩家子弹</param>
            /// <param name="type">子弹类型</param>
            /// <param name="color">子弹颜色</param>
            /// <param name="position">中心点的位置</param>
            /// <param name="startAngle">扇形的起始角度(向右为0°)</param>
            /// <param name="endAngle">扇形的结束角度(顺时针)</param>
            /// <param name="speed">子弹移动速度</param>
            /// <param name="accel">子弹移动加速度</param>
            /// <param name="interval">每颗子弹的角度间隔</param>
            /// <param name="duration">发射持续时间(为0表示瞬间发射所有)</param>
            /// <param name="delay">延迟发射时间</param>
            public void CreateRotateAndShoot(bool isOwn, int type, int color, Vector2 position, int startAngle, int endAngle, float speed, float accel, int interval, float duration, float delay)
            {
                int count = startAngle;
                int angle = Math.Abs(endAngle - startAngle);
                float wait = duration / (float)angle;
                
                if ( endAngle > startAngle )
                {
    	            while (count < endAngle)
    	            {
    	            	mScene.AddBullet(new GameBullet(isOwn, position, Helper.GetSpeedWithAngle(count, speed), Helper.GetSpeedWithAngle(count, accel), type, color, (count - startAngle) * wait + delay ));
    	                count += interval;
    	            }
                }
                else
                {
                	while (count > endAngle)
    	            {
                		mScene.AddBullet(new GameBullet(isOwn, position, Helper.GetSpeedWithAngle(count, speed), Helper.GetSpeedWithAngle(count, accel), type, color, (startAngle - count) * wait + delay ));
    	                count += interval;
    	            }
                }
            }
    
            /// <summary>
            /// 直线交叉弹幕
            /// </summary>
            /// <param name="isOwn">是否为玩家子弹</param>
            /// <param name="type">子弹类型</param>
            /// <param name="color">子弹颜色</param>
            /// <param name="spawnCount">子弹数量</param>
            /// <param name="beginPos1">开始地点一</param>
            /// <param name="beginPos2">开始地点二</param>
            /// <param name="crossPos">交叉点</param>
            /// <param name="speed">子弹移动速度</param>
            /// <param name="accel">子弹移动加速度</param>
            /// <param name="duration">发射持续时间(为0表示瞬间发射所有)</param>
            /// <param name="delay">延迟发射时间</param>
            public void CreateCrossLine(bool isOwn, int type, int color, int spawnCount, Vector2 beginPos1, Vector2 beginPos2, Vector2 crossPos, float speed, float accel, float duration, float delay)
            {
                int count = 0;
                float wait = duration / (float)spawnCount;
    
                while (count < spawnCount)
                {
                    mScene.AddBullet(new GameBullet(isOwn, beginPos1,
                        Helper.GetSpeedWithPosition(beginPos1, crossPos, speed), Helper.GetSpeedWithPosition(beginPos1, crossPos, accel),
                        type, color,
                        wait * count + delay));
                    mScene.AddBullet(new GameBullet(isOwn, beginPos2,
                        Helper.GetSpeedWithPosition(beginPos2, crossPos, speed), Helper.GetSpeedWithPosition(beginPos2, crossPos, accel),
                        type, color,
                        wait * count + delay));
                    count++;
                }
            }
    
            /// <summary>
            /// 直线弹幕
            /// </summary>
            /// <param name="isOwn">是否为玩家子弹</param>
            /// <param name="type">子弹类型</param>
            /// <param name="color">子弹颜色</param>
            /// <param name="spawnCount">子弹数量</param>
            /// <param name="startPos">开始地点</param>
            /// <param name="targetPos">目标地点(用于确定方向而已)</param>
            /// <param name="speed">子弹移动速度</param>
            /// <param name="accel">子弹移动加速度</param>
            /// <param name="duration">发射持续时间</param>
            /// <param name="delay">延迟发射时间</param>
            public void CreateLine(bool isOwn, int type, int color, int spawnCount, Vector2 startPos, Vector2 targetPos, float speed, float accel, float duration, float delay)
            {
                int count = 0;
                float wait = duration / (float)spawnCount;
    
                while (count < spawnCount)
                {
                    mScene.AddBullet(new GameBullet(isOwn, startPos,
                	    Helper.GetSpeedWithPosition(startPos, targetPos, speed), Helper.GetSpeedWithPosition(startPos, targetPos, accel),
                        type, color,
                        wait * count + delay));
                    count++;
                }
            }
            
            /// <summary>
            /// 随机弹幕
            /// </summary>
            /// <param name="isOwn">是否为玩家子弹</param>
            /// <param name="type">子弹类型</param>
            /// <param name="color">子弹颜色</param>
            /// <param name="spawnCount">子弹数量</param>
            /// <param name="boundary">子弹范围</param>
            /// <param name="speed">子弹速度(包括方向)</param>
            /// <param name="accel">子弹加速度(包括方向)</param>
            /// <param name="duration">发射持续时间(为0表示瞬间发射所有)</param>
            /// <param name="delay">延迟发射时间</param>
            public void CreateRandom(bool isOwn, int type, int color, int spawnCount, RangeBox boundary, RangeVector speed, RangeVector accel, float duration, float delay)
            {
            	int count = 0;
            	float wait = duration / (float)spawnCount;
            	
            	while (count < spawnCount)
            	{
            		Vector2 sp = speed.Get();
            		Vector2 dir = sp;
            		dir.Normalize();
            		mScene.AddBullet(new GameBullet(isOwn, boundary.Get(), sp, dir * accel.Get(), type, color, count * wait + delay));
            		count++;
            	}
            }
        }


    就来圆形弹幕来讲解吧。

    首先大家可以看看前面GameBullet,有个字段是用于延迟显示的(就是说子弹已经添加了,只是必须要等到规定时间过去才能显示出来)

    解析来看具体的实现

    public void CreateCircle(bool isOwn, int type, int color, Vector2 position, float speed, float accel, int interval, float delay)
            {
                int angle = 0;
    
                while (angle < 360)
                {
                	mScene.AddBullet(new GameBullet(isOwn, position, Helper.GetSpeedWithAngle(angle, speed), Helper.GetSpeedWithAngle(angle, accel), type, color, delay));
                    angle += interval;
                }
            }


    核心思想就是通过“极坐标公式”算出不同角度对应的坐标,然后生成子弹

    具体的计算就是Helper.GetSpeedWithAngle,这个忘记的可以看前面章节,有写~

    通过累加angle,循环360°,然后计算出不同角度的速度方向和延迟就能创建圆形弹幕了

    再来一个用的最多的扇形弹幕

    public void CreateRotateAndShoot(bool isOwn, int type, int color, Vector2 position, int startAngle, int endAngle, float speed, float accel, int interval, float duration, float delay)
            {
                int count = startAngle;
                int angle = Math.Abs(endAngle - startAngle);
                float wait = duration / (float)angle;
                
                if ( endAngle > startAngle )
                {
    	            while (count < endAngle)
    	            {
    	            	mScene.AddBullet(new GameBullet(isOwn, position, Helper.GetSpeedWithAngle(count, speed), Helper.GetSpeedWithAngle(count, accel), type, color, (count - startAngle) * wait + delay ));
    	                count += interval;
    	            }
                }
                else
                {
                	while (count > endAngle)
    	            {
                		mScene.AddBullet(new GameBullet(isOwn, position, Helper.GetSpeedWithAngle(count, speed), Helper.GetSpeedWithAngle(count, accel), type, color, (startAngle - count) * wait + delay ));
    	                count += interval;
    	            }
                }
            }


    原理和上面类似,只是需要注意的是时间延迟传递的是  (startAngle-Count)*wait+delay

    其实就是通过计算出需要产生的子弹数量已经总的延迟,然后算出每一个子弹的延迟时间。

    这样的效果就是,瞬间产生了一堆子弹,但是看到的是一个个显示出来,有一种旋转着发射子弹的感觉。。。表达不好。。

    有了这个东西,就可以看看 BOSS弹幕的生成了,其实就是用了这些函数而已

    public static class BossBarrage
    	{
    		/// <summary>
    		/// 花瓣型弹幕
    		/// </summary>
    		/// <param name="barrage"></param>
    		/// <param name="position"></param>
    		/// <returns>弹幕持续8秒</returns>
    		public static int Petal(GameBarrage barrage, Vector2 position)
    		{
    			barrage.CreateRotateAndShoot(false, 0, 0, position, 0, 300, 300, -350, 5, 8, 0);
    			barrage.CreateRotateAndShoot(false, 0, 0, position, 30, 330, 300, -350, 5, 8, 0);
    			barrage.CreateRotateAndShoot(false, 0, 0, position, 60, 360, 300, -350, 5, 8, 0);
    			barrage.CreateRotateAndShoot(false, 0, 0, position, 90, 390, 300, -350, 5, 8, 0);
    			barrage.CreateRotateAndShoot(false, 0, 0, position, 120, 420, 300, -350, 5, 8, 0);
    			barrage.CreateRotateAndShoot(false, 0, 0, position, 150, 450, 300, -350, 5, 8, 0);
    			barrage.CreateRotateAndShoot(false, 0, 0, position, 180, 480, 300, -350, 5, 8, 0);
    			barrage.CreateRotateAndShoot(false, 0, 0, position, 210, 510, 300, -350, 5, 8, 0);
    			barrage.CreateRotateAndShoot(false, 0, 0, position, 240, 540, 300, -350, 5, 8, 0);
    			barrage.CreateRotateAndShoot(false, 0, 0, position, 270, 570, 300, -350, 5, 8, 0);
    			barrage.CreateRotateAndShoot(false, 0, 0, position, 300, 600, 300, -350, 5, 8, 0);
    			barrage.CreateRotateAndShoot(false, 0, 0, position, 330, 630, 300, -350, 5, 8, 0);
    			
    			return 8;
    		}
    		
    		/// <summary>
    		/// 直线型弹幕
    		/// </summary>
    		/// <param name="barrage"></param>
    		/// <returns>弹幕持续5秒</returns>
    		public static int Line(GameBarrage barrage)
    		{
    			barrage.CreateLine(false, 0, 0, 50, new Vector2(25, 0), new Vector2(25, 400), 100, 0, 15, 0);
    			barrage.CreateLine(false, 0, 1, 50, new Vector2(75, 0), new Vector2(75, 400), 100, 0, 15, 0);
    			barrage.CreateLine(false, 0, 2, 50, new Vector2(125, 0), new Vector2(125, 400), 100, 0, 15, 0);
    			barrage.CreateLine(false, 0, 3, 50, new Vector2(175, 0), new Vector2(175, 400), 100, 0, 15, 0);
    			barrage.CreateLine(false, 0, 4, 50, new Vector2(225, 0), new Vector2(225, 400), 100, 0, 15, 0);
    			barrage.CreateLine(false, 0, 5, 50, new Vector2(275, 0), new Vector2(275, 400), 100, 0, 15, 0);
    			barrage.CreateLine(false, 0, 6, 50, new Vector2(325, 0), new Vector2(325, 400), 100, 0, 15, 0);
    			barrage.CreateLine(false, 0, 0, 50, new Vector2(375, 0), new Vector2(375, 400), 100, 0, 15, 0);
    			
    			return 5;
    		}
    		
    		/// <summary>
    		/// 连续两拨错开的扇形弹幕
    		/// </summary>
    		/// <param name="barrage"></param>
    		/// <param name="position"></param>
    		/// <returns>弹幕持续时间2秒</returns>
    		public static int TwoFans(GameBarrage barrage, Vector2 position)
    		{
    			barrage.CreateRotateAndShoot(false, 0, 0, position, 45, 135, 300, 0, 5, 0, 0);
    			barrage.CreateRotateAndShoot(false, 0, 0, position, 43, 143, 300, 0, 5, 0, 0.1f);
    			
    			return 2;
    		}
    		
    		/// <summary>
    		/// 左右两边出现相反的漩涡型弹幕
    		/// </summary>
    		/// <param name="barrage"></param>
    		/// <param name="position"></param>
    		/// <returns>弹幕持续时间4秒</returns>
    		public static int TwoReverseRotate(GameBarrage barrage, Vector2 position)
    		{
    			barrage.CreateRotateAndShoot(false, 2, 0, new Vector2(50, position.Y), 0, 180, 350, -80, 8, 1, 0);
    			barrage.CreateRotateAndShoot(false, 2, 4, new Vector2(50, position.Y), 90, 270, 350, -80, 8, 1, 0);
    			barrage.CreateRotateAndShoot(false, 2, 0, new Vector2(50, position.Y), 180, 360, 350, -80, 8, 1, 0);
    			barrage.CreateRotateAndShoot(false, 2, 4, new Vector2(50, position.Y), 270, 450, 350, -80, 8, 1, 0);
    			
    			barrage.CreateRotateAndShoot(false, 2, 0, new Vector2(350, position.Y), 180, 0, 350, -80, -8, 1, 0);
    			barrage.CreateRotateAndShoot(false, 2, 4, new Vector2(350, position.Y), 270, 90, 350, -80, -8, 1, 0);
    			barrage.CreateRotateAndShoot(false, 2, 0, new Vector2(350, position.Y), 360, 180, 350, -80, -8, 1, 0);
    			barrage.CreateRotateAndShoot(false, 2, 4, new Vector2(350, position.Y), 450, 270, 350, -80, -8, 1, 0);
    			
    			return 4;
    		}
    		
    		/// <summary>
    		/// 三个园型弹幕
    		/// </summary>
    		/// <param name="barrage"></param>
    		/// <param name="position"></param>
    		/// <returns>弹幕持续时间5秒</returns>
    		public static int ThreeCircle(GameBarrage barrage, Vector2 position)
    		{
    			barrage.CreateCircle(false, 0, 0, position, 250, 0, 10, 0);
    			barrage.CreateCircle(false, 0, 1, position + new Vector2(-100, 50), 250, 0, 10, 0);
    			barrage.CreateCircle(false, 0, 2, position + new Vector2(100, 50), 250, 0, 10, 0);
    			
    			barrage.CreateCircle(false, 0, 3, position, 250, 0, 10, 0.5f);
    			barrage.CreateCircle(false, 0, 4, position + new Vector2(-100, 50), 250, 0, 10, 0.5f);
    			barrage.CreateCircle(false, 0, 5, position + new Vector2(100, 50), 250, 0, 10, 0.5f);
    			
    			return 5;
    		}
    	}


    代码很多,只是通过一些数学计算,产生不同效果而已~~大家可以自由发挥~~

    展开全文
  • 简介DooMissO是本人使用JS、jQuery开发的一款弹幕射击游戏,现已推出1.8版 Js和jQuery代码混搭得比较厉害,后续几个版本会陆续修正统一github仓库地址:https://github.com/Kagashino/doomisso-demo 试玩地址:...

    简介

    DooMissO是本人使用JS、jQuery开发的一款弹幕射击游戏,现已推出1.8版
    HTML DOM操作和jQuery代码混搭得比较厉害,后续几个版本会陆续修正统一

    github仓库地址:https://github.com/Kagashino/doomisso-demo
    试玩地址:https://kagashino.github.io/doomisso-demo/

    实现步骤:

    一、游戏画布(伪)

    这个不是CANVAS开发的游戏,所以游戏画布是一个600*600的DIV,包括各种按钮和界面,画布右侧显示各种游戏即时数据。附上HTML代码:

    <!--    游戏设置画面   -->
        <div class="layout layout2">
            <center>
                <h2>游戏设置</h2>
                <h3>点击下方选项设置按键</h3><br>
                <h3 class="keySet">  向上    按键:<span id="UPKeySet"></span></h3>
                <h3 class="keySet">  向下    按键:<span id="DNKeySet"></span></h3>
                <h3 class="keySet">  向左    按键:<span id="LTKeySet"></span></h3>
                <h3 class="keySet">  向右    按键:<span id="RTKeySet"></span></h3>
                <h3 class="keySet">  低速    按键:<span id="LSKeySet"></span></h3>
                <h3 class="keySet">  还原默认按键</h3>
                <h3>难易度调节</h3>
                <span class="selectDiff" title="浅尝辄止的难度">简单</span><span class="selectDiff" title="代表大多数玩家的水平">正常</span><span class="selectDiff" title="难度越大,回报越高">疯狂</span>
                <input type="hidden" id="difficult" value="正常">
    
            </center>
    
            <h1 class="returnMain btn-style">返回</h1>
        </div>
    
        <!--    游戏说明        -->
        <div class="layout layout3">
            <center><h2>游戏说明</h2></center>
            <p class="howToPlay">
            1、默认<strong>Enter</strong>键开始,<strong>上下左右</strong>移动,<strong>shift</strong>低速移动,同时显示擦弹范围和判定点(见3、),您在下一个版本可以在“游戏设置”中修改游戏操作按键;<br />
            2、为了将来该游戏系统的布局计划,现在已开启<strong>自动开火</strong>模式;<br />
            3、擦弹范围是一个围绕着机体的<span class="gray">灰色的圈</span>,当圈内有子弹时分数会加得很快,判定点只有机体中间一个小<span class="wdot">白点</span>的范围,击中一次<span class="orange">生命值-10</span>(注意:碰到飞行物不会触发碰撞);<br />
            4、今后还会推出更丰富的游戏系统,敬请期待;<br />
            5、If it ran into any bug, REFRESH can solve 99% problems.(有任何问题,请使用刷新大法);<br>   
            6、good luck, have fun!
            </p>
            <img id="control" src="img/operate.png" />
            <h1 class="returnMain btn-style">返回</h1>
        </div>
    
        <!--                    游戏主面板                   -->
        <section id="gameScreen">
            <!-- 自机 -->
            <div id="p_ship" class="player1">
                <div id="laser"></div>
                <div id="p_spot"></div>
            </div>
    
        </section>
    
    
        <!--            数据面板            -->
        <aside>
            <button id="startBtn" >开始游戏</button><br>
    
            <button id="endTest"  onclick="endGame()">结束游戏</button><br><br>
    
            <p>生命值:<span id="hitCount">50</span></p><br>
            <p>擦弹:<span id="zijiPos">0</span></p><br>
            <p>分数:<span id="score">0</span></p><br><br>
            <p>屏幕弹幕数 <span id="dmkcount">0</span></p>
            <h4>HTML5弹幕射击游戏 ver1.8 作者:Kagashino</h4>
            <p>图片素材均来自恶魔之星</p><br>
            <p>历史版本 <br>
                <!--逻辑初成-->
                ver1.6.12.28 <br>
                <!--优化代码结构-->
                ver1.7.1.18 <br>
                <!--优化界面布局,增加自定义按键,优化代码结构-->
                ver1.7.2.28 <br>
                <!--重做了弹幕生成算法-->
                <!--新增自机狙、五角星和螺旋弹的算法-->
                <!--1.8更新:把弹幕添加到了Boss中,整个游戏流程基本成型-->
            </p><br><br>
    
        </aside>

    CSS设置

    body{
        margin: 0;
        padding: 0;
        z-index: -1000;
    }
    section,p,aside{
        margin: 0;padding: 0;
        display: inline-block;
        vertical-align: top;
    }
    aside{
        width: 30%;
    }
    
    
    .gray{
        border: 2px dashed #666;
        color:#666;
    }
    .orange{
    
        color: #f51;
    }
    .wdot{
        border: dashed 1px gray;
        color: #eee;
    }
    #gameScreen{
        width: 600px;
        height: 600px;
        background: #336 url(../img/bg.png);
        position: relative;
        /*overflow: hidden;*/
        z-index: 1;
    }
    
    #laser{
        position: absolute;
        width: 15px;
        height: 600px;
        background: transparent;
        border: 2px #fff solid;
        border-bottom: none;
        left: 35px;
        top: -560px;
        display: none;
    }
    #hitCount{
    }
    .btn-style{
        padding: 5px;
        position: absolute;
        font-weight: normal;
        border:3px solid white;
        color:white;
        z-index: 1000;
    }
    .btn-style:hover{
        cursor: pointer;
        font-weight: bold;
        box-shadow: 0 0 15px 5px white;
    }
    .btn-style::selection {
        background: none;
    }
    .btn-style::-moz-selection {
        background: none;
    }
    
    .keySet{
        text-align:left;
        width: 70%;
        border:2px solid transparent;
    }
    .keySet:hover{
        cursor: default;
        border-color: #fff;
    }
    .keySet::selection {
        background: none;
    }
    .keySet::-moz-selection {
        background: none;
    }
    .layout{
        width: 600px;
        height: 600px;
        color: #ffffff;
        position: absolute;
        z-index: 100;
    }
    .layout1{
        display: block;
    }
    .layout2{
        display: none;
    }
    .selectDiff{
        padding: 5px;
        border:solid 2px transparent;
    }
    .selectDiff:hover{
        cursor: pointer;
        text-shadow: 0px 0px 3px #fff;
    }
    .layout3{
        display: none;
        width: 550px;
        top: 10px;
        left: 30px;
    
    }
    .howToPlay{
        margin-top: 20px;
    }
    .currDifficult {
        position: absolute;
        top: 150px;
        left: 220px;
    }
    #startBtn2{
        top: 180px;
        left: 220px;
    }
    #gameConfig{
        top: 250px;
        left: 220px;
    }
    #readme{top: 320px;
        left:220px;
    }
    .returnMain {
        top: 500px;
        left: 450px;
    }
    #control{
        position: absolute;
        left: 50px;
        top: 300px;
        z-index: 1000;
    }
    
    /***************游戏物体样式******************/
    #p_ship{
        width:90px;
        height: 90px;
        background: url(../img/fighterAll.png) 0px 0px no-repeat;
        position: absolute;
        top: 300px;
        left: 250px;
        z-index: 10;
    }
    #p_spot{
        margin: 37px 37px;
        width: 10px;
        height: 10px;
        background: #fff;
        border: dotted 1px gray;
        border-radius: 50%;
        z-index: 100;
        display:block;
    }
    #p_spot:after{
        content: " ";
        display: block;
        width: 60px;
        height: 60px;
        border: dashed gray;border-radius: 50%;
        position: relative;
        top: -30px;
        left: -28px;
    }
    
    .enemy1{
        width: 60px;
        height: 60px;
        background: pink;
        position: absolute;
    }
    
    .left{
        background: url(../img/enemy1.png) no-repeat;
    }
    
    .right{
        background: url(../img/enemy1.png) -60px 0px no-repeat;
    }
    
    .enemy2{
        width: 60px;
        height: 60px;
        background: url(../img/enemy2.png) no-repeat;
        position: absolute;
    }
    
    #theBoss{
        width: 185px;
        height: 208px;
        background: url(../img/boss1.png) no-repeat;
        position: absolute;
        top: 85px;
        z-index:5;
    
    }
    
    .damagedBoss{
        background: url(../img/boss1dmg.png) no-repeat !important;
    }
    #bossHealthBar{
        width: 200px;
        height: 10px;
        position: absolute;
        top: 0;
        left: -10px;
        background: rgb(0,255,0);
        display: block;
    }
    
    .bullet,.poolBlt{
        width: 10px;
        height: 10px;
        background:red;
        border-radius: 50%;
        position: absolute;
        z-index: 100;
    }
    
    

    最终呈现的画面

    脚本引用:

    <script src="js/jquery.min.js"></script><!--jQuery-->
    <script src="js/keymap.js"></script><!-- 键盘对应名称 -->
    <script src="js/animateEasing.js"></script><!-- JQ动画速度曲线扩展 -->
    <script src="js/bulletClass.js"></script><!-- 子弹行为 -->
    <script src="js/newscript.js"></script><!-- 主逻辑 -->
    <script src="js/objAction.js"></script><!-- 物体移动逻辑 -->
    <script src="js/eventListener.js"></script><!-- 事件监听器 -->
    

    二、游戏逻辑控制

    初始化需要用到的变量:

    //键盘编码和对应条件判断变量
    var keySt = {
                UP: 38,
                DN: 40,
                LT: 37,
                RT: 39,
                LS: 16,
    
                toUp: false, 
                toDown: false, 
                toLeft: false, 
                toRight: false, 
                toLSpeed: false, 
                toShow: false,
                toPause: false, 
            }
    
    
    
    //状态
    var gameSt = {
                playing: false, //游戏是否进行中 
                boss: false,//是否在打BOSS
                graze : 0,//擦弹
                hp : 50,//生命值
                score : 0,//本轮得分
                highScore: 0,//最高分,目前尚未用到
                diff : 1 //难易度 0简单 1正常 2疯狂
            }
    /*定时器对象*/
    var timer = {
                general: null,//主定时器
                enemyCreator: null,//敌机1生成器
                enemy2Creator: null,//敌机2生成器
                overflowOperator: null,//子弹溢出处理器
                releaseBoss:null,
    
    }
    
    var boss = {
        health:300,
        position:[],
        state:1
    }
    /*检查是否与服务器连接,如果是,则开启分数记录功能*/
    var ajax = false;
    
    //自机
    var ziji = document.getElementById('p_ship');
    var spot = document.getElementById('p_spot');//判定点
    
    //画布
    var $gameScreen = $('#gameScreen');
    var $startBtn = $('#startBtn,#startBtn2');//两处可以控制开始的按钮

      点击开始按钮执行初始化游戏

    //初始化
    function gameInit(){
        gameSt.stop = keySt.toShow = false;
        initDanmaku();
        gameContinue();
    }

      开始游戏后关闭判定点显示,隐藏所有选单,gameProcess为生成敌人、boss等相关操作,同时必须先清除一次定时器,否则游戏进行到第二轮或以上,定时器会叠加执行
    fireUp为开火行为,第一次延迟1毫秒,之后每200毫秒执行一次

    function gameContinue(){
    
        $('.layout').hide();
        gameSt.playing = true;
        gameProccess();
        clearInterval(timer.general);
        timer.general=null;
    
        timer.openFire = setTimeout(fireUp,1);
        timer.general = setInterval(zijiOperator,21);
    }

    弹幕在游戏一开始就生成,只是将其放在屏幕看不见的地方作为弹幕池以备用,需要时再发出请求,子弹击中或者飞出屏幕后再回收,弹幕池里共有512枚子弹,因为屏幕子弹数达到250左右就会出现明显的卡顿了,所以我在后面将活动的弹幕控制在150左右,理论上是用不完的。

    /*生成弹幕池*/
    function initDanmaku(){
        var dmFrag = document.createDocumentFragment();//以文档碎片的形式添加弹幕
        for(var i=0; i<511; i++){
            var elem = document.createElement("p");
            elem.id = "danmaku"+i;
            elem.className = "poolBlt";
            elem.title = "1";
            resetObject(elem,"620px","620px")
            dmFrag.appendChild(elem);
        }
        $gameScreen.append(dmFrag);
        return true;
    }
    /*请求弹幕池*/
    function requestDanmaku() {
        if(gameSt.playing){
            for(var i=0; i<511; i++){
    
                if(document.getElementById("danmaku"+i).title==="1"){
                    document.getElementById("danmaku"+i).title = "2";
                    return i;
                }
    
            }
        }
    }

    生成敌人和boss

    /*游戏流程控制*/
    function gameProccess(){
        clearInterval(timer.overflowOperator);//防止Interval叠加,下同
        clearInterval(timer.enemyCreator);
        timer.overflowOperator = timer.enemyCreator = null;
    
    
        if(gameSt.playing) {
            createEnemy();//生成敌人
            //回收子弹和横向敌人
            timer.overflowOperator = setInterval(function () {
                ammoOperating();
                enemy1Operating();
    
            }, 200)//每200毫秒回收一次飞出屏幕的敌机和弹幕
        }
    
        //游戏开始30秒后生成BOSS
        timer.releaseBoss = setTimeout(BossInSight,30000)
    }

    生成BOSS和BOSS死亡后的回调,BOSS具体如何运动的,请参考第三节:

    /*BOSS状态*/
    function BossInSight() {
    
        if(gameSt.playing && !gameSt.boss){
            clearTimeout(timer.releaseBoss);//防止定时器叠加
            console.log("Enemy Ultra Cruiser is Approaching!");
    
            /*BOSS出现后,小怪不再生成*/
            clearInterval(timer.enemy2Creator);
            clearInterval(timer.enemyCreator);
            timer.enemy2Creator = timer.enemyCreator = null;
            gameSt.boss = true;//BOSS状态为真
            drawBoss();//绘制BOSS
        }
    
    }
    /*BOSS死亡后*/
    function  BossDie() {
        $('#theBoss').removeClass('damagedBoss').stop().remove();
        gameSt.boss = false;
        boss.state = 1;
    
        createEnemy();
        setTimeout(BossInSight,30000);
    }

    游戏结束后执行:清除定时器,停止自机行为,删除屏幕上的所有弹幕和敌人,弹出主菜单:

    function endGame(){
        gameSt.playing = gameSt.boss = false;//游戏进行和打BOSS状态关闭
    
        clearTimeout(timer.openFire);//先停火
        alert("你的生命值被耗尽,游戏结束");
    
        alert("最终得分:"+gameSt.score);
    
        keySt.toUp = keySt.toDown = keySt.toLeft = keySt.toRight = keySt.toLSpeed = keySt.toFire =  false;//停止移动
        /*下次将这些对象属性进一步细分,以便能够遍历*/
        clearInterval(timer.enemyCreator);
        clearInterval(timer.overflowOperator);
        clearInterval(timer.general);
        clearTimeout(timer.enemy2Creator);
        clearTimeout(timer.releaseBoss);
        clearTimeout(bossAction);
    
        for(item in timer){
            timer[item]=null
        }
        /*删除所有子弹、敌机*/
        $('.enemy1,.enemy2,.bullet,#theBoss,.poolBlt').stop().remove();
        /*重置自机和数据*/
        resetObject(ziji,"250px","300px");
        resetData();
        /*显示主菜单*/
        $('.layout1').show();
    
    }
    
    
    //重置数据
    function resetData(hp,score,graze){
        gameSt.hp = hp || 50;
        gameSt.score = score || 0;
        gameSt.graze = graze || 0;
        $('#hitCount').html(gameSt.hp);
        $('#zijiPos').html(gameSt.graze);
        $('#score').html(gameSt.score);
    }
    //重置对象CSS,传px
    function resetObject(obj,x,y){
        obj.style.left = x ;
        obj.style.top = y;
    
    }

    游戏画面:
    这里写图片描述

    三、游戏物体行为

      开火时显示2根激光柱,并且执行歼灭逻辑(消灭正前方的敌人),为了节约性能,我把自机的武器做成了瞬发,即时用即时判断:

    /*/开火/*/
    function fireUp(){
        eliminate();//歼灭敌人
    
        $('#laser').show();
        $('#laser').fadeOut(100);
    
        if(gameSt.playing){
            setTimeout(fireUp,250);
        }
    
    }
    
    *       敌人被击中判定     */
        function eliminate(){
    
            var px = ziji.offsetLeft+43,py = ziji.offsetTop;//开火判定位置偏移到元素视觉中心
    
            $('.theEnemy').each(function(){
                var ex = this.offsetLeft+30,ey = this.offsetTop;
                if(ex+35>px && ex-35<px && ey<py){
                    gameSt.score+=(100+gameSt.diff*100);
                    explode(0,ex,ey);//在被歼灭的位置绘制爆炸效果
                    $(this).remove();
                }
            });
            /*打BOSS另算*/
            if(gameSt.boss){
                hitTheBoss(px,py)
            }
        }

      zijiOperator是自机实现连续移动和碰撞的核心,每21毫秒判断执行一次,需要注意的是我们希望子弹元素与自机元素判断碰撞的位置是自机元素视觉中心,而非元素锚点位置(元素左上角),所以必须向右和向下偏移一定的位置:

    function zijiOperator() {
        var mvrg = keySt.toLSpeed? 4:10;//低速/高速状态下的移动距离
        if(keySt.toLeft){
            ziji.style.left=ziji.offsetLeft-mvrg+"px";
            ziji.style.backgroundPositionX = "-90px";//自机左右移动会变换贴图样式
        }
        if(keySt.toRight){
            ziji.style.left=ziji.offsetLeft+mvrg+"px";
            ziji.style.backgroundPositionX = "-180px";
        }
        if(keySt.toUp){
            ziji.style.top=ziji.offsetTop-mvrg+"px";
            ziji.style.backgroundPositionX = "0px";
    
        }
        if(keySt.toDown){
            ziji.style.top=ziji.offsetTop+mvrg+"px";
           ziji.style.backgroundPositionX = "0px";
        }
        if(!keySt.toRight && !keySt.toLeft){
            ziji.style.backgroundPositionX = "0px";
        }
        if(keySt.toLSpeed){
            spot.style.visibility = "visible";//低速模式下显示判定点和擦弹圈
        }else{
            spot.style.visibility = "hidden";
        }
    
        if(gameSt.playing){
            gameSt.score ++;
            $('#score').text(gameSt.score);
        }
    
        limit(ziji);//防止自机飞出游戏屏幕
        collision(ziji.offsetLeft+37,ziji.offsetTop+37);//传入自己XY坐标判断与子弹的碰撞,但是需要偏移到元素视觉中心
    
    }

    接下来处理碰撞与防溢出逻辑

    /***********************防溢出**********************************/
    function limit(ziji){/*对象*/
            //上下溢出
            ziji.style.top = ziji.offsetTop<-35?"-35px":ziji.offsetTop>550?"550px":ziji.style.top;
            //左右溢出
            ziji.style.left = ziji.offsetLeft<-35?"-35px":ziji.offsetLeft>550?"550px":ziji.style.left;
        }
    
    
        /************************自机碰撞判定***************************/
        /*
            *通过遍历所有子弹相对自机的距离判断是否碰撞
            *判断区域是圆形
            *擦弹加分
            *中弹减生命,生命为0结束游戏
        */
    function collision(x,y){
    
        $('.bullet').each(function () {
            var bltX = this.style.left.slice(0,-2)
            var bltY = this.style.top.slice(0,-2)
            if( Math.pow((x-bltX),2)+Math.pow((y-bltY),2)<750 ){
            /*擦弹距离根号750*/
                    gameSt.graze++;
                    gameSt.score += parseInt(gameSt.graze/20);
                    $('#zijiPos').text(gameSt.graze);
            }
            if( Math.pow((x-bltX),2)+Math.pow((y-bltY),2)<150 ){
            /*中弹距离约14左右*/
                    gameSt.hp-=10;
                    explode(1,bltX,bltY);//在中弹位置生成视觉效果
                    recycleBullet(this);//删除击中的子弹
                    $('#hitCount').text(gameSt.hp);//更新显示生命值
            if(gameSt.hp<=0)
                    endGame();//gg
            }
        });
      }

    敌人和BOSS的生成,注意下面很多代码都出现了gameSt.diff变量,这个是难度系数对游戏数值的补正

    /*生成敌人*/
    function createEnemy(){
        //生成敌人1和敌人2
        timer.enemyCreator = setInterval(function(){
            if($('.left').length==0 && $('.right').length==0){
                var both = Math.random()>0.1?1:0;//是否两边出飞机
                if(both){
                    drawEnemy1(1,2+gameSt.diff,4500 - gameSt.diff*500);
                    drawEnemy1(0,2+gameSt.diff,4500 - gameSt.diff*500);
                } else {
                /*单边出飞机*/
                    var lr = Math.random()>0.5?1:0;
                    drawEnemy1(lr,3+gameSt.diff*2,4500 - gameSt.diff*500);
                }
    
            }
            timer.enemy2Creator = setTimeout(drawEnemy2,(Math.random()*1000+200))
        },1000)
    }

    敌人1是从屏幕左上或右上飞入的战机,飞到一定距离发射2枚子弹,其中一枚是自机狙,敌机2是从屏幕顶部垂直飞下来的战机,降到一定高度发射开花弹,我们首先构造这两种敌人,在绘制函数中实例化

    //构造横向飞行物
    function Xobject(sign,speed,hp){
        this.ox = sign?-60:620//初始位置(左还是右)
        this.x = sign?660:-90;//结束位置与初始位置相对
        this.oy = parseInt(Math.random()*100);//初始高度
        this.y = parseInt(Math.random()*100);//到达目的地的高度
        this.hp = hp || 10;//这个貌似没用
        this.speed = speed || 3000;
    }
    
    //构造纵向飞行物
    function Yobject(hp,speed){
    
        this.x = Math.random()*500+10 || 300;//随机出现在顶端
        this.oy = -30;
        this.y = -60;
        this.hp = hp || 10;
        this.speed = speed || 1000;
    
    }
    
    //绘制横向飞行物,0为左,1为右
    function drawEnemy1(lr,howmany,speed){
        var obj = new Xobject(lr,speed)//左边还是右边生成
    
        var frag = document.createDocumentFragment()
    
        for(var i = 0; i<howmany ; i++){
            var div = $('<div>');
            div.addClass("theEnemy");
            div.addClass(lr?'enemy1 left':'enemy1 right');
            div.css({
                left:obj.ox + "px",
                top:obj.oy + "px"
            });
            div.appendTo($(frag));
    
        }
        $(frag).appendTo($gameScreen);
    
    
        for(var i = 0; i<howmany; i++){
            /*判断左右。。。。这里还需要再完善*/
            if(lr){
                var eachObj = $('.left:eq('+i+')');
                eachObj.delay(speed/5*i).animate({left: obj.x+'px', top: obj.y+'px'},obj.speed,"linear");
            }
    
            else{
                var eachObj = $('.right:eq('+i+')')
                eachObj.delay(speed/5*i).animate({left: obj.x+'px', top: obj.y+'px'},obj.speed,"linear");
            }
    
        }
    
    }
    //敌人1开火与回收
    function enemy1Operating(){
    
        $('.enemy1').each(function(){
            var obj = $(this);
            var objX = obj.offset().left, objY = obj.offset().top;
    
            /*指定一个随机范围的位置,如果敌人1到达指定位置即开火*/
            var ranL = parseInt(50+Math.random()*10);
            var ranR = parseInt(550-Math.random()*10);
    
            if(objX>ranL && objX<ranR){
    
                if(obj.css('opacity')=="1"){
                    /*发射完毕后试图隐身撤离*/
                    doLaunch(objX,objY+20);//发射直线弹
                    doLaunch(objX,objY+20,1);
                    obj.css('opacity','0.7');
    
                }
            }
    
            if(objX>650 || objX<-80){
                $(this).remove();
            }
    
        })
    }
    
    //敌人2行为
    function drawEnemy2(howmany,speed){
        var obj2 = new Yobject(howmany,speed);
        /*从屏幕顶端随机X坐标下落*/
        var ranY = Math.random()*100+50;
        var div = $('<div>').addClass('enemy2').addClass('theEnemy');
        div.css({
            top: obj2.y + 'px',
            left: obj2.x + 'px'
        });
    
        div.appendTo($gameScreen);
        /*2层动画,先下落,再发射,再向上逃离,并删除*/
        div.animate({top: ranY+'px'},obj2.speed,"linear",function(){
            if(!$(div).is(':hidden')){
    
                blossomLaunch(div[0].offsetLeft+10,div[0].offsetTop+20,"linear",0,1);//发射开花弹
            }
        }).animate({top: '-100px'},obj2.speed,"linear",function(){
            div.remove();
        });
    }

    BOSS是一个血超厚的敌人,随机在屏幕上半部分乱飞,并且发射有规则的弹幕

    /*绘制BOSS*/
    function drawBoss(){
        $('#theBoss').remove();
        var div = $('<div>').attr('id','theBoss');//创建一个BOSS元素
    
        resetObject(div[0],"200px","-200px");//设置BOSS起始位置
        boss.health = 150+100*gameSt.diff;//定义BOSS血量
        div.appendTo($gameScreen);
        $('<div id="bossHealthBar">').appendTo($('#theBoss'));//给BOSS添加生命条
    /*首先让BOSS从初始位置下降到屏幕中*/
        div.animate({top:"50px",left:"200px"},1500,"linear",function () {
    
            blossomLaunch(290,130,"easeOutQuad",0,1,1);//初见杀
            bossAction();//BOSS开始乱跑
        })
    
    
    }
    /*Boss行动*/
    function bossAction() {
        var bossX = Math.floor(Math.random()*400) , bossY = Math.floor(Math.random()*100);//随机BOSS飞行位置
        /*BOSS移动到目的地后才发射弹幕*/
        $('#theBoss').animate({
            top: bossY+"px",
            left: bossX+"px"
        },1000,function () {
            if(gameSt.boss){
            /*根据BOSS形态,分别发射2组不同的弹幕*/
                switch (boss.state){
                    case 1:{
                        blossomLaunch(bossX+90,bossY+80,"linear",1,1+gameSt.diff,$('#theBoss'));//开花弹
                        OutLaunch(gameSt.diff*5);//召唤支援火力
                    };break;
                    case 2:{
                        pentagonStar(ziji.offsetLeft+37,ziji.offsetTop+37,80,5+2*gameSt.diff,1)
                        pentagonStar(ziji.offsetLeft+37,ziji.offsetTop-13,240,5+2*gameSt.diff,2)//用五角星围住玩家
                    };break;
                    default:{
                        blossomLaunch(bossX+90,bossY+80,"linear",1,1+gameSt.diff,$('#theBoss'))//默认是螺旋弹
                    };
                }
            }
        })
        if(gameSt.boss){
            setTimeout(bossAction,3500+gameSt.diff*500);//重复这个函数
        }
    }

    正常状态下的BOSS
    这里写图片描述
    半血线以下的BOSS
    这里写图片描述

    四、弹幕算法

    本游戏中暂时没有拐弯飞行的子弹,所有的子弹都是直线飞行
    核心实现:请求弹幕池,定义初始坐标,用animate发射到目的地坐标去,
    先定义最简单的,单个子弹直线发射函数,自机狙指的是朝玩家当前方向射去的子弹:

    //直线发射,snipe为自机狙
    function doLaunch(x,y,snipe,direct){
    
        var toXY = [];
        var blt1 = $('#danmaku' + requestDanmaku());//请求弹幕池中不在动画序列中的子弹的,返回ID值
    
        /*如果不是自机狙,则子弹落点随机*/
        if(!snipe){
            toXY = [direct?direct:parseInt(Math.random()*800),650];
    
        } else {
            toXY = snipeCoord(x,y);//这里返回的是子弹-玩家连线的延长线终点坐标,终点确保能飞出屏幕外以被子弹回收器回收
    
        }
    
        shootIt(blt1,x,y,toXY[0],toXY[1],2500-gameSt.diff*250,"linear",0);//只要朝着给定的坐标射去就好了
    
    }

    开花弹逻辑(包括螺旋弹)

      落点位置是CircleMatrix使用函数调用后返回的一圈圆形离散坐标,setTimeout不能用for循环实现,我这里根据变量作用域链原理去执行有限次数的setTimeout

    /*
    *开花弹
    *参数:起始X,起始Y坐标,速度曲线,是否螺旋发射,几层圆圈(多为定义螺旋弹的圈数),发射本体是否还存在
    */
    function blossomLaunch(x,y,ease,spiral,lay,byWho){
        var ammo2Speed = 8000-400*gameSt.diff;//飞行速度
        var ammo2Number = parseInt(Math.random()*20+5)*gameSt.diff;//多少颗子弹组成的圆圈
        var mtx = new Array;//声明发射坐标数组
        for(var i=0;i<lay;i++){
            mtx=mtx.concat(CircleMatrix(ammo2Number));//有几层就请求多少圈圆形坐标
        }
        var mtxRest = mtx.length-1;//螺旋发射弹幕调用timeout次数
        /*内部定义螺旋发射弹幕*/
        function spiralLaunch() {
            var bltSpiral = $('#danmaku' + requestDanmaku());
            shootIt(bltSpiral,x,y,mtx[mtxRest][1],mtx[mtxRest][0],ammo2Speed,ease);//发射
            mtxRest--;
            if(mtxRest>=0 && byWho){
            /*如果发射本体不存在了或者子弹发射完毕,螺旋弹幕将不再继续发射*/
                setTimeout(spiralLaunch,10)
            }
        }
        if(spiral){
            spiralLaunch();//螺旋发射
        } else {
            for(var i = mtx.length-1; i>=0; i--){
    
                var blt2 = $('#danmaku' + requestDanmaku());
                shootIt(blt2,x,y,mtx[i][1],mtx[i][0],ammo2Speed,ease)
    
            }//正常发射
        }
    
    }

    CircleMatrix函数:

    function CircleMatrix(num) {/*传入多少颗*/
        var len = num || 20;/*默认20颗*/
        var arr = [];
        for(var i=0; i<len; i++){
            arr.push([1291*Math.sin(2*Math.PI*i/len),1291*Math.cos(2*Math.PI*i/len)]);
        }
        return arr;
    }



    从屏幕外360度随机飞入的自机狙:

    /*外部自机狙*/
    function OutLaunch(num) {
        var delay = Math.floor(Math.random()*500+100);//不同时发射
        var rest = num;
        delayLaunch();
        //内部定义执行的timeout
        function delayLaunch() {
            /*随机一个屏幕外的坐标*/
            var ranX,ranY;
            do{
                ranX = Math.floor(Math.random()*630-15);
                ranY = Math.floor(Math.random()*630-15);
            } while(ranX>-10 && ranX<610 && ranY>-10 && ranY<610)
    
            doLaunch(ranX,ranY,1);//直线发射
            rest--;
            if(rest>0){
                setTimeout(delayLaunch,delay)
            }
        }




    五角星弹幕

    逻辑比之前的复杂,游戏中我选用了反向扩张的五角星:实际上还有另外3种发射方式:

    *五角星*/
    /*
    *起始X,起始Y,宽度,每条边子弹数量,运动模式:
    *1. 正向扩张
    * 2. 反向扩张
    * 3. 自机狙
    * 其他 竖直砸下
    * */
    function pentagonStar(x,y,size,num,mode) {
        var coord = fillPentagon(size,x,y,num);//获取五角星初始坐标
        var coolen = coord.length;
    
        for(var i=0;i<coolen ;i++){
    
            if( Math.pow((ziji.offsetLeft+37-coord[i][0]),2)+Math.pow((ziji.offsetTop+37-coord[i][1]),2)>1000 ){
    
                var blt3 = $('#danmaku' + requestDanmaku());
                var toXY = [];
                switch(mode){
                    case 1:{
                        if(i>=0 && i<coolen/5){
                            toXY[0] = 0;
                            toXY[1] = -650;
                        }else if(i>=coolen/5 && i<coolen*2/5){
                            toXY[0] =  650*Math.tan(Math.PI/5)/// + coord[i][0];
                            toXY[1] = 650;
                        } else if(i>=coolen*2/5 && i<coolen*3/5){
                            toXY[0] = -650;
                            toXY[1] = -650*Math.tan(Math.PI/5)// - coord[i][0];
    
                        } else if(i>=coolen*3/5 && i<coolen*4/5){
                            toXY[0] = 650;
                            toXY[1] = -650*Math.tan(Math.PI/5)// - coord[i][0];
    
                        } else {
                            toXY[0] = -650;
                            toXY[1] = 650*Math.tan(Math.PI/5);
                        }
                        toXY[0] = "+=" + toXY[0];
                        toXY[1] = "+=" + toXY[1];
                    };break;
                    case 2:{
                        if(i>=0 && i<coolen/5){
                            toXY[0] = 0;
                            toXY[1] = 650;
                        }else if(i>=coolen/5 && i<coolen*2/5){
                            toXY[0] =  -650*Math.tan(Math.PI/5) // + coord[i][0];
                            toXY[1] = -650;
                        } else if(i>=coolen*2/5 && i<coolen*3/5){
                            toXY[0] = 650;
                            toXY[1] = 650*Math.tan(Math.PI/5) //+ coord[i][0];
    
                        } else if(i>=coolen*3/5 && i<coolen*4/5){
                            toXY[0] = -650;
                            toXY[1] = 650*Math.tan(Math.PI/5) //+ coord[i][0];
    
                        } else {
                            toXY[0] = 650;
                            toXY[1] = -650*Math.tan(Math.PI/5) //- coord[i][0];
                        }
                        toXY[0] = "+=" + toXY[0];
                        toXY[1] = "+=" + toXY[1];
                    };break;
                    case 3:{
                        toXY = snipeCoord();
                    };break;
                    default:{
                        toXY[0] = "+=0";
                        toXY[1] = "+=650";
                    };
                }
                blt3.hide();//一开始不显示
    
    
                shootIt(blt3,coord[i][0],coord[i][1],toXY[0],toXY[1],5000-400*gameSt.diff,"linear",2500-gameSt.diff*200);//先绑定发射动画
                blt3.delay(i*20).fadeIn(1,function () {});//加入延迟使每颗五角星一个个按轨迹出现
    
            }
    
        }
    
    }

    如何返回一个离散的正五角星坐标?

    需要传入五角星横边中点坐标、五角星的边长和每条边要生成多少枚子弹。先定下五角星五个角的坐标ABCDE,再分别给五条边AD\AC\CE\EB\BD填充一定数量的子弹的坐标:
    五角星坐标公式
    实现原理:

     /* 构造五角星的五个端点坐标    */
     /*传入边长,O点相对于画布X,Y的位置*/
    function Pentagon(a,x,y) {
        var pt = [
            [-a-a*Math.sin(Math.PI/10)+x,y],//-a-a*sin18°,0
            [a+a*Math.sin(Math.PI/10)+x,y],//a+a*sin18°,0
            [-a*Math.cos(Math.PI/5)+x,
                a*Math.cos(Math.PI/5)*(1/Math.tan(Math.PI/10))-a*Math.cos(Math.PI/10)+y],//-a*cos36°,-a*cos36°*cot18°+a*cos18°
            [x,-a*Math.cos(Math.PI/10)+y],//0,a*cos18°
            [a*Math.cos(Math.PI/5)+x,
                a*Math.cos(Math.PI/5)*(1/Math.tan(Math.PI/10))-a*Math.cos(Math.PI/10)+y]//a*cos36°,-a*cos36°*cot18°+a*cos18°
        ];//这里是个[[x1,y1],[x2,y2],[x3,y3],[x4,y4],[x5,y5]]结构的二位数组
    
    
        return pt;//返回这五个点的坐标
    }
    
    /*连线五角星*/
    function  fillPentagon(a,x,y,num) {
        a = a || 120;
        num = num || 7+2*gameSt.diff;
        var pt = Pentagon(a,x,y);//获取五角星端点
        var arr = new Array();
    
        for(var i=0; i<5; i++){
            var j = 0;
    
    
                var len = pt[(i+1)%5][0]-pt[i][0];
                while(Math.abs(j)<Math.abs(len)){
                    arr.push(
                        [pt[i][0]+j,pt[i][1]+j*(pt[(i+1)%5][1]-pt[i][1])/len]
                    )
                        j += len/num;
                }
    
        }
        return arr;//这下返回的就是一个离散五角星点了
    }

    shootIt是将执行的动画序列封装起来,以便不同类型的弹幕调用

    /*参数依次是:要发射的子弹对象,起始横坐标,起始纵坐标,目的地横坐标,目的地纵坐标,飞行速度,速度曲线,延迟发射时间*/
    function shootIt($obj,x,y,toX,toY,speed,ease,delay) {
    
        speed = speed || 2500-gameSt.diff*250;
        ease = ease || "linear"
        $obj.addClass('bullet');
        $obj.css({
            left: x+"px",
            top: y+"px"
        });
    
        function delayShoot() {
            if($obj.attr("title")==2){
                $obj.animate({top: toY + "px", left: toX + "px"},speed,ease);
            }
        }
    
        if(!delay){
            $obj.animate({top: toY + "px", left: toX + "px"},speed,ease);
        } else {
            setTimeout(delayShoot,delay)//不会出现for循环setTimeout问题了
        }
    
    }

    五、事件监听器

    给自机绑定键盘事件

    //Bind KeyBoardEvent
    window.onkeydown = function(event){
    
        var event=event||window.event;//兼容
        var code = event.keyCode || event.which;
        //console.log(event.key)//获取按键名称
        /*开控制台*/
    
        if(code==38 || code == 40){
            event.preventDefault();
        }//防止滚屏
    
        switch(code){
            case 13:{                   /*ENTER*/
                if(!gameSt.playing){
                    gameInit();
                }
            };break;
            case keySt.LS : {keySt.toLSpeed=true; }                 ;break;//You can check Keycode at Keymap.js at this path
            case keySt.LT : {keySt.toLeft=true; }                   ;break;
            case keySt.UP : {keySt.toUp=true; }                     ;break;
            case keySt.RT : {keySt.toRight=true; }                  ;break;
            case keySt.DN : {keySt.toDown=true; }                   ;break;
            // case keySt.FR : {keySt.tofire=true; fireUp() }       ;break;
            case 80       : pauseTheGame()                          ;break;
    
        }
    
    }
    
    
    
    window.onkeyup = function(event){
    
        var event=event||window.event;
        var code = event.keyCode || event.which;
        switch(code){
            case keySt.LS : keySt.toLSpeed=false        ;break;//Release Key to break the loop
            case keySt.LT : keySt.toLeft=false          ;break;
            case keySt.UP : keySt.toUp=false            ;break;
            case keySt.RT : keySt.toRight=false         ;break;
            case keySt.DN : keySt.toDown=false          ;break;
            // case keySt.FR : keySt.toFire = false     ;break;
    
        }
    
    }

    给按钮绑定鼠标点击事件

    /*This is for Mouse-click Test for Creating barrage*/
    /*鼠标点击游戏画面可以测试弹幕*/
    $gameScreen.click(function (e) {
        var X = e.clientX,Y=e.clientY;
        // pentagonStar(X,Y,80,7+2*gameSt.diff,1)
        // pentagonStar(X,Y-50,240,7+2*gameSt.diff,2)
        //OutLaunch(9);
        //blossomLaunch(X,Y,"linear",1,3,1)
        //doLaunch(X,Y,1,0)
    
    })
    
    $('#readme').click(function () {
        $('.layout1').hide();
        $('.layout3').show();
    })
    $('#gameConfig').click(function () {
        $('.layout1').hide();
        $('.layout2').show();
    })
    $('.returnMain').click(function () {
        $('.layout').hide();
        $('.layout1').show();
    })
    
    
    /*      难易度选择   */
    $('.selectDiff').click(function () {
        switch(this.innerText){
            case "简单":gameSt.diff = 0;break;
            case "正常":gameSt.diff = 1;break;
            case "疯狂":gameSt.diff = 2;break;
            default: console.log(this.innerText);break;
        }
        $('#difficult').val(this.innerText);
        $('.currDiff').text($('#difficult').val());
        $('.selectDiff').css({ borderColor:"transparent" });
        $(this).css({ borderColor:"#fff" });
    
    })
    
    /*这个日后开发*/
    $('.keySet').click(function(){
        alert('developing');
    })
    展开全文
  • html5弹幕制作(探索ing)

    千次阅读 2019-03-14 21:24:31
    一、乞丐版的弹幕实现 html &lt;div class="tanmu"&gt; &lt;div class="tanmuContent"&gt; &lt;span class="headImg"&gt;&lt;img src="img/...
  • 用的libgdx引擎写的ACE 弹幕射击游戏设计与开发,包含源代码,可供参考
  • 定位滚动条 $("html,body").animate({scrollTop:$(".middle1").offset().top},... 弹幕 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>marqu...
  • 导出游戏 导出报错解决 学校老师给了个主题是Ten Seconds,所以就写了一个弹幕游戏。 主题就是生存十秒,第一次写了一个完整的小游戏,之前的游戏都没有封面UI什么的,基本只实现了逻辑。 效果预览 选关界面: ...
  • Android游戏开发示例——弹幕+战棋

    千次阅读 多人点赞 2018-11-11 00:14:41
    Android游戏开发示例——弹幕+战棋
  • html5游戏开发-弹幕+仿雷电小游戏demo

    万次阅读 多人点赞 2011-12-01 14:03:29
    游戏使用的是html5的canvas,运行游戏需要浏览器支持html5。本篇文章详细讲解如何用html5来开发一款射击游戏,雷电可以说是射击游戏中的经典,下面就来模仿一下。先看一下游戏截图演示地址...
  • unity弹幕功能实现

    千次阅读 2016-11-17 16:03:32
    近年来直播app和视频app如日中天,在这些app里往往会有一个弹幕功能。实际的项目中肯定是用服务器客户端直接的数据来控制的,这里只在客户端进行测试实现弹幕核心功能。 下面说的就是我是如何实现弹幕功能的。 首先...
  • 因为之前已经做好了固定的模式,所以弹幕的添加就变的很容易了。 这次只要把弹幕函数进行改动就行了。 我们就用shot_bullet_H008来实现洩矢大人的弹幕吧。 【洩矢大人】这个弹幕是东方风神录EX面的最后一个弹幕。 ...
  • 谁能给我讲讲原理——视频弹幕游戏!!

    千次阅读 多人点赞 2014-09-24 18:11:48
    偶然发现一个有趣的视频互动游戏,自己不懂原理,感到十分不可思议,发上来与大家共享!
  • 上一节实现了人物的移动 ...开始画好20个弹幕,全部隐掉,按下射击键,弹幕1的位置设置到人物的位置上,播放弹幕1的缓动动画,再次按下射击键,弹幕2的位置设置到人物的位置上,播放弹幕2的缓动动画,循环...
  • 本文作为开发过程记录用。 目前游戏画面: ...弹幕类 from pygame.sprite import Sprite from pygame import transform import math class Barrage(Sprite): def __init__(self,raiden_setting...
  • unity弹幕效果实现

    2019-12-04 15:58:34
    unity弹幕效果实现 *弹幕会在范围内随机位置出现 *出现频率/间隔可控 *可以动态增加弹幕 ---------- *引用了DOTween插件
  •  bilibili是中国大陆一个动画、游戏相关的弹幕视频分享网站,也被称为哔哩哔哩、B站,其前身为视频分享网站Mikufans。该网站由原AcFun网友“⑨bishi”于2009年6月26日创建。由于AcFun网站在运行时往往不稳定,所以...
  • Unity3D实现弹幕的效果

    千次阅读 2015-06-18 09:08:26
    孙广东 2015.6.15对于逗比的游戏、无厘头、可以让大家吐糟的游戏,如果有弹幕的功能是极好的。使用U5和 UGUI 目前实现的很简陋而已。 /// /// 实现看视频时的弹幕效果 /// public class DanMu : MonoBehaviour...
  • 那些麻烦的设定终于搞定了,那么这次我们就来做各种各样的弹幕数据吧。 一直发射自机狙的弹幕函数001.
  • 通过动画效果简单实现小弹幕功能。主要利用简单的DoubleAnimation制作动画效果。通过动画效果简单实现小弹幕功能。主要利用简单的DoubleAnimation制作动画效果。
  • 游戏的也好,颜值的也好,尤其是在一些热度高的直播间,会发现有一大片的刷屏弹幕,要是人工的话也太累了,现在就用15行的代码给大家带来弹幕水军的制作。 网站:https://www.douyu.com/ 工具:python3; selenium...
  • 射击游戏弹幕的开发http://blog.vckbase.com/knight/archive/2005/03/29/4174.html作者:牛阿牛 2005-02-28nhf_2008@hotmail.comnhf_2003@hotmail.comemail:nhf20021166@163.com留言:希望能给各位游戏开发一点的...
  • 前排广告:技术书籍、视频会员、掘金小册。创宇前端中奖率超高的抽奖活动还有最后几天喔。 导读:本文内容是笔者最近实现的 web 端弹幕组件—— Barrage UI 的一个延伸。...蒙版弹幕 是由知名弹幕视频网站 ...
  • unity3d实现的弹幕功能

    2016-10-12 19:03:19
    使用Unity5.0以上版本配合UGUI和DOTween插件设计的弹幕功能,完整项目!有不懂的问题直接评论问我。3Q
  • 四圣龙神录 官网地址 ...开源仿东方STG-四圣龙神录,带弹幕制作教程60讲,使用C语言+DXLib 非常不错的仿东方的STG,网站上面带制作教程,可以用来参考。 附带60讲教程详细介绍怎样修改他的弹幕STG工
  • 这个库在日本那边很流行,很多同人游戏都是使用这个库开发的。这跟大陆不同,我们更喜欢使用FLASH开发小游戏。   题外话,那个无比恶搞的猫玛丽就是使用DxLib来开发的。   先从小日本那边下载DxLib,官方的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,007
精华内容 802
关键字:

弹幕游戏制作