๋ณธ๋ฌธ์œผ๋กœ ๋ฐ”๋กœ๊ฐ€๊ธฐ
728x90

 

 

 

์˜ค๋žœ๋งŒ์— ๋‹ค์‹œ ํฌ์ŠคํŒ…์„ ์˜ฌ๋ฆฌ๋„ค์š”. ๊ทธ๋™์•ˆ ๋‹ค๋ฅธ ๊ต์œก ํ”„๋กœ๊ทธ๋žจ์„ ๋“ฃ๋Š”๋‹ค๊ณ  ๋ฐ”๋น ์„œ ์นด๋ก  ๊ฐœ๋ฐœ์„ ๋ชป ํ–ˆ์Šต๋‹ˆ๋‹ค.

FPS ๊ฒŒ์ž„ ๊ฐœ๋ฐœ์€ ์ฒ˜์Œ ์ง„ํ–‰ํ•ด๋ดค๋Š”๋ฐ, ์–ด๋ฆด ๋•Œ ์นด์šดํ„ฐ์ŠคํŠธ๋ผ์ดํฌ ์˜จ๋ผ์ธ ์ข€๋น„ ์‹œ๋‚˜๋ฆฌ์˜ค ๋ชจ๋“œ๋ฅผ ์ข‹์•„ํ–ˆ๋˜ ๊ธฐ์–ต์„ ๋˜์‚ด๋ ค ์žฌ๋ฏธ์žˆ๊ฒŒ ๋งŒ๋“ค์—ˆ๋˜ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ๋‚˜์ค‘์— ๊ธฐํš์„ ์ œ๋Œ€๋กœ ํ•ด์„œ ์‚ฌ์ด๋“œ ํ”„๋กœ์ ํŠธ๋กœ ํ•˜๋‚˜ ์ง„ํ–‰ํ•ด๋ณด๊ณ  ์‹ถ๋„ค์š”. ๋ฉ€ํ‹ฐ ํ”Œ๋ ˆ์ด๋„ ๋œ๋‹ค๋ฉด ๊ธˆ์ƒ์ฒจํ™”๊ฒ ์ง€๋งŒ ๊ทธ๊ฑฐ๊นŒ์ง€ ํ•˜๋ ค๋ฉด ๊ฐˆ ๊ธธ์ด ๊ต‰์žฅํžˆ ๋ฉ€ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.ใ…Žใ…Žใ…Ž...

 

์žก๋‹ด์€ ์—ฌ๊ธฐ๊นŒ์ง€ ํ•˜๋„๋ก ํ•˜๊ณ , ์ €๋ฒˆ ๊ธ€์—์„œ๋Š” ์ด๋™ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์‹ฑํฌ ๋งž์ถ”๋Š” ๊ฒƒ๊นŒ์ง€ ํ–ˆ์—ˆ์ฃ ?

ํ‰์ง€์—์„œ๋Š” ์•„๋ฌด๋Ÿฐ ๋ฌธ์ œ๊ฐ€ ์—†์—ˆ์ง€๋งŒ ๊ณ„๋‹จ๊ณผ ๊ฐ™์€ ๊ฒฝ์‚ฌ ์ง€ํ˜•์ธ ๊ฒฝ์šฐ์—๋Š” ์ˆ˜๋งŽ์€ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์—ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ์†์„ ๋ด์•ผํ•  ๋‚ด์šฉ๋“ค์ด ์ข€ ๊ต‰์žฅํžˆ ๋งŽ์•˜์–ด์š”. ๊ทธ๋ž˜๋„ ํ•˜๋‚˜ํ•˜๋‚˜ ์ฐพ์•„๋ณด๋ฉฐ ๊ณต๋ถ€ํ–ˆ๊ธฐ์— ๋‚˜๋ฆ„ ์˜๋ฏธ์žˆ๋Š” ์‹œ๊ฐ„์ด ์•„๋‹ˆ์—ˆ๋‚˜ ์‹ถ์Šต๋‹ˆ๋‹ค.

 

๋ฒ„๊ทธ๋ฅผ ๊ฑฐ์˜ ๋‹ค ํ•ด๊ฒฐํ•˜๊ณ  ๋‚œ ๋’ค์— ๋“  ์ƒ๊ฐ์ด์ง€๋งŒ, ์ด ๊ฒŒ์ž„์—์„œ๋Š” Rigidbody๊ฐ€ ์•„๋‹ˆ๋ผ Character Controller๋ฅผ ์“ฐ๋Š” ๊ฒƒ์ด ์ฐจ๋ผ๋ฆฌ ๋” ๋‚˜์•˜์„ ๊ฒƒ ๊ฐ™๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“œ๋„ค์š”...ใ…Žใ…Ž,, ๊ทธ๋ž˜๋„ ์ด์™• ๋งŒ๋“  ๊ฑฐ ๊ณ„์† ํ•ด๋ณด์ฃ .

 

๊ทธ๋Ÿผ ์ด์ œ๋ถ€ํ„ฐ ์–ด๋– ํ•œ ๋ฒ„๊ทธ๋“ค์ด ์žˆ์—ˆ๋Š”์ง€ ํ•˜๋‚˜ํ•˜๋‚˜์”ฉ ์‚ดํŽด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

 

 

โ˜ข๏ธ๋ฐœ์ƒํ•œ ๋ฌธ์ œ๋“ค

์ฒซ ๋ฒˆ์งธ  |  ํ‰ํ‰ํ•œ ์ง€๋ฉด์—์„œ ๊ฒฝ์‚ฌ๋กœ๋กœ ์˜ค๋ฅผ ๋•Œ, ํ‚ค ์ž…๋ ฅ์„ ๋–ผ๋ฉด ์œ„๋กœ ํŠ€์–ด์˜ค๋ฅด๋˜ ๋ฒ„๊ทธ

 

