[월간TECH프로젝트] 로블록스 "몬스터 Bloody Ghost Girl" 스크립트 분석기

2021. 11. 8. 22:48
728x90
반응형

 

=== Prologue ===

 

스크립트에 대한 이론(문서)을 읽었으니 

로블록스 플레이어 or 몬스터를 작동시키려면 스크립트를 알아야 한다.

해서 하나하나 분석해보려고 한다. 

구조를 알아야 나도 플레이어를 작동시킬 수 있지 않은가! 

 

🧨 긴 글이 예상되오니 숨 한번 크게 쉬고 따라와 주십시오! 

 

 

==== Mission ====

 

"빨간 마스크"를 만들어야 하므로 몬스터들을 먼저 분석해 보기로 했다.

로블록스 스튜디오에는 다른 개발자 또는 크리에이터들이 만들어 공유해 놓은 오브젝트들이 많다.

그중에서 몬스터를 검색해 몬스터들을 데리고 왔다. 많은 몬스터들을 분석해보자. 

(개발자 야잴씨가 불러다 놓은 몬스터 친구들 중 Bloody Ghost Girl 이 어떻게 구성되어 있는지 보기로 했다.)  

게임 플레이를 하면 저 친구들이 빨간 아우라를 내뿜으면서 하체가 없어지던데... 

 

Bloody Ghost Girl 오브젝트

 

=== description ===

 

게임을 플레이했을 때 이런 식으로 움직인다! 

(다른 몬스터들도 있으니 저 중간.. 빨간 기운 내뿜는 친구들에 주목하자!) 

📍 1:00 분부터 볼 수 있다! 

 

 

 

▼ 이렇게 생긴 친구들이다. Twins 들인지는 움직여 봐야 알겠지?  

 

 

 

반응형

 

==== detail progress ====

 

◆ Bloody Ghost Girl의 구조

◆ Appearance 들은 상체 / 하체로 나뉘어서 구성할 수 있다.

 

01. Body colors --> 몸 색깔들을 각각 지정할 수 있다. 

우리 피부색을 지정한다고 생각하면 될 것 같다. 

머리, 팔색, 다리 색, 몸통 색

HeadColor / HeadColor3로 색이 나뉘어 있는데 name 명과, rgb 색상을 구분해 놓은 파트 같다. 

 

🔎 torso - 몸통

(머리·손·발이 없이 몸통만으로 된 소상(塑像).) 

 

Body colors 속성

 

02. Pants --> 바지 

색상을 입히고, PantsTemplate를 가져왔다.

3D로 제작한 템플릿을 오브젝트에 적용시켰다. 

적용시키기 위해서는 id값이 필요한데, asset폴더에 올린 후 id값을 추출해서 주소로 적용시키면 되는 것 같다. 

 

지난 포스팅의 배경음악 올리는 법과 동일하다고 보면 될 것 같다. 

외부에서 무언가를 가져올 때는 id값이 필요하다는 것을 숙지하자.

 

Pants 속성

03. Shirts --> 상의

마찬가지로 색상 설정, 그리고 Template를 불러올 수 있다. 

템플릿을 보니 이미지를 불러와도, 그 이미지가 본체에 프린팅 된 것처럼 입혀질 수 있다는 것을 알았다. 

잘 보면 저 입모양이 캐릭터의 몸에 입혀져 있는 것을 볼 수 있다! 

3d가 필요 없을 수도 있지 않을까? 

 

shirst 속성

 

03. Animatesauce --> 이 애니메이트 소스는 로블록스 자체 속성들이다. 

title에서 직관적으로 알 수 있게 적어놓았다. 아마 기본적 움직임에 관련된 속성이지 않을까 

오르고, 떨어지고, 대기상태이거나, 점프, 달리고 앉기, toolnone(?), 걷기 

 

🔎 toolnone은 이런 속성이라고 한다. 

뭔가 장착되어있는 도구(연장)들과 함께 서있는 경우인 듯하다.

 

tool slash is basically what anime you do when you click when tool equipped , tool none is the anime when you just stand with a tool equipped. idk what is tool lunge

 

animate tool

04. Coldblooded killer (변수명)

스크립트들의 집합인데, 하나하나 살펴보자

🧨 However, 너무 깊게 정독하지 않으셔도 되는 part입니다.

저의 간단한 코멘트들만 봐주셔도 무방합니다. 

 

Health part  = Humanoid의 HP 값에 관한 스크립트인 듯하다.  

몬스터와 닿았을 때나 이벤트가 일어났을 때 HP의 값을 담당하는 스크립트이지 않을까

 

그에 대한 필자의 해석 / 분석

--Responsible for regening a player's humanoid's health

