Unity Day6 切换武器

之前做好了玩家的背包,接下来继续完成玩家补充弹药和切换武器的操作。

对之前的思路进行修改。既然要快节奏就要参考doom,重新对每个栏位添加弹药槽,让玩家在开始或者中途整备可以自由更改选用的装备。

换弹按R,1~7切武器

1:无限备弹的低伤害人类武器
2:小型泛用武器
3:小型近/远距离武器
4:中型泛用武器
5:中型近/远距离武器
6:大型武器
7:大型爆发武器(填满才允许使用)

大概的切换方法就是把当前的武器直接丢了,然后根据要更换的新武器的切换速度构造一个出来
特效:丢出去的武器化作火花消失,新武器先构建描边框架,然后扫描线逐渐造满
如果在切换途中更换别的武器,则也需要一个消失的特效,不过是叠加在没有造完的武器上面;
先给每一种槽位设计一个武器:1:人类突击步枪
2:轻型离子疾速炮
3:轻型霰弹炮
4:中型离子疾速炮
5:中型25mm磁轨炮
6:双联25mm磁轨炮
7:重型离子疾速炮

现根据设计出来的武器对现有的武器类进行修改:
1.将发射点改为数组,每次射击时轮换
2.增加一个后坐力参数,每次射击时使武器模型向后推,增强一下视觉表现

   public void shoot()
   {
       int sbNum = 1;
       for(int i = 0; i < sbNum; i++)
       {
           lastShootFP += 1;
           lastShootFP %= weaponBase.render.FPOffset.Count;
           shootFrom(lastShootFP);
       }
   }
   public void shootFrom(int index)
   {
       ammoLeft -= 1;
       GameObject FPoint=FPoints[index];
       lastShoot= (long)(DateTimeOffset.UtcNow.ToUnixTimeMilliseconds());
       Vector2 dir = FPoint.transform.position - transform.position;
       EntityBullet bullet = EntityBullet.CreateBullet(weaponBase.damage, FPoint.transform.position);
       dir = dir.normalized;
       float ang = Vector2.SignedAngle(Vector2.right, dir);
       ang += UnityEngine.Random.Range(-weaponBase.shootSpread,weaponBase.shootSpread);
       ang = Mathf.Deg2Rad * ang;
       dir = new Vector2((float)Math.Cos(ang), (float)Math.Sin(ang));
       bullet.setVelocity(weaponBase.bullet.speed, dir.normalized);
       bullet.setOwner(owner);
       if(owner is Player)
       {
           Player player = (Player)owner;
           player.addShootEffect();

       }
       ParticleSystem flash_Gun=FPoint.GetComponentInChildren<ParticleSystem>();
       if (flash_Gun != null)
       {
           flash_Gun.Play();
       }
       //AudioSource source=FPoint.GetComponent<AudioSource>();
       //if (source != null)
       //{
       //    source.Play();
       //}
       ///TODO:给这个音效组件加一个管理机制,在播放完之后自动删了
       GameObject obj = new GameObject("FireSound");
       obj.transform.parent = FPoint.transform;
       AudioSource tmpclip = obj.AddComponent<AudioSource>();
       tmpclip.playOnAwake = false;
       tmpclip.clip = FPoint.GetComponent<AudioSource>().clip;
       tmpclip.Play();
   }
   public GameObject Instance()
   {
       GameObject tmp=GameObject.Instantiate(WeaponInstance.WIPrefab);
       WeaponInstance WI= tmp.GetComponent<WeaponInstance>();
       WI.weaponBase = weaponBase;
       WI.ammoLeft = ammoLeft;
       WI.owner= owner;
       WI.Rerend();
       return tmp;
   }
   void Start()
   {
       
   }

   // Update is called once per frame
   void Update()
   {
       
   }
   public void Rerend()
   {
       if (transform == null) return;
       transform.localPosition = Vector3.zero;
       //FPoint = transform.GetChild(0);
       //FPoint.transform.localPosition = new Vector3(
       //    Base.render.FPOffset[0], 
       //    Base.render.FPOffset[1], 
       //    0);     
       transform.DetachChildren();
       FPoints.Clear();
       string resourcePath = Path.Combine("Sound", "Weapons", weaponBase.render.shootSound);
       AudioClip clip = Resources.Load<AudioClip>(resourcePath);
       if (clip == null)
       {
           Player.Log("Audio clip not found in Resources: " + resourcePath);
       }
       averageFPLoc = Vector3.zero;
       for (int i=0;i< weaponBase.render.FPOffset.Count; i++)
       {
           GameObject tmp=GameObject.Instantiate (WeaponInstance.FPPrefab);
           tmp.transform.SetParent(transform);
           tmp.transform.localPosition = new Vector3(
               weaponBase.render.FPOffset[i][0],
               weaponBase.render.FPOffset[i][1],
               0
               );
           tmp.name = String.Format("FiringPosition{0}", i);
           AudioSource fireSound = tmp.GetComponent<AudioSource>();
           fireSound.clip = clip;
           FPoints.Add(tmp);
           averageFPLoc += tmp.transform.localPosition;
       }
       averageFPLoc /= weaponBase.render.FPOffset.Count;
       sprite=GetComponent<SpriteRenderer>();
       Texture2D ta = Resources.Load<Texture2D>(Path.Combine("Image", "Items", "Weapon", weaponBase.id));
       if (ta != null)
       {
           sprite.sprite = Sprite.Create(ta, new Rect(0, 0, ta.width, ta.height), new Vector2(0.5f, 0.5f));
       }
       else
       {
           Player.Log("Can't find Texture Resource for"+ Path.Combine("Image", "Items", "Weapon", weaponBase.id));
       }
       //flash_Gun = FPoint.Find("Flash_Gun").GetComponent<ParticleSystem>();
       
       //Texture2D tex= (Texture2D)Resources.Load(Path.Combine(Application.dataPath,"res","Image","Items","Weapon", Base.id+".png"));
   }