์ฒซ ๋ฒˆ์งธ ๋ฒ„๊ทธ (๋ฌผ๋ก  ์ € ์ •๋„๊นŒ์ง€ ํŠ€์–ด ์˜ค๋ฅธ ๊ฑด ์•„๋‹Œ๋ฐ, ๋ฒ„๊ทธ๋ฅผ ๋‹ค ํ•ด๊ฒฐํ•˜๊ณ  ๋‹ค์‹œ ์ƒํ™ฉ์„ ์žฌ์—ฐํ•˜๋‹ค ๋ณด๋‹ˆ ์ด๋ ‡๊ฒŒ ๋๋„ค์š”.)

 

์ด ๋ฌธ์ œ๋ฅผ ์•ผ๊ธฐํ•œ ๊ฑด ๋ฐ”๋กœ ๋‹ค์Œ ์ฝ”๋“œ์˜€์Šต๋‹ˆ๋‹ค.

rigidBody.velocity = direction * currentMoveSpeed + Vector3.up * rigidBody.velocity.y;

 

๊ฒฝ์‚ฌ๋กœ๋ฅผ ์˜ค๋ฅผ ๋•Œ์—๋Š” rigidBody.velocity.y๊ฐ€ ์–‘์ˆ˜๊ฐ€ ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์œ„ ์ชฝ์œผ๋กœ ๋ฒกํ„ฐ ํž˜์ด ๋”ํ•ด์ ธ์„œ ๋‚˜ํƒ€๋‚œ ๊ฒฐ๊ณผ๋ผ๊ณ  ๋ณผ ์ˆ˜ ์žˆ์ง€์š”.

 

 

๋‘ ๋ฒˆ์งธ  |  ์บ๋ฆญํ„ฐ๊ฐ€ ๊ฒฝ์‚ฌ๋กœ์—์„œ ๋‚ด๋ ค ์˜ฌ ๋•Œ, ํ‰ํ‰ ํŠ•๊ธฐ๋ฉฐ ๋‚ด๋ ค์˜ค๋Š” ๋ฒ„๊ทธ

 

๋‘ ๋ฒˆ์งธ ๋ฒ„๊ทธ

 

์ด๊ฒƒ์€ ์ด๋™ ๋ฐฉํ–ฅ ๋ฒกํ„ฐ์— ์ค‘๋ ฅ ๋ฒกํ„ฐ๊ฐ€ ๋”ํ•ด์กŒ๊ธฐ ๋•Œ๋ฌธ์— ๋ฐœ์ƒํ•œ ๊ฒฐ๊ณผ์ž…๋‹ˆ๋‹ค. ์ด๋™ํ•  ๋ฐฉํ–ฅ์„ ํ‰๋ฉด ์ชฝ์œผ๋กœ ํˆฌ์˜์‹œํ‚ค๋Š” ๊ณผ์ •์ด ํ•„์š”ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ๋ฌผ๋ก  ์ด์™€ ๊ฐ™์€ ๋กœ์ง์„ ์›ํ•˜๋Š” ๊ฒŒ์ž„๋„ ์žˆ์„ ํ…Œ์ง€๋งŒ, ์šฐ๋ฆฌ ๊ฒŒ์ž„์—์„œ๋Š” ์•„๋‹ˆ๊ธฐ์— ์ˆ˜์ •ํ•ด์ค˜์•ผ ํ•ฉ๋‹ˆ๋‹ค.

 

 

์„ธ ๋ฒˆ์งธ  |  ์บ๋ฆญํ„ฐ๊ฐ€ ๊ฒฝ์‚ฌ๋กœ์— ๊ฐ€๋งŒํžˆ ์„œ ์žˆ์„ ๋•Œ, ์„œ์„œํžˆ ๋ฏธ๋„๋Ÿฌ์ ธ ๋‚ด๋ ค์˜ค๋Š” ๋ฒ„๊ทธ

 

์„ธ ๋ฒˆ์งธ ๋ฒ„๊ทธ

 

๋งˆ์ฐฐ๋ ฅ ๋ฌธ์ œ๋ฅผ ์ƒ๊ฐํ•ด๋ณผ ์ˆ˜ ์žˆ๊ฒ ์œผ๋‚˜, PhysicsMaterial์˜ Static Friction์„ ์ตœ๋Œ€๋กœ ์ค˜๋ด๋„ ๋˜‘๊ฐ™์ด ์ ์šฉ๋˜๋”๋ผ๊ตฌ์š”.

์ด๊ฒƒ ์—ญ์‹œ ์ค‘๋ ฅ๊ณผ Vector3.up * rigidBody.velocity.y์˜ ํž˜์— ์˜ํ•ด ๋ฐœ์ƒํ•˜๋Š” ๋ฒ„๊ทธ๋ผ๊ณ  ๋ณผ ์ˆ˜ ์žˆ๊ฒ ์Šต๋‹ˆ๋‹ค.

 

 

๋„ค ๋ฒˆ์งธ  |  ์บ๋ฆญํ„ฐ๊ฐ€ ๊ฒฝ์‚ฌ๋กœ์— ์˜ค๋ฅผ ๋•Œ์™€ ๋‚ด๋ ค์˜ฌ ๋•Œ ์†๋„๊ฐ€ ๋‹ฌ๋ผ์ง€๋Š” ๋ฒ„๊ทธ

 

ํ†ตํ†ต ํŠ€๋Š” ๋ฒ„๊ทธ๋ฅผ ํ•ด๊ฒฐํ•˜๊ณ  ๋‚œ ๋’ค์— ์ฐ์€ GIF์ž…๋‹ˆ๋‹ค.

 

์ด๊ฒƒ์€ ํ˜„์‹ค ์„ธ๊ณ„์—์„œ๋Š” ๋‹น์—ฐํ•œ ๋ฒ•์น™์ด์ง€๋งŒ, ๊ฒŒ์ž„ ํ”Œ๋ ˆ์ด๋ฅผ ํ•  ๋•Œ ๋ถˆํŽธํ•˜๊ฒŒ ์ž‘์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์ˆ˜์ •ํ•ด์ค˜์•ผ ํ•  ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. 

 

 

