Merge pull request 'DEG-55-플레이어-피격-구현' (!5) from DEG-55-플레이어-피격-구현 into main

Reviewed-on: #5
Reviewed-by: Sehyeon <sehyeon1837@gmail.com>
Reviewed-by: fiore <cjsdlf44@gmail.com>
This commit is contained in:
jay 2025-04-29 07:54:31 +00:00
commit be0193573a
27 changed files with 348 additions and 2793 deletions

13
.idea/.idea.Degulleo/.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,13 @@
# 디폴트 무시된 파일
/shelf/
/workspace.xml
# Rider에서 무시된 파일
/.idea.Degulleo.iml
/contentModel.xml
/modules.xml
/projectSettingsUpdater.xml
# 에디터 기반 HTTP 클라이언트 요청
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
</project>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>

6
.idea/.idea.Degulleo/.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

BIN
Assets/JAY/Animation/GetHit.anim (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e5e300b40f91e7746956016cd1a33da6
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 7400000
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

BIN
Assets/JAY/Character Test Scene.unity (Stored with Git LFS)

Binary file not shown.

View File

@ -1,6 +1,7 @@
fileFormatVersion: 2
guid: 2dfbb63c9cdf7504faf4ff26b0581598
PrefabImporter:
guid: 264ff5a75e22d3f489e174962ff5f899
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:

BIN
Assets/JAY/Image/red_splatter.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -0,0 +1,127 @@
fileFormatVersion: 2
guid: 1020bfc1c110a2e47a7f4eda301bd3db
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f85f06ffdab841aba19658d505e1a75a
timeCreated: 1745899456

View File

@ -0,0 +1,41 @@
using UnityEngine;
using System.Collections;
public class CameraShake : MonoBehaviour
{
[SerializeField] private float shakeDuration = 0.2f;
[SerializeField] private float shakeMagnitude = 0.1f;
private Vector3 initialLocalPosition;
private Coroutine shakeCoroutine;
private void Awake()
{
initialLocalPosition = transform.localPosition;
}
public void Shake()
{
if (shakeCoroutine != null)
{
StopCoroutine(shakeCoroutine);
}
shakeCoroutine = StartCoroutine(ShakeRoutine());
}
private IEnumerator ShakeRoutine()
{
float elapsed = 0f;
while (elapsed < shakeDuration)
{
Vector3 randomPoint = Random.insideUnitSphere * shakeMagnitude;
transform.localPosition = initialLocalPosition + randomPoint;
elapsed += Time.deltaTime;
yield return null;
}
transform.localPosition = initialLocalPosition;
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 84537ad9e9c54885bdc75a1cd0e7a1df
timeCreated: 1745902439

View File

@ -0,0 +1,48 @@
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
public class PlayerHitEffectController : MonoBehaviour
{
[SerializeField] private Image hitFlashImage;
private float flashDuration = 0.7f;
private Color flashColor = new Color(1, 0, 0, 0.7f); // 반투명 빨간색
private Coroutine flashCoroutine;
private void Start()
{
hitFlashImage.color = Color.clear; // 처음에는 투명함
}
public void PlayHitEffect()
{
if (flashCoroutine != null)
{
StopCoroutine(flashCoroutine);
}
flashCoroutine = StartCoroutine(FlashRoutine());
}
private IEnumerator FlashRoutine()
{
// 일단 바로 최대 알파로 세팅
hitFlashImage.color = flashColor;
float timer = 0f;
while (timer < flashDuration)
{
timer += Time.deltaTime;
// 알파만 줄이기
Color c = flashColor;
c.a = Mathf.Lerp(flashColor.a, 0, timer / flashDuration);
hitFlashImage.color = c;
yield return null;
}
hitFlashImage.color = Color.clear;
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0d7446d96b0c4470b42ee010cbbd809f
timeCreated: 1745899493

View File

@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor.TextCore.Text;
using UnityEngine;
public enum PlayerState { None, Idle, Move, Win, Hit, Dead }
@ -10,8 +11,10 @@ public class PlayerController : CharacterBase, IObserver<GameObject>
// 외부 접근 가능 변수
[Header("Attach Points")]
[SerializeField] private Transform rightHandTransform;
[SerializeField] private CameraShake cameraShake;
// 내부에서만 사용하는 변수
private PlayerHitEffectController hitEffectController;
private CharacterController _characterController;
private bool _isBattle;
private GameObject weapon;
@ -52,25 +55,9 @@ public class PlayerController : CharacterBase, IObserver<GameObject>
{
base.Start();
// 상태 초기화
_playerStateIdle = new PlayerStateIdle();
_playerStateMove = new PlayerStateMove();
_playerStateWin = new PlayerStateWin();
_playerStateDead = new PlayerStateDead();
_playerStates = new Dictionary<PlayerState, IPlayerState>
{
{ PlayerState.Idle, _playerStateIdle },
{ PlayerState.Move, _playerStateMove },
{ PlayerState.Win, _playerStateWin },
{ PlayerState.Dead, _playerStateDead },
};
_attackAction = new PlayerActionAttack();
_actionDash = new PlayerActionDash();
hitEffectController = GetComponentInChildren<PlayerHitEffectController>();
PlayerInit();
SwitchBattleMode();
}
@ -91,7 +78,6 @@ public class PlayerController : CharacterBase, IObserver<GameObject>
// 공격 입력 처리
if (Input.GetKeyDown(KeyCode.X) && (_currentAction == null || !_currentAction.IsActive)
&& (CurrentState != PlayerState.Win && CurrentState != PlayerState.Dead)) {
Debug.Log("X 버튼 Down 됨");
StartAttackAction();
}
@ -101,11 +87,36 @@ public class PlayerController : CharacterBase, IObserver<GameObject>
_currentAction.UpdateAction();
}
}
private void OnDestroy()
{
OnGetHit -= TakeDamage;
}
#region
private void PlayerInit()
{
// 상태 초기화
_playerStateIdle = new PlayerStateIdle();
_playerStateMove = new PlayerStateMove();
_playerStateWin = new PlayerStateWin();
_playerStateDead = new PlayerStateDead();
_playerStates = new Dictionary<PlayerState, IPlayerState>
{
{ PlayerState.Idle, _playerStateIdle },
{ PlayerState.Move, _playerStateMove },
{ PlayerState.Win, _playerStateWin },
{ PlayerState.Dead, _playerStateDead },
};
_attackAction = new PlayerActionAttack();
_actionDash = new PlayerActionDash();
OnGetHit -= TakeDamage;
OnGetHit += TakeDamage;
SetState(PlayerState.Idle);
InstantiateWeapon();
@ -182,7 +193,6 @@ public class PlayerController : CharacterBase, IObserver<GameObject>
if (_weaponController.IsAttacking) return; // 이미 공격 중이면 실행 안함
if (_currentAction == _attackAction) {
Debug.Log($"Attack True");
_attackAction.EnableCombo();
_weaponController.AttackStart();
}
@ -191,7 +201,6 @@ public class PlayerController : CharacterBase, IObserver<GameObject>
public void SetAttackComboFalse()
{
if (_currentAction == _attackAction) {
Debug.Log($"Attack False");
// 이벤트 중복 호출? 공격 종료 시 SetAttackComboFalse가 아니라 ~True로 끝나서 오류 발생. (공격 안하는 상태여도 공격으로 판정됨)
_attackAction.DisableCombo();
_weaponController.AttackEnd(); // IsAttacking = false로 변경
@ -245,4 +254,40 @@ public class PlayerController : CharacterBase, IObserver<GameObject>
#endregion
#region
// TODO: Editor에서 확인하기 위한 임시용
public void TakeDamage()
{
if (CurrentState == PlayerState.Dead) return;
// 피격 이벤트 재생
PlayerHitEffect();
// 죽었는지 체크
if (currentHP <= 0) SetState(PlayerState.Dead);
}
public void TakeDamage(CharacterBase character)
{
if (character != this) return; // 혹시 다른 애가 맞은 경우 무시
if (CurrentState == PlayerState.Dead) return;
// 피격 이벤트 재생
PlayerHitEffect();
// 죽었는지 체크
if (currentHP <= 0) SetState(PlayerState.Dead);
}
private void PlayerHitEffect()
{
if (_currentAction != _attackAction || !_attackAction.IsActive)
PlayerAnimator.SetTrigger("GetHit");
hitEffectController.PlayHitEffect();
cameraShake.Shake();
}
#endregion
}

View File

@ -41,8 +41,8 @@ public class PlayerControllerEditor : Editor
playerController.SwitchBattleMode();
if (GUILayout.Button("Win"))
playerController.SetState(PlayerState.Win);
// if (GUILayout.Button("Hit"))
// playerController.SetState(PlayerState.Hit);
if (GUILayout.Button("Hit"))
playerController.TakeDamage();
if (GUILayout.Button("Dead"))
playerController.SetState(PlayerState.Dead);

View File

@ -936,11 +936,11 @@ PlayerSettings:
apiCompatibilityLevel: 6
activeInputHandler: 0
windowsGamepadBackendHint: 0
cloudProjectId: d6cf79ae-9af7-48a0-9630-2e601e15eb4b
cloudProjectId:
framebufferDepthMemorylessMode: 0
qualitySettingsNames: []
projectName: Degulleo3D 2025-04-16_10-27-08
organizationId: aaniot
projectName:
organizationId:
cloudEnabled: 0
legacyClampBlendShapeWeights: 0
hmiLoadingImage: {fileID: 0}