-- declarations //선언- 변수정의  
local Figure = script.Parent // 이 스크립트의 부모 그러니까 object를 Figure라고 하자. 
local Head = Figure:WaitForChild("Head") // 이 본체의 자식중에 head라는 부분을 찾아 head라고 정의하자 
local Humanoid; 
for _,Child in pairs(Figure:GetChildren())do // for문을 돌렸는데 비워둘 곳은 _ 로 표시한다. Getchilderen() 을 실행시키고 반복해라.
if Child and Child.ClassName=="Humanoid"then //만약 child와 child의 클래스이름 "Humanoid" 가 같으면 이 반복문을 종료시킨다.
Humanoid=Child;
end;
end;
local regening = false // regening이라는 변수는 false로 정의해놓자.

-- regeneration //재생, 부활 
function regenHealth()
	if regening then return end
	regening = true
	
	while Humanoid.Health < Humanoid.MaxHealth do // Humanoid에서 health를 찾아, health가 최대치보다 작으면
		local s = wait(1) // 1초 기다림을 s로 정의하자 
		local health = Humanoid.Health
		if health~=0 and health < Humanoid.MaxHealth then // health 가 0이아니거나, 최대치보다 작으면,
			local newHealthDelta = 0.007 * s * Humanoid.MaxHealth // 계속 최대치에서 1초이후에 0.007을 곱한 수 만큼 새로운 델타값에(변수) 넣어준다.
			health = health + newHealthDelta // 기존 healt 값과 + 새로운 newHelathDelta값을 더해 그 수치를 health에 다시 넣어준다.
			Humanoid.Health = math.min(health,Humanoid.MaxHealth) //최소값은 health와 최대치 사이에 존재하도록한다. 
		end
	end
	
	if Humanoid.Health > Humanoid.MaxHealth then // 만약에 최대보다 humanoid 의 수치가 컸을때에는 
		Humanoid.Health = Humanoid.MaxHealth // 최대치로 같도록 하자. 
	end
	
	regening = false
end

Humanoid.HealthChanged:connect(regenHealth)

 

 

MonsterMain  = 몬스터 본체에 대한, 몬스터 움직임에 대한 내용이지 싶다.

 

그에 대한 필자의 해석 / 분석

 

몬스터에 대해 경우에 따라 조건문으로 분리시켜놓았다.

- 빨간불 빛(광선)의 위치 

몬스터와 범위가 수치상으로 (25,50 기준) 멀어졌을 때와 아닐 때

- 타깃 플레이어에게 어떤 대미지를 줄 것인지 

- 배경음악을 어떻게 할 건지

몬스터의 동작에 (점프, 앉았을 때, 회전했을 때) 따라 어떤 조건을 줄 것인지 분리시켜놓았다.

 

--[[ By: Brutez. ]]--

--[[ 본체에 대한 스크립트]] --
local JeffTheKillerScript=script;
repeat Wait(0)until JeffTheKillerScript and JeffTheKillerScript.Parent and JeffTheKillerScript.Parent.ClassName=="Model"and JeffTheKillerScript.Parent:FindFirstChild("Head")and JeffTheKillerScript.Parent:FindFirstChild("Torso");
--[[ 기다림없이 시작한다. JeffTheKiller 오브젝트를 반복.]]--

--[[JeffTheKiller 는 JeffTheKillerScript와 같다.]]
local JeffTheKiller=JeffTheKillerScript.Parent; 

--[[함수 생성]]--
--[[광선투사 : 빨간색 빛 줄기를 나타내는 함수 생성
    계산식을 나타내주는 것 같다. 
    워크스페이스에서 FindPartOnRay를 찾아 그 Ray에 새롭게 벡터를 계산해줘서 (vec) 현재 거리를 곱하고 그것을 위치에 나타낼 수 있게 해주는 함수인것같다.) 
    //The CanCollide property determines whether a part will physically interact with other parts. 
]]--

function raycast(Spos,vec,currentdist)
    local hit2,pos2=game.Workspace:FindPartOnRay(Ray.new(Spos+(vec*.05),vec*currentdist),JeffTheKiller);
        if hit2~=nil and pos2 then -- 만약 hit2가 nil이아니고, pos2일때에는 
            if hit2.Name=="Handle" and not hit2.CanCollide or string.sub(hit2.Name,1,6)=="Effect"and not hit2.CanCollide then -- hit2의 name이 "handle"이고, hit2의 cancollide 거나  hit2의 cancollide가 ..string.sub는 어떤 것을 의미하는지 모르겠다.
                local currentdist=currentdist-(pos2-Spos).magnitude; -- 새로 계산한 값을 원래의 값에 넣어주고, 
                return raycast(pos2,vec,currentdist); -- return
            end;
        end;
    return hit2,pos2;
end;


function RayCast(Position,Direction,MaxDistance,IgnoreList) -- 광선의 최대치를 나타내줄 수 있도록 하는 함수 인것 같다. 
return Game:GetService("Workspace"):FindPartOnRayWithIgnoreList(Ray.new(Position,Direction.unit*(MaxDistance or 999.999)),IgnoreList);
end;
--[[if JeffTheKillerScript and JeffTheKiller and JeffTheKiller:FindFirstChild("Thumbnail")then]]--
--[[JeffTheKiller:FindFirstChild("Thumbnail"):Destroy();]]--
--[[end;]]--
local JeffTheKillerHumanoid; -- 플레이어 
for _,Child in pairs(JeffTheKiller:GetChildren())do
    if Child and Child.ClassName=="Humanoid"and Child.Health~=0 then
    JeffTheKillerHumanoid=Child;
    end;
