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
}
}
}

效果还不错,接下来完成丢弃时的特效和控制:
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();
}
}
换武器的功能就做好了。下一步要设计并实现锁定系统:
- 决定锁定的方式
- 设计锁定的视觉表现和实际用途
- 完成自瞄功能的代码

浙公网安备 33010602011771号