动态创建火力点,并且把后坐力表现参数交由entity处理。接下来完成切换武器的逻辑

考虑如何实现这一整套流程。给武器建立一个状态机,状态分为Preparing,Using,Destroying,根据不同状态进行渲染和流程控制。注意这个状态机不是单例,敌人的武器也要用这套状态机。
根据设计的武器对Using进行细分,分为Available,Charging,Shooting,Colding。Charging是蓄力武器的蓄力阶段,非蓄力武器没有这个状态。根据武器射出子弹类型的不同,各个状态的表现也有所变化,例如激光武器会在Shooting阶段停留至激光的持续时间结束,而射弹武器在进入Shooting后会马上退出到Colding。
WeaponState基类:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class WeaponState 
{
    public enum WeaponStateType
    {
        NONE,
        PREPARING,
        AVAILABLE,
        CHARGING,
        SHOOTING,
        COLDING,
        DESTROYING
    }
    public WeaponStateType type;
    public WeaponInstance instance;
    public long startTime = 0;
    public WeaponState(WeaponInstance weapon)
    {
        type = WeaponStateType.NONE;
        this.instance = weapon;
    }
    public static WeaponState createWeaponState( WeaponStateType type ,WeaponInstance weapon)
    {
        WeaponState res = new WeaponState(weapon);
        switch (type)
        {
            case WeaponStateType.PREPARING:
                return new WeaponStatePreparing(weapon);
        }
        return res;
    }
    public virtual void onEnter()
    {
        startTime= (long)(DateTimeOffset.UtcNow.ToUnixTimeMilliseconds());
    }
    public virtual void onExit()
    {

    }
    public virtual void onUpdate()
    { }
}

WeaponStateMachine基类:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class WeaponStateMachine 
{
    public EntityBase owner;
    public WeaponInstance weapon;
    public WeaponState nowState;
    public WeaponStateMachine(EntityBase owner, WeaponInstance weapon)
    {
        this.owner = owner;
        this.weapon = weapon;
        nowState = WeaponState.createWeaponState(WeaponState.WeaponStateType.NONE, weapon);
    }
    public WeaponState WeaponStateNow { get; private set; }
    public void changeWeaponState(WeaponState next)
    {
        nowState.onExit();
        nowState = next;
        nowState.onEnter();
    }
    public void onUpdate()
    {
        if (nowState != null && nowState.type != WeaponState.WeaponStateType.NONE)
        {
            nowState.onUpdate();
        }
    }

}

接下来把换武器的操作并入ShotManager:

 public Player player;
 public static KeyCode[] weaponKeys =
 {
     KeyCode.Alpha1,
     KeyCode.Alpha2,
     KeyCode.Alpha3,
     KeyCode.Alpha4,
     KeyCode.Alpha5,
     KeyCode.Alpha6,
     KeyCode.Alpha7
 };
 private void Awake()
 {
     player = GetComponentInParent<Player>();
 }
 private void Update()
 {
     if (Input.GetMouseButton(1))
     {
         if (player.inventory.nowWeapon.checkCouldShoot())
         {
             player.inventory.nowWeapon.shoot();
         }
     }
     for (int i = 0; i < weaponKeys.Length; i++)
     {
         if (Input.GetKeyDown(weaponKeys[i]))
         {
             player.inventory.changeWeapon(i);
             break;
         }
     }
 }

