화살 #2

Eunho Bae·2022년 6월 25일

로직 분석

Client::MyPlayerController

protected override void UpdateIdle()
	{
		// 이동 상태로 갈지 확인
		if (_moveKeyPressed)
		{
			State = CreatureState.Moving;
			return;
		}

		if (_coSkillCooltime == null && Input.GetKey(KeyCode.Space))
		{
			Debug.Log("Skill !");

			C_Skill skill = new C_Skill() { Info = new SkillInfo() };
			skill.Info.SkillId = 2;
			Managers.Network.Send(skill);

			_coSkillCooltime = StartCoroutine("CoInputCooltime", 0.2f);
		}
	}

Server::PacketHandler

public static void C_SkillHandler(PacketSession session, IMessage packet)
	{
		C_Skill skillPacket = packet as C_Skill;
		ClientSession clientSession = session as ClientSession;

		Player player = clientSession.MyPlayer;
		if (player == null)
			return;

		GameRoom room = player.Room;
		if (room == null)
			return;

		room.HandleSkill(player, skillPacket);
	}

Server::GameRoom

public void HandleSkill(Player player, C_Skill skillPacket)
		{
			if (player == null)
				return;

			lock (_lock)
			{
				ObjectInfo info = player.Info;
				if (info.PosInfo.State != CreatureState.Idle)
					return;

				// TODO : 스킬 사용 가능 여부 체크
				info.PosInfo.State = CreatureState.Skill;
				S_Skill skill = new S_Skill() { Info = new SkillInfo() };
				skill.ObjectId = info.ObjectId;
				skill.Info.SkillId = skillPacket.Info.SkillId;
				Broadcast(skill);


			...
            
				else if (skillPacket.Info.SkillId == 2)
				{
					Arrow arrow = ObjectManager.Instance.Add<Arrow>();
					if (arrow == null)
						return;

					arrow.Owner = player;
					arrow.PosInfo.State = CreatureState.Moving;
					arrow.PosInfo.MoveDir = player.PosInfo.MoveDir;
					arrow.PosInfo.PosX = player.PosInfo.PosX;
					arrow.PosInfo.PosY = player.PosInfo.PosY;
					EnterGame(arrow);
				}
			}
		}
public void EnterGame(GameObject gameObject)
		{
			if (gameObject == null)
				return;

			GameObjectType type = ObjectManager.GetObjectTypeById(gameObject.Id);

			lock (_lock)
			{
                ...
                
                	else if (type == GameObjectType.Projectile)
				{
					Projectile projectile = gameObject as Projectile;
					_projectiles.Add(gameObject.Id, projectile);
					projectile.Room = this;
				}
			
				// 타인한테 정보 전송
				{
					S_Spawn spawnPacket = new S_Spawn();
					spawnPacket.Objects.Add(gameObject.Info);
					foreach (Player p in _players.Values)
					{
						if (p.Id != gameObject.Id)
							p.Session.Send(spawnPacket);
					}
				}
            }    
        }

플레이어가 스페이스 바를 누르면 스킬 패킷을 만들어 서버로 보내주게 된다. 서버는 스킬 패킷을 클라이언트 측으로부터 받으면 C_SkillHandler에서 처리를 해주게 되는데 클라이언트가 속해있는 게임 룸의 HandleSkill 함수 호출을 통해 스킬 패킷을 전달해주게 된다.

여기서 어떤 스킬이 사용되었는지 스킬 id를 넣어준 후 스킬 패킷을 게임룸에 존재하는 모든 플레이어들에게 뿌려준다. 여기서 뿌려준 스킬 패킷은 각 클라이언트에서 S_SkillHandler에서 처리하게 되는데 PlayerController의 UseSkill 함수에서 CoStartShootArrow 코루틴 함수를 호출시켜 주게된다.
화살 스킬 유지 시간은 0.3초로 정의되어 있는데 이 시간이 지나게 되면 CreatureState가 Idle 상태로 갱신되고, 그 갱신된 상태를 CheckUpdatedFlag() 함수를 호출시켜서 이동 패킷에 넣어 서버로 전송하여 서버에 저장된 내 정보를 갱신시키고, 주변 플레이어들에게 뿌려준다.

화살 스킬 아이디는 2이기 때문에 2번인 경우로 분기하게 되고, 여기서 ObjectManager의 GenerateId 함수를 통해 카운터와 타입을 비트 연산한 결과를 부여받게 된다. 그리고 EnterGame 함수를 호출해서 Projectile인 경우 Projectile 타입만 모아두는 딕셔너리에 추가해준다.

그리고 화살을 쏜 플레이어를 제외한 모든 클라이언트들에게 스폰 패킷을 뿌려주게 된다. 그러면 스폰 패킷을 받은 각 클라이언트는 S_SpawnHandler에서 처리하게 되는데 모든 게임오브젝트를 관리하는 ObjectManager에서 _objects 딕셔너리에 추가하는데 이때 화살 오브젝트를 생성하고 ArrowController를 붙여준 후 실제 SyncPos() 함수 호출을 통해 위치를 변경시켜 주게된다.

Server::Program

			while (true)
			{
				RoomManager.Instance.Find(1).Update();
				Thread.Sleep(100);
			}

Server::Arrow

public class Arrow : Projectile
	{
		public GameObject Owner { get; set; }

		long _nextMoveTick = 0;

		public override void Update()
		{
			if (Owner == null || Room == null)
				return;

			if (_nextMoveTick >= Environment.TickCount64)
				return;

			_nextMoveTick = Environment.TickCount64 + 50;

			Vector2Int destPos = GetFrontCellPos();
			if (Room.Map.CanGo(destPos))
			{
				CellPos = destPos;

				S_Move movePacket = new S_Move();
				movePacket.ObjectId = Id;
				movePacket.PosInfo = PosInfo;
				Room.Broadcast(movePacket);

				Console.WriteLine("Move Arrow");
			}
			else
			{
				GameObject target = Room.Map.Find(destPos);
				if (target != null)
				{
					// TODO : 피격 판정
				}

				// 소멸
				Room.LeaveGame(Id);
			}
		}
	}

게임 룸의 Projectile 타입 딕셔너리에 추가한 후 무한 루프를 돌면서 0.1초마다 1번 게임 룸의 Update() 함수를 호출시켜주는데 0.05초마다 화살이 나아가는 방향 한 칸 앞이 갈 수 있는 영역이면 CellPos를 갱신(PosInfo 갱신)시켜주고 화살이 이동했으니 이동 패킷을 만들어 룸에 존재하는 모든 플레이어들에게 뿌려준다. (이동 패킷을 받은 클라이언트들은 ArrowController도 CreatureController의 자식클래스이기 때문에 위치 갱신)

만약 한 칸 앞이 갈 수 없는 영역이라면 그 위치에 존재하는 게임 오브젝트를 찾고, 피격 판정 로직을 실행한 후 딕셔너리에서 제거하고 디스폰 패킷을 뿌려준다. (EnterRoom과 비슷한 로직)

profile
개인 공부 정리

0개의 댓글