目录
战斗开发分析
场景制作与光照烘焙
配置数据字段更新技巧
客户端请求战斗逻辑
服务器副本处理系统
合法性检验与数据更新
战斗逻辑框架介绍
战斗业务系统
框架代码与地图加载
场景地图初始化
主角人物初始化
动画控制器设置
角色控制界面制作技巧
控制界面初始化
操作数据传递
状态机定义
状态机切换
状态管理器注入逻辑实体
控制器注入逻辑实体
状态输入切换测试
ARPG游戏的战斗模式
一般ARPG游戏的战斗模式:
1.请求服务器开始战斗,校验合法则开始加载战斗资源
2.玩家操控角色释放技能
本质是控制角色播放动作
对应时间点播放技能特效与音效
3.计算攻击范围与伤害
4.保留相应运算结果,战斗数据发往服务器校验
5.结果合法则发送关卡奖励
ARPG游戏不涉及战斗部分的网络同步。
建立新的场景,拖拽资源包中的副本场景,然后烘焙灯光。
使用新的map.xml,里面新增power端,用来表示去该区域消耗的体力值。
为了保证战斗消息通信,我们首先在服务器端的GameMsg定义两个消息
#region 副本战斗相关
[Serializable]
public class ReqFBFight
{
public int fbid;
}
[Serializable]
public class RspFBFight
{
public int power;
public int fbid;
}
#endregion
然后在客户端发送战斗信息
//点击副本关卡按钮
public void ClickTaskBtn(int fbid)
{
audioSvc.PlayUIAudio(Constants.UIClickBtn);
//校验体力是否足够
int power = resSvc.GetMapCfg(fbid).power;
if(power > pd.power)
{
GameRoot.AddTips("体力值不足");
}
else
{
netSvc.SendMsg(new GameMsg
{
cmd = (int)CMD.ReqFBFight,
reqFBFight = new ReqFBFight
{
fbid = fbid
}
});
}
}
public void ReqFBFight(MsgPack pack)
{
ReqFBFight data = pack.msg.reqFBFight;
GameMsg msg = new GameMsg
{
cmd = (int)CMD.RspFBFight,
};
PlayerData pd = cacheSvc.GetPlayerDataBySession(pack.session);//用户信息
int power = cfgSvc.GetMapCfg(data.fbid).power;//所需体力
}
完善服务器副本处理系统和添加客户端接收消息处理
public void ReqFBFight(MsgPack pack)
{
ReqFBFight data = pack.msg.reqFBFight;
GameMsg msg = new GameMsg
{
cmd = (int)CMD.RspFBFight,
};
PlayerData pd = cacheSvc.GetPlayerDataBySession(pack.session);
int power = cfgSvc.GetMapCfg(data.fbid).power;
if(pd.fuben < data.fbid)
{
msg.err = (int)ErrorCode.ClientDataError;
}
else if(pd.power < power)
{
msg.err = (int)ErrorCode.LackPower;
}
else
{
pd.power -= power;
if (cacheSvc.UpdatePlayerData(pd.id, pd))
{
RspFBFight rspFBFight = new RspFBFight
{
fbid = data.fbid,
power = pd.power
};
msg.rspFBFight = rspFBFight;
}
else
msg.err = (int)ErrorCode.UpdateDBError;
}
pack.session.SendMsg(msg);
}
public void RspFBFight(GameMsg msg)
{
GameRoot.Instance.SetPlayerDataByFBStart(msg.rspFBFight);
MainCitySys.Instance.maincityWnd.SetWndState(false);
}
添加新的战斗业务系统BattleSys
using UnityEngine;
public class BattleSys : SystemRoot
{
public static BattleSys Instance = null;
public override void InitSys()
{
base.InitSys();
Instance = this;
PECommon.Log("Init BattleSys...");
}
public void StartBattle(int mapid)
{
GameObject go = new GameObject
{
name = "BattleRoot"
};
go.transform.SetParent(GameRoot.Instance.transform);
BattleMgr battleMgr = go.AddComponent();
battleMgr.Init(mapid);
}
}
其中StartBattle方法在处理服务器返回消息时调用
新添加一个战场管理器BattleMgr
using UnityEngine;
public class BattleMgr : MonoBehaviour
{
public void Init(int mapid)
{
}
}
添加几个管理器
其中BattleMgr负责各部分的初始化
using UnityEngine;
public class BattleMgr : MonoBehaviour
{
private ResSvc resSvc;
private StateMgr stateMgr;
private SkillMgr skillMgr;
private MapMgr mapMgr;
public void Init(int mapid)
{
resSvc = ResSvc.Instance;
//初始化各管理器
stateMgr = gameObject.AddComponent();
stateMgr.Init();
skillMgr = gameObject.AddComponent();
skillMgr.Init();
//加载战斗场景地图
MapCfg mapData = resSvc.GetMapCfg(mapid);
resSvc.AsyncLoadScene(mapData.sceneName, () =>
{
//初始化地图数据
});
}
}
完善之前的代码
//初始化地图数据
GameObject map = GameObject.FindGameObjectWithTag("MapRoot");
mapMgr = map.GetComponent();
mapMgr.Init();
map.transform.localPosition = Vector3.zero;
map.transform.localScale = Vector3.one;
Camera.main.transform.position = mapData.mainCamPos;
Camera.main.transform.localEulerAngles = mapData.mainCamRote;
LoadPlayer(mapData);
audioSvc.PlayBGMusic(Constants.BGHuangYe);
private void LoadPlayer(MapCfg mapData)
{
GameObject player = resSvc.LoadPrefab(PathDefine.AAssissnBattleyPlayerPrefab);
player.transform.position = mapData.playerBornPos;
player.transform.localEulerAngles = mapData.playerBornRote;
player.transform.localScale = Vector3.one;
}
给角色添加新的Animator Controller
然后添加之前的PlayerController,用来控制角色移动
创建一个PlayerCtrlWnd脚本挂载在该窗口上,并进行初始化
using PEProtocol;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
public class PlayerCtrlWnd :WindowRoot
{
public Image imgTouch;
public Image imgDirBg;
public Image imgDirPoint;
public Text txtLevel;
public Text txtName;
public Text txtExpPrg;
private float pointDis;
private Vector2 startPos = Vector2.zero;
private Vector2 defaultPos = Vector2.zero;
public Transform expPrgTrans;
protected override void InitWnd()
{
base.InitWnd();
pointDis = Screen.height * 1.0f / Constants.ScreenStandardHeight * Constants.ScreenOPDis;
defaultPos = imgDirBg.transform.position;
SetActive(imgDirPoint, false);
RegisterTouchEvts();
RefreshUI();
}
public void RegisterTouchEvts()
{
OnClickDown(imgTouch.gameObject, (PointerEventData evt) =>
{
startPos = evt.position;
SetActive(imgDirPoint);
imgDirBg.transform.position = evt.position;
});
OnClickUp(imgTouch.gameObject, (PointerEventData evt) =>
{
imgDirBg.transform.position = defaultPos;
SetActive(imgDirPoint, false);
imgDirPoint.transform.localPosition = Vector2.zero;
//MainCitySys.Instance.SetMoveDir(Vector2.zero);
});
OnDrag(imgTouch.gameObject, (PointerEventData evt) =>
{
Vector2 dir = evt.position - startPos;
float len = dir.magnitude;
if (len > pointDis)
{
Vector2 clampDir = Vector2.ClampMagnitude(dir, pointDis);
imgDirPoint.transform.position = startPos + clampDir;
}
else
{
imgDirPoint.transform.position = evt.position;
}
//MainCitySys.Instance.SetMoveDir(dir.normalized);
});
}
public void RefreshUI()
{
PlayerData pd = GameRoot.Instance.PlayerData;
SetText(txtLevel, pd.lv);
SetText(txtName, pd.name);
//expprg
int expPrgVal = (int)(pd.exp * 1.0f / PECommon.GetExpUpValByLv(pd.lv) * 100);
SetText(txtExpPrg, expPrgVal + "%");
int index = expPrgVal / 10;
GridLayoutGroup grid = expPrgTrans.GetComponent();
float globalRate = 1.0F * Constants.ScreenStandardHeight / Screen.height;
float screenWidth = Screen.width * globalRate;
float width = (screenWidth - 180) / 10;
grid.cellSize = new Vector2(width, 7);
for (int i = 0; i < expPrgTrans.childCount; i++)
{
Image img = expPrgTrans.GetChild(i).GetComponent();
if (i < index)
{
img.fillAmount = 1;
}
else if (i == index)
{
img.fillAmount = expPrgVal % 10 * 1.0f / 10;
}
else
{
img.fillAmount = 0;
}
}
}
}
然后在开始战斗的时候调用显示该窗口
首先在BattleMgr中实现最原始的玩家移动和释放技能的方法
public void SetSelfPlayerMoveDir(Vector2 dir)
{
//设置玩家移动
PECommon.Log(dir.ToString());
}
public void ReqReleaseSkill(int index)
{
switch(index)
{
case 0:
ReleaseNormalAtk();
break;
case 1:
ReleaseSkill1();
break;
case 2:
ReleaseSkill2();
break;
case 3:
ReleaseSkill3();
break;
}
}
private void ReleaseNormalAtk()
{
PECommon.Log("Click Nomr Atk");
}
private void ReleaseSkill1()
{
PECommon.Log("Click Skill1");
}
private void ReleaseSkill2()
{
PECommon.Log("Click Skill2");
}
private void ReleaseSkill3()
{
PECommon.Log("Click Skill3");
}
然后在BattleSys中封装一下
最后在PlayerCtrlWnd上通过BattleSys进行调用
public void ClickNormalAtk()
{
BattleSys.Instance.ReqReleaseSkill(0);
}
public void ClickSkill1Atk()
{
BattleSys.Instance.ReqReleaseSkill(1);
}
public void ClickSkill2Atk()
{
BattleSys.Instance.ReqReleaseSkill(2);
}
public void ClickSkill3Atk()
{
BattleSys.Instance.ReqReleaseSkill(3);
}
通过BattleMgr上的StateMgr来管理各种各样的状态,比如移动和待机。 它们都继承一个IState接口
public interface Istate
{
void Enter(EntityBase entity);
void Process(EntityBase entity);
void Exit(EntityBase entity);
}
public class StateIdle : Istate
{
public void Enter(EntityBase entity)
{
}
public void Exit(EntityBase entity)
{
}
public void Process(EntityBase entity)
{
}
}
public class StateMove : Istate
{
public void Enter(EntityBase entity)
{
}
public void Exit(EntityBase entity)
{
}
public void Process(EntityBase entity)
{
}
}
在StateMgr中添加状态装换的方法
private Dictionary fsm = new Dictionary();
public void Init()
{
//初始化状态
fsm.Add(AniState.Idle, new StateIdle());
fsm.Add(AniState.Move, new StateMove());
PECommon.Log("Init StateMgr Done.");
}
public void ChangeStatus(EntityBase entity,AniState targetState)
{
if (entity.currentAniState == targetState)
return;
if(fsm.ContainsKey(targetState))
{
fsm[entity.currentAniState].Exit(entity);
fsm[targetState].Enter(entity);
fsm[targetState].Process(entity);
}
}
逻辑实体类也需要StateMgr用来改变状态
public class EntityBase
{
public AniState currentAniState = AniState.None;
public StateMgr stateMgr = null;
public void Move()
{
stateMgr.ChangeStatus(this, AniState.Move);
}
public void Idle()
{
stateMgr.ChangeStatus(this, AniState.Idle);
}
}
我们在BattleSys加载人物时来让逻辑实体赋值状态管理
逻辑实体基类新增Controller属性
public Controller controller = null;
我们在BattleSys加载人物时来让逻辑实体赋值控制器
PlayerController playerCtrl = player.GetComponent();
playerCtrl.Init();
entitySelfPlayer.controller = playerCtrl;
BattleMgr中修改下列代码进行测试