任务32:前端-游戏交互操作控制,抢地主出牌等
前端部分
本节增加了交互操作需要的界面资源,下载后放到\Assets\Bundles\Landlords
PromptButton是获得出牌提示按钮
PlayButton与DiscardButton是出牌与放弃出牌按钮
GrabButton与DisgrabButton是抢地主与放弃抢地主按钮
增加游戏操控交互组件
没什么难理解的,交互操控的服务端请求都在LandInteractionComponent组件中。
LandInteractionComponent
\Assets\Model\Landlords\LandUI\LandInteraction\LandInteractionComponent.cs
using UnityEngine; using UnityEngine.UI; using ETModel; using System.Collections.Generic; namespace ETModel { [ObjectSystem] public class LandInteractionComponentAwakeSystem : AwakeSystem<LandInteractionComponent> { public override void Awake(LandInteractionComponent self) { self.Awake(); } } public class LandInteractionComponent : Component { private Button playButton; //出牌 private Button promptButton; //提示 private Button discardButton; //不出 private Button grabButton; //抢地主 private Button disgrabButton; //不抢 private List<Card> currentSelectCards = new List<Card>(); public bool IsFirst { get; set; } public void Awake() { ReferenceCollector rc = this.GetParent<UI>().GameObject.GetComponent<ReferenceCollector>(); playButton = rc.Get<GameObject>("PlayButton").GetComponent<Button>(); promptButton = rc.Get<GameObject>("PromptButton").GetComponent<Button>(); discardButton = rc.Get<GameObject>("DiscardButton").GetComponent<Button>(); grabButton = rc.Get<GameObject>("GrabButton").GetComponent<Button>(); disgrabButton = rc.Get<GameObject>("DisgrabButton").GetComponent<Button>(); //绑定事件 playButton.onClick.Add(OnPlay); promptButton.onClick.Add(OnPrompt); discardButton.onClick.Add(OnDiscard); grabButton.onClick.Add(OnGrab); disgrabButton.onClick.Add(OnDisgrab); //默认隐藏UI playButton.gameObject.SetActive(false); promptButton.gameObject.SetActive(false); discardButton.gameObject.SetActive(false); grabButton.gameObject.SetActive(false); disgrabButton.gameObject.SetActive(false); } public override void Dispose() { if(this.IsDisposed) { return; } base.Dispose(); Game.Scene.GetComponent<ResourcesComponent>()?.UnloadBundle($"{LandUIType.LandInteraction}.unity3d"); } /// <summary> /// 选中卡牌 /// </summary> /// <param name="card"></param> public void SelectCard(Card card) { currentSelectCards.Add(card); } /// <summary> /// 取消选中卡牌 /// </summary> /// <param name="card"></param> public void CancelCard(Card card) { currentSelectCards.Remove(card); } /// <summary> /// 清空选中卡牌 /// </summary> public void Clear() { currentSelectCards.Clear(); } /// <summary> /// 开始抢地主 /// </summary> public void StartGrab() { grabButton.gameObject.SetActive(true); disgrabButton.gameObject.SetActive(true); } /// <summary> /// 开始出牌 /// </summary> public void StartPlay() { playButton.gameObject.SetActive(true); promptButton.gameObject.SetActive(!IsFirst); discardButton.gameObject.SetActive(!IsFirst); } /// <summary> /// 结束抢地主 /// </summary> public void EndGrab() { grabButton.gameObject.SetActive(false); disgrabButton.gameObject.SetActive(false); } /// <summary> /// 结束出牌 /// </summary> public void EndPlay() { playButton.gameObject.SetActive(false); promptButton.gameObject.SetActive(false); discardButton.gameObject.SetActive(false); } /// <summary> /// 出牌 /// </summary> private async void OnPlay() { CardHelper.Sort(currentSelectCards); Actor_GamerPlayCard_Req request = new Actor_GamerPlayCard_Req(); foreach(var a in currentSelectCards) { request.Cards.Add(a); } Actor_GamerPlayCard_Back response = (Actor_GamerPlayCard_Back)await SessionComponent.Instance.Session.Call(request); //出牌错误提示 LandlordsGamerPanelComponent gamerUI = LandRoomComponent.LocalGamer.GetComponent<LandlordsGamerPanelComponent>(); if (response.Error == ErrorCode.ERR_PlayCardError) { gamerUI.SetPlayCardsError(); } } /// <summary> /// 提示 /// </summary> private async void OnPrompt() { Actor_GamerPrompt_Req request = new Actor_GamerPrompt_Req(); Actor_GamerPrompt_Back response = (Actor_GamerPrompt_Back)await SessionComponent.Instance.Session.Call(request); HandCardsComponent handCards = LandRoomComponent.LocalGamer.GetComponent<HandCardsComponent>(); //清空当前选中 while (currentSelectCards.Count > 0) { Card selectCard = currentSelectCards[currentSelectCards.Count - 1]; handCards.GetSprite(selectCard).GetComponent<HandCardSprite>().OnClick(null); } //自动选中提示出牌 if (response.Cards != null) { for (int i = 0; i < response.Cards.Count; i++) { handCards.GetSprite(response.Cards[i]).GetComponent<HandCardSprite>().OnClick(null); } } } /// <summary> /// 不出 /// </summary> private void OnDiscard() { SessionComponent.Instance.Session.Send(new Actor_GamerDontPlayCard_Ntt()); } /// <summary> /// 抢地主 /// </summary> private void OnGrab() { SessionComponent.Instance.Session.Send(new Actor_GamerGrabLandlordSelect_Ntt() { IsGrab = true }); } /// <summary> /// 不抢 /// </summary> private void OnDisgrab() { SessionComponent.Instance.Session.Send(new Actor_GamerGrabLandlordSelect_Ntt() { IsGrab = false }); } } }
LandInteractionFactory
\Assets\Model\Landlords\LandUI\LandInteraction\LandInteractionFactory.cs
using ETModel; using System; using UnityEngine; namespace ETModel { public class LandInteractionFactory { public static UI Create(string type, UI parent) { ResourcesComponent resourcesComponent = ETModel.Game.Scene.GetComponent<ResourcesComponent>(); resourcesComponent.LoadBundle($"{type}.unity3d"); GameObject prefab = (GameObject)resourcesComponent.GetAsset($"{type}.unity3d", $"{type}"); GameObject interaction = UnityEngine.Object.Instantiate(prefab); interaction.layer = LayerMask.NameToLayer("UI"); UI ui = ComponentFactory.Create<UI, GameObject>(interaction); parent.Add(ui); ui.GameObject.transform.SetParent(parent.GameObject.transform, false); ui.AddComponent<LandInteractionComponent>(); return ui; } } }
LandRoomComponent组件中添加Interaction
\Assets\Model\Landlords\LandUI\LandRoom\LandRoomComponent.cs
public LandInteractionComponent interaction; public LandInteractionComponent Interaction { get { if (interaction == null) { UI uiRoom = this.GetParent<UI>(); UI uiInteraction = LandInteractionFactory.Create(LandUIType.LandInteraction, uiRoom); interaction = uiInteraction.GetComponent<LandInteractionComponent>(); } return interaction; } }
添加 HandCardSprite 使手牌可以交互
这个脚本挂在手牌元件上,这样所有手牌就可以点选和取消选择了
\Assets\Model\Landlords\Other\HandCardSprite.cs
using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; using UnityEngine.EventSystems; namespace ETModel { public class HandCardSprite : MonoBehaviour { public Card Poker { get; set; } private bool isSelect; void Start() { EventTrigger eventTrigger = gameObject.AddComponent<EventTrigger>(); eventTrigger.triggers = new List<EventTrigger.Entry>(); EventTrigger.Entry clickEntry = new EventTrigger.Entry(); clickEntry.eventID = EventTriggerType.PointerClick; clickEntry.callback = new EventTrigger.TriggerEvent(); clickEntry.callback.AddListener(new UnityAction<BaseEventData>(OnClick)); eventTrigger.triggers.Add(clickEntry); } public void OnClick(BaseEventData data) { float move = 50.0f; if (isSelect) { move = -move; Game.EventSystem.Run(EventIdType.CancelHandCard, Poker); } else { Game.EventSystem.Run(EventIdType.SelectHandCard, Poker); } RectTransform rectTransform = this.GetComponent<RectTransform>(); rectTransform.anchoredPosition += Vector2.up * move; isSelect = !isSelect; } } }
添加选择与取消选择手牌的事件
SelectHandCardEvent
\Assets\Model\Landlords\LandUI\Event\SelectHandCardEvent.cs
namespace ETModel { [Event(ETModel.EventIdType.SelectHandCard)] public class SelectHandCardEvent : AEvent<Card> { public override void Run(Card card) { Game.Scene.GetComponent<UIComponent>().Get(LandUIType.LandRoom).GetComponent<LandRoomComponent>().Interaction.SelectCard(card); } } }
CancelHandCardEvent
\Assets\Model\Landlords\LandUI\Event\CancelHandCardEvent.cs
namespace ETModel { [Event(ETModel.EventIdType.CancelHandCard)] public class CancelHandCardEvent : AEvent<Card> { public override void Run(Card card) { Game.Scene.GetComponent<UIComponent>().Get(LandUIType.LandRoom).GetComponent<LandRoomComponent>().Interaction.CancelCard(card); } } }
前端收到广播显示抢地主交互Actor_AuthorityGrabLandlord_NttHandler
\Assets\Model\Landlords\Handler\Actor05_Grab_LandlordAuthority_NttHandler.cs
using System; using System.Collections.Generic; namespace ETModel { [MessageHandler] public class Actor_AuthorityGrabLandlord_NttHandler : AMHandler<Actor_AuthorityGrabLandlord_Ntt> { protected override async ETTask Run(ETModel.Session session, Actor_AuthorityGrabLandlord_Ntt message) { UI uiRoom = Game.Scene.GetComponent<UIComponent>().Get(LandUIType.LandRoom); if (message.UserID == LandRoomComponent.LocalGamer.UserID) { //显示抢地主交互 uiRoom.GetComponent<LandRoomComponent>().Interaction.StartGrab(); } await ETTask.CompletedTask; } } }
LandlordsGamerPanelComponent添加出牌错误提示
\Assets\Model\Landlords\Component\LandlordsGamerPanelComponent.cs
/// <summary> /// 出牌错误 /// </summary> public void SetPlayCardsError() { prompt.text = "您出的牌不符合规则!"; }
LandUIType添加LandInteraction
\Assets\Model\Landlords\LandUI\UIEventType.cs
public const string LandInteraction = "LandInteraction";
交互操控请求消息定义
可以看到下面消息体中用到repeated Card Cards = 1,Card在OuterMessage中定义的,所以本节这些方法我们也在OuterMessage中定义。(本课程HotfixMessage与OuterMessage并没有区别,你全部放HotfixMessage中也可以的,但是要注意一点,如果消息字段中有repeated Card Cards这类,那这个消息就要与Card 类定义在同一个消息定义文件,并且在这个类定义之后)
\Proto\OuterMessage.proto
//游戏交互操控消息=====> message Actor_GamerPlayCard_Req // IActorRequest { int32 RpcId = 90; int64 ActorId = 93; repeated Card Cards = 1; } message Actor_GamerPlayCard_Back // IActorResponse { int32 RpcId = 90; int32 Error = 91; string Message = 92; } message Actor_GamerDontPlayCard_Ntt // IActorMessage { int32 RpcId = 90; int64 ActorId = 93; int64 UserID = 1; } message Actor_GamerPrompt_Req // IActorRequest { int32 RpcId = 90; int64 ActorId = 93; } message Actor_GamerPrompt_Back // IActorResponse { int32 RpcId = 90; int32 Error = 91; string Message = 92; repeated Card Cards = 1; } //开始抢地主消息 message Actor_AuthorityGrabLandlord_Ntt // IActorMessage { int32 RpcId = 90; int64 ActorId = 93; int64 UserID = 1; } //选择抢地方消息 message Actor_GamerGrabLandlordSelect_Ntt // IActorMessage { int32 RpcId = 90; int64 ActorId = 93; int64 UserID = 1; bool IsGrab = 2; } //设置地主消息 message Actor_SetLandlord_Ntt // IActorMessage { int32 RpcId = 90; int64 ActorId = 93; int64 UserID = 1; repeated Card LordCards = 2; }
生成消息文件后,运行前后端项目(需要打包输出在Release中运行三个游戏程序分别登录账号,全部准备游戏后,才会显示游戏交互界面)。
后端部分
为了在前端显示操控交互界面需要在服务端增加随机先手的方法
添加随机先手方法的调用
GameControllerComponentSystem组件扩展类中有StartGame方法,
\Server\Hotfix\Landlords\System\GameControllerComponentSystem.cs
//随机先手玩家 gameController.RandomFirstAuthority();
还有随机先手玩家的方法
/// <summary> /// 随机先手玩家 /// </summary> public static void RandomFirstAuthority(this GameControllerComponent self) { Room room = self.GetParent<Room>(); Gamer[] gamers = room.gamers; int index = RandomHelper.RandomNumber(0, gamers.Length); long firstAuthority = gamers[index].UserID; //广播先手抢地主玩家 room.Broadcast(new Actor_AuthorityGrabLandlord_Ntt() { UserID = firstAuthority }); }
操控交互界面显示效果
此时服务端还没有交互操作请求对应的功能,所以只能看到交互操作界面,还不能抢地主,也没有出牌按钮。
后面的课时我们来实现这些交互操作对应的服务端功能。