Menu

第一楼架构设计之基类设计

2018年6月24日 - 游戏开发

总体基类一共分为三种,EntityBase SkillBase MonsterBase Bullet

当时是想人物继承自EntityBase使代码的附庸,怪物继承自MonsterBase ,人物发射子弹的功能Bullet类

EntityBase类设计

using Attributes;
using Game.Path;
using Skill;
using Skill.Formula;
using Skill.Formula.Json;
using System;
using System.Collections.Generic;
using System.IO;
using UnityEngine;

namespace Entity
{
[Flags]
internal enum EntityStatus : uint
{
Stand = 0u,
Forward = 1u << 0,
Backward = 1u << 1,
Sprint = 1u << 2,

//Midair
FirstJump = 1u << 3,

SecondJump = 1u << 4,

Downed = 1u << 5,
TakeHit = 1u << 6,
Hold = 1u << 7,

Hurting = 1u << 8,//受伤
Floor = 1u<<9,//躺在地上

Grounded = 1u << 31,
Midair = FirstJump | SecondJump,
Movement = Stand | Sprint | Backward | Forward | Midair
}

[RequireComponent(typeof(Animator))]
//实体基类
internal abstract class EntityBase : MonoBehaviour
{
public int MaxHealthPoint = 100;//血量
public int MaxShield = 100;//法强

private SkillManager m_skillManager;//单列

public SkillManager SkillManager
{
get { return m_skillManager; }
protected set { m_skillManager = value; }
}

[SerializeField, ReadOnly]
private bool m_isSurvival = true;

public bool IsSurvival { get { return m_isSurvival; } }

[SerializeField, ReadOnly]
private EntityStatus m_status = EntityStatus.Stand;

public EntityStatus Status { get { return m_status; } }

#region Attribute

[SerializeField, ReadOnly]
private int m_healthPoint;

public int HealthPoint { get { return m_healthPoint; } }

[SerializeField, ReadOnly]
private int m_shield;

public int Shield { get { return m_shield; } }

public int ATK = 1;

public PathNode LastPassNode = null;
public PathNode.PathLink SelectLink = null;
[NonSerialized]
public Transform ParentTransform;

private Vector3 m_impactForce;//物理力
public Vector3 Impact { get { return m_impactForce; } }

#endregion Attribute

#region Event

public delegate void OnHealthPointChangedEvt(EntityBase self, int last_hp, int hp);

public event OnHealthPointChangedEvt OnHealthPointChanged;

public delegate void OnShieldChangedEvt(EntityBase self, int last_shield, int Shield);

public event OnShieldChangedEvt OnShieldChanged;

public delegate void OnHurtEvt(EntityBase self, int hp);

public event OnHurtEvt OnHurt;

public delegate void OnRecoverEvt(EntityBase self, int hp);

public event OnRecoverEvt OnRecover;

public delegate void OnDeadEvt(EntityBase self);

public event OnDeadEvt OnDead;
/// <summary>
/// 状态改变
/// </summary>
/// <param name=”lastStatus”></param>
/// <param name=”currentStatus”></param>
public delegate void OnStatusChangedEvt(EntityStatus lastStatus, EntityStatus currentStatus);

public event OnStatusChangedEvt OnStatusChanged;

#endregion Event

protected abstract string[] HurtAnimationNames { get; }

protected Animator m_animator;
protected CharacterController m_controller;
public static LinkedList<EntityBase> AllEntitys = new LinkedList<EntityBase>();

public bool IsGrounded
{
get
{
return m_controller.isGrounded;
}
}

protected virtual void Awake()
{
m_animator = GetComponent<Animator>();
m_controller = GetComponentInParent<CharacterController>();
m_skillManager = new SkillManager(this, m_animator);
ParentTransform = transform.parent;

m_healthPoint = MaxHealthPoint;
m_shield = MaxShield;

AllEntitys.AddLast(this);
///
OnHealthPointChanged += (self, last_hp, hp) =>
{
if (last_hp < hp && OnRecover != null)
OnRecover(self, hp);
else if (last_hp > hp && OnHurt != null)
OnHurt(self, hp);

if (hp == 0)
{
m_isSurvival = false;
if (OnDead != null)
{
OnDead(self);
}
}
};

/*OnStatusChanged += (l, c) =>
{
if (!HasStatusFlag(l, EntityStatus.Grounded) && HasStatusFlag(c, EntityStatus.Grounded))
LastPassNode = PathManager.Instance.FindNearNode(this);
};*/
}

protected virtual void OnDestroy()
{
AllEntitys.Remove(this);
}

#region 初始化技能公式

protected virtual void Start()
{
if (!Directory.Exists(“json”))
Directory.CreateDirectory(“json”);
string json_file = “json/” + GetType().Name + “.json”;

if (!File.Exists(json_file))
{
FormulaConfigCollection tmp = new FormulaConfigCollection();
tmp.SkillList = new List<FormulaConfigJson>();
foreach (var skill in SkillManager)
{
tmp.SkillList.Add(new FormulaConfigJson()
{
SkillName = skill.Skill.GetType().Name,
FormulaName = “CombatFormulaNormal”,
Arguments = new List<ArgumentPair>()
});
}
File.WriteAllText(json_file, JsonUtility.ToJson(tmp, true));
}

FormulaConfigCollection obj = JsonUtility.FromJson<FormulaConfigCollection>(File.ReadAllText(json_file));
foreach (var skill in SkillManager)
{
foreach (var arg in obj.SkillList)
{
if (arg.SkillName == skill.Skill.GetType().Name)
{
Dictionary<string, float> dic = new Dictionary<string, float>();
foreach (var a in arg.Arguments)
{
dic.Add(a.Var, a.Value);
}
var f = Activator.CreateInstance(Type.GetType(“Skill.Formula.” + arg.FormulaName), dic) as IFormula;
(skill.Skill as SkillBase).SetFormula(f);
}
}
}
}

#endregion 初始化技能公式

/// <summary>
/// 恢复实体HealthPoint
/// </summary>
/// <param name=”hp”>如果hp+HealthPoint大于MaxHealthPoint则HealthPoint=MaxHealthPoint</param>
public void RecoverHealthPoint(int hp)
{
int new_hp = Mathf.Min(m_healthPoint + hp, MaxHealthPoint);
if (new_hp != m_healthPoint)
if (OnHealthPointChanged != null)
OnHealthPointChanged(this, m_healthPoint, new_hp);
m_healthPoint = new_hp;
}

/// <summary>
/// 恢复实体HealthPoint值到hp
/// </summary>
/// <param name=”hp”>如果hp大于MaxHealthPoint则HealthPoint=MaxHealthPoint</param>
public void RecoverHealthPointTo(int hp)
{
int new_hp = Mathf.Min(hp, MaxHealthPoint);
if (new_hp != m_healthPoint)
if (OnHealthPointChanged != null)
OnHealthPointChanged(this, m_healthPoint, new_hp);
m_healthPoint = new_hp;
}

/// <summary>
/// 恢复实体Shield
/// </summary>
/// <param name=”hp”>如果hp+HealthPoint大于MaxHealthPoint则HealthPoint=MaxHealthPoint</param>
public void RecoverShieldPoint(int shield)
{
int new_shield = Mathf.Min(m_shield + shield, MaxShield);
if (new_shield != m_shield)
if (OnShieldChanged != null)
OnShieldChanged(this, m_shield, new_shield);
m_shield = new_shield;
}

/// <summary>
/// 恢复实体HealthPoint值到hp
/// </summary>
/// <param name=”hp”>如果hp大于MaxHealthPoint则HealthPoint=MaxHealthPoint</param>
public void RecoverShieldPointTo(int shield)
{
int new_shield = Mathf.Min(shield, MaxShield);
if (new_shield != m_shield)
if (OnShieldChanged != null)
OnShieldChanged(this, m_shield, new_shield);
m_shield = new_shield;
}

/// <summary>
/// 消耗实体Shield
/// </summary>
/// <param name=”shield”>消耗的Shield</param>
public void CostShield(int shield)
{
int new_shield = Mathf.Max(m_shield – shield, 0);
if (new_shield != m_shield)
if (OnShieldChanged != null)
OnShieldChanged(this, m_shield, new_shield);
m_shield = new_shield;
}

/// <summary>
/// 造成伤害,如果在无敌时间内则不造成伤害,每次成功造成伤害
/// 会设置由InvincibleTime指定的无敌时间
/// </summary>
/// <param name=”damage”>造成的伤害</param>
public void TakeDamage(int damage)
{
SetStatusFlag(EntityStatus.TakeHit);

int new_hp = Mathf.Max(0, m_healthPoint – damage);
if (new_hp != m_healthPoint)
if (OnHealthPointChanged != null)
OnHealthPointChanged(this, m_healthPoint, new_hp);
m_healthPoint = new_hp;
ResetStatusFlag(EntityStatus.TakeHit);
}
/// <summary>
/// 状态转换
/// </summary>
/// <param name=”status”></param>
public void SetStatusFlag(EntityStatus status)
{
var old_status = m_status;
m_status |= status;
if (m_status != old_status)
if (OnStatusChanged != null)
OnStatusChanged(old_status, m_status);
}
/// <summary>
/// 重置当前状态
/// </summary>
/// <param name=”status”></param>
public void ResetStatusFlag(EntityStatus status)
{
var old_status = m_status;
m_status &= ~status;
if (m_status != old_status)
if (OnStatusChanged != null)
OnStatusChanged(old_status, m_status);
}
/// <summary>
/// 清除状态
/// </summary>
public void ClearStatusFlag()
{
if (OnStatusChanged != null)
OnStatusChanged(m_status, EntityStatus.Stand);
m_status = EntityStatus.Stand;
}
/// <summary>
///
/// </summary>
/// <param name=”status”></param>
/// <returns></returns>
public bool HasStatusFlag(EntityStatus status)
{
return (m_status & status) == status;
}
/// <summary>
///
/// </summary>
/// <param name=”src”></param>
/// <param name=”status”></param>
/// <returns></returns>
public static bool HasStatusFlag(EntityStatus src, EntityStatus status)
{
return (src & status) == status;
}

private int m_velocityPriority = 0;
private Vector3 m_moveVelocity = Vector3.zero;
private Vector3 m_jumpVelocity = Vector3.zero;
private Vector3 m_gravityVelocity = Vector3.zero;
private Vector3 m_Velocity = Vector3.zero;

protected Vector3 Velocity { get { return m_Velocity; } }

public float Gravity = 30.0f;

private int m_groundCount = 0;
private const int MAX_COUNT = 5;

protected virtual void Update()
{
SkillManager.Update(Status);
m_impactForce = Vector3.zero;

m_gravityVelocity += Vector3.down * Gravity * Time.deltaTime;
m_Velocity = m_moveVelocity + m_jumpVelocity;

m_controller.Move((m_Velocity + m_gravityVelocity) * Time.deltaTime);

//if (SelectLink.CurveFunc != null&&m_moveVelocity!=Vector3.zero)
//{
// float t = LastPassNode.GetTFormPosition(this, SelectLink.NextNode);
// var p = SelectLink.CurveFunc(t);
// p.y = ParentTransform.position.y;
// ParentTransform.position = p;
//}

if (m_controller.isGrounded)
{
m_groundCount = 0;
m_jumpVelocity = Vector3.zero;
m_gravityVelocity = Vector3.zero;
SetStatusFlag(EntityStatus.Grounded);
}
else
{
//去除下坡时的落地状态抖动
if (m_groundCount == MAX_COUNT)
{
ResetStatusFlag(EntityStatus.Grounded);
m_groundCount = 0;
}
m_groundCount++;
}

if (CheckHurtStatus())
SetStatusFlag(EntityStatus.Hurting);
else
ResetStatusFlag(EntityStatus.Hurting);
}
/// <summary>
/// 检查受伤状态
/// </summary>
/// <returns></returns>
private bool CheckHurtStatus()
{
foreach(var s in HurtAnimationNames)
{
var info=m_animator.GetCurrentAnimatorStateInfo(0);
if (info.IsName(s))
return true;

info = m_animator.GetNextAnimatorStateInfo(0);
if (info.IsName(s))
return true;
}
return false;
}

protected virtual void FixedUpdate()
{

}

public void Jump(float h)
{
m_gravityVelocity = Vector3.zero;
m_jumpVelocity = Vector3.up * Mathf.Sqrt(2 * h * Gravity);
ResetStatusFlag(EntityStatus.Grounded);
}

/// <summary>
/// 移动
/// </summary>
/// <param name=”x”>米每秒</param>
public void Move(float x,int priority = 0, bool turn = true)
{
float absx = Mathf.Abs(x);

#region 模型方向控制
if(turn)
transform.LookAt(transform.position + ParentTransform.forward * x);

#endregion 模型方向控制

#region Entity状态操作

if (absx > 0.5)
SetStatusFlag(EntityStatus.Forward);
else
ResetStatusFlag(EntityStatus.Forward);

#endregion Entity状态操作

Vector3 dir = ParentTransform.forward;

if (LastPassNode != null && SelectLink != null)
dir = SelectLink.DirectionFunc(this);
dir.y = 0;
dir.Normalize();

//根据节点方向改变Entity方向
if (Vector3.Dot(dir, ParentTransform.forward) >= 0)
ParentTransform.LookAt(ParentTransform.position + dir);
else
ParentTransform.LookAt(ParentTransform.position – dir);

if (priority<m_velocityPriority) return;

if (absx <= 10e-6)
{
m_velocityPriority = 0;
m_moveVelocity = Vector3.zero;
}
else
{
m_velocityPriority = priority;
if (turn)
m_moveVelocity = transform.forward * absx;
else
m_moveVelocity = transform.forward * x;
}
}

/// <summary>
/// 设置冲击力(下一帧清除)
/// </summary>
/// <param name=”a”></param>
public void SetImpact(Vector3 a)
{
m_impactForce = a;
}

public virtual void OnPassNode(PathNode node) { }
}
}