后续考虑一下这样性能消耗会不会太高。
不过现在继续制作武器销毁/拿出时的特效。预计的特效是,拿出时像eve那样由扫描线进行构建,销毁时从右到左扫描线销毁,并附带火花的粒子特效。
首先建立材质和ImageShader,然后新建一个WeaponInstanceRender的脚本。
先完成shader,按照功能来讲要把两个功能合并到一个shader里,销毁特效比构造特效先处理。
首先完成构建:考虑需要哪些参数,构建进度,描边颜色,内部未构建的颜色,描边粗细:

Shader "Hidden/WeaponInstanceFX"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _OutlineColor("OutlineColor",Color)=(1,0.6,0,1)
        _OutlineThickness("OutlineThickness",Range(0,1))=0.02
        _InnerUnconstructColor("InnerUnconstructColor",Color)=(1,1,0,1)
        _ConstructProgress("ConstructProgress",Range(0,1))=0.5
        _ConstructLineColor("ConstructLineColor",Color)=(1,0.6,0,1)
        _ConstructLineThick("ConstructLineThick",range(0,0.5))=0.1

    }
    SubShader
    {
        // No culling or depth
        Cull Off ZWrite Off ZTest Always
        Blend SrcAlpha OneMinusSrcAlpha

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }

            sampler2D _MainTex;
            fixed4 _MainTex_TexelSize;
            fixed4 _OutlineColor;
            float _OutlineThickness;
            fixed4 _InnerUnconstructColor;
            float _ConstructProgress;
            fixed4 _ConstructLineColor;
            float _ConstructLineThick;


            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                if(col.a<0.01)
                discard;
                if(_ConstructProgress<1){
                    float2 pixelSize = _MainTex_TexelSize.xy;
                    fixed4 innerOutline = fixed4(0,0,0,0);
                    float outlineStrength = 0;
                    for (int x = -1; x <= 1; x++)
                    {
                        for (int y = -1; y <= 1; y++)
                        {
                            if (x == 0 && y == 0) continue;
                        
                            float2 offset = float2(x, y) * pixelSize * _OutlineThickness * 10;
                            fixed4 sample = tex2D(_MainTex, i.uv + offset);
                            if (col.a > 0.5 && sample.a < 0.5)
                            {
                                outlineStrength = max(outlineStrength, 1 - sample.a);
                            }
                        }
                    }
                    if(outlineStrength>0&&i.uv.x>_ConstructProgress){
                        innerOutline=_OutlineColor;
                        innerOutline.a*=outlineStrength;
                        col.rgb=lerp(col.rgb,innerOutline.rgb,innerOutline.a);
                    }
                    else{
                        if(abs(i.uv.x-_ConstructProgress)<=_ConstructLineThick/2){
                            col=_ConstructLineColor;
                        }
                        if(i.uv.x>_ConstructProgress){
                            col.rgb=lerp(col.rgb,_InnerUnconstructColor.rgb,0.8);
                            col.a=(1.0-i.uv.x+_ConstructProgress)/(1.0-_ConstructProgress)*_InnerUnconstructColor.a;
                        }
                         
                     }
                    }
                    
                
                return col;
            }
            ENDCG
        }
    }
}

image
效果还不错,接下来完成丢弃时的特效和控制:

if(_DestructProgress>0){
     if(abs(i.uv.x-(1-_DestructProgress))<=_DestructLineThick/2){
            col=_DestructLineColor;
        }
    if(1-i.uv.x<=_DestructProgress){
            col.a=0;
        }
    }

一样是一个简单的扫描线
对inventory完善换弹等操作:

 public void addAmmo(int cat, int x)
 {
     ItemBase item = ItemManager.getItemBase(weapons[cat]);
     if (item == null) return;
     if (!(item is WeaponBase)) return;
     WeaponBase weapon = (WeaponBase)item;
     ammo[cat] += x;
     ammo[cat] = Math.Min(ammo[cat], weapon.maxStorangeAmmo);
 }
 public void setNowWeapon(WeaponInstance weapon) {
     nowWeapon = weapon;
 }
 public WeaponInstance getNowWeapon()
 {
     return nowWeapon;
 }
 public void changeWeapon(int target)
 {
     if ("".Equals(weapons[target])) return;
     WeaponInstance instance = new WeaponInstance(weapons[target]);
     ammo[nowWeapon.weaponBase.category] += nowWeapon.ammoLeft;
     int loaded = Math.Min(ammo[target], instance.weaponBase.capacity);
     ammo[target]-=loaded;
     nowWeapon.drop();
     nowWeapon = instance;
     nowWeapon.owner = owner;
     nowWeapon.setAmmoLeft(loaded);
     if(owner is Player)
     {
         ((Player)owner).RerendEquipment();
     }
 }

换武器的功能就做好了。下一步要设计并实现锁定系统:

  • 决定锁定的方式
  • 设计锁定的视觉表现和实际用途
  • 完成自瞄功能的代码
posted @ 2025-09-08 17:27  国土战略局特工  阅读(22)  评论(0)    收藏  举报