Spaces:
Running
Running
구조별 모듈화
Browse files
npc_social_network/npc/emotion_config.py
ADDED
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# portfolio/npc_social_network/npc/emotion_config.py
|
2 |
+
# 감정 초기 상태 및 decay 설정
|
3 |
+
|
4 |
+
# 고차원 감정 상태 정의 (GoEmotions 기반, 보여주기용)
|
5 |
+
EMOTION_STATE_TEMPLATE = {
|
6 |
+
# 기본 정서 (긍정)
|
7 |
+
"core_positive":{
|
8 |
+
"joy": 0, # 기쁨
|
9 |
+
"satisfaction": 0, # 만족
|
10 |
+
"gratitude": 0, # 감사
|
11 |
+
"calm": 0, # 평온
|
12 |
+
"anticipation": 0, # 기대
|
13 |
+
"pride": 0, # 자부심
|
14 |
+
"connectedness": 0 # 유대감
|
15 |
+
},
|
16 |
+
# 기본 정서 (부정)
|
17 |
+
"core_negative": {
|
18 |
+
"sadness": 0, # 슬픔
|
19 |
+
"anger": 0, # 분노
|
20 |
+
"anxiety": 0, # 불안
|
21 |
+
"disgust": 0, # 혐오
|
22 |
+
"fear": 0, # 공포
|
23 |
+
"regret": 0, # 후회
|
24 |
+
"frustration": 0 # 좌절
|
25 |
+
},
|
26 |
+
# 사회적 감정
|
27 |
+
"social_emotion": {
|
28 |
+
"jealousy": 0, # 질투
|
29 |
+
"shame": 0, # 수치심
|
30 |
+
"guilt": 0, # 죄책감
|
31 |
+
"compassion": 0, # 동정심
|
32 |
+
"awe": 0, # 경외심
|
33 |
+
"empathy": 0 # 공감
|
34 |
+
},
|
35 |
+
# 인지적 상태
|
36 |
+
"cognitive_state": {
|
37 |
+
"confusion": 0, # 혼란
|
38 |
+
"nervousness": 0, # 긴장
|
39 |
+
"apathy": 0, # 무관심
|
40 |
+
"excitement": 0, # 흥분
|
41 |
+
"immersion": 0, # 몰입
|
42 |
+
"skepticism": 0 # 회의
|
43 |
+
}
|
44 |
+
}
|
45 |
+
|
46 |
+
# 감정별 회복 속도 (ms 단위가 아닌 단위당 감소 속도)
|
47 |
+
EMOTION_DECAY_RATE = {
|
48 |
+
# 기본 정서 (긍정)
|
49 |
+
"joy": 1, # 기쁨
|
50 |
+
"satisfaction": 1, # 만족
|
51 |
+
"gratitude": 1, # 감사
|
52 |
+
"calm": 1, # 평온
|
53 |
+
"anticipation": 1, # 기대
|
54 |
+
"pride": 1, # 자부심
|
55 |
+
"connectedness": 1, # 유대감
|
56 |
+
|
57 |
+
# 기본 정서 (부정) - 느림
|
58 |
+
"sadness": 0.2, # 슬픔
|
59 |
+
"anger": 0.5, # 분노
|
60 |
+
"anxiety": 0.4, # 불안
|
61 |
+
"disgust": 0.8, # 혐오
|
62 |
+
"fear": 0.6, # 공포
|
63 |
+
"regret": 0.3, # 후회
|
64 |
+
"frustration": 0.5, # 좌절
|
65 |
+
|
66 |
+
# 사회적 감정
|
67 |
+
"jealousy": 0.4, # 질투
|
68 |
+
"shame": 0.4, # 수치심
|
69 |
+
"guilt": 0.4, # 죄책감
|
70 |
+
"compassion": 1, # 동정심
|
71 |
+
"awe": 1, # 경외심
|
72 |
+
"empathy": 1, # 공감
|
73 |
+
|
74 |
+
# 인지적 상태
|
75 |
+
"confusion": 0.8, # 혼란
|
76 |
+
"nervousness": 0.7, # 긴장
|
77 |
+
"apathy": 0.4, # 무관심
|
78 |
+
"excitement": 1.2, # 흥분
|
79 |
+
"immersion": 1.0, # 몰입
|
80 |
+
"skepticism": 0.5, # 회의
|
81 |
+
}
|
npc_social_network/npc/npc_base.py
CHANGED
@@ -1,5 +1,8 @@
|
|
1 |
# portfolio/npc_social_network/npc/npc_base.py
|
2 |
from .npc_memory import Memory, MemoryStore
|
|
|
|
|
|
|
3 |
|
4 |
# NPC 클래스 정의
|
5 |
class NPC:
|
@@ -16,88 +19,16 @@ class NPC:
|
|
16 |
|
17 |
# npc 기억
|
18 |
self.memory_store = MemoryStore()
|
|
|
|
|
|
|
|
|
19 |
|
20 |
-
#
|
21 |
-
self.
|
22 |
-
# 기본 정서 (긍정)
|
23 |
-
"core_positive":{
|
24 |
-
"joy": 0, # 기쁨
|
25 |
-
"satisfaction": 0, # 만족
|
26 |
-
"gratitude": 0, # 감사
|
27 |
-
"calm": 0, # 평온
|
28 |
-
"anticipation": 0, # 기대
|
29 |
-
"pride": 0, # 자부심
|
30 |
-
"connectedness": 0 # 유대감
|
31 |
-
},
|
32 |
-
# 기본 정서 (부정)
|
33 |
-
"core_negative": {
|
34 |
-
"sadness": 0, # 슬픔
|
35 |
-
"anger": 0, # 분노
|
36 |
-
"anxiety": 0, # 불안
|
37 |
-
"disgust": 0, # 혐오
|
38 |
-
"fear": 0, # 공포
|
39 |
-
"regret": 0, # 후회
|
40 |
-
"frustration": 0 # 좌절
|
41 |
-
},
|
42 |
-
# 사회적 감정
|
43 |
-
"social_emotion": {
|
44 |
-
"jealousy": 0, # 질투
|
45 |
-
"shame": 0, # 수치심
|
46 |
-
"guilt": 0, # 죄책감
|
47 |
-
"compassion": 0, # 동정심
|
48 |
-
"awe": 0, # 경외심
|
49 |
-
"empathy": 0 # 공감
|
50 |
-
},
|
51 |
-
# 인지적 상태
|
52 |
-
"cognitive_state": {
|
53 |
-
"confusion": 0, # 혼란
|
54 |
-
"nervousness": 0, # 긴장
|
55 |
-
"apathy": 0, # 무관심
|
56 |
-
"excitement": 0, # 흥분
|
57 |
-
"immersion": 0, # 몰입
|
58 |
-
"skepticism": 0 # 회의
|
59 |
-
}
|
60 |
-
}
|
61 |
-
|
62 |
-
# 감정별 회복 속도 (ms 단위가 아닌 단위당 감소 속도)
|
63 |
-
self.emotion_decay_rate = {
|
64 |
-
# 기본 정서 (긍정)
|
65 |
-
"joy": 1, # 기쁨
|
66 |
-
"satisfaction": 1, # 만족
|
67 |
-
"gratitude": 1, # 감사
|
68 |
-
"calm": 1, # 평온
|
69 |
-
"anticipation": 1, # 기대
|
70 |
-
"pride": 1, # 자부심
|
71 |
-
"connectedness": 1, # 유대감
|
72 |
-
|
73 |
-
# 기본 정서 (부정) - 느림
|
74 |
-
"sadness": 0.2, # 슬픔
|
75 |
-
"anger": 0.5, # 분노
|
76 |
-
"anxiety": 0.4, # 불안
|
77 |
-
"disgust": 0.8, # 혐오
|
78 |
-
"fear": 0.6, # 공포
|
79 |
-
"regret": 0.3, # 후회
|
80 |
-
"frustration": 0.5, # 좌절
|
81 |
-
|
82 |
-
# 사회적 감정
|
83 |
-
"jealousy": 0.4, # 질투
|
84 |
-
"shame": 0.4, # 수치심
|
85 |
-
"guilt": 0.4, # 죄책감
|
86 |
-
"compassion": 1, # 동정심
|
87 |
-
"awe": 1, # 경외심
|
88 |
-
"empathy": 1, # 공감
|
89 |
-
|
90 |
-
# 인지적 상태
|
91 |
-
"confusion": 0.8, # 혼란
|
92 |
-
"nervousness": 0.7, # 긴장
|
93 |
-
"apathy": 0.4, # 무관심
|
94 |
-
"excitement": 1.2, # 흥분
|
95 |
-
"immersion": 1.0, # 몰입
|
96 |
-
"skepticism": 0.5, # 회의
|
97 |
-
}
|
98 |
|
99 |
# 내부: float 감정 수치 관리용 버퍼
|
100 |
-
self._emotion_buffer =
|
101 |
|
102 |
# 확장 가능 속성
|
103 |
# self.emotion = "neutral"
|
@@ -118,13 +49,8 @@ class NPC:
|
|
118 |
|
119 |
# 감정 상태와 직업에 따른 대사 생성 (대표 감정 하나 반영)
|
120 |
def generate_dialogue(self):
|
121 |
-
dominant = self.get_dominant_emotion()
|
122 |
-
|
123 |
-
job_line = {
|
124 |
-
"farmer": "밭일을 해야겠어요.",
|
125 |
-
"blacksmith": "대장간에 불을 지펴야겠군요."
|
126 |
-
}.get(self.job, "오늘도 바쁘네요.")
|
127 |
-
return f"{emotion_line} {job_line}"
|
128 |
|
129 |
# NPC가 새로운 기억을 저장하고 감정 상태에 반영
|
130 |
def remember(self, content: str, importance: int = 5, emotion: str = None, strength: float = 1.0):
|
@@ -133,56 +59,33 @@ class NPC:
|
|
133 |
if emotion:
|
134 |
self.update_emotion(emotion, strength) # strength = importance / 5.0으로도 가능
|
135 |
|
136 |
-
#
|
137 |
-
def
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
def update_emotion(self, emotion: str, strength: float = 1.0):
|
142 |
-
if emotion in self._emotion_buffer:
|
143 |
-
self._emotion_buffer[emotion] += strength
|
144 |
-
self._emotion_buffer[emotion] = max(0.0, self._emotion_buffer[emotion])
|
145 |
-
|
146 |
# 시간이 지남에 따라 모든 감정이 서서히 감소
|
147 |
def decay_emotion(self):
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
self._emotion_buffer[emotion_key] -= rate
|
152 |
-
if self._emotion_buffer[emotion_key] < 0:
|
153 |
-
self._emotion_buffer[emotion_key] = 0.0
|
154 |
-
|
155 |
-
# 정수 기반 출력용 감정 상태 반환
|
156 |
-
def get_current_emotion_state(self):
|
157 |
-
display_state = {cat: {} for cat in self.emotion_state}
|
158 |
-
for cat_name, emotions_in_cat in self.emotion_state.items():
|
159 |
-
for emo_name in emotions_in_cat:
|
160 |
-
if emo_name in self._emotion_buffer:
|
161 |
-
display_state[cat_name][emo_name] = self._emotion_buffer[emo_name]
|
162 |
-
else:
|
163 |
-
display_state[cat_name][emo_name] = 0.0
|
164 |
-
return display_state
|
165 |
|
166 |
-
#
|
167 |
-
def
|
168 |
-
|
169 |
-
max_emotion = None
|
170 |
-
for emo, val in self._emotion_buffer.items():
|
171 |
-
if val > max_value:
|
172 |
-
max_value = val
|
173 |
-
max_emotion = emo
|
174 |
-
return max_emotion
|
175 |
|
176 |
# 복합 감정 계산 (상위 N개 감정 반환)
|
177 |
def get_composite_emotion_state(self, top_n: int = 3):
|
178 |
-
|
|
|
179 |
composite = [(emo,round(score, 2)) for emo, score in sorted_emotions[:top_n] if score > 0]
|
180 |
return composite # 예 [('fear', 2.0), ('anger'), 1.5]
|
181 |
|
182 |
# 감정 상태 요약 텍스트 생성
|
183 |
def summarize_emotional_state(self):
|
184 |
-
|
|
|
185 |
avg_strength = round(sum(nonzero) / len(nonzero), 2) if nonzero else 0.0
|
186 |
-
composite =
|
|
|
187 |
composite_str = ", ".join(f"{emo}({val})" for emo, val in composite)
|
188 |
return f"감정 평균 강도: {avg_strength} / 대표 감정: {composite_str if composite else '없음' }"
|
|
|
1 |
# portfolio/npc_social_network/npc/npc_base.py
|
2 |
from .npc_memory import Memory, MemoryStore
|
3 |
+
from .npc_emotion import EmotionManager
|
4 |
+
from .npc_behavior import BehaviorManager
|
5 |
+
from .emotion_config import EMOTION_STATE_TEMPLATE, EMOTION_DECAY_RATE
|
6 |
|
7 |
# NPC 클래스 정의
|
8 |
class NPC:
|
|
|
19 |
|
20 |
# npc 기억
|
21 |
self.memory_store = MemoryStore()
|
22 |
+
# 감정 상태 및 감정 관리자 초기화
|
23 |
+
self.emotion_state = EMOTION_STATE_TEMPLATE
|
24 |
+
self.emotion_decay_rate = EMOTION_DECAY_RATE
|
25 |
+
self.emotion = EmotionManager(self.emotion_state, self.emotion_decay_rate)
|
26 |
|
27 |
+
# 행동 관리자
|
28 |
+
self.behavior = BehaviorManager()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
29 |
|
30 |
# 내부: float 감정 수치 관리용 버퍼
|
31 |
+
self._emotion_buffer = self.emotion.get_buffer()
|
32 |
|
33 |
# 확장 가능 속성
|
34 |
# self.emotion = "neutral"
|
|
|
49 |
|
50 |
# 감정 상태와 직업에 따른 대사 생성 (대표 감정 하나 반영)
|
51 |
def generate_dialogue(self):
|
52 |
+
dominant = self.emotion.get_dominant_emotion()
|
53 |
+
return self.behavior.perform(self.name, self.job, dominant)
|
|
|
|
|
|
|
|
|
|
|
54 |
|
55 |
# NPC가 새로운 기억을 저장하고 감정 상태에 반영
|
56 |
def remember(self, content: str, importance: int = 5, emotion: str = None, strength: float = 1.0):
|
|
|
59 |
if emotion:
|
60 |
self.update_emotion(emotion, strength) # strength = importance / 5.0으로도 가능
|
61 |
|
62 |
+
# 특정 감정을 감정 상태에 반영
|
63 |
+
def update_emotion(self, emotion:str, strength: float = 1.0):
|
64 |
+
self.emotion.update_emotion(emotion, strength)
|
65 |
+
self._emotion_buffer = self.emotion.get_buffer()
|
66 |
+
|
|
|
|
|
|
|
|
|
|
|
67 |
# 시간이 지남에 따라 모든 감정이 서서히 감소
|
68 |
def decay_emotion(self):
|
69 |
+
self.emotion.decay_emotion()
|
70 |
+
self._emotion_buffer = self.emotion.get_buffer()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
71 |
|
72 |
+
# 저장된 모든 기억(단기+장기)을 리스트로 반환
|
73 |
+
def recall(self):
|
74 |
+
return [m.content for m in self.memory_store.get_all_memories()]
|
|
|
|
|
|
|
|
|
|
|
|
|
75 |
|
76 |
# 복합 감정 계산 (상위 N개 감정 반환)
|
77 |
def get_composite_emotion_state(self, top_n: int = 3):
|
78 |
+
buffer = self.emotion.get_buffer() # EmotionManager에서 최신 감정 상태 복사본 가져오기
|
79 |
+
sorted_emotions = sorted(buffer.items(), key=lambda x: x[1], reverse=True)
|
80 |
composite = [(emo,round(score, 2)) for emo, score in sorted_emotions[:top_n] if score > 0]
|
81 |
return composite # 예 [('fear', 2.0), ('anger'), 1.5]
|
82 |
|
83 |
# 감정 상태 요약 텍스트 생성
|
84 |
def summarize_emotional_state(self):
|
85 |
+
buffer = self.emotion.get_buffer() # EmotionManager에서 최신 감정 상태 복사본 가져오기
|
86 |
+
nonzero = [v for v in buffer.values() if v > 0]
|
87 |
avg_strength = round(sum(nonzero) / len(nonzero), 2) if nonzero else 0.0
|
88 |
+
composite = sorted(buffer.items(), key=lambda x: x[1], reverse=True)
|
89 |
+
composite = [(emo, round(score, 2)) for emo, score in composite[:3] if score > 0]
|
90 |
composite_str = ", ".join(f"{emo}({val})" for emo, val in composite)
|
91 |
return f"감정 평균 강도: {avg_strength} / 대표 감정: {composite_str if composite else '없음' }"
|
npc_social_network/npc/npc_behavior.py
ADDED
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# 감정-행동 연동 매핑 및 행동 생성
|
2 |
+
# portfolio/npc_social_network/npc/npc_behavior.py
|
3 |
+
|
4 |
+
class BehaviorManager:
|
5 |
+
def __init__(self):
|
6 |
+
self.mapping = {
|
7 |
+
# 기본 정서 (긍정)
|
8 |
+
"joy": "jump", # 기쁨 : 점프 뛰다
|
9 |
+
"satisfaction": "nod", # 만족 : 고개를 끄덕이다
|
10 |
+
"gratitude": "thank", # 감사 : 감사하다
|
11 |
+
"calm": "relax", # 평온 : 진정하다
|
12 |
+
"anticipation": "prepare", # 기대 : 준비하다
|
13 |
+
"pride": "show_off", # 자부심 : 자랑하다
|
14 |
+
"connectedness": "wave", # 유대감 : 손을 흔들다
|
15 |
+
|
16 |
+
# 기본 정서 (부정) - 느림
|
17 |
+
"sadness": "sit_down", # 슬픔 : 주저 앉는다
|
18 |
+
"anger": "shout", # 분노 : 소리치다.
|
19 |
+
"anxiety": "look_around", # 불안 : 주위를 살핀다
|
20 |
+
"disgust": "avoid", # 혐오 : 피하다
|
21 |
+
"fear": "run_away", # 공포 : 도망치다
|
22 |
+
"regret": "look_down", # 후회 : 아래를 보다
|
23 |
+
"frustration": "sigh", # 좌절 : 한숨 쉬다
|
24 |
+
|
25 |
+
# 사회적 감정
|
26 |
+
"jealousy": "glare", # 질투 : 노려보다
|
27 |
+
"shame": "hide_face", # 수치심 : 얼굴을 감추다
|
28 |
+
"guilt": "apologize", # 죄책감 : 미안하다고 말함
|
29 |
+
"compassion": "console", # 동정심 : 위로하는 행동
|
30 |
+
"awe": "gasp", # 경외심 : 감탄하며 숨을 들이쉼
|
31 |
+
"empathy": "listen_carefully", # 공감 : 공감하며 집중해서 경청
|
32 |
+
|
33 |
+
# 인지적 상태
|
34 |
+
"confusion": "scratch_head",# 혼란 : 머리를 긁적이다
|
35 |
+
"nervousness": "fidget", # 긴장 : 불안해서 몸을 계속 움직임
|
36 |
+
"apathy": "idle", # 무관심 : 아무것도 하지 않음
|
37 |
+
"excitement": "move_fast", # 흥분 : 빠르게 움직이다
|
38 |
+
"immersion": "focus", # 몰입 : 집중하는 모습
|
39 |
+
"skepticism": "raise_eyebrow", # 회의 : 의심스러운 듯 눈썹을 찌푸림
|
40 |
+
}
|
41 |
+
|
42 |
+
def decide(self, dominant_emotion):
|
43 |
+
return self.mapping.get(dominant_emotion, "idle")
|
44 |
+
|
45 |
+
def perform(self, name, job, emotion):
|
46 |
+
action = self.decide(emotion)
|
47 |
+
behavior_lines = {
|
48 |
+
"jump": f"{name}은(는) 기뻐서 깡충 뜁니다!",
|
49 |
+
"shout": f"{name}은(는) 화가 나서 소리를 지릅니다!",
|
50 |
+
"run_away": f"{name}은(는) 무서워 도망갑니다!",
|
51 |
+
"sit_down": f"{name}은(는) 슬퍼서 주저앉습니다....",
|
52 |
+
"wave": f"{name}은(는) 손을 흔듭니다.",
|
53 |
+
"sigh": f"{name}은(는) 한숨을 쉽니다..."
|
54 |
+
}
|
55 |
+
msg = behavior_lines.get(action, f"{name}은(는) {action} 행동을 합니다.")
|
56 |
+
job_line = {"farmer": "밭일도 해야 하겠군요.", "blacksmith": "대장간 불도 지펴야죠."}.get(job, "오늘도 바쁜 하루네요.")
|
57 |
+
return f"{msg} {job_line}"
|