/// <summary> /// 아직 완전히 넘기지 못한 swipe에 관한 것 /// </summary> /// <param name="direction"></param> /// <param name="ratio">0 ~ 1 사이, 1은 완전히 넘긴 것 (그러나 완전히 넘길 때 처리는 다른 곳에서 한다)</param> /// <param name="backward">반대 방향으로 swipe하는 경우인지 여부</param> public void PartialSwipe(FSNInGameSetting.FlowDirection direction, float ratio) { if (!CanSwipe) // Swipe 불가능한 상태면 리턴 { return; } var nextshot = m_snapshotTraveler.GetLinkedSnapshot(direction); if (nextshot != null) // * 넘길 수 있는 경우만 처리 { //Debug.Log("Link available"); var curshot = m_snapshotTraveler.Current; foreach (var module in m_layerModules.Values) // 현재 로드된 모든 LayerModule 에 맞는 레이어를 찾아 각각 처리한다 { int layerID = module.LayerID; var oldLayer = curshot.GetLayer(layerID) ?? FSNSnapshot.Layer.Empty; var newLayer = nextshot.GetLayer(layerID) ?? FSNSnapshot.Layer.Empty; if (oldLayer.IsEmpty && newLayer.IsEmpty) // * 둘 다 비어있으면 아무것도 하지 않는다 { continue; } bool isBackward = InGameSetting.BackwardFlowDirection == direction; float trRatio = ratio * c_maxSwipeToTransitionRatio; module.OldElementOnlyTransition(newLayer, trRatio, isBackward); // 트랜지션 } } }
/// <summary> /// 해당 방향의 조건 함수를 실행하여 모두 true일 때만 해당 방향 segment 리턴. 아니면 null /// </summary> /// <param name="dir"></param> /// <returns></returns> public Segment CheckAndGetConditionFlow(FSNInGameSetting.FlowDirection dir) { var condlinks = Flows[(int)dir].ConditionedLinks; if (condlinks == null) // 조건부 링크가 없다면 null { return(null); } var engine = FSNEngine.Instance; foreach (var link in condlinks) // 링크마다 반복 { Segment foundSeg = link.Linked; // 찾아낸 segment. 일단은 조건을 만족하는 것으로 가정 foreach (var funcinfo in link.funcinfo) { if (!engine.ScriptUnityCallBool(funcinfo.funcname, funcinfo.param)) // 조건이 하나라도 false가 나왔다면 비교 중단, 찾아낸 것은 null로 { foundSeg = null; break; } } if (foundSeg != null) // 해당 조건이 모두 만족되었다면 이 segment 리턴 { return(foundSeg); } } return(null); }
/// <summary> /// 해당 방향으로 움직일 수 있는지 여부. 조건부 점프는 제외된다. (따로 체크해야함) /// </summary> /// <param name="dir"></param> /// <returns></returns> public bool CanFlowTo(FSNInGameSetting.FlowDirection dir) { if (dir == FSNInGameSetting.FlowDirection.None) { Debug.LogWarning("dir is None??"); return(false); } return(Flows[(int)dir].Linked != null); }
/// <summary> /// 해당 방향 스냅샷 구하기 /// </summary> /// <param name="dir"></param> /// <returns></returns> public FSNSnapshot GetLinkedSnapshot(FSNInGameSetting.FlowDirection dir) { var linked = GetLinkedToDir(dir); if (linked != null) { return(linked.snapshot); } return(null); }
static void _addOptions(FSNInGameSetting.FlowDirection dir, FSNScriptSequence.Parser.ICommandGenerateProtocol protocol) { var data = protocol.GetStateVar(c_key_optionData) as string[][]; if (data == null) { Debug.LogError("You can't make options without starting an option sequence."); } else { // 0번째 인덱스는 점프할 레이블, 1번째 인덱스는 텍스트 string text = protocol.parameters.Length > 1? protocol.parameters[1] : ""; // 텍스트가 지정되지 않았을 때는 빈 문자열로 대체 data[(int)dir] = new string[2] { protocol.parameters[0], text }; } }
/// <summary> /// 스냅샷 단순 연결, 선택지 버전 /// </summary> /// <param name="prev"></param> /// <param name="next"></param> /// <param name="dir"></param> static void LinkSnapshotAsOption(Segment prev, Segment next, FSNInGameSetting.FlowDirection dir, bool isNonTextOption) { var backDir = FSNInGameSetting.GetOppositeFlowDirection(dir); next.BackDirection = backDir; prev.SetDirectFlow(dir, next); next.SetDirectFlow(backDir, prev); if (!isNonTextOption) // 텍스트가 있는 일반 선택지일 때만 { // FIX : 선택지 분기는 period 세그먼트의 isChaining을 체크하는 부분이 위쪽 UserChoice를 체크하는 조건문에 걸려 실행되지 못함. // 선택지 바로 다음에는 LastOption 텍스트를 표시하는 snapshot이 무조건 나온다고 가정하고, 여기서 강제로 chaining을 해준다. next.snapshot.NonstopToForward = true; next.snapshot.NonstopToBackward = true; } }
/// <summary> /// 완전히 넘기는 Swipe 처리 /// </summary> /// <param name="direction"></param> /// <param name="transitionStartRatio">트랜지션 애니메이션을 어느 지점부터 시작할지 여부. 0은 처음부터, 1은 종료부분. 기본값은 c_maxSwipeToTransitionRatio</param> public void FullSwipe(FSNInGameSetting.FlowDirection direction, float transitionStartRatio = c_maxSwipeToTransitionRatio) { if (!CanSwipe) // Swipe 불가능한 상태면 리턴 { return; } var nextshot = m_snapshotTraveler.GetLinkedSnapshot(direction); if (nextshot != null) // * 실제로 가능한 경우만 처리 { var curshot = m_snapshotTraveler.Current; float transTime = 0f; // 트랜지션 시간 // FIX : setting에 backward 방향이 등록되어있더라도 DisableBackward 플래그가 서있다면 역방향으로 따지지 않는다. bool isBackward = !curshot.DisableBackward && InGameSetting.BackwardFlowDirection == direction; foreach (var module in m_layerModules.Values) // 현재 로드된 모든 LayerModule 에 맞는 레이어를 찾아 각각 처리한다 { int layerID = module.LayerID; var oldLayer = curshot.GetLayer(layerID) ?? FSNSnapshot.Layer.Empty; var newLayer = nextshot.GetLayer(layerID) ?? FSNSnapshot.Layer.Empty; if (oldLayer.IsEmpty && newLayer.IsEmpty) // * 둘 다 비어있으면 아무것도 하지 않는다 { continue; } float curtt = module.StartTransition(newLayer, nextshot.InGameSetting, transitionStartRatio, isBackward); // 트랜지션 if (transTime < curtt) // 제일 긴 트랜지션 시간 추적 { transTime = curtt; } } m_lastSwipeWasBackward = isBackward; // swipe 방향성 보관해두기 (연결된 snapshot 처리에 사용) transTime += nextshot.AfterSwipeDelay; // swipe 시간에 스냅샷에 지정된 딜레이 시간까지 더하기 m_swipeAvailableTime = Time.time + transTime; // 현재 시간 + 트랜지션에 걸리는 시간 뒤에 swipe가 가능해짐 m_snapshotTraveler.TravelTo(direction); // 해당 방향으로 넘기기 CurrentSession.SnapshotIndex = m_snapshotTraveler.CurrentIndex; // Session 정보 업데이트 (스크립트는 변하지 않았으므로 snapshot index만 바꿔주면 된다) } }
/// <summary> /// 해당 방향으로 진행하여 현재 상태 바꾸기. 덤으로 해당 snapshot에 지정된 함수들도 호출한다. /// </summary> /// <param name="dir"></param> /// <returns>진행 가능할 경우, 해당 방향의 snapshot. 아니면 null</returns> public FSNSnapshot TravelTo(FSNInGameSetting.FlowDirection dir) { FSNSnapshot next = null; var linked = GetLinkedToDir(dir); if (linked != null) // 해당 방향으로 진행 가능한 경우만 변경 { m_current = linked; next = linked.snapshot; ExecuteSnapshotFunctions(); // 함수 실행 if (m_current.Type == FlowType.Load) // 스크립트 로딩 이벤트 { ScriptLoadRequested(m_current.Parameter as string); } } return(next); }
/// <summary> /// 텍스트들을 일정 방향으로 모두 밀어내며 삭제한다. /// </summary> /// <param name="layer"></param> /// <param name="direction"></param> private static void ClearTextsToDirection(FSNSnapshot.Layer layer, FSNInGameSetting.FlowDirection direction) { Vector3 screenHalf = FSNEngine.Instance.ScreenDimension * 0.5f; // 화면 크기 절반 Vector3 dirVec = FSNInGameSetting.GetUnitVectorFromFlowDir(direction); // 흐름 방향 벡터 var uidList = layer.UniqueIDList; int count = uidList.Count; int[] removeIDList = new int[uidList.Count]; uidList.CopyTo(removeIDList, 0); for (int i = 0; i < count; i++) { var textElem = layer.GetElement(removeIDList[i]) as SnapshotElems.Text; textElem.FinalState.Position = textElem.Position + Vector3.Scale(screenHalf, dirVec); // 화면 절반 거리만큼 해당 방향으로 이동 layer.RemoveElement(removeIDList[i]); } }
/// <summary> /// 텍스트 좌표를 가운데로 밀어내기. LastOption이나 가운데 텍스트 옵션 등에서 사용 /// </summary> /// <param name="textpos"></param> /// <param name="setting"></param> private static void TextPositionToCenter(ref Vector3 textpos, Vector2 textSize, FSNInGameSetting.FlowDirection direction, IInGameSetting setting) { var oldPos = textpos; switch (direction) { case FSNInGameSetting.FlowDirection.Up: case FSNInGameSetting.FlowDirection.Down: textpos.y = textSize.y / 2f; ApplyCenterTextMargin(ref textpos, setting); textpos.x = oldPos.x; break; case FSNInGameSetting.FlowDirection.Left: case FSNInGameSetting.FlowDirection.Right: textpos.x = -textSize.x / 2f; ApplyCenterTextMargin(ref textpos, setting); textpos.y = oldPos.y; break; } }
//============================================================== /// <summary> /// 없는 방향으로 swipe 시도, 시작 /// </summary> /// <param name="direction"></param> public void OnTryingSwipeToWrongDirection(FSNInGameSetting.FlowDirection direction) { SetBusy(); // 움직임이므로 일단 바쁜 상태로 GameObject orig = null; switch (direction) { case FSNInGameSetting.FlowDirection.Up: orig = m_indicatorInvalidUp; break; case FSNInGameSetting.FlowDirection.Down: orig = m_indicatorInvalidDown; break; case FSNInGameSetting.FlowDirection.Left: orig = m_indicatorInvalidLeft; break; case FSNInGameSetting.FlowDirection.Right: orig = m_indicatorInvalidRight; break; } if (orig != null) // swipe 불가 표시 생성 { var newobj = Instantiate <GameObject>(orig); var newtrans = newobj.transform; var origtrans = orig.transform; newtrans.SetParent(origtrans.parent); newtrans.position = origtrans.position; newobj.SetActive(true); Destroy(newobj, m_swipeInvalidIndicatorTime); // 일정 시간 뒤에는 무조건 제거하도록 } }
/// <summary> /// 캐싱된 조건부 링크 등등을 종합해서 해당 방향으로 링크되는 Segment를 구한다. 없을 경우 null /// </summary> /// <param name="dir"></param> /// <returns></returns> private FSNSnapshotSequence.Segment GetLinkedToDir(FSNInGameSetting.FlowDirection dir) { if (dir != FSNInGameSetting.FlowDirection.None) { var condlink = m_cachedConditionLinks[(int)dir]; // 조건부 링크 구하기 if (condlink != null) { return(condlink); } } else { Debug.LogWarning("dir is None??"); } if (m_current.CanFlowTo(dir)) // 일반 링크 구하기 { return(m_current.GetLinked(dir)); } else { return(null); // 링크 없음 } }
/// <summary> /// Layer 안에 들어있는 텍스트들을 특정 방향으로 모두 밀어낸다. 알파값도 변경. 수명이 다 된 것은 제거 처리. /// </summary> /// <param name="layer">변경할 레이어 (이미 복제된 상태여야함)</param> /// <param name="direction"></param> /// <param name="newTextSize"></param> private static void PushTextsToDirection(FSNSnapshot.Layer layer, FSNInGameSetting.FlowDirection direction, Vector2 newTextSize, float paraSpacing = 0) { Vector2 dirVec = FSNInGameSetting.GetUnitVectorFromFlowDir(direction); List <int> UIDtoRemove = new List <int>(); // 삭제 리스트 foreach (var uId in layer.UniqueIDList) { var textElem = layer.GetElement(uId) as SnapshotElems.Text; int elemAge = textElem.ChainedParentCount; // 텍스트의 종류에 따라서 다른 룰을 적용한다. if (textElem.type == SnapshotElems.Text.Type.Normal) // ** 일반 텍스트 { Vector3 transVec = Vector2.Scale(newTextSize, dirVec); // 이동할 벡터 양 if (elemAge > 0) { transVec += (Vector3)(dirVec * paraSpacing); // 최초에 등장한 이후엔 문단 간격도 적용 } if (elemAge < c_textLife) // 텍스트가 아직 살아있어야하는 경우 { textElem.Alpha = (float)(c_textLife - elemAge) / (float)c_textLife; textElem.Position = textElem.Position + transVec; CopyCurrentToFinal(textElem); // 임시 FinalState } else { // 텍스트가 죽어야하는 경우 textElem.FinalState.Position = textElem.Position + transVec; UIDtoRemove.Add(uId); } } else { // ** 기타 (선택지 관련 텍스트) int killAge = textElem.type == SnapshotElems.Text.Type.OptionTexts? 1 : 2; //(선택한 텍스트는 1턴 더 살아있어야 하므로) if (elemAge == killAge) // 없어지는 타이밍 { // NOTE : 현재 구조상의 문제로 인해 분기점 이후 바로 없어지는 오브젝트의 FinalState를 여러개 둘 수 없음. // 따라서 분기점 이후에도 한번은 오브젝트를 살려놓은 뒤 안보이게만 하고 다음번에 없애는 식으로. Vector2 halfScreen = FSNEngine.Instance.ScreenDimension / 2f; Vector3 transVec = Vector2.Scale(dirVec, halfScreen); textElem.Position = textElem.Position + transVec; textElem.Alpha = 0f; // TODO : Alpha를 0으로 하는 것 이외에 실제로 visible을 끌 수 있는 방법이 있다면 사용하도록 한다. 지금도 딱히 문제는 없긴 한데... } else if (elemAge == killAge + 1) // 원래 없어져야했던 타이밍이 지나고 나서 실제로 없앤다. { UIDtoRemove.Add(uId); } } } int rmvCount = UIDtoRemove.Count; for (int i = 0; i < rmvCount; i++) // 삭제 리스트 처리 { layer.RemoveElement(UIDtoRemove[i]); } }
public void SetConditionFlow(FSNInGameSetting.FlowDirection dir, Segment.FlowInfo.ConditionLink [] links) { Flows[(int)dir].ConditionedLinks = links; }
/// <summary> /// swipe 변위 보내기 /// </summary> public void Swipe(FSNInGameSetting.FlowDirection direction, float distance) { if (m_enginePause) // 엔진 일시정지시에는 이벤트 처리를 받지 않는다. { return; } m_swippedAnyway = true; // 어쨌든 swipe를 하긴 했음. release 하더라도 메뉴 토글은 콜하지 않도록 if (!m_swipeCompleted && m_seqEngine.CanSwipe) // swipe 가능한 상태 { var engine = FSNEngine.Instance; m_swipeDirection = direction; // 민 방향을 보관해둔다 var dirvalid = m_seqEngine.SwipeDirectionAvailable(direction); if (dirvalid) // 밀 수 있는 방향일 때 { float weight = engine.InGameSetting.SwipeWeight; float maxdist = (direction == FSNInGameSetting.FlowDirection.Left || direction == FSNInGameSetting.FlowDirection.Right)? engine.ScreenXSize : engine.ScreenYSize; // 해당 방향으로 최대한 밀 수 있는 거리 float fullDist = weight * maxdist; float curSwipeRatio = Mathf.Pow(distance / fullDist, 0.5f); // 드래그할 때 약간 저항을 주기 위해 if (curSwipeRatio < 1.0f) // 아직 덜 밀었을 때 { //m_seqEngine.PartialSwipe(direction, curSwipeRatio * c_partialSwipeLimit);// Partial swipe m_seqEngine.PartialSwipe(direction, curSwipeRatio); // Partial swipe m_swipeRatio = curSwipeRatio; if (!m_swipeEventSent || m_swipeEventSentWasWrongDir) // 아직 이벤트를 보낸 적이 없거나 잘못된 방향으로 이벤트를 보냈을 경우 { ExecuteSwipeEvent((obj, param) => { obj.OnTryingSwipe(direction); // 옳은 방향으로 swipe 이벤트 }); m_swipeEventSent = true; m_swipeEventSentWasWrongDir = false; } } else if (!m_swipeCompleted) // 완전히 밀었을 때, 아직 이전에 완전히 밀지는 않았을 경우 { m_seqEngine.FullSwipe(direction, c_partialSwipeLimit); // Full swipe m_swipeRatio = 0; m_swipeCompleted = true; ExecuteSwipeEvent( (obj, param) => { obj.OnTryingSwipe(direction); // swipe완료 이벤트 }); } } else { // 밀 수 없는 방향일 때 if (!m_swipeEventSent || !m_swipeEventSentWasWrongDir) // 아직 이벤트를 보낸 적이 없거나 올바른 방향으로 이벤트를 보냈을 경우 { ExecuteSwipeEvent((obj, param) => { obj.OnTryingSwipeToWrongDirection(direction); // 잘못된 방향으로 swipe 이벤트 }); m_swipeEventSent = true; m_swipeEventSentWasWrongDir = true; } } } }
/// <summary> /// 없는 방향으로 swipe 시도하다가 중단함 /// </summary> /// <param name="direction"></param> public void OnReleaseSwipeToWrongDirection(FSNInGameSetting.FlowDirection direction) { StartCoroutine(CheckIdle()); // 조건이 완전히 성립할 때까지 계속 Idle 체크 }
/// <summary> /// /// </summary> /// <param name="direction"></param> public void OnCompleteSwipe(FSNInGameSetting.FlowDirection direction) { StartCoroutine(CheckIdle()); // 조건이 완전히 성립할 때까지 계속 Idle 체크 }
/// <summary> /// FSNSequence 해석. 분기마다 builderState를 복제해야 하므로 새로 호출된다. /// </summary> /// <param name="builderState"></param> /// <param name="snapshotSeq">이번 콜에서 생성할 시퀀스 이전의 스냅샷 인덱스</param> /// <param name="bs"></param> /// <param name="linkToLast">이 메서드를 호출하기 바로 이전의 Snapshot에 연결할지 여부. 기본은 true. 선택지 등등에서 false로 해줘야함</param> /// <returns>이번 콜에서 생성한 시퀀스들 중 제일 첫번째 것. 다른 시퀀스 흐름과 연결할 때 사용 가능</returns> static Segment ProcessSnapshotBuild(State bs, FSNSnapshotSequence snapshotSeq, int prevSnapshotIndex, bool linkToLast = true) { ModuleCallQueue moduleCalls = new ModuleCallQueue(); Segment sseg; // 새로 생성할 Segment/Snapshot FSNSnapshot sshot; NewSnapshot(out sseg, out sshot); var firstSeg = sseg; // 최초 스냅샷 var prevCallSeg = snapshotSeq.Get(prevSnapshotIndex); // 이번에 생성할 시퀀스의 이전 스냅샷 var lastSeg = prevCallSeg; // 바로 이전에 처리한 스냅샷 (최초 루프시에는 실행 이전의 마지막 스냅샷과 동일) List <Segments.Control> jumpSegs = new List <Segments.Control>(); // 점프(goto, SwipeOption 등) 세그먼트가 등장했을 경우 여기에 보관된다 List <Segment.CallFuncInfo> funcCalls = new List <Segment.CallFuncInfo>(); // 함수 콜이 있을 경우 여기에 먼저 누적하고 period때 한번에 처리한다 float snapshotDelay = 0f; // 스냅샷에 적용할 지연 시간 (/기다리기 커맨드) // // 처음 시작할 때는 무조건 모든 객체들을 Hard-Clone한다 (분기점에서 Initial/Final 스테이트 독립을 하기 위해) moduleCalls.CatchupPreviousLayerState(prevCallSeg.snapshot); // 기존의 유효한 Layer들을 활성화 (안그러면 모듈 콜을 누락해버림) moduleCalls.AddAllModuleCall(new Segments.HardClone(), bs.settings); // 스크립트 Sequence가 끝나거나 특정 명령을 만날 때까지 반복 bool keepProcess = true; while (keepProcess) { var curSeg = bs.sequence[bs.segIndex]; // 현재 명령어 (Sequence 세그먼트) FSNDebug.currentProcessingScriptLine = curSeg.scriptLineNumber; // 디버깅 정보 세팅 (줄 번호) switch (curSeg.type) // 명령어 타입 체크 후 분기 { ////////////////////////////////////////////////////////////// case FSNScriptSequence.Segment.Type.Setting: // ** 세팅 { var settingSeg = curSeg as Segments.Setting; if (settingSeg.settingMethod == Segments.Setting.SettingMethod.Pop) // * 세팅 pop { if (bs.settings.ParentChain != null) { bs.settings = bs.settings.ParentChain; } else { Debug.LogError("Cannot pop settings anymore - there is no settings pushed"); } } else { if (settingSeg.settingMethod == Segments.Setting.SettingMethod.Push) // * Push일 경우에는 새 Chain을 생성한다 { bs.settings = new FSNInGameSetting.Chain(bs.settings); } foreach (var settingPair in settingSeg.RawSettingTable) // Setting 설정 { bs.settings.SetPropertyByString(settingPair.Key, settingPair.Value); } } bs.SetSettingDirty(); // 세팅 변경됨 플래그 올리기 } break; ////////////////////////////////////////////////////////////// case FSNScriptSequence.Segment.Type.Text: // ** 텍스트 { var module = FSNEngine.Instance.GetModule(FSNEngine.ModuleType.Text) as IFSNProcessModule; moduleCalls.AddCall(module, curSeg, bs.FrozenSetting); // 해당 명령 저장 } break; ////////////////////////////////////////////////////////////// case FSNScriptSequence.Segment.Type.Object: // ** 오브젝트 (이미지 등) { var objSeg = curSeg as Segments.Object; var module = FSNEngine.Instance.GetModuleByLayerID(objSeg.layerID) as IFSNProcessModule; moduleCalls.AddCall(module, objSeg, bs.FrozenSetting); } break; ////////////////////////////////////////////////////////////// case FSNScriptSequence.Segment.Type.Label: // ** 레이블 { //var labelSeg = curSeg as Segments.Label; // 현재 이 시점에서는 labelSeg로 하는 일이 없다... //Debug.Log("Label : " + labelSeg.labelName); } break; ////////////////////////////////////////////////////////////// case FSNScriptSequence.Segment.Type.Control: // ** 엔진 컨트롤 { var controlSeg = curSeg as Segments.Control; switch (controlSeg.controlType) // 종류에 따라 처리 { case Segments.Control.ControlType.Block: keepProcess = false; // 블로킹 - 이 분기에서는 해석 종료 break; case Segments.Control.ControlType.SwipeOption: sseg.Type = FlowType.UserChoice; // 스냅샷 세그먼트 종류 변경 (유저 선택으로) jumpSegs.Add(controlSeg); // 점프 명령어로 보관해두고 나중에 처리한다. break; case Segments.Control.ControlType.Goto: case Segments.Control.ControlType.ReverseGoto: case Segments.Control.ControlType.ConditionJump: jumpSegs.Add(controlSeg); // 점프 명령어로 보관해두고 나중에 처리한다. break; case Segments.Control.ControlType.Clear: // 모든 모듈에 clear를 보낸다 moduleCalls.AddAllModuleCall(controlSeg, bs.FrozenSetting); break; case Segments.Control.ControlType.Oneway: sseg.OneWay = true; break; case Segments.Control.ControlType.ForceBack: sseg.snapshot.ForceBackward = true; break; case Segments.Control.ControlType.Load: sseg.Type = FlowType.Load; sseg.Parameter = controlSeg.GetLoadScriptData(); // 스냅샷 세그먼트의 parameter에 스크립트 파일명 보관 break; case Segments.Control.ControlType.UnityCall: // 함수콜 { var callinfo = new Segment.CallFuncInfo(); controlSeg.GetUnityCallData(out callinfo.funcname, out callinfo.param); funcCalls.Add(callinfo); } break; case Segments.Control.ControlType.Delay: // 딜레이 snapshotDelay = controlSeg.GetDelay(); // 지연 시간을 미리 지정해둔다 break; } } break; ////////////////////////////////////////////////////////////// case FSNScriptSequence.Segment.Type.Period: // ** Period : 현재까지 누적한 call을 실행하는 개념으로 사용중 { var periodSeg = curSeg as Segments.Period; // 다음 snapshot을 위해 현재 진행 방향의 반대방향으로 FlowDirection 정해놓기 bs.settings.BackwardFlowDirection = FSNInGameSetting.GetOppositeFlowDirection(bs.settings.CurrentFlowDirection); bs.SetSettingDirty(); sshot.InGameSetting = bs.FrozenSetting; // 현재까지의 세팅 적용 (굳힌거 사용) moduleCalls.ProcessCall(lastSeg.snapshot, sshot); // 지금까지 모인 모듈 콜 집행하기 if (linkToLast) // 이전 스냅샷에 붙여야하는경우 { LinkSnapshots(lastSeg, sseg); if (bs.prevPeriodWasChain) // 이전 period가 chaining이었다면, 역방향 chaining 걸기 { sseg.snapshot.NonstopToBackward = true; bs.prevPeriodWasChain = false; // (플래그 해제) } if (periodSeg.isChaining) // Chaining 옵션이 켜져있을 경우 { sseg.snapshot.NonstopToForward = true; bs.prevPeriodWasChain = true; // (chaining 상태 기록) } } else { linkToLast = true; // FIX : linkToLast는 맨 첫번째 스냅샷에만 해당하는 파라미터임. 두번째는 꺼준다 } sseg.FunctionCalls = funcCalls.ToArray(); // 쌓인 함수콜 리스트를 배열로 변환하여 세팅 funcCalls.Clear(); snapshotSeq.Add(sseg); // 현재 스냅샷을 시퀀스에 추가 // sseg.snapshot.AfterSwipeDelay = snapshotDelay; // 현재 지정된 딜레이를 적용. snapshotDelay = 0f; // // Condition 링크 임시 보관소 - 나중에 한번에 특정 방향으로 몰아넣는다 FSNInGameSetting.FlowDirection conditionLinkDir = FSNInGameSetting.FlowDirection.None; FSNInGameSetting.FlowDirection conditionLinkBackDir = FSNInGameSetting.FlowDirection.None; List <Segment.FlowInfo.ConditionLink> conditionLinks = null; Dictionary <string, Segment> conditionLinkSegCache = new Dictionary <string, Segment>(); // 조건부 점프시, 중복되는 점프는 이전 것을 사용하게 foreach (var jumpSeg in jumpSegs) // * 점프 세그먼트가 있을 경우 처리 { // NOTE : 현재 Label은 Soft만 구현한다. if (jumpSeg.controlType == Segments.Control.ControlType.SwipeOption) // *** 선택지 { bool isNonTextOption = jumpSeg.IsNonTextOption(); for (int i = 0; i < 4; i++) // 모든 방향마다 처리 { var dir = (FSNInGameSetting.FlowDirection)i; string label = jumpSeg.GetLabelFromSwipeOptionData(dir); if (!string.IsNullOrEmpty(label)) // 라벨이 지정된 경우만 처리(= 해당 방향으로 분기가 있을 때만) { // FIX : 만약 해당 선택지 방향이 원래의 역방향에 해당하는 것이었다면, 역방향을 None으로 재설정한다. (역방향 오버라이드 지원) if (dir == sseg.BackDirection) { sseg.BackDirection = FSNInGameSetting.FlowDirection.None; sseg.snapshot.DisableBackward = true; // 역방향 비활성화 } int labelIndex = bs.sequence.GetIndexOfLabel(label); var labelSeg = bs.sequence.GetSegment(labelIndex) as Segments.Label; if (labelSeg.labelType == Segments.Label.LabelType.Soft) // * SOFT 라벨로 점프 { if (labelIndex < bs.segIndex) // SOFT 라벨은 거슬러올라갈 수 없다. { Debug.LogError("Cannot jump to previous soft label"); } var clonnedState = bs.Clone(); // 상태 복제 clonnedState.segIndex = labelIndex; // 라벨 인덱스 세팅 clonnedState.settings.CurrentFlowDirection = dir; // 진행 방향 세팅 - 선택지 방향으로 진행 방향을 강제 세팅한다 clonnedState.settings.BackwardFlowDirection = FSNInGameSetting.GetOppositeFlowDirection(dir); var newSeg = ProcessSnapshotBuild(clonnedState, snapshotSeq, sseg.Index, false); // 새 분기 해석하기. 이전 스냅샷에 바로 붙이지 않는다. LinkSnapshotAsOption(sseg, newSeg, dir, isNonTextOption); // 선택지로 연결하기 } else { // * HARD 라벨로 점프 Debug.LogError("Not implemented"); } } } } else if (jumpSeg.controlType == Segments.Control.ControlType.Goto) // *** GOTO { string label = jumpSeg.GetGotoLabel(); int labelIndex = bs.sequence.GetIndexOfLabel(label); var labelSeg = bs.sequence.GetSegment(labelIndex) as Segments.Label; if (labelSeg.labelType == Segments.Label.LabelType.Soft) // * SOFT 라벨로 점프 { if (labelIndex < bs.segIndex) // SOFT 라벨은 거슬러올라갈 수 없다. { Debug.LogError("Cannot jump to previous soft label"); } var clonnedState = bs.Clone(); // 상태 복제 clonnedState.segIndex = labelIndex; // 라벨 인덱스 세팅 ProcessSnapshotBuild(clonnedState, snapshotSeq, sseg.Index); // 새 분기 해석하기 // SOFT 라벨로 점프하는 경우엔 사실상 이 분기점으로 다시 되돌아올 일이 생기지 않는다. // 추가 스크립트 해석을 중단한다. keepProcess = false; } else { // * HARD 라벨로 점프 Debug.LogError("Not implemented"); } } else if (jumpSeg.controlType == Segments.Control.ControlType.ReverseGoto) // ** ReverseGoto { string label = jumpSeg.GetReverseGotoLabel(); int labelIndex = bs.sequence.GetIndexOfLabel(label); var labelSeg = bs.sequence.GetSegment(labelIndex) as Segments.Label; if (labelSeg.labelType == Segments.Label.LabelType.Soft) // * SOFT 라벨로 점프 { if (labelIndex < bs.segIndex) // SOFT 라벨은 거슬러올라갈 수 없다. { Debug.LogError("Cannot jump to previous soft label"); } var clonnedState = bs.Clone(); // 상태 복제 clonnedState.segIndex = labelIndex; // 라벨 인덱스 세팅 // 진행 방향을 역방향으로 세팅 clonnedState.settings.CurrentFlowDirection = FSNInGameSetting.GetOppositeFlowDirection(clonnedState.settings.CurrentFlowDirection); clonnedState.settings.BackwardFlowDirection = FSNInGameSetting.GetOppositeFlowDirection(clonnedState.settings.BackwardFlowDirection); // 가장 마지막 세그먼트를 잠시동안만 UserChoice로 변경해서 새 스냅샷시퀀스를 정방향에 붙이지 못하게 막는다. // 생성한 스냅샷을 역방향에 직접 붙여줘야하기 때문. // 좀 Hacky한 방법이라서 변경이 필요할지도. var newSeg = ProcessSnapshotBuild(clonnedState, snapshotSeq, sseg.Index, false); // 새 분기 해석한 후 레퍼런스 받기. 링크는 하지 않음 LinkSnapshotsReverseOverride(sseg, newSeg); //붙이기 sseg.snapshot.DisableBackward = true; // 역방향 비활성화 } else { // * HARD 라벨로 점프 Debug.LogError("Not implemented"); } } else if (jumpSeg.controlType == Segments.Control.ControlType.ConditionJump) // ** 조건부 점프 { string funcname; string [] param; string label = jumpSeg.GetConditionJumpLabel(); int labelIndex = bs.sequence.GetIndexOfLabel(label); var labelSeg = bs.sequence.GetSegment(labelIndex) as Segments.Label; if (labelSeg.labelType == Segments.Label.LabelType.Soft) // * SOFT 라벨로 점프 { if (labelIndex < bs.segIndex) // SOFT 라벨은 거슬러올라갈 수 없다. { Debug.LogError("Cannot jump to previous soft label"); } Segment newSeg; if (!conditionLinkSegCache.TryGetValue(label, out newSeg)) // 이전에 캐싱된 것이 없을 때만 새로 해석한다. { var clonnedState = bs.Clone(); // 상태 복제 clonnedState.segIndex = labelIndex; // 라벨 인덱스 세팅 // 가장 마지막 세그먼트를 잠시동안만 UserChoice로 변경해서 새 스냅샷시퀀스를 정방향에 붙이지 못하게 막는다. // 생성한 스냅샷을 역방향에 직접 붙여줘야하기 때문. // 좀 Hacky한 방법이라서 변경이 필요할지도. newSeg = ProcessSnapshotBuild(clonnedState, snapshotSeq, sseg.Index, false); // 새 분기 해석한 후 레퍼런스 받기. 링크는 하지 않음 conditionLinkSegCache[label] = newSeg; // 캐싱해두기 } // Note : 현재 스크립트 스펙 상, 한 스냅샷 안에서 Condition Link은 한쪽 방향으로밖에 나올 수 없다. // 따라서 방향 구분 없이 리스트 하나에 모두 모아둔 후, 기록해둔 방향에 모두 집어넣는다. conditionLinkDir = newSeg.snapshot.InGameSetting.CurrentFlowDirection; conditionLinkBackDir = newSeg.snapshot.InGameSetting.BackwardFlowDirection; // 만약 일반 링크가 등장하지 않아 방향이 설정되지 않을 때를 대비해서 세팅 if (sseg.FlowDirection == FSNInGameSetting.FlowDirection.None) { sseg.FlowDirection = conditionLinkDir; } if (newSeg.BackDirection == FSNInGameSetting.FlowDirection.None) { newSeg.BackDirection = conditionLinkBackDir; } List <Segment.CallFuncInfo> callfuncs = new List <Segment.CallFuncInfo>(); while (jumpSeg.DequeueConditionJumpData(out funcname, out param)) // AND 조건으로 묶인 모든 condition 읽기 { callfuncs.Add(new Segment.CallFuncInfo() { funcname = funcname, param = param }); } if (conditionLinks == null) { conditionLinks = new List <Segment.FlowInfo.ConditionLink>(); } conditionLinks.Add(new Segment.FlowInfo.ConditionLink() { funcinfo = callfuncs.ToArray(), Linked = newSeg }); if (!newSeg.OneWay) { newSeg.SetDirectFlow(conditionLinkBackDir, sseg); // 역방향 설정 } } else { // * HARD 라벨로 점프 Debug.LogError("Not implemented"); } } else { Debug.LogError("Unknown or not-yet-implemented jump statement!"); } } jumpSegs.Clear(); if (conditionLinks != null) // 모아뒀던 조건부 점프 한번에 세팅 { sseg.SetConditionFlow(conditionLinkDir, conditionLinks.ToArray()); } lastSeg = sseg; NewSnapshot(out sseg, out sshot); // 새 스냅샷 인스턴스 준비 } break; ///////////////////////////////////////////////////////////// default: Debug.LogError("?????????"); break; } // bs.segIndex++; // 다음 명령어 인덱스 if (bs.segIndex >= bs.sequence.Length) // * Sequence 가 끝났다면 루프 종료 { keepProcess = false; } } return(firstSeg); }
/// <summary> /// SwipeOption일 때, 방향에 따른 진행 라벨 구하기 /// </summary> /// <param name="dir"></param> public string GetLabelFromSwipeOptionData(FSNInGameSetting.FlowDirection dir) { var data = CheckOptionData <SwipeOptionData>(ControlType.SwipeOption); return(data.m_dirLabelDict[(int)dir]); }
/// <summary> /// SwipeOption일 때, 방향에 따른 진행 라벨 추가 /// </summary> /// <param name="dir"></param> /// <param name="label"></param> public void SetSwipeOptionData(FSNInGameSetting.FlowDirection dir, string label) { var data = CheckOptionData <SwipeOptionData>(ControlType.SwipeOption); data.m_dirLabelDict[(int)dir] = label; }
/// <summary> /// 특정 방향으로 진행 가능한지 여부 /// </summary> /// <param name="direction"></param> /// <returns></returns> public bool SwipeDirectionAvailable(FSNInGameSetting.FlowDirection direction) { return(CanSwipe && m_snapshotTraveler.GetLinkedSnapshot(direction) != null); }
//======================================================================= public Segment() { Flows = new FlowInfo[4]; FlowDirection = FSNInGameSetting.FlowDirection.None; BackDirection = FSNInGameSetting.FlowDirection.None; }
/// <summary> /// 해당 방향의 Segment 구하기 /// </summary> /// <param name="dir"></param> /// <returns></returns> public Segment GetLinked(FSNInGameSetting.FlowDirection dir) { return(Flows[(int)dir].Linked); }
public void SetDirectFlow(FSNInGameSetting.FlowDirection dir, Segment linked) { Flows[(int)dir].Linked = linked; }
/// <summary> /// 있는 방향으로 swipe 시도, 시작 /// </summary> /// <param name="direction"></param> public void OnTryingSwipe(FSNInGameSetting.FlowDirection direction) { SetBusy(); }
public FSNSnapshot snapshot; // 스냅샷 (본체) #endregion Fields #region Constructors //======================================================================= public Segment() { Flows = new FlowInfo[4]; FlowDirection = FSNInGameSetting.FlowDirection.None; BackDirection = FSNInGameSetting.FlowDirection.None; }
/// <summary> /// swipe 변위 보내기 /// </summary> public void Swipe(FSNInGameSetting.FlowDirection direction, float distance) { if (m_enginePause) // 엔진 일시정지시에는 이벤트 처리를 받지 않는다. return; m_swippedAnyway = true; // 어쨌든 swipe를 하긴 했음. release 하더라도 메뉴 토글은 콜하지 않도록 if(!m_swipeCompleted && m_seqEngine.CanSwipe) // swipe 가능한 상태 { var engine = FSNEngine.Instance; m_swipeDirection= direction; // 민 방향을 보관해둔다 var dirvalid = m_seqEngine.SwipeDirectionAvailable(direction); if (dirvalid) // 밀 수 있는 방향일 때 { float weight = engine.InGameSetting.SwipeWeight; float maxdist = (direction == FSNInGameSetting.FlowDirection.Left || direction == FSNInGameSetting.FlowDirection.Right)? engine.ScreenXSize : engine.ScreenYSize; // 해당 방향으로 최대한 밀 수 있는 거리 float fullDist = weight * maxdist; float curSwipeRatio = Mathf.Pow(distance / fullDist, 0.5f); // 드래그할 때 약간 저항을 주기 위해 if (curSwipeRatio < 1.0f) // 아직 덜 밀었을 때 { //m_seqEngine.PartialSwipe(direction, curSwipeRatio * c_partialSwipeLimit);// Partial swipe m_seqEngine.PartialSwipe(direction,curSwipeRatio); // Partial swipe m_swipeRatio = curSwipeRatio; if (!m_swipeEventSent || m_swipeEventSentWasWrongDir) // 아직 이벤트를 보낸 적이 없거나 잘못된 방향으로 이벤트를 보냈을 경우 { ExecuteSwipeEvent((obj, param) => { obj.OnTryingSwipe(direction); // 옳은 방향으로 swipe 이벤트 }); m_swipeEventSent = true; m_swipeEventSentWasWrongDir = false; } } else if(!m_swipeCompleted) // 완전히 밀었을 때, 아직 이전에 완전히 밀지는 않았을 경우 { m_seqEngine.FullSwipe(direction, c_partialSwipeLimit); // Full swipe m_swipeRatio = 0; m_swipeCompleted = true; ExecuteSwipeEvent( (obj, param) => { obj.OnTryingSwipe(direction); // swipe완료 이벤트 }); } } else { // 밀 수 없는 방향일 때 if (!m_swipeEventSent || !m_swipeEventSentWasWrongDir) // 아직 이벤트를 보낸 적이 없거나 올바른 방향으로 이벤트를 보냈을 경우 { ExecuteSwipeEvent((obj, param) => { obj.OnTryingSwipeToWrongDirection(direction); // 잘못된 방향으로 swipe 이벤트 }); m_swipeEventSent = true; m_swipeEventSentWasWrongDir = true; } } } }
//================================================================= /// <summary> /// 가운데가 아닌 텍스트의 위치를 여백에 따라 보정 /// </summary> /// <param name="textpos"></param> /// <param name="flow"></param> private static void ApplySideTextMargin(ref Vector3 textpos, IInGameSetting setting, FSNInGameSetting.FlowDirection flow) { //float xoffset = setting.TextMarginLeft - setting.TextMarginRight; float yoffset = setting.TextMarginBottom - setting.TextMarginTop; // 해당 사이드에서 여백만큼 떨어트리기 switch (flow) { case FSNInGameSetting.FlowDirection.Up: textpos.y += setting.TextMarginBottom; textpos.x += setting.TextMarginLeft; break; case FSNInGameSetting.FlowDirection.Down: textpos.y -= setting.TextMarginTop; textpos.x += setting.TextMarginLeft; break; case FSNInGameSetting.FlowDirection.Left: textpos.x -= setting.TextMarginRight; textpos.y += yoffset; break; case FSNInGameSetting.FlowDirection.Right: textpos.x += setting.TextMarginLeft; textpos.y += yoffset; break; } }