SkillBase设计

using Attributes;
using Game.Path;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Entity.Monster
{
class MonsterBase : EntityBase
{
public AudioClip RunSoundClip;

public float MoveSpeed = 5.0f;//移动速度
public float IdleTime = 0.7f;
public float CanAttackDistance = 1.5f;//攻击距离
public float SeePlayerDistance = 5f;//看到人物的距离
public float ChaseLostDistance = 10f;//巡逻丢失的距离

public PathNode StartNode;
public PathNode EndNode;

protected FSMSystem FSM;
private bool _stop_fsm = false;
[ReadOnly]
public string _fsm_status_debug;

private AudioSource m_source;

private Material m_mainMaterial;

private float _fade_step = 0.05f;
//readonly内部类调用
private static readonly string[] s_hurt_animations = new string[] { “hurt1”, “hurt2”, “hurt3”, “hurt4”, “hurt5”, “hurt6”, “hurt7” };
protected override string[] HurtAnimationNames
{
get
{
return s_hurt_animations;
}
}
/// <summary>
/// 受伤攻击
/// </summary>
protected override void Awake()
{
base.Awake();
OnHurt += (self, hp) =>
{
var dir = (Player.PlayerEntity.Instance.transform.position – transform.position).normalized;
if (Vector3.Dot(dir, transform.forward) < 0.0f)
self.transform.LookAt(transform.position – transform.forward);//受击转向,有BUG

if (self.Impact.y > 0.5f)//浮空控制(丑,但没办法了)
{
var info = m_animator.GetCurrentAnimatorStateInfo(0);
if (info.IsName(“hurt5”))
{
if (info.normalizedTime > 0.58f)
{
m_animator.SetTrigger(“hurt7”);
}
else
{
m_animator.SetTrigger(“hurt6”);
}
}
else if(!(info.IsName(“hurt5”)|| info.IsName(“hurt6”)|| info.IsName(“hurt7”)))
{
m_animator.SetTrigger(“hurt5”);
}
return;
}
else if(self.Impact.y<-0.5)
{
m_animator.SetTrigger(“hurt7”);
}

// z∈(0.0,0.5] hurt1
// z∈(0.5,1.0] hurt2
// z∈(1.0,1.5] hurt3
if (GameMath.InRange(self.Impact.z,0.0f,0.5f))
{
m_animator.SetTrigger(“hurt1”);
return;
}
else if (GameMath.InRange(self.Impact.z, 0.5f, 1.0f))
{
m_animator.SetTrigger(“hurt2”);
return;
}
else if (GameMath.InRange(self.Impact.z,1.0f,1.5f))
{
m_animator.SetTrigger(“hurt3”);
return;
}
};

OnDead += (self) =>
{
ResetAnimator(m_animator);

m_animator.SetTrigger(“dead”);
_stop_fsm = true;
InvokeRepeating(“DeadFade”,1.5f,_fade_step);
};
m_mainMaterial = GetComponentInChildren<SkinnedMeshRenderer>().material;
FSM = new FSMSystem(this.gameObject);
m_source = GetComponent<AudioSource>();
}
/// <summary>
/// 重置动画
/// </summary>
/// <param name=”ani”></param>
private static void ResetAnimator(Animator ani)
{
foreach (var p in ani.parameters)
{
switch (p.type)
{
case AnimatorControllerParameterType.Bool:
ani.SetBool(p.name, false);
break;
case AnimatorControllerParameterType.Float:
ani.SetFloat(p.name, 0.0f);
break;
case AnimatorControllerParameterType.Int:
ani.SetInteger(p.name, 0);
break;
case AnimatorControllerParameterType.Trigger:
ani.ResetTrigger(p.name);
break;
}
}
}

protected override void Start()
{
ParentTransform.position = new Vector3(StartNode.transform.position.x,ParentTransform.position.y, StartNode.transform.position.z);
var path=PathManager.Instance.FindPath(StartNode,EndNode);
if (path.Length > 1)
{
var link=path[0].GetLink(path[1]);
transform.LookAt(transform.position + link.NextNodeDir);
}
base.Start();
}
/// <summary>
/// 死亡颜色变化
/// </summary>
private void DeadFade()
{
if (m_mainMaterial.color.a > 0.0f)
//攻击颜色变化
m_mainMaterial.color = new Color(m_mainMaterial.color.r, m_mainMaterial.color.g, m_mainMaterial.color.b, m_mainMaterial.color.a – _fade_step);
else
Destroy(ParentTransform.gameObject);
}

protected override void Update()
{
_fsm_status_debug = FSM.CurrentState.ToString();
base.Update();
}

protected override void FixedUpdate()
{
base.FixedUpdate();
if(!_stop_fsm)
FSM.Update(this.transform.parent.gameObject, m_animator);
}

public override void OnPassNode(PathNode node)
{
float a = 0.0f;

foreach (var link in node.ConnectLinks)
{
float cos = Vector3.Dot(link.NextNodeDir, transform.forward);
if (cos > a)
{
a = cos;
SelectLink = link;
}
}
}

private void PlayRunSound()
{
m_source.PlayOneShot(RunSoundClip, 1.0f);
}
}
/// <summary>
///
/// </summary>
static class GameMath
{
public static bool InRange(float x,float a,float b)
{
return x>a && x < b;
}
}
}

 