๋‹ค์„ฏ ๋ฒˆ์งธ  |   ์ƒ์‹์ ์œผ๋กœ ์˜ฌ๋ผ๊ฐˆ ์ˆ˜ ์—†๋Š” ๊ฒฝ์‚ฌ ๊ฐ๋„์ธ๋ฐ๋„ ์บ๋ฆญํ„ฐ๊ฐ€ ์˜ฌ๋ผ๊ฐˆ ์ˆ˜ ์žˆ์—ˆ๋˜ ๋ฒ„๊ทธ

 

 

์‚ฌ์‹ค์ƒ ์ œ์ผ ๋‚œ๊ฐํ•œ ๋ฒ„๊ทธ์˜€์Šต๋‹ˆ๋‹ค. ์‚ฌ์‹ค์ƒ ์•„์ง๋„ ์›์ธ ํŒŒ์•…์„ ์ •ํ™•ํ•˜๊ฒŒ๋Š” ํ•˜์ง€ ๋ชปํ•œ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

์ด๊ฒƒ๋„ ๋ฌธ์ œ๊ฐ€ ๋  ์ˆ˜ ์žˆ์œผ๋‹ˆ, ๋ง‰์•„์ฃผ๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ ‡๋‹ค๋ฉด ์ด์ œ ๋ฌธ์ œ๋ฅผ ์ฐจ๊ทผ์ฐจ๊ทผ ํ•ด๊ฒฐํ•ด ๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

 

 

 

โœ…ํ•ด๊ฒฐ ๊ณผ์ •

๊ฒฝ์‚ฌ(Slope) ์ง€ํ˜• ์ฒดํฌํ•˜๊ธฐ

 

์บ๋ฆญํ„ฐ๊ฐ€ ํ‰์ง€์— ์žˆ๋Š”์ง€, ๊ฒฝ์‚ฌ๋กœ์— ์žˆ๋Š”์ง€ ํŒ๋‹จํ•˜๋Š” ๊ฒƒ์€ Raycast๋ฅผ ์•„๋ž˜๋กœ ์˜์•„ ๋ถ€๋”ชํžŒ ํ‰๋ฉด์˜ ๋ฒ•์„  ๋ฒกํ„ฐ(Normal)์„ ์•Œ์•„๋‚ด๊ณ , ์ด ๋ฒ•์„  ๋ฒกํ„ฐ์™€ Vector3.up ๋ฒกํ„ฐ ์‚ฌ์ด์˜ ๊ฐ๋„๋กœ ํŒ๋ณ„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

๊ฒฝ์‚ฌ ์ง€ํ˜• ์ฒดํฌํ•˜๋Š” ๋ฐฉ๋ฒ•

 

์ด ์›๋ฆฌ๋ฅผ ์ ์šฉํ•˜์—ฌ ํ˜„์žฌ ์บ๋ฆญํ„ฐ๊ฐ€ ๊ฒฝ์‚ฌ ์ง€ํ˜•์— ์žˆ๋Š”์ง€ ์•„๋‹Œ์ง€๋ฅผ ํŒ๋ณ„ํ•ด์ฃผ๋Š” ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.

๊ฐœ์ธ์ ์œผ๋กœ Raycast์™€ ๊ฐ™์€ ๋ถ€๋ฅ˜์˜ ๋ฉ”์†Œ๋“œ๋“ค์„ ์‚ฌ์šฉํ•˜๊ธฐ ์–ด๋ ค์› ๋Š”๋ฐ, ์ด๋ฒˆ ๊ธฐํšŒ์— ์—ฐ์Šต์„ ์ข€ ํ•  ์ˆ˜ ์žˆ์—ˆ๋˜ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

private const float RAY_DISTANCE = 2f;
private RaycastHit slopeHit;
private int groundLayer = 1 << LayerMask.NameToLayer("Ground");  // ๋•…(Ground) ๋ ˆ์ด์–ด๋งŒ ์ฒดํฌ

public bool IsOnSlope()
{
    Ray ray = new Ray(transform.position, Vector3.down);
    if(Physics.Raycast(ray, out slopeHit, RAY_DISTANCE, groundLayer))
    {
        var angle = Vector3.Angle(Vector3.up, slopeHit.normal);
        return angle != 0f && angle < maxSlopeAngle;
    }
    return false;
}

 

 

๊ฒฝ์‚ฌ ์ง€ํ˜• ํ‰๋ฉด์— ๋งž๊ฒŒ ์ด๋™ ๋ฐฉํ–ฅ ๋ฒกํ„ฐ ํˆฌ์˜ํ•˜๊ธฐ

 

์ด์ œ ๊ฒฝ์‚ฌ ์ง€ํ˜•์ธ์ง€ ์•„๋‹Œ์ง€๋ฅผ ํŒ๋‹จํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ์œผ๋‹ˆ ๋งŒ์•ฝ ๊ฒฝ์‚ฌ ์ง€ํ˜•์ด๋ผ๋ฉด, ํ˜„์žฌ ์บ๋ฆญํ„ฐ๊ฐ€ ์„œ ์žˆ๋Š” ๊ฒฝ์‚ฌ ์ง€ํ˜• ํ‰๋ฉด ๋ฒกํ„ฐ๋กœ ์ด๋™ ๋ฐฉํ–ฅ ๋ฒกํ„ฐ๋ฅผ ํˆฌ์˜ํ•˜๋Š” ์ž‘์—…์„ ํ•ด์ค˜์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ž˜์•ผ ์บ๋ฆญํ„ฐ๊ฐ€ ๊ฒฝ์‚ฌ ์ง€ํ˜•์—์„œ ๋‚ด๋ ค๊ฐˆ ๋•Œ ํ†ตํ†ต ํŠ€์ง€ ์•Š๊ณ  ๋‚ด๋ ค๊ฐˆ ์ˆ˜ ์žˆ์–ด์š”.

 

๊ฒฝ์‚ฌ ์ง€ํ˜• ํ‰๋ฉด์— ๋งž์ถฐ ๋ฒกํ„ฐ ํˆฌ์˜ํ•˜๊ธฐ

 

Vector3์—์„œ ์ œ๊ณตํ•˜๋Š” ProjectOnPlane() ๋ฉ”์†Œ๋“œ๋ฅผ ํ™œ์šฉํ•˜๋ฉด ์‰ฝ๊ฒŒ ๊ตฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ, ํˆฌ์˜๋œ ๋ฒกํ„ฐ ๋ฐฉํ–ฅ ์ •๋ณด๋งŒ ํ•„์š”ํ•œ ๊ฒƒ์ด๋ฏ€๋กœ ์ •๊ทœํ™”(normalized)๋ฅผ ํ•ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

protected Vector3 AdjustDirectionToSlope(Vector3 direction)
{
    return Vector3.ProjectOnPlane(direction, slopeHit.normal).normalized;
}

 

์ด ๋•Œ๊นŒ์ง€ ํ•œ ๊ฒƒ๋“ค์„ ํ†ตํ•ด, ๊ฒฝ์‚ฌ ์ง€ํ˜•์—์„œ ํ†ตํ†ต ํŠ€๋ฉฐ ๋‚ด๋ ค์˜ค๋˜ ๋ฒ„๊ทธ๋ฅผ ์ˆ˜์ •ํ•ด ์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

protected void Move()
{
    float currentMoveSpeed = player.MoveSpeed * CONVERT_UNIT_VALUE;
    float animationPlaySpeed = DEFAULT_ANIMATION_PLAYSPEED +
                               GetAnimationSyncWithMovement(currentMoveSpeed);
    
    // ---------์ถ”๊ฐ€-----------
    bool isOnSlope = IsOnSlope();
    Vector3 velocity = isOnSlope ? AdjustDirectionToSlope(Vector3 direction) : direction;
    Vector3 gravity = isOnSlope ? Vector3.zero : Vector3.down * Mathf.Abs(rigidBody.velocity.y);
    // ------------------------
    LookAt();
    rigidBody.velocity = velocity * currentMoveSpeed + gravity;
    animator.SetFloat("Velocity", animationPlaySpeed);
}

 

์ถ”๊ฐ€๋กœ, ์ฒซ ๋ฒˆ์งธ ๋ฒ„๊ทธ์˜€๋˜ ์ง€ํ˜• → ๊ฒฝ์‚ฌ ์ง€ํ˜• ์ด๋™ ์‹œ ์œ„๋กœ ํŠ€๋˜ ๋ฒ„๊ทธ๋„ ์ˆ˜์ •ํ•ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

Vector3 gravity = isOnSlope ? Vector3.zero : Vector3.down * Mathf.Abs(rigidBody.velocity.y);

 

๊ฒฝ์‚ฌ ์ง€ํ˜•์—์„œ ํ†ตํ†ต ํŠ€๋ฉฐ ์ด๋™ํ•˜๋˜ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•œ ๋ชจ์Šต

 

 

์œ„์— ์ž‘์„ฑํ•œ ์ฝ”๋“œ๋“ค์€ ์ด๋ ‡๊ฒŒ ์‚ฌ์šฉํ•œ๋‹ค๋Š” ์˜ˆ์‹œ๋ฅผ ๋ณด์—ฌ๋“œ๋ฆฐ ๊ฒƒ์ผ ๋ฟ, ๋ชจ๋“  ๋ฒ„๊ทธ๋“ค์„ ํ•ด๊ฒฐํ•œ ์ฝ”๋“œ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค.

๋‹ค์Œ์œผ๋กœ ์ด๋™ํ•˜์—ฌ ๋˜ ํ•˜๋‚˜ํ•˜๋‚˜ ์ฐจ๊ทผ์ฐจ๊ทผ ํ•ด๊ฒฐํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

 

 

๋•…(Ground)์— ๋ถ™์–ด ์žˆ๋Š”์ง€ ์ฒดํฌํ•˜๊ธฐ

 

์ด์ œ ๊ฒฝ์‚ฌ ์ง€ํ˜•์— ์„œ ์žˆ์„ ๋•Œ, ์„œ์„œํžˆ ๋ฏธ๋„๋Ÿฌ์ ธ ๋‚ด๋ ค์˜ค๋˜ ๋ฒ„๊ทธ๋ฅผ ์ˆ˜์ •ํ•  ๊ฒ๋‹ˆ๋‹ค. ๊ฒฝ์‚ฌ ์ง€ํ˜•์ผ ๋•Œ์™€ ์•„๋‹ ๋•Œ๋ฅผ ๊ตฌ๋ณ„ํ•˜์—ฌ ๋”ฐ๋กœ ์ ์šฉํ•ด์ฃผ๋ฉด ๋˜๋Š”๋ฐ, ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๋‹ค.

 

  • Rigidbody์˜ Freeze Position Y ์„ ์ฒดํฌ/ํ•ด์ œ ํ•˜๋Š” ๋ฐฉ์‹
  • Rigidbody์˜ Use Gravity ์„ ์ฒดํฌ/ํ•ด์ œ ํ•˜๋Š” ๋ฐฉ์‹

 

์ €๋Š” ๋‘ ๋ฒˆ์งธ ๋ฐฉ๋ฒ•์„ ์„ ํƒํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ์ด๊ฑฐ๋ž‘ ๋•…์— ๋ถ™์–ด ์žˆ๋Š”์ง€ ์ฒดํฌํ•˜๋Š” ๊ฒƒ์€ ๋ฌด์Šจ ์ƒ๊ด€์ด๋ƒ๊ตฌ์š”? ์บ๋ฆญํ„ฐ๊ฐ€ ๋ฐ”๋‹ฅ๊ณผ ๊ฒฝ์‚ฌ ์ง€ํ˜•์„ ๋”ฐ๋ผ ์ด๋™ํ•˜๊ธฐ๋งŒ ํ•˜๋ฉด ์‚ฌ์‹ค์ƒ ํ•„์š” ์—†์Šต๋‹ˆ๋‹ค. ๋ฌธ์ œ๋Š” ๊ณต์ค‘์—์„œ ๋–จ์–ด์ง€๋Š” ์ƒํ™ฉ์ด์ง€์š”.

 

์ด๋ฏธ ๊ฒฝ์‚ฌ ์ง€ํ˜• ์ฒดํฌ์šฉ์œผ๋กœ ์‚ฌ์šฉํ•˜๋˜ Raycast๋ฅผ ์žฌํ™œ์šฉํ•˜์ž๋‹ˆ, Raycast์˜ ๊ธธ์ด ๋•Œ๋ฌธ์— ๊ณต์ค‘์— ๋œฌ ์ƒํƒœ๋กœ ๋‹ค๋‹ˆ๊ฒŒ ๋˜๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ์ฝœ๋ผ์ด๋”์˜ OnCollisionEnter() ๋ฉ”์†Œ๋“œ๋„ ๊ฒฝ์‚ฌ ์ง€ํ˜•์—์„œ ๊ทธ๋‹ฅ ์ข‹์€ ๋ชจ์Šต์„ ๋ณด์—ฌ์ฃผ์ง„ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋ž˜์„œ ์ƒˆ๋กœ์šด Raycast๋ฅผ ์“ฐ์ž๋‹ˆ, Raycast๋Š” ํŠน์ • ์ƒํ™ฉ์—์„œ๋Š” ๋•… ์ฒดํฌ๋ฅผ ํ•˜์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค. 

 

๋•… ๋์— ๊ฑธ์ณ ์žˆ์„ ๊ฒฝ์šฐ, ์œ„์™€ ๊ฐ™์ด Raycast๋Š” ๋•… ์ฒดํฌ๋ฅผ ๋ชป ํ•ฉ๋‹ˆ๋‹ค.

 

๊ทธ๋ž˜์„œ ์ €๋Š” ๋ถ€๋”ชํžŒ ๋Œ€์ƒ์— ๋Œ€ํ•œ ์ •๋ณด๋Š” ํ•„์š”์—†๊ณ , ๋‹จ์ง€ ๋„“์€ ๋ฉด์ ์œผ๋กœ ์ฒดํฌ๋งŒ ํšจ์œจ์ ์œผ๋กœ ํ•  ์ˆ˜ ์žˆ์œผ๋ฉด ๋˜๋Š” Physics.CheckBox()๋ฅผ ์ด์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. ์šฐ์„  ์บ๋ฆญํ„ฐ ์˜ค๋ธŒ์ ํŠธ์˜ ํ•˜์œ„ ๋นˆ ์˜ค๋ธŒ์ ํŠธ๋กœ "GroundCheck"๋ฅผ ๋งŒ๋“ค์–ด ์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

 

์œ„์น˜๋Š” ๋Œ€๋žต ์บ๋ฆญํ„ฐ์˜ ๋ฐœ๋ณด๋‹ค ์‚ด์ง ์•„๋ž˜์ด๋ฉด ๋ฉ๋‹ˆ๋‹ค.

 

๊ทธ๋ฆฌ๊ณ  ์บ๋ฆญํ„ฐ์˜ ํฌ๊ธฐ์— ๋งž์ถฐ์„œ CheckBox ํฌ๊ธฐ๊ฐ€ ์œ ๋™์ ์œผ๋กœ ๋ณ€ํ•  ์ˆ˜ ์žˆ๋˜, Y์ถ•์€ ์กฐ๊ธˆ ์ž‘๊ฒŒ ์„ธํŒ…ํ•ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

[SerializeField] Transform groundCheck;

public bool IsGrounded()
{
    Vector3 boxSize = new Vector3(transform.lossyScale.x, 0.4f, transform.lossyScale.z);
    return Physics.CheckBox(groundCheck.position, boxSize, Quaternion.identity, groundLayer);
}

// Quaternion.identity๋Š” ํšŒ์ „๊ฐ’์ด ์—†๋‹ค๋Š” ์˜๋ฏธ์ž…๋‹ˆ๋‹ค.

 

์‹œ๊ฐ์ ์œผ๋กœ ๊ทธ๋ ค์„œ ํ•œ ๋ฒˆ ๋ณด์—ฌ๋“œ๋ฆฌ๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

private void OnDrawGizmos()
{
    Gizmos.color = Color.red;
    Vector3 boxSize = new Vector3(transform.lossyScale.x, 0.4f, transform.lossyScale.z);
    Gizmos.DrawWireCube(groundCheck.position, boxSize);
}

 

CheckBox ๋ชจ์Šต

 

์ด์ œ ์›ํ™œํ•œ ๋•… ์ฒดํฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ๋„ค์š”. ์ด๊ฒƒ์„ ํ† ๋Œ€๋กœ ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•˜์—ฌ ์ฃผ๋ฉด, ๊ฒฝ์‚ฌ ์ง€ํ˜•์—์„œ ๋ฏธ๋„๋Ÿฌ์ ธ ๋‚ด๋ ค๊ฐ€๋˜ ๋ฒ„๊ทธ๋ฅผ ๊ณ ์น  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฒฝ์‚ฌ ์ง€ํ˜•์—์„œ ์ค‘๋ ฅ์„ ๊บผ์ฃผ๋‹ˆ, ์˜ฌ๋ผ๊ฐˆ ๋•Œ์™€ ๋‚ด๋ ค๊ฐˆ ๋•Œ ์†๋„๊ฐ€ ๋‹ฌ๋ผ์ง€๋Š” ๋ฌธ์ œ๋„ ํ•ด๊ฒฐ๋˜๊ฒ ๋„ค์š”.

protected void Move()
{
    float currentMoveSpeed = player.MoveSpeed * CONVERT_UNIT_VALUE;
    float animationPlaySpeed = DEFAULT_ANIMATION_PLAYSPEED +
                               GetAnimationSyncWithMovement(currentMoveSpeed);
                               
    //-----------------์ˆ˜์ •----------------------------------
    bool isOnSlope = IsOnSlope();
    bool isGrounded = IsGrounded();
    Vector3 velocity = direction;
    Vector3 gravity = Vector3.down * Mathf.Abs(rigidBody.velocity.y);

    if (isGrounded && isOnSlope)         // ๊ฒฝ์‚ฌ๋กœ์— ์žˆ์„ ๋•Œ
    {
        velocity = AdjustDirectionToSlope(direction);
        gravity = Vector3.zero;
        rigidBody.useGravity = false;
    }
    else
    {
        rigidBody.useGravity = true;
    }
    //-------------------------------------------------------

    LookAt();
    rigidBody.velocity = velocity * currentMoveSpeed + gravity;
    animator.SetFloat("Velocity", animationPlaySpeed);
}

 

๊ฒฝ์‚ฌ ์ง€ํ˜•์—์„œ ๋ฏธ๋„๋Ÿฌ์ ธ ๋‚ด๋ ค์˜ค๋˜ ๋ฒ„๊ทธ ํ•ด๊ฒฐํ•œ ๋ชจ์Šต

 

 

 

๊ฐˆ ์ˆ˜ ์žˆ๋Š” ์ง€ํ˜•์ธ์ง€ ์•„๋‹Œ์ง€ ์ฒดํฌํ•˜๊ธฐ

 

์ด์ œ ๋งˆ์ง€๋ง‰ ๋ฒ„๊ทธ๋งŒ ๋‚จ์•˜๋„ค์š”. ์˜ค๋ฅผ ์ˆ˜ ์—†๋Š” ๊ฒฝ์‚ฌ ์ง€ํ˜•์ธ๋ฐ๋„ ๋ถˆ๊ตฌํ•˜๊ณ  ํ‚ค ์ž…๋ ฅ์„ ๊ณ„์† ์ฃผ๋ฉด ์˜ฌ๋ผ๊ฐ€๋Š” ๋ฒ„๊ทธ์˜€์Šต๋‹ˆ๋‹ค.

์ค‘๋ ฅ๊ฐ’์„ ๋” ๋Š˜๋ฆฌ๋ฉด ํž˜์ด ๋”ธ๋ ค์„œ ๋ชป ์˜ฌ๋ผ๊ฐ€๊ธด ํ–ˆ์ง€๋งŒ, ๊ทธ๋ ‡๊ฒŒ ํ•˜๋ฉด ์ž‘์€ ๋ฐฉ์ง€ํ„ฑ ํ•˜๋‚˜์กฐ์ฐจ ๋ชป ๋„˜๋”๋ผ๊ตฌ์š”.

๊ทธ๋ž˜์„œ ๋‹ค์Œ ํ”„๋ ˆ์ž„์— ์ด๋™ํ•  ์œ„์น˜๋ฅผ ๋จผ์ € ๊ณ„์‚ฐํ•œ ํ›„, ๊ฑฐ๊ธฐ์—์„œ ์ƒˆ๋กœ์šด Raycast๋ฅผ ์•„๋ž˜๋กœ ์˜์•„ ์ด๋™ํ•˜๊ธฐ ์ „์— ํŒ๋‹จํ•˜๋Š” ๊ฑธ๋กœ ํ•ด๊ฒฐ ๋ฐฉ์•ˆ์„ ๋งˆ๋ จํ–ˆ์Šต๋‹ˆ๋‹ค.

 

์šฐ์„  ์บ๋ฆญํ„ฐ๊ฐ€ ๋ฐ”๋ผ๋ณด๋Š” ์ •๋ฉด ์ชฝ์œผ๋กœ, ์ฝœ๋ผ์ด๋” ๊ฐ€์žฅ ์™ธ๊ณฝ์— "RaycastOrigin"์ด๋ผ๋Š” ๋นˆ ์˜ค๋ธŒ์ ํŠธ๋ฅผ ์ž์‹์œผ๋กœ ์ƒ์„ฑํ•ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๋™ํ•˜๋Š” ์บ๋ฆญํ„ฐ ์˜ค๋ธŒ์ ํŠธ ์ขŒํ‘œ๋Š” ์บ๋ฆญํ„ฐ ์ค‘์‹ฌ์ชฝ์ด์ง€๋งŒ, ์ฝœ๋ผ์ด๋” ๋ฐ˜์ง€๋ฆ„์„ ์ƒ๊ฐํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์ด์ฃ .

 

 

์—ฌ๊ธฐ์—์„œ ์ด๋™ํ•  ๋ฐฉํ–ฅ * ์ด๋™์†๋„ * Time.fixedDeltaTime ๋ฒกํ„ฐ๋ฅผ ๋”ํ•ด์ฃผ๋ฉด ๋‹ค์Œ ํ”„๋ ˆ์ž„์—์„œ์˜ ์บ๋ฆญํ„ฐ ๋งจ ์•ž ์œ„์น˜๊ฐ€ ๋˜๊ฒ ์ง€์š”. ์ €๋Š” Move()๋ฅผ FixedUpdate() ํ•จ์ˆ˜์—์„œ ๊ตฌ๋™ ์ค‘์ด๊ธฐ ๋•Œ๋ฌธ์— Time.fixedDeltaTime์„ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.

์•„๋ฌดํŠผ ๋‹ค์Œ ํ”„๋ ˆ์ž„ ์บ๋ฆญํ„ฐ ์œ„์น˜์—์„œ ์•„๋ž˜๋กœ Raycast๋ฅผ ์˜์•„, ๋‹ฟ์€ ํ‰๋ฉด์˜ ๋ฒ•์„  ๋ฒกํ„ฐ์™€ Vector3.up ๋ฒกํ„ฐ ์‚ฌ์ด์˜ ๊ฐ๋„๋ฅผ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.

private float CalculateNextFrameGroundAngle(float moveSpeed)
{
    // ๋‹ค์Œ ํ”„๋ ˆ์ž„ ์บ๋ฆญํ„ฐ ์•ž ๋ถ€๋ถ„ ์œ„์น˜
    var nextFramePlayerPosition =
                       raycastOrigin.position + direction * moveSpeed * Time.fixedDeltaTime;
    
    if (Physics.Raycast(nextFramePlayerPosition, Vector3.down, out RaycastHit hitInfo,
                        RAY_DISTANCE, groundLayer))
        return Vector3.Angle(Vector3.up, hitInfo.normal);
    return 0f;
}

 

๊ทธ๋ฆฌ๊ณ  ์ด๊ฒŒ maxSlopeAngle๋กœ ์„ค์ •ํ•œ ๊ฐ’๋ณด๋‹ค ํด ๋•Œ๋Š” velocity๋ฅผ Vector3.zero๋กœ ์„ค์ •ํ•˜์—ฌ ์ด๋™ํ•˜์ง€ ๋ชปํ•˜๋„๋ก ํ•˜๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.

protected void Move()
{
    float currentMoveSpeed = player.MoveSpeed * CONVERT_UNIT_VALUE;
    float animationPlaySpeed = DEFAULT_ANIMATION_PLAYSPEED +
                               GetAnimationSyncWithMovement(currentMoveSpeed);
    bool isOnSlope = IsOnSlope();
    bool isGrounded = IsGrounded();
    
    //----------------์ˆ˜์ •-----------------
    Vector3 velocity = CalculateNextFrameGroundAngle(currentMoveSpeed) < maxSlopeAngle ?
                       direction : Vector3.zero;
    //-------------------------------------
    Vector3 gravity = Vector3.down * Mathf.Abs(rigidBody.velocity.y);

    if (isGrounded && isOnSlope)
    {
        velocity = AdjustDirectionToSlope(direction);
        gravity = Vector3.zero;
        rigidBody.useGravity = false;
    }
    else
    {
        rigidBody.useGravity = true;
    }

    LookAt();
    rigidBody.velocity = velocity * currentMoveSpeed + gravity;
    animator.SetFloat("Velocity", animationPlaySpeed);
}

 

์ตœ๋Œ€ ๊ฒฝ์‚ฌ ๊ฐ๋„(maxSlopeAngle)์„ 40๋„๋กœ ํ•ด๋†“๊ณ  ํ…Œ์ŠคํŠธ ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

 

 

35๋„ ๊ฒฝ์‚ฌ ์ง€ํ˜•์ผ ๊ฒฝ์šฐ

 

55๋„ ๊ฒฝ์‚ฌ ์ง€ํ˜•์ผ ๊ฒฝ์šฐ

 

 

ํ…Œ์ŠคํŠธ ํ•ด๋ณด๋‹ˆ ์ž˜ ์ž‘๋™ํ•˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ถ”๊ฐ€๋กœ, ๋ฐ”๋‹ฅ์— ์žˆ๋Š” ๋ฐฉ์ง€ํ„ฑ์ด๋‚˜ ๊ณ„๋‹จ ๋†’์ด์— ๋Œ€ํ•ด์„œ๋„ ์˜ค๋ฅผ ์ˆ˜ ์žˆ๋Š”์ง€ ์—†๋Š”์ง€๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ ์ €๋Š” ๊ฑฐ๊ธฐ๊นŒ์ง€๋Š” ํ•„์š” ์—†์„ ๊ฒƒ ๊ฐ™์•„ ๊ตฌํ˜„ํ•˜์ง€ ์•Š๊ฒ ์Šต๋‹ˆ๋‹ค. ํ•„์š”ํ•˜์‹œ๋ฉด ๐Ÿ”—์ด ์˜์ƒ์„ ์ฐธ๊ณ ํ•˜๋ฉด ์ข‹์„ ๊ฒƒ ๊ฐ™๋„ค์š”.

 

 

๋งˆ๋ฌด๋ฆฌ

 

3D ๋ฌผ๋ฆฌ๊ฐ€ ์ด๋ ‡๊ฒŒ๋‚˜ ํž˜๋“ค๊ณ  ์–ด๋ ค์šด ์ค„์€ ๊ฟˆ์—๋„ ๋ชฐ๋ž์Šต๋‹ˆ๋‹ค. ๊ฝค๋‚˜ ๊ณ ์ƒ์„ ์ข€ ํ–ˆ๋„ค์š”... ์ด๊ฒƒ์ €๊ฒƒ ๋งŽ์ด ์ฐพ์•„๋ณด๋ฉฐ ๊ณต๋ถ€ํ–ˆ๋˜ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ์—๋Š” ์–ด๋–ค ๋‚œ๊ด€์ด ์ €๋ฅผ ๊ณ ํ†ต์Šค๋Ÿฝ๊ฒŒ ํ• ๊นŒ์š”...ใ…Žใ…Žใ…Ž ๊ธด ๊ธ€ ์ฝ์–ด์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค. ์ „์ฒด ์†Œ์Šค์ฝ”๋“œ ํ•„์š”ํ•˜์‹  ๋ถ„์€ ์•„๋ž˜์— ๋‚จ๊ฒจ ๋†“์„๊ฒŒ์š”.

๋”๋ณด๊ธฐ
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;


[RequireComponent(typeof(Player))]
public class PlayerController : MonoBehaviour
{

    #region #๊ธฐ๋ณธ ์ปดํฌ๋„ŒํŠธ
    public Vector3 direction { get; private set; }
    protected Player player;
    protected Rigidbody rigidBody;
    protected Animator animator;
    protected CapsuleCollider capsuleCollider;
    #endregion

    #region #์ด๋™ ๊ด€๋ จ ๋ณ€์ˆ˜
    protected const float CONVERT_UNIT_VALUE = 0.01f;
    protected const float DEFAULT_CONVERT_MOVESPEED = 3f;
    protected const float DEFAULT_ANIMATION_PLAYSPEED = 0.9f;
    protected float frontGroundHeight;
    #endregion

    #region #๊ฒฝ์‚ฌ ์ฒดํฌ ๋ณ€์ˆ˜
    [Header("๊ฒฝ์‚ฌ ์ง€ํ˜• ๊ฒ€์‚ฌ")]
    [SerializeField, Tooltip("์บ๋ฆญํ„ฐ๊ฐ€ ๋“ฑ๋ฐ˜ํ•  ์ˆ˜ ์žˆ๋Š” ์ตœ๋Œ€ ๊ฒฝ์‚ฌ ๊ฐ๋„์ž…๋‹ˆ๋‹ค.")]
    float maxSlopeAngle;
    [SerializeField, Tooltip("๊ฒฝ์‚ฌ ์ง€ํ˜•์„ ์ฒดํฌํ•  Raycast ๋ฐœ์‚ฌ ์‹œ์ž‘ ์ง€์ ์ž…๋‹ˆ๋‹ค.")]
    Transform raycastOrigin;

    private const float RAY_DISTANCE = 2f;
    private RaycastHit slopeHit;
    #endregion

    #region #๋ฐ”๋‹ฅ ์ฒดํฌ ๋ณ€์ˆ˜
    [Header("๋•… ์ฒดํฌ")]
    [SerializeField, Tooltip("์บ๋ฆญํ„ฐ๊ฐ€ ๋•…์— ๋ถ™์–ด ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•œ CheckBox ์‹œ์ž‘ ์ง€์ ์ž…๋‹ˆ๋‹ค.")]
    Transform groundCheck;
    private int groundLayer;
    #endregion

    #region #UNITY_FUNCTIONS
    void Start()
    {
        rigidBody = GetComponent<Rigidbody>();
        animator = GetComponent<Animator>();
        player = GetComponent<Player>();
        capsuleCollider = GetComponent<CapsuleCollider>();
        groundLayer = 1 << LayerMask.NameToLayer("Ground");
    }

    void FixedUpdate()
    {
        Move();
    }

    #endregion


    private float CalculateNextFrameGroundAngle(float moveSpeed)
    {
        var nextFramePlayerPosition = raycastOrigin.position + direction * moveSpeed * Time.fixedDeltaTime;   // ๋‹ค์Œ ํ”„๋ ˆ์ž„ ์บ๋ฆญํ„ฐ ์•ž ๋ถ€๋ถ„ ์œ„์น˜

        if (Physics.Raycast(nextFramePlayerPosition, Vector3.down, out RaycastHit hitInfo, RAY_DISTANCE, groundLayer))
        {
            return Vector3.Angle(Vector3.up, hitInfo.normal);
        }
        return 0f;
    }



    protected void Move()
    {
        float currentMoveSpeed = player.MoveSpeed * CONVERT_UNIT_VALUE;
        float animationPlaySpeed = DEFAULT_ANIMATION_PLAYSPEED + GetAnimationSyncWithMovement(currentMoveSpeed);
        bool isOnSlope = IsOnSlope();
        bool isGrounded = IsGrounded();

        Vector3 velocity = CalculateNextFrameGroundAngle(currentMoveSpeed) < maxSlopeAngle ? direction : Vector3.zero;
        Vector3 gravity = Vector3.down * Mathf.Abs(rigidBody.velocity.y);

        if (isGrounded && isOnSlope)         // ๊ฒฝ์‚ฌ๋กœ์— ์žˆ์„ ๋•Œ
        {
            velocity = AdjustDirectionToSlope(direction);
            gravity = Vector3.zero;
            rigidBody.useGravity = false;
        }
        else
        {
            rigidBody.useGravity = true;
        }

        LookAt();
        rigidBody.velocity = velocity * currentMoveSpeed + gravity;
        animator.SetFloat("Velocity", animationPlaySpeed);
    }



    public bool IsGrounded()
    {
        Vector3 boxSize = new Vector3(transform.lossyScale.x, 0.4f, transform.lossyScale.z);
        return Physics.CheckBox(groundCheck.position, boxSize, Quaternion.identity, groundLayer);
    }

    public bool IsOnSlope()
    {
        Ray ray = new Ray(transform.position, Vector3.down);
        if(Physics.Raycast(ray, out slopeHit, RAY_DISTANCE, groundLayer))
        {
            var angle = Vector3.Angle(Vector3.up, slopeHit.normal);
            return angle != 0f && angle < maxSlopeAngle;
        }
        return false;
    }

    public void OnMoveInput(InputAction.CallbackContext context)
    {
        Vector2 input = context.ReadValue<Vector2>();
        direction = new Vector3(input.x, 0f, input.y);
    }

    protected void LookAt()
    {
        if (direction != Vector3.zero)
        {
            Quaternion targetAngle = Quaternion.LookRotation(direction);
            rigidBody.rotation = targetAngle;
        }
    }

    protected float GetAnimationSyncWithMovement(float changedMoveSpeed)
    {
        if (direction == Vector3.zero)
        {
            return -DEFAULT_ANIMATION_PLAYSPEED;
        }

        // (๋ฐ”๋€ ์ด๋™ ์†๋„ - ๊ธฐ๋ณธ ์ด๋™์†๋„) * 0.1f
        return (changedMoveSpeed - DEFAULT_CONVERT_MOVESPEED) * 0.1f;
    }
    protected Vector3 AdjustDirectionToSlope(Vector3 direction)
    {
        Vector3 adjustVelocityDirection = Vector3.ProjectOnPlane(direction, slopeHit.normal).normalized;
        return adjustVelocityDirection;
    }
}

 

728x90
๋ฐ˜์‘ํ˜•