end;



local AttackDebounce=false;
local JeffTheKillerKnife=JeffTheKiller:FindFirstChild("Knife");
local JeffTheKillerHead=JeffTheKiller:FindFirstChild("Head");
local JeffTheKillerHumanoidRootPart=JeffTheKiller:FindFirstChild("HumanoidRootPart");
local WalkDebounce=false;
local Notice=false;
local JeffLaughDebounce=false;
local MusicDebounce=false;
local NoticeDebounce=false;
local ChosenMusic;
JeffTheKiller:FindFirstChild("Torso"):FindFirstChild("Neck").C0=CFrame.new(0,1,0,-1,0,0,0,0,1,0,1,-0);
local OriginalC0=JeffTheKiller:FindFirstChild("Torso"):FindFirstChild("Neck").C0;


--[[타겟플레이어를 찾아 찾았다면 이벤트]]--
function FindNearestBae()
    local NoticeDistance=300;
    local TargetMain;
    for _,TargetModel in pairs(Game:GetService("Workspace"):GetChildren())do -- 게임이 시작되었을때, 워크스페이스에서 getchildren() 실행시키고, targetModel을 찾아서, 반복
        if JeffTheKillerScript and JeffTheKiller and JeffTheKillerHumanoid and JeffTheKillerHumanoid.Health~=0 and TargetModel.className=="Model"and TargetModel~=JeffTheKiller and TargetModel.Name~=JeffTheKiller.Name and TargetModel:FindFirstChild("Torso")and TargetModel:FindFirstChild("Head")then -- 살아있는 타겟을 찾아서 
            local TargetPart=TargetModel:FindFirstChild("Torso"); --몸통을 먼저 찾아
            local FoundHumanoid;
            for _,Child in pairs(TargetModel:GetChildren())do 
                if Child and Child.ClassName=="Humanoid"and Child.Health~=0 then
                FoundHumanoid=Child; -- 살아있는 타켓을 찾아서 foundhumanoid 를 child 함수로 일치시키고 빠져나오자.
                end;
    end;

    -- 살아있는 타겟 플레이어를 찾아서 타켓메인을 타겟파트로 지정하고, 타겟파트의 위치에서 몬스터의 기본위치를 뺀값의 magnitude를 거리 적정선에 넣어준다.
    if TargetModel and TargetPart and FoundHumanoid and FoundHumanoid.Health~=0 and(TargetPart.Position-JeffTheKillerHumanoidRootPart.Position).magnitude<NoticeDistance then
    TargetMain=TargetPart;
    NoticeDistance=(TargetPart.Position-JeffTheKillerHumanoidRootPart.Position).magnitude;

    -- raycast 함수를 불러오자
    local hit,pos=raycast(JeffTheKillerHumanoidRootPart.Position,(TargetPart.Position-JeffTheKillerHumanoidRootPart.Position).unit,500)

    -- hit 이라는 오브젝트 또는 변수이면  타겟모델이 살아있다면 
    if hit and hit.Parent and hit.Parent.ClassName=="Model"and hit.Parent:FindFirstChild("Torso")and hit.Parent:FindFirstChild("Head")then
        if TargetModel and TargetPart and FoundHumanoid and FoundHumanoid.Health~=0 and(TargetPart.Position-JeffTheKillerHumanoidRootPart.Position).magnitude<9 and not AttackDebounce then
        Spawn(function()
            AttackDebounce=true; -- spawn(에서 공격을 true로 바꾸자. AttackDebounce)

            local SwingAnimation=JeffTheKillerHumanoid:LoadAnimation(JeffTheKiller:FindFirstChild("Swing"));
            local SwingChoice=math.random(1,2);
            local HitChoice=math.random(1,3);

            SwingAnimation:Play(); -- 스윙애니메이션 실행, 스피드도 조정한다. 
            SwingAnimation:AdjustSpeed(1.5+(math.random()*0.1));

            -- 스윙할때 나는 사운드도 플레이 시킨다. 
            if JeffTheKillerScript and JeffTheKiller and JeffTheKillerKnife and JeffTheKillerKnife:FindFirstChild("Swing")then
                local SwingSound=JeffTheKillerKnife:FindFirstChild("Swing");
                SwingSound.Pitch=1+(math.random()*0.04);
                SwingSound:Play();
        end;

    Wait(0.2);


    -- 타겟모델의 거리가 8보다 작을때에는 takeDamage를 25정도 주자. 
    if TargetModel and TargetPart and FoundHumanoid and FoundHumanoid.Health~=0 and(TargetPart.Position-JeffTheKillerHumanoidRootPart.Position).magnitude<8 then
    FoundHumanoid:TakeDamage(25);

    -- 위에 hitchoice가 랜덤으로 실행되던데 hitchoice에 관한 사운드가 조건문으로 걸려있다. 
    if HitChoice==1 and JeffTheKillerScript and JeffTheKiller and JeffTheKillerKnife and JeffTheKillerKnife:FindFirstChild("Hit1")then
        local HitSound=JeffTheKillerKnife:FindFirstChild("Hit1");
        HitSound.Pitch=1+(math.random()*0.04);
        HitSound:Play();
    elseif HitChoice==2 and JeffTheKillerScript and JeffTheKiller and JeffTheKillerKnife and JeffTheKillerKnife:FindFirstChild("Hit2")then
        local HitSound=JeffTheKillerKnife:FindFirstChild("Hit2");
        HitSound.Pitch=1+(math.random()*0.04);
        HitSound:Play();
    elseif HitChoice==3 and JeffTheKillerScript and JeffTheKiller and JeffTheKillerKnife and JeffTheKillerKnife:FindFirstChild("Hit3")then
        local HitSound=JeffTheKillerKnife:FindFirstChild("Hit3");
        HitSound.Pitch=1+(math.random()*0.04);
        HitSound:Play();
        end;
    end;
    Wait(0.1);

    AttackDebounce=false;
    end);
    end;
    end;
    end;
    end;
    end;
    return TargetMain;