Bullet子弹类的代码设计

using Entity;
using Skill;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Bullet
{

[RequireComponent(typeof(Rigidbody))]
internal class BulletBase : MonoBehaviour
{

public ISkillLongDistanceable Skill;
public EntityBase Shooter;

protected virtual void OnCollisionEnter(Collision other)
{
if (Skill != null)
{
if (other.collider.tag == “Monster”)
Skill.OnBulletHit(this, Shooter, other.collider.GetComponentInChildren<EntityBase>());
}
}
}

static class ShotHelper
{
/// <summary>
/// 射出子弹
/// </summary>
/// <param name=”shooter”>射击者</param>
/// <param name=”bullet_prefab”>子弹预制体</param>
/// <param name=”skill”>技能</param>
/// <param name=”force”>子弹初始力</param>
public static void Shot(this EntityBase shooter, Vector3 shoot_position, GameObject bullet_prefab,
ISkillLongDistanceable skill, Vector3 velocity)
{
var obj = GameObject.Instantiate(bullet_prefab, shoot_position, Quaternion.identity);
var bullet = obj.GetComponent<BulletBase>();
bullet.Skill = skill;
bullet.Shooter = shooter;
bullet.GetComponent<Rigidbody>().velocity= velocity;
}
}
}

 

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注

%d 博主赞过: