NDC11_슈퍼클래스
description
Transcript of NDC11_슈퍼클래스
목차01. 지향점02. 개발관점의변화추적03. 슈퍼클래스소개04. 슈퍼클래스기반게임오브젝트시스템05. 기타사례및아이디어06. 결론07. Q&A
지향점1. 납득가능한기획적명세는가능한모두구현하자생산성, 확장성, 재사용성
2. 빠르게개발하되코드의품질을일정수준이상으로유지하자단순화, 표준화성능, 우아함
솔루션 : 개발 역량을 높이거나 더 오래 일을 하거나 ??
개발관점의변화추적절차적에서객체지향적으로일반화된효과적이고유연하며, 확장성높은개발추상화, 상속fread(header, 1, len, fp);f->Read(&header, len);
CreateTextureFromMemory(ptr, size, pTexture);CreateTextureFromFile(fp, &pTexture);CFileIO* f1 = CMemIO::Create(ptr, size);CreateTextureFromFile(f1, &pTexture);CFileIO* f2 = CStdIO::Open(L"checkbox.dds");CreateTextureFromFile(f2, &pTexture);CFileIO* f3 = CHttpIO::Open(L“http://softnette.com/checkbox.dds");CreateTextureFromFile(f3, &pTexture);
#Object Oriented Programming
개발관점의변화추적절차적에서객체지향적으로재사용성확장성을위한적절한설계가요구됨구성및설계에따른시행착오와정비로인한개발지연복잡도가높아질수록개발효율이떨어짐
개발관점의변화추적자료주도적으로프로그래머의존도축소개발관점에서생산성향상확장성
개발관점의변화추적자료주도적으로스크립트손쉽게프로그램과연동가능높은유연성
개발관점의변화추적자료주도적으로스크립트스크립트자체의접근성비프로그래머가작성한스크립트의품질과오류디버깅이슈활용도가높을수록성능이슈
개발관점의변화추적자료주도적으로그래프편집비프로그래머들도쉽게접근훌륭하게시각화된작업환경손쉬운바인딩확장성
Quartz composer
vvvvNDC2008: 차세대 게임 개발을 위한 실시간 비주얼 그래프 편집http://dev.ntreev.com/publications
개발관점의변화추적자료주도적으로그래프편집개발도구의높은편의성이요구됨기반시스템구축의난이도접근성복잡도와무관하게양이증가할수록직관성감소
vvvv video visualizer
개발관점의변화추적컴포넌트기반시스템객체를컴포넌트조합으로구성누구나기능조합가능다양한구성이가능
GDC Canada 2009:Theory and Practice of Game Object Component Architecture
NDC2010:M2아키텍처리뷰
컴포넌트기반시스템비의존적, 독립적인컴포넌트제작의난이도개발효율저하덜(?)직관적인코드간접참조의스트레스
내부통신으로인한성능문제
개발관점의변화추적
GDC Canada 2009:Theory and practi…
개발요구사항일반화도잘되면서구조가단순하고, 자료주도적이며,다양한기능조합도가능하고,개발스트레스없이빠르게개발가능한방법이목표입니다
슈퍼클래스한마디로, 하나의클래스에서모두구현모두(?)익숙하지만지양하고싶은모델
슈퍼클래스기반게임오브젝트한마디로, 하나의클래스에서모두구현모두(?)익숙하지만지양하고싶은모델역발상으로다기능을지향
슈퍼클래스기반게임오브젝트한마디로, 하나의클래스에서모두구현모두(?)익숙하지만지양하고싶은모델역발상으로극단적인다기능을지향
셰이더로보는사례슈퍼셰이더사례 (aka. UberShader)실시간렌더링의패러다임변화고정파이프라인에서프로그래머블셰이더시대로
셰이더시스템렌더링에최적화된프로세스유닛단순한구성, 대량의프로세스유닛추구동적분기취약하나의머트리얼은하나의셰이더 (혹은 technique)
셰이더로보는사례조합이슈
float4 ps(PS_IN In) : COLOR{
return tex2D(DiffuseMapSampler, In.Texcoord0.xy);}float4 ps_light(PS_IN In) : COLOR{
return tex2D(DiffuseMapSampler, In.Texcoord0.xy) * Lambert(In.Normal.xyz);}float4 ps_lightmap(PS_IN In) : COLOR{
return tex2D(DiffuseMapSampler, In.Texcoord0.xy) * tex2D(LightMapSampler, In.Texcoord1.xy);}
technique textureonly {pass p0 {
VertexShader = compile vs_3_0 vs();PixelShader = compile ps_3_0 ps();
}}
technique light {pass p0 {
VertexShader = compile vs_3_0 vs();PixelShader = compile ps_3_0 ps_light();
}}
technique lightmap {pass p0 {
VertexShader = compile vs_3_0 vs();PixelShader = compile ps_3_0 ps_lightmap();
}}
셰이더로보는사례조합이슈그림자기능추가시 (두배증가)
float4 ps(PS_IN In) : COLOR{
return tex2D(DiffuseMapSampler, In.Texcoord0.xy);}float4 ps_light(PS_IN In) : COLOR{
return tex2D(DiffuseMapSampler, In.Texcoord0.xy) * Lambert(In.Normal.xyz);}
float4 ps_lightmap(PS_IN In) : COLOR{
return tex2D(DiffuseMapSampler, In.Texcoord0.xy) * tex2D(LightMapSampler, In.Texcoord1.xy);}
float4 ps_withshadow(PS_IN In) : COLOR{
return tex2D(DiffuseMapSampler, In.Texcoord0.xy) * ProjShadow(In.ShadowPos);}
float4 ps_light_withshadow(PS_IN In) : COLOR{
return tex2D(DiffuseMapSampler, In.Texcoord0.xy) * Lambert(In.Normal.xyz) * ProjShadow(In.ShadowPos);}
float4 ps_lightmap_withshadow(PS_IN In) : COLOR{
return tex2D(DiffuseMapSampler, In.Texcoord0.xy) * tex2D(LightMapSampler, In.Texcoord1.xy) * ProjShadow(In.ShadowPos);}
셰이더로보는사례조합이슈그림자기능추가스키닝추가 (4배)스펙큘러마스크 (8배)리플렉션매핑 (16배)UV스크롤 (32배)노멀맵 (64배)대단한 의지가 아니면 관리가 불가능일정 시점 이상이 되면 셰이더의 난이도와 상관없이 작업 불가능한 상태대부분 정책적으로 기능을 제한하는 방향으로
셰이더로보는사례조합해법코드생성그래프편집셰이더조합스크립트
UberShader
Unreal3 Material Editor
셰이더로보는사례UberShader소스코드는유지하고전처리를통해서기능선택float4 ps(PS_IN In) : COLOR{
float4 out = tex2D(DiffuseMapSampler, In.Texcoord0.xy);#ifdef _LIGHT
out *= Lambert(In.Normal.xyz);#endif#ifdef _LIGHTMAP
out *= tex2D(LightMapSampler, In.Texcoord1.xy);#endif#ifdef _SHADOW
out *= ProjShadow(In.ShadowPos);#endif
return out;}
technique complete {pass p0 {
VertexShader = compile vs_3_0 vs();PixelShader = compile ps_3_0 ps();
}}
슈퍼클래스특징1. 요구사항을구현함에있어서구조적인부담이굉장히적다2. 쉽게자료화할수있다3. 도구화할수있다4. 표준화하여다양한파트에적용할수있다
슈퍼클래스적용시나리오이동하는코드를이용해가상의사례를하나구성해봅니다
void CActor::Move(const Vec &dv, int lv) {
float t = SweepTest(m_Pos, m_Radius, dv, NULL);m_Pos += dv * t;
}
슈퍼클래스적용시나리오요구사항 : 어떤객체는충돌이없습니다void CActor::Move(const Vec &dv){
if (m_flags&_NOCOLLISION){
m_Pos += dv;return ;
}
float t = SweepTest(m_Pos, m_Radius, dv, NULL);m_Pos += dv * t;
}
슈퍼클래스적용시나리오요구사항 : 슬라이딩이되어야합니다void CActor::Move(const Vec &dv, int lv) {
if (m_flags&_NOCOLLISION){
m_Pos += dv;return ;
}
Plane p;
float t = CMomo::GetInstance().SweepTest(m_Pos, m_Radius, dv, &p);
if (t < 1.0f){
if (t > 0)m_Pos += dv * t;
if (lv < 3){
Vec vr = dv * (1-t);float d1 = p.Distance(m_Pos) - m_Radius;Move(vr - p.Normal() * Vec::dot(vr, p.Normal()), lv+1);
}return;
}
m_Pos += dv;}
슈퍼클래스적용시나리오요구사항 : 슬라이딩안하는객체도있어요void CActor::Move(const Vec &dv, int lv) {
if (m_flags&_NOCOLLISION){
m_Pos += dv;return ;
}
Plane p;
float t = CMomo::GetInstance().SweepTest(m_Pos, m_Radius, dv, &p);
if (t < 1.0f){
if (t > 0)m_Pos += dv * t;
if ((m_flags&_SLIDING) && lv < 3){
Vec vr = dv * (1-t);float d1 = p.Distance(m_Pos) - m_Radius;Move(vr - p.Normal() * Vec::dot(vr, p.Normal()), lv+1);
}return;
}
m_Pos += dv;}
슈퍼클래스적용시나리오요구사항 : 바닥에만슬라이딩하게해주세요. 바운딩도필요합니다. 슬라이딩시마찰력을적용해주세요. 단, 무적상태가아닐때만.충돌할때이벤트를발생시켜주세요. 대미지중이동할때는충돌지점에파티클좀뿌려주세요. 단,캐릭터가 HP10%이하인경우만….
슈퍼클래스적용시나리오요구사항 : 바닥에만슬라이딩하게해주세요. 바운딩도필요합니다. 슬라이딩시마찰력을적용해주세요. 단, 무적상태가아닐때만.충돌할때이벤트를발생시켜주세요. 대미지중이동할때는충돌지점에파티클좀뿌려주세요. 단,캐릭터가 HP10%이하인경우만….NO PROBLEM
슈퍼클래스기반프로그래밍의장단점
장점1. 구조적으로큰부담없이기존코드를유지하면서새로운기능추가가능기능코드만추가하고객체를생성할때선택적으로해당기능을 set 시켜주기만하면끝
2. 쉽고단순하고직관적단점1. 코드가조잡해지는느낌2. 하드코딩하는느낌
인식의전환코드가조잡해보인다어떤식으로구성하더라도결과적으로핵심구현로직이다른코드일까현재코드의쾌적함을위해기획자와명세를협상하고있는건아닌지성찰해봅니다
“우리는바닥에서만슬라이딩은불가능합니다. 끝”약간심적인부담이있겠지만쿨~하게구현해주는것이완벽한프로그래머보다멋진프로그래머솔루션 : 심리적인 해결. “조잡해 보이는 건 명세 탓!! 하지만 로직을단순하고 멋지게 유지하는 것보다는 명세가 우선이다”라고 암시 암시 암시
인식의전환하드코딩같아보인다일관성을유지하면서도관리가가능하게작성된코드라면하드코딩과다르다 (암시)
if (m_Type == _WOLF && m_HP <= 10)PlaySound(L"아파");
if ((m_Flags&_HURT_SOUND) && m_HP <= m_HurtsAlramHP)PlaySound(L"아파");
void CGameRoom::LeaveRoom(CSession* session, bool graceful){
if (!IsLobby())NotifyLeaveMessage(session);
if (IsDungeon() && graceful == false)session->ExitPenalty();
}void CGameRoom::LeaveRoom(CSession* session, bool graceful){
if (m_flags&_NOTIFY_LEAVE)NotifyLeaveMessage(session);
if ((m_flags&_EXIT_PENALTY) && graceful == false)session->ExitPenalty();
}
슈퍼클래스화하나의클래스에기능을통합인스턴스생성시기능선택
자료화자료주도적으로개발하려면필수슈퍼클래스구성요소기능리스트기능에필요한속성
컴포넌트기반오브젝트시스템과유사
자료화단순한계층의 XML(혹은 jason)로쉽게표현가능<actor>
<player mask="_UPDATE,_RENDER,_GRAVITY,_USERCONTROL,_CHECKEVENTBOX,_MOUSEACTION"><pos>0 15</pos><radius>0.5</radius>
</player><helper mask="_UPDATE,_RENDER,_STATIC">
<pos>0 2</pos><radius>0.4</radius><color>ffffff00</color><name>helper</name>
</helper><camera mask="_UPDATE,_CAMERA,_NOCOLLISION">
<pos>0 10</pos><follow>player</follow>
</camera></actor>
도구화기능리스트 (on/off/enum)기능이 set 되었을때필요한속성/에디터객체
12
도구화기능명시 <particlemask type="propertysheet" minheight="75" height="25%" headervisible="false">
<shape text="형태"><_SHAPE text="타입" type="enum" items="빌보드;트레일;메시">랜덤</_SHAPE><_COLOR text="색상" type="enum" items="상수;보간;화이트">화이트</_COLOR><_ALPHA text="알파" type="enum" items="없음;상수;보간">없음</_ALPHA><_SIZE text="크기" type="enum" items="기본;랜덤;랜덤보간">기본</_SIZE><_ROTATE text="회전" type="enum" items="없음;상수;랜덤">없음</_ROTATE>
</shape><lifetime text="생성">
<_DELAY text="생성지연" type="enum" items="없음;상수;랜덤;순차적">없음</_DELAY><_LIFETIME text="라이프타임" type="enum" items="상수;랜덤">랜덤</_LIFETIME>
</lifetime><position text="위치">
<_POSITION text="생성위치" type="enum" items="원점;랜덤구">원점</_POSITION><_VELOCITY text="속도" type="enum" items="없음">없음</_VELOCITY><_DIRECTION text="방향/목표" type="enum" items="없음;랜덤구;목표지점">없음</_DIRECTION><_GRAVITY text="중력" type="bool">false</_GRAVITY>
</position><parameters type="propertysheet" height="100%">
<general text="일반" mask="default"><COUNT text="생성갯수" type="int" min="1" singleStep="5">10</COUNT><LOOP text="반복" type="bool">false</LOOP><BLEND text="블랜딩" type="enum" items="알파블랜딩;가산;곱하기">가산</BLEND>
</general> <billboard text="빌보드" mask="_SHAPE=빌보드">
<TEXTURE text="texture" type="texture">check.bmp</TEXTURE></billboard><randomlifetime text="라이프타임" mask="_LIFETIME=랜덤">
<LIFETIME_MIN text="min" type="float" min="0" singleStep="1">5</LIFETIME_MIN><LIFETIME_MAX text="max" type="float" min="0" singleStep="1">10</LIFETIME_MAX>
</randomlifetime><constlifetime text="라이프타임" mask="_LIFETIME=상수">
<CONSTLIFETIME text="lifetime" type="float" min="0" singleStep="1">5</CONSTLIFETIME></constlifetime><constcolor text="색상" mask="_COLOR=상수">
<CONSTCOLOR text="색상" type="color">ffffff</CONSTCOLOR></constcolor><blendcolor text="색상" mask="_COLOR=보간">
도구화기능간의관계기능간의관계를데이터상에서검증
<shading text="셰이딩"><_NO_LIGHTING text="셰이딩안함" type="bool">false</_NO_LIGHTING><_USE_SPECULAR text="스펙큘러채널" type="bool" enabletest="!_NO_LIGHTING">false</_USE_SPECULAR><_RIMLIGHTING text="림라이트" type="bool" enabletest="!_NO_LIGHTING">false</_RIMLIGHTING>
</shading>
도구화클래스화많은기능중에서클래스에필요한기능한툴상에서표시클래스는데이터로만존재
극단적인사용안을제안합니다게임객체를하나의오브젝트타입으로구성모든게임객체는 Actor데이터에의해서만기능 set / reset단순한구조신입도알수있어요
슈퍼클래스기반게임오브젝트
Note) 현재 이 구조로 프로젝트 진행하고 있습니다 (소프트네트)
슈퍼클래스기반게임오브젝트Actor(SuperActor)모든기능을구현모든객체는길찾기, Head tag, 동기화등을맘대로활용가능기능추가하고데이터연동만해주면 OK (신입도할수있어요)재사용성 Better (vs ObjectOrientDesign)
“아이템사라질때 mob 사라질때처럼해주시면안되나요 ?” “Re:그렇게하세요”직접참조 OK (vs Component-based, OOD)메시모델의손의위치를사운드출력부에서직접참조카메라객체에서플레이어의머리본의 TM을직접참조빠르고직관적
슈퍼클래스기반게임오브젝트SuperActor“구현부가너무비대해지지않을까요 ?”구현부를적절히컴포넌트화한다면납득할수준으로관리가가능각컴포넌트는계층적이지않기때문에단순하게구성가능 (일반화의제약도없음)다만 과도하게 컴포넌트화하고 정리를 하는 것보다는 관리 가능한 수준에서느슨하게 처리하는 것을 추천
슈퍼클래스기반게임오브젝트SuperActor“일일이비트검사하면속도상불이익이있지않을까요 ?”구현은트리구조를이루게됨그룹단위로체크해서최상위에서 skip 하도록점프는이동하위에, 2단점프는점프하위좀더도전적으로 DOD로확장가능(슈퍼클래스기반파티클참조)#Data Oriented Designhttp://www.lameproof.com/zboard/zboard.php?id=bbs2&no=790
슈퍼클래스기반게임오브젝트SuperActor“그럼모든속성도가지고있다는건데, 메모리낭비가심하지않을지요” (필요한경우 heap으로)template <class _Tx> class CValuePtr {public :
CValuePtr() { m_Ptr = NULL; }~CValuePtr() { if (m_Ptr != NULL) delete m_Ptr; }_Tx* operator-> () { if (m_Ptr == NULL) m_Ptr = new _Tx; return m_Ptr; }
private :_Tx* m_Ptr;
} ;class CActor {private:
struct _DIALOG {_DIALOG() { Shape = NULL; }~_DIALOG() { if (Shape) delete Shape; }float OpenStep;Sphere* Shape;float Offset[2];
};CValuePtr<_DIALOG> m_Dialog;
} ;
value = *node.FindChild(L"dialog_offset");swscanf_s(value, L"%f %f", &m_Dialog->Offset[0], &m_Dialog->Offset[1]);value = *node.FindChild(L"dialog_size");m_Dialog->Shape = new Sphere(Vec(0, 0), unicode::atof(value), 14);
슈퍼클래스기반게임오브젝트슈퍼클래스기반게임오브젝트장점단순한구조로빠른개발이가능기능추가가용이하며많은코드공유가능소규모여러프로젝트에서공유가능사이드뷰액션게임과 TPS, 스포츠게임이같은프로젝트코드관리용이성데이터검증으로 dead code 판단가능Legacy code를유지하면서방어적으로기능교체
슈퍼클래스기반게임오브젝트슈퍼클래스기반게임오브젝트장점툴연동가능툴에서필요한기능만활성하면툴상에서인터렉션가능툴내게임플레이도가능생산성향상
(데모)
슈퍼클래스기반파티클하나의파티클객체에서모든기능을구현표준화된도구사용절차
1. 데이터를읽어서사용되는기능과툴에서세팅한값들을저장2. 파티클인스턴스생성시인스턴스에초기값세팅3. 파티클업데이트
슈퍼클래스기반파티클생성지연시간예
void CParticle::InitParticle(void *data, int count) const{
for(int i=0; i<count; i++){
_PARTICLE* ptr = (_PARTICLE*)((char*)data + m_Pitch * i);char * extra = (char*)ptr + sizeof(_PARTICLE);
if (m_InitMask&_DELAY_CONST){
ptr->curTime = - *(const float*)GetParam(_I_DELAY_CONST);}else if (m_InitMask&_DELAY_RANDOM){
const float* delayrandom = (const float*)GetParam(_I_DELAY_RANDOM);ptr->curTime = - Random(delayrandom[0], delayrandom[1]);
}else if (m_InitMask&_DELAY_SEQ){
ptr->curTime = - *(const float*)GetParam(_I_DELAY_SEQ) * i;}else{
ptr->curTime = 0;}
슈퍼클래스기반파티클업데이트예비트연산으로기능체크해서구현
bool CParticle::Update(float dt, void * data, int count, unsigned long updatemask) const{
for(int i=0, off=0; i<count; i++, off+=m_Pitch){
_PARTICLE* p = (_PARTICLE*)((char*)data + off);
p->curTime += dt;char * extra = (char*)p + sizeof(_PARTICLE);
if (updatemask&_COLOR_BLEND){
unsigned long* blendcolor = (unsigned long*) GetData(extra, _U_BLENDCOLOR);p->color = LerpColor(blendcolor[0], blendcolor[1], p->curTime / p->lifeTime);
}if (updatemask&_GRAVITY){
Vec3* vel = (Vec3*) GetData(extra, _U_VELOCITY);*vel += Vec3(0, -9.8f, 0) * dt; // 중력
}if (updatemask&_VELOCITY){
const Vec3* vel = (const Vec3*) GetData(extra, _U_VELOCITY);p->pos += (*vel) * dt;
}
슈퍼클래스기반파티클메모리문제오프셋테이블을이용하여최소의용량만사용기능상필요한데이터만할당
슈퍼클래스기반파티클성능문제분기문(if)로인한제어해저드대량의연산이이루어지기때문에성능을고려할때는민감한사안
#control hazard
슈퍼클래스기반파티클성능문제해법#1 자료지향설계(DOD)bool CParticle::Update(float dt, void * data, int count, unsigned long updatemask) const{
_PARTICLE* p = (_PARTICLE*)((char*)data + off);int i;
for(i=0; i<count; i++)p->curTime[i] += dt;
if (updatemask&_COLOR_BLEND){
unsigned long* blendcolor = (unsigned long*) GetData(p, _U_BLENDCOLOR);for(i=0; i<count; i++)
p->color[i] += LerpColor(blendcolor[i*2+0], blendcolor[i*2+1], p->curTime / p->lifeTime);}if (updatemask&_GRAVITY){
Vec3* vel = (Vec3*) GetData(p, _U_VELOCITY);for(i=0; i<count; i++)
vel[i] += += Vec3(0, -5.0f, 0) * dt;}if (updatemask&_VELOCITY){
const Vec3* vel = (const Vec3*) GetData(p, _U_VELOCITY);for(i=0; i<count; i++)
p->pos[i] += vel[i] * dt;}
슈퍼클래스기반파티클성능문제해법#2 컴파일러마법도달하지않는코드, 상수연산은제거됨#include <windows.h>
inline void update(unsigned long mask){
if (mask&1)::Sleep(0xFF);
if (mask&2)::Sleep(0xF);
}
void main(){
unsigned long mask = rand();update(mask);update(2);
}
슈퍼클래스기반파티클성능문제해법#2 컴파일러마법많이호출되는타입은아래처럼
해당기능만실행하는코드생성BEST
bool CParticle::Update(float dt, void * data, int count) const{
if (m_UpdateMask == (_COLOR_BLEND|_GRAVITY|_VELOCITY))return Update(dt, data, count, _COLOR_BLEND|_GRAVITY|_VELOCITY);
else if (m_UpdateMask == _COLOR_BLEND)return Update(dt, data, count, _COLOR_BLEND);
return Update(dt, data, count, m_UpdateMask);}
슈퍼클래스기반파티클슈퍼클래스기반파티클의장점다양한조합의파티클손쉬운도구연동이펙트디자이너의요구를빠르게반영가능바로구현후데이터연동만해놓으면 OK“호혹시파티클이회오리모양의경로로움질일수있을까요?” “네만들어드리겠습니다”빠른연산시스템이단순하여병렬화나메모리친화적인다양한최적화가가능
슈퍼클래스기반유저인터페이스시스템컴포넌트의예외적인명세
포스트프로세싱 / 모션컨트롤자료주도적인구성셰이더 , 셰이더변수, 블룸, 렌더타겟,…조합으로패스정의
여러패스
슈퍼클래스기반서버일반적인서버구성게임의성격에맞도록서버를구성기본모듈을공유하는서로다른프로젝트로예상
게임테크 2010:Extracting MMORPG Server Engine from TERA
슈퍼클래스기반서버슈퍼클래스기반의서버구성서버는한종류데이터에의해서기능명세
슈퍼클래스기반의서버구성서버는한종류데이터에의해서기능명세다기능서버게임성격과서비스상황에맞도록세팅인증+필드, 인증+던전, 던전인증+던정,필드…기능단위보다는지역, 처리유저수기준
슈퍼클래스기반서버
클라이언트와코드공유클라이언트의게임객체를서버에서도사용필요한기능만활성가능하도록제한“길찾기따로만들어야되나요 ?”“클라이언트에서구현한 AI캐릭터를서버에서사용할수없나요 ?”“서버용데이터안만들고애니메이션이벤트검증할수있나요?”NO PROBLEM
슈퍼클래스기반서버
정책적으로 클라이언트, 툴, 서버에서 게임 객체 공유 (소프트네트)
(데모)
결론슈퍼클래스를이용하면구조적인복잡도가낮아직관적으로구성가능단순한구조로설계및관리의스트레스에서해방구현에좀더집중가능하며개발속도개선도구를이용하여프로젝트개발속도개선
코멘트 : 우아하고섹시하진않지만합리적으로검토해볼만한아이디어
슈퍼클래스, 상태 머신, 스크립트 3S 삼총사면 무서울 게 없습니다
감사합니다
강연자이메일 [email protected]