end;




while Wait(0)do
    
    local TargetPoint=JeffTheKillerHumanoid.TargetPoint; -- 타겟 포인트를 정의한다. 
    -- 광선의 기본 위치를 정의. (빨간색 불빛)
    local Blockage,BlockagePos=RayCast((JeffTheKillerHumanoidRootPart.CFrame+CFrame.new(JeffTheKillerHumanoidRootPart.Position,Vector3.new(TargetPoint.X,JeffTheKillerHumanoidRootPart.Position.Y,TargetPoint.Z)).lookVector*(JeffTheKillerHumanoidRootPart.Size.Z/2)).p,JeffTheKillerHumanoidRootPart.CFrame.lookVector,(JeffTheKillerHumanoidRootPart.Size.Z*2.5),{JeffTheKiller,JeffTheKiller})
    
    local Jumpable=false; -- 점프할수있는 것을 false로 세팅
    if Blockage then -- 블럭에이지가 되면, 
        Jumpable=true; -- 점프능력을 true 
        if Blockage and Blockage.Parent and Blockage.Parent.ClassName~="Workspace"then
        local BlockageHumanoid; 
        for _,Child in pairs(Blockage.Parent:GetChildren())do
            if Child and Child.ClassName=="Humanoid"and Child.Health~=0 then
            BlockageHumanoid=Child;
        end;
    end;

    if Blockage and Blockage:IsA("Terrain")then
        local CellPos=Blockage:WorldToCellPreferSolid((BlockagePos-Vector3.new(0,2,0)));
        local CellMaterial,CellShape,CellOrientation=Blockage:GetCell(CellPos.X,CellPos.Y,CellPos.Z);
        if CellMaterial==Enum.CellMaterial.Water then
        Jumpable=false;
    end;
    elseif BlockageHumanoid or Blockage.ClassName=="TrussPart"or Blockage.ClassName=="WedgePart"or Blockage.Name=="Handle"and Blockage.Parent.ClassName=="Hat"or Blockage.Name=="Handle"and Blockage.Parent.ClassName=="Tool"then
    Jumpable=false;
    end;
    end;

    if JeffTheKillerScript and JeffTheKiller and JeffTheKillerHumanoid and JeffTheKillerHumanoid.Health~=0 and not JeffTheKillerHumanoid.Sit and Jumpable then
    JeffTheKillerHumanoid.Jump=true;
    end;
    end;

    if JeffTheKillerScript and JeffTheKiller and JeffTheKillerHead and JeffTheKillerHumanoidRootPart and JeffTheKillerHead:FindFirstChild("Jeff_Step")and (JeffTheKillerHumanoidRootPart.Velocity-Vector3.new(0,JeffTheKillerHumanoidRootPart.Velocity.y,0)).magnitude>=5 and not WalkDebounce and JeffTheKillerHumanoid and JeffTheKillerHumanoid.Health~=0 then
    Spawn(function()
    WalkDebounce=true;
    local FiredRay=Ray.new(JeffTheKillerHumanoidRootPart.Position,Vector3.new(0,-4,0));
    local RayTarget,endPoint=Game:GetService("Workspace"):FindPartOnRay(FiredRay,JeffTheKiller);
    if RayTarget then
    local JeffTheKillerHeadFootStepClone=JeffTheKillerHead:FindFirstChild("Jeff_Step"):Clone();
    JeffTheKillerHeadFootStepClone.Parent=JeffTheKillerHead;
    JeffTheKillerHeadFootStepClone:Play();
    JeffTheKillerHeadFootStepClone:Destroy();
    if JeffTheKillerScript and JeffTheKiller and JeffTheKillerHumanoid and JeffTheKillerHumanoid.Health~=0 and JeffTheKillerHumanoid.WalkSpeed<17 then
    Wait(0.4);
    elseif JeffTheKillerScript and JeffTheKiller and JeffTheKillerHumanoid and JeffTheKillerHumanoid.Health~=0 and JeffTheKillerHumanoid.WalkSpeed>17 then
    Wait(0.15);
    end
    end;
    WalkDebounce=false;
    end);
    end;

    --[[위의 함수를 불러와서 메인 타겟이라고 정의한다.]]--
    local MainTarget=FindNearestBae();
    local FoundHumanoid;

    if MainTarget then
    for _,Child in pairs(MainTarget.Parent:GetChildren())do
    if Child and Child.ClassName=="Humanoid"and Child.Health~=0 then
    FoundHumanoid=Child;
    end;
    end;
    end;

    if JeffTheKillerScript and JeffTheKiller and JeffTheKillerHumanoid and JeffTheKillerHumanoid.Health~=0 and MainTarget and MainTarget.Parent and FoundHumanoid and FoundHumanoid.Jump then
    JeffTheKillerHumanoid.Jump=true;
    end;
    
    -- 메인타겟의 범위가 25보다 작을때와 클때로 나눠, 웃음소리를 play 하고 off한다. -소리가 멀어지는 경우도 있다. 
    if JeffTheKillerScript and JeffTheKiller and JeffTheKillerHumanoid and JeffTheKillerHumanoid.Health~=0 and MainTarget and FoundHumanoid and FoundHumanoid.Health~=0 and(MainTarget.Position-JeffTheKillerHumanoidRootPart.Position).magnitude<25 then
        if JeffTheKillerScript and JeffTheKiller and JeffTheKillerHead and JeffTheKillerHead:FindFirstChild("Jeff_Laugh")and not JeffTheKillerHead:FindFirstChild("Jeff_Laugh").IsPlaying then
        JeffTheKillerHead:FindFirstChild("Jeff_Laugh").Volume=1;
        JeffTheKillerHead:FindFirstChild("Jeff_Laugh"):Play();
        end;
    elseif JeffTheKillerScript and JeffTheKiller and JeffTheKillerHumanoid and JeffTheKillerHumanoid.Health~=0 and MainTarget and FoundHumanoid and FoundHumanoid.Health~=0 and(MainTarget.Position-JeffTheKillerHumanoidRootPart.Position).magnitude>25 then
    if JeffTheKillerScript and JeffTheKiller and JeffTheKillerHead and JeffTheKillerHead:FindFirstChild("Jeff_Laugh")and JeffTheKillerHead:FindFirstChild("Jeff_Laugh").IsPlaying then
    if not JeffLaughDebounce then
    Spawn(function()
    JeffLaughDebounce=true;
    repeat Wait(0);if JeffTheKillerScript and JeffTheKiller and JeffTheKillerHead and JeffTheKillerHead:FindFirstChild("Jeff_Laugh")then JeffTheKillerHead:FindFirstChild("Jeff_Laugh").Volume=JeffTheKillerHead:FindFirstChild("Jeff_Laugh").Volume-0.1;else break;end;until JeffTheKillerHead:FindFirstChild("Jeff_Laugh").Volume==0 or JeffTheKillerHead:FindFirstChild("Jeff_Laugh").Volume<0;
    JeffTheKillerHead:FindFirstChild("Jeff_Laugh").Volume=0;
    JeffTheKillerHead:FindFirstChild("Jeff_Laugh"):Stop();
    JeffLaughDebounce=false;
    end);
    end;
    end;
    end;

    -- 메인타겟의 범위가 50보다 작을때와 클때로 나눠 sound를 다르게 한다. 
    if not ChosenMusic and JeffTheKillerScript and JeffTheKiller and JeffTheKillerHumanoid and JeffTheKillerHumanoid.Health~=0 and MainTarget and FoundHumanoid and FoundHumanoid.Health~=0 and(MainTarget.Position-JeffTheKillerHumanoidRootPart.Position).magnitude<50 then
        local MusicChoice=math.random(1,2);
    if MusicChoice==1 and JeffTheKillerScript and JeffTheKiller and JeffTheKiller:FindFirstChild("Jeff_Scene_Sound1")then
    ChosenMusic=JeffTheKiller:FindFirstChild("Jeff_Scene_Sound1");
    elseif MusicChoice==2 and JeffTheKillerScript and JeffTheKiller and JeffTheKiller:FindFirstChild("Jeff_Scene_Sound2")then
    ChosenMusic=JeffTheKiller:FindFirstChild("Jeff_Scene_Sound2");
    end;
    if JeffTheKillerScript and JeffTheKiller and ChosenMusic and not ChosenMusic.IsPlaying then
    ChosenMusic.Volume=1;
    ChosenMusic:Play();
    end;
    elseif JeffTheKillerScript and JeffTheKiller and JeffTheKillerHumanoid and JeffTheKillerHumanoid.Health~=0 and MainTarget and FoundHumanoid and FoundHumanoid.Health~=0 and(MainTarget.Position-JeffTheKillerHumanoidRootPart.Position).magnitude>50 then
    if JeffTheKillerScript and JeffTheKiller and ChosenMusic and ChosenMusic.IsPlaying then
    if not MusicDebounce then
    Spawn(function()
    MusicDebounce=true;
    repeat Wait(0);if JeffTheKillerScript and JeffTheKiller and ChosenMusic then ChosenMusic.Volume=ChosenMusic.Volume-0.01;else break;end;until ChosenMusic.Volume==0 or ChosenMusic.Volume<0;
    if ChosenMusic then
    ChosenMusic.Volume=0;
    ChosenMusic:Stop();
    end;
    ChosenMusic=nil;
    MusicDebounce=false;
    end);
    end;
    end;
    end;
    if not MainTarget and not JeffLaughDebounce then
    Spawn(function()
    JeffLaughDebounce=true;
    repeat Wait(0);if JeffTheKillerScript and JeffTheKiller and JeffTheKillerHead and JeffTheKillerHead:FindFirstChild("Jeff_Laugh")then JeffTheKillerHead:FindFirstChild("Jeff_Laugh").Volume=JeffTheKillerHead:FindFirstChild("Jeff_Laugh").Volume-0.1;else break;end;until JeffTheKillerHead:FindFirstChild("Jeff_Laugh").Volume==0 or JeffTheKillerHead:FindFirstChild("Jeff_Laugh").Volume<0;
    JeffTheKillerHead:FindFirstChild("Jeff_Laugh").Volume=0;
    JeffTheKillerHead:FindFirstChild("Jeff_Laugh"):Stop();
    JeffLaughDebounce=false;
    end);
    end;
    if not MainTarget and not MusicDebounce then
    Spawn(function()
    MusicDebounce=true;
    repeat Wait(0);if JeffTheKillerScript and JeffTheKiller and ChosenMusic then ChosenMusic.Volume=ChosenMusic.Volume-0.01;else break;end;until ChosenMusic.Volume==0 or ChosenMusic.Volume<0;
    if ChosenMusic then
    ChosenMusic.Volume=0;
    ChosenMusic:Stop();
    end;
    ChosenMusic=nil;
    MusicDebounce=false;
    end);
    end;
    if MainTarget then
    Notice=true;
    if Notice and not NoticeDebounce and JeffTheKillerScript and JeffTheKiller and JeffTheKillerHead and JeffTheKillerHead:FindFirstChild("Jeff_Susto2")then
    JeffTheKillerHead:FindFirstChild("Jeff_Susto2"):Play();
    NoticeDebounce=true;
    end
    if JeffTheKillerScript and JeffTheKiller and JeffTheKillerHumanoid and JeffTheKillerHumanoid.Health~=0 then
    if MainTarget and FoundHumanoid and FoundHumanoid.Health~=0 and(MainTarget.Position-JeffTheKillerHumanoidRootPart.Position).magnitude>5 then
    JeffTheKillerHumanoid.WalkSpeed=36;
    elseif MainTarget and FoundHumanoid and FoundHumanoid.Health~=0 and(MainTarget.Position-JeffTheKillerHumanoidRootPart.Position).magnitude<5 then
    JeffTheKillerHumanoid.WalkSpeed=0.004;
    end;
    JeffTheKillerHumanoid:MoveTo(MainTarget.Position+(MainTarget.Position-JeffTheKillerHumanoidRootPart.Position).unit*2,Game:GetService("Workspace"):FindFirstChild("Terrain"));
    local NeckRotation=(JeffTheKiller:FindFirstChild("Torso").Position.Y-MainTarget.Parent:FindFirstChild("Head").Position.Y)/10;
    if NeckRotation>-1.5 and NeckRotation<1.5 then
    JeffTheKiller:FindFirstChild("Torso"):FindFirstChild("Neck").C0=OriginalC0*CFrame.fromEulerAnglesXYZ(NeckRotation,0,0);
    end;
    if NeckRotation<-1.5 then
    JeffTheKiller:FindFirstChild("Torso"):FindFirstChild("Neck").C0=CFrame.new(0,1,0,-1,0,0,0,-0.993636549,0.112633869,0,0.112633869,0.993636549);
    elseif NeckRotation>1.5 then
    JeffTheKiller:FindFirstChild("Torso"):FindFirstChild("Neck").C0=CFrame.new(0,1,0,-1,0,0,0,0.996671617,0.081521146,0,0.081521146,-0.996671617);
    end;
    else
    end;
    else
    Notice=false;
    NoticeDebounce=false;
    JeffTheKiller:FindFirstChild("Torso"):FindFirstChild("Neck").C0=CFrame.new(0,1,0,-1,0,0,0,0,1,0,1,-0);
    local RandomWalk=math.random(1,150);
    if JeffTheKillerScript and JeffTheKiller and JeffTheKillerHumanoid and JeffTheKillerHumanoid.Health~=0 then
    JeffTheKillerHumanoid.WalkSpeed=7;
    if RandomWalk==1 then
    JeffTheKillerHumanoid:MoveTo(Game:GetService("Workspace"):FindFirstChild("Terrain").Position+Vector3.new(math.random(-2048,2048),0,math.random(-2048,2048)),Game:GetService("Workspace"):FindFirstChild("Terrain"));
    end;
    end;
    end;

    if JeffTheKillerScript and JeffTheKiller and JeffTheKillerHumanoid then
    JeffTheKillerHumanoid.DisplayDistanceType="None";
    JeffTheKillerHumanoid.HealthDisplayDistance=0;
    JeffTheKillerHumanoid.Name="ColdBloodedKiller";
    JeffTheKillerHumanoid.NameDisplayDistance=0;
    JeffTheKillerHumanoid.NameOcclusion="EnemyOcclusion";
    JeffTheKillerHumanoid.AutoJumpEnabled=true;
    JeffTheKillerHumanoid.AutoRotate=true;
    JeffTheKillerHumanoid.MaxHealth=800;
    JeffTheKillerHumanoid.JumpPower=60;
    JeffTheKillerHumanoid.MaxSlopeAngle=89.9;
    end;
    
    if JeffTheKillerScript and JeffTheKiller and JeffTheKillerHumanoid and not JeffTheKillerHumanoid.AutoJumpEnabled then
    JeffTheKillerHumanoid.AutoJumpEnabled=true;
    end;
    if JeffTheKillerScript and JeffTheKiller and JeffTheKillerHumanoid and not JeffTheKillerHumanoid.AutoRotate then
    JeffTheKillerHumanoid.AutoRotate=true;
    end;
    if JeffTheKillerScript and JeffTheKiller and JeffTheKillerHumanoid and JeffTheKillerHumanoid.PlatformStand then
    JeffTheKillerHumanoid.PlatformStand=false;
    end;
    if JeffTheKillerScript and JeffTheKiller and JeffTheKillerHumanoid and JeffTheKillerHumanoid.Sit then
    JeffTheKillerHumanoid.Sit=false;
    end;
end;
--[[ By: Brutez. ]]--

 

 

Respawn= 플레이어가 죽었을 때 새로 살아나는 위치를 재정의한다. 

 

죽으면 플레이어들의 관절이 파괴되게 되는데, 

다시 5초 기다렸다가 파괴된 관절들을 재 조립해주는

MakeJoints()를 써준다.

 

🔎 SpawnLocations, or “spawns” determine where a Player respawns when they die.

 

 

그에 대한 필자의 해석 / 분석

 

--[[ By: Brutez, 2/28/2015, 1:34 AM, (UTC-08:00) Pacific Time (US & Canada) ]]--
local PlayerSpawning=false; --[[ Change this to true if you want the NPC to spawn like a player, and change this to false if you want the NPC to spawn at it's current position. ]]--
local AdvancedRespawnScript=script;
repeat Wait(0)until script and script.Parent and script.Parent.ClassName=="Model";
local JeffTheKiller=AdvancedRespawnScript.Parent;

if AdvancedRespawnScript and JeffTheKiller and JeffTheKiller:FindFirstChild("Thumbnail")then
    JeffTheKiller:FindFirstChild("Thumbnail"):Destroy();
end;

--[[게임이 시작되면, debris를 불러온다. ]]--
local GameDerbis=Game:GetService("Debris");
local JeffTheKillerHumanoid;
for _,Child in pairs(JeffTheKiller:GetChildren())do
    if Child and Child.ClassName=="Humanoid"and Child.Health~=0 then
    JeffTheKillerHumanoid=Child;
    end;
end;

local Respawndant=JeffTheKiller:Clone();
if PlayerSpawning then --[[ LOOK AT LINE: 2. ]]--
    coroutine.resume(coroutine.create(function()
        --만약 JeffTheKiller가 status상태거나 availablespawns 상태라면 새로운 model 인스턴스를 주고, 
    if JeffTheKiller and JeffTheKillerHumanoid and JeffTheKillerHumanoid:FindFirstChild("Status")and not JeffTheKillerHumanoid:FindFirstChild("Status"):FindFirstChild("AvalibleSpawns")then
    SpawnModel=Instance.new("Model");
    SpawnModel.Parent=JeffTheKillerHumanoid.Status;
    SpawnModel.Name="AvalibleSpawns";
    else -- 그게 아니라면, spawnModel을 그 상태가 되게끔 정의해준다.
    SpawnModel=JeffTheKillerHumanoid:FindFirstChild("Status"):FindFirstChild("AvalibleSpawns");
    end;

    function FindSpawn(SearchValue)
    local PartsArchivable=SearchValue:GetChildren();
        for AreaSearch=1,#PartsArchivable do
            if PartsArchivable[AreaSearch].className=="SpawnLocation"then
            local PositionValue=Instance.new("Vector3Value",SpawnModel);
            PositionValue.Value=PartsArchivable[AreaSearch].Position;
            PositionValue.Name=PartsArchivable[AreaSearch].Duration;
            end;
            FindSpawn(PartsArchivable[AreaSearch]);
        end;
    end;
    FindSpawn(Game:GetService("Workspace"));

    local SpawnChilden=SpawnModel:GetChildren();
    if#SpawnChilden>0 then
    local SpawnItself=SpawnChilden[math.random(1,#SpawnChilden)];
    local RespawningForceField=Instance.new("ForceField");
    RespawningForceField.Parent=JeffTheKiller;
    RespawningForceField.Name="SpawnForceField";
    GameDerbis:AddItem(RespawningForceField,SpawnItself.Name);
    JeffTheKiller:MoveTo(SpawnItself.Value+Vector3.new(0,3.5,0));
    else

    if JeffTheKiller:FindFirstChild("SpawnForceField")then
    JeffTheKiller:FindFirstChild("SpawnForceField"):Destroy();
    end;
    JeffTheKiller:MoveTo(Vector3.new(0,115,0));
    end;
    end));



end;


-- 다시 위치를 나타내주는데 5초 후에,  makejoints로 파괴된 관절을  붙여줘야한다.
function Respawn()
Wait(5);
Respawndant.Parent=JeffTheKiller.Parent;
Respawndant:makeJoints();
Respawndant:FindFirstChild("Head"):MakeJoints();
Respawndant:FindFirstChild("Torso"):MakeJoints();
JeffTheKiller:remove();
end;
if AdvancedRespawnScript and JeffTheKiller and JeffTheKillerHumanoid then
JeffTheKillerHumanoid.Died:connect(Respawn);
end;
--[[ By: Brutez, 2/28/2015, 1:34 AM, (UTC-08:00) Pacific Time (US & Canada) ]]--

 

Vaccine= 뭔가를 흩뿌려주는 이벤트 함수와, getAllItems()처럼,, object를 주웠을 때 찾아주는.. 그런 코드인 것 같다. 

게임 규칙이나 튜토리얼 대로 뭔가를 주었을 때 반응하는 스크립트인 것 같다.

 

05. 캐릭터들을 나타내는 ui적 요소들

 

제목에서도 알 수 있듯이, Thumbnail, head,, 몸통 다리 등등을 개별적으로 조립한 것이다 보니 그에 대한 색상을 지정하는 것들로 주를 이루었다. 

 

06.  ScriptTeacher 

 

스크립트 티쳐들은 뭐지..? 뭔가 아이콘을 보면 공유되어있거나 링크 같은 표시인데 swing이 코드에 들어있던 것으로 봐서는 뭔가 애니메이션 같은 움직임을 나타내는것 같다. 비디오아이콘인데, 어떤 함수가 실행될때 같이 실행되는 것같다. 

Lol이라는 스크립트는 웃음소리에 관한 스크립트였다! 

 

==== Result ====

 

🌵 lua 문법의 주석처리는 -- or --[[ ]]--으로 처리한다. 

🌵 클래스는 : 로 이어 표시한다. (클래스가 없다고 했는데 편의상 그렇게 부르겠다)

🌵 몬스터에 관해 전반적으로 플레이어에 대한 코드, 몬스터 본체, 위치에 대한 부분, 아이템을 주웠을 때(?) 이벤트 등으로 나뉘었다. 

🌵 어떤 식으로 로직을 짜야하는지 대충 감이 오는 것 같기도 하다.  

🌵 막막함도 차근차근하다 보면 길이 있다는 것을 느꼈다. 

 

 

 

==== Reference====

 

📌 라이브러리에 있는 몬스터 개체를 가지고 분석하였습니다. 

📌 로블록스 공식 문서 페이지를 참고하였습니다. 

https://developer.roblox.com/en-us/api-reference

 

Roblox API Reference Manual

This Platform uses cookies to offer you a better experience, to personalize content, to provide social media features and to analyse the traffic on our site. For further information, including information on how to prevent or manage the use of cookies on t

developer.roblox.com

 

 

 

 

==== See More ====

 

🧐 로블록스 "빨간 마스크" 게임 제작기에 대한 개발자야잴씨의 테크 일지가 궁금하다면? 

 

https://jaylee222.github.io/tech/

 

테크노트 | 중얼대는 테크블로그

중얼대는 테크블로그

jaylee222.github.io

 

 

🧐 로블록스 "빨간 마스크" 게임 제작기가 궁금하다면? 

 

https://bokartstudio.tistory.com/68

 

[월간TECH프로젝트] 로블록스 "빨간마스크" 게임 제작기 1주차

=== Prologue === 첫 번째 월간 프로젝트는 "로블록스" 게임 제작으로 정했다. 최근 HOT 한 이슈인 메타버스의 대표주자이기도 했고, 앞으로 무궁무진한 발전 가능성을 보여주는 로블록스에 흥미가 갔

bokartstudio.tistory.com




 

 



 

 



728x90
반응형