rorshi commited on
Commit
785b76a
·
1 Parent(s): 64fce5c

Complete step 9: 감정 행동 시퀀스 구성

Browse files
npc_social_network/npc/emotion_config.py CHANGED
@@ -46,13 +46,13 @@ EMOTION_STATE_TEMPLATE = {
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, # 슬픔
@@ -67,16 +67,16 @@ EMOTION_DECAY_RATE = {
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
  }
82
 
 
46
  # 감정별 회복 속도 (ms 단위가 아닌 단위당 감소 속도)
47
  EMOTION_DECAY_RATE = {
48
  # 기본 정서 (긍정)
49
+ "joy": 0.7, # 기쁨
50
+ "satisfaction": 0.8, # 만족
51
+ "gratitude": 0.4, # 감사
52
+ "calm": 0.2, # 평온
53
+ "anticipation": 0.6, # 기대
54
+ "pride": 0.8, # 자부심
55
+ "connectedness": 0.5, # 유대감
56
 
57
  # 기본 정서 (부정) - 느림
58
  "sadness": 0.2, # 슬픔
 
67
  "jealousy": 0.4, # 질투
68
  "shame": 0.4, # 수치심
69
  "guilt": 0.4, # 죄책감
70
+ "compassion": 0.8, # 동정심
71
+ "awe": 0.8, # 경외심
72
  "empathy": 1, # 공감
73
 
74
  # 인지적 상태
75
  "confusion": 0.8, # 혼란
76
  "nervousness": 0.7, # 긴장
77
  "apathy": 0.4, # 무관심
78
+ "excitement": 0.9, # 흥분
79
+ "immersion": 0.6, # 몰입
80
  "skepticism": 0.5, # 회의
81
  }
82
 
npc_social_network/npc/npc_base.py CHANGED
@@ -42,54 +42,74 @@ class NPC:
42
  # self.emotion = "neutral"
43
  # self.personality = {"kind": 0.5, "lazy": 0.2} # 예시
44
 
45
- # 이동
46
  def move(self):
 
 
 
47
  self.current_index = (self.current_index + 1) % len(self.path)
48
 
49
- # 현재 위치 좌표 반환
50
  def get_position(self):
 
 
 
51
  return self.path[self.current_index]
52
 
53
- # NPC 이미지 화면에 렌더링
54
  def draw(self, screen, tile_size):
 
 
 
55
  x, y = self.get_position()
56
  screen.blit(self.image, (x * tile_size, y * tile_size))
57
 
58
- # 감정 상태와 직업에 따른 대사 생성 (대표 감정 하나 반영)
59
  def generate_dialogue(self):
60
- dominant = self.emotion.get_dominant_emotion()
61
- return self.behavior.perform(self.name, self.job, dominant)
 
 
 
62
 
63
- # NPC가 새로운 기억을 저장하고 감정 상태에 반영
64
  def remember(self, content: str, importance: int = 5, emotion: str = None, strength: float = 1.0):
 
 
 
65
  memory = Memory(content=content, importance = importance, emotion=emotion or "neutral")
66
  self.memory_store.add_memory(memory)
67
  if emotion:
68
  self.update_emotion(emotion, strength) # strength = importance / 5.0으로도 가능
69
 
70
- # 특정 감정을 감정 상태에 반영
71
  def update_emotion(self, emotion:str, strength: float = 1.0):
 
 
 
72
  self.emotion.update_emotion(emotion, strength)
73
  self._emotion_buffer = self.emotion.get_buffer()
74
 
75
- # 시간이 지남에 따라 모든 감정이 서서히 감소
76
  def decay_emotion(self):
 
 
 
77
  self.emotion.decay_emotion()
78
  self._emotion_buffer = self.emotion.get_buffer()
79
 
80
- # 저장된 모든 기억(단기+장기)을 리스트로 반환
81
  def recall(self):
 
 
 
82
  return [m.content for m in self.memory_store.get_all_memories()]
83
 
84
- # 복합 감정 계산 (상위 N개 감정 반환)
85
  def get_composite_emotion_state(self, top_n: int = 3):
 
 
 
86
  buffer = self.emotion.get_buffer() # EmotionManager에서 최신 감정 상태 복사본 가져오기
87
  sorted_emotions = sorted(buffer.items(), key=lambda x: x[1], reverse=True)
88
  composite = [(emo,round(score, 2)) for emo, score in sorted_emotions[:top_n] if score > 0]
89
  return composite # 예 [('fear', 2.0), ('anger'), 1.5]
90
 
91
- # 감정 상태 요약 텍스트 생성
92
  def summarize_emotional_state(self):
 
 
 
93
  buffer = self.emotion.get_buffer() # EmotionManager에서 최신 감정 상태 복사본 가져오기
94
  nonzero = [v for v in buffer.values() if v > 0]
95
  avg_strength = round(sum(nonzero) / len(nonzero), 2) if nonzero else 0.0
@@ -98,13 +118,17 @@ class NPC:
98
  composite_str = ", ".join(f"{emo}({val})" for emo, val in composite)
99
  return f"감정 평균 강도: {avg_strength} / 대표 감정: {composite_str if composite else '없음' }"
100
 
101
- # 다른 NPC와 상호작용 시 감정에 따른 관계 변화를 반영
102
  def interact_with(self, other_npc_name: str, emotion: str, positive: bool):
 
 
 
103
  delta = self._get_emotion_influence(emotion, positive)
104
  self.relationships.update_relationship(other_npc_name, delta)
105
 
106
- # 감정과 상호작용의 긍/부정 여부에 따른 관계 변화량 결정
107
  def _get_emotion_influence(self, emotion: str, positive: bool) -> float:
 
 
 
108
  base = 5.0 # 기본 영향력
109
 
110
  # 감정 종류별 base 수정
@@ -120,12 +144,16 @@ class NPC:
120
  # 정의되지 않은 감정 처리
121
  return 0.0
122
 
123
- # 상대방과의 현재 관계 설명 반환
124
  def get_relationship_description(self, other_npc_name: str) -> str:
 
 
 
125
  return self.relationships.describe_relationship(other_npc_name)
126
 
127
- # 기억이 관계에 미치는 영향 반영
128
  def reflect_memory_on_relationship(self, other_npc_name:str):
 
 
 
129
  # 최근 기억 + 최근 감정을 기반으로 관계 변화량 결정
130
  memories = self.memory_store.get_all_memories()
131
  dominant_emotion = self.emotion.get_dominant_emotion()
 
42
  # self.emotion = "neutral"
43
  # self.personality = {"kind": 0.5, "lazy": 0.2} # 예시
44
 
 
45
  def move(self):
46
+ """
47
+ 이동
48
+ """
49
  self.current_index = (self.current_index + 1) % len(self.path)
50
 
 
51
  def get_position(self):
52
+ """
53
+ 현재 위치 좌표 반환
54
+ """
55
  return self.path[self.current_index]
56
 
 
57
  def draw(self, screen, tile_size):
58
+ """
59
+ NPC 이미지 화면에 렌더링
60
+ """
61
  x, y = self.get_position()
62
  screen.blit(self.image, (x * tile_size, y * tile_size))
63
 
 
64
  def generate_dialogue(self):
65
+ """
66
+ 감정 상태와 직업에 따른 복합 행동 시퀀스 기반 대사 생성
67
+ """
68
+ composite = self.get_composite_emotion_state(top_n=3) # 상위 3개 감정 사용
69
+ return self.behavior.perform_sequence(self.name, self.job, composite)
70
 
 
71
  def remember(self, content: str, importance: int = 5, emotion: str = None, strength: float = 1.0):
72
+ """
73
+ NPC가 새로운 기억을 저장하고 감정 상태에 반영
74
+ """
75
  memory = Memory(content=content, importance = importance, emotion=emotion or "neutral")
76
  self.memory_store.add_memory(memory)
77
  if emotion:
78
  self.update_emotion(emotion, strength) # strength = importance / 5.0으로도 가능
79
 
 
80
  def update_emotion(self, emotion:str, strength: float = 1.0):
81
+ """
82
+ 특정 감정을 감정 상태에 반영
83
+ """
84
  self.emotion.update_emotion(emotion, strength)
85
  self._emotion_buffer = self.emotion.get_buffer()
86
 
 
87
  def decay_emotion(self):
88
+ """
89
+ 시간이 지남에 따라 모든 감정이 서서히 감소
90
+ """
91
  self.emotion.decay_emotion()
92
  self._emotion_buffer = self.emotion.get_buffer()
93
 
 
94
  def recall(self):
95
+ """
96
+ 저장된 모든 기억(단기 + 장기)을 리스트로 반환
97
+ """
98
  return [m.content for m in self.memory_store.get_all_memories()]
99
 
 
100
  def get_composite_emotion_state(self, top_n: int = 3):
101
+ """
102
+ 복합 감정 계산 (상위 N개 감정 반환)
103
+ """
104
  buffer = self.emotion.get_buffer() # EmotionManager에서 최신 감정 상태 복사본 가져오기
105
  sorted_emotions = sorted(buffer.items(), key=lambda x: x[1], reverse=True)
106
  composite = [(emo,round(score, 2)) for emo, score in sorted_emotions[:top_n] if score > 0]
107
  return composite # 예 [('fear', 2.0), ('anger'), 1.5]
108
 
 
109
  def summarize_emotional_state(self):
110
+ """
111
+ 감정 상태 요약 텍스트 생성
112
+ """
113
  buffer = self.emotion.get_buffer() # EmotionManager에서 최신 감정 상태 복사본 가져오기
114
  nonzero = [v for v in buffer.values() if v > 0]
115
  avg_strength = round(sum(nonzero) / len(nonzero), 2) if nonzero else 0.0
 
118
  composite_str = ", ".join(f"{emo}({val})" for emo, val in composite)
119
  return f"감정 평균 강도: {avg_strength} / 대표 감정: {composite_str if composite else '없음' }"
120
 
 
121
  def interact_with(self, other_npc_name: str, emotion: str, positive: bool):
122
+ """
123
+ 다른 NPC와 상호작용 시 감정에 따른 관계 변화를 반영
124
+ """
125
  delta = self._get_emotion_influence(emotion, positive)
126
  self.relationships.update_relationship(other_npc_name, delta)
127
 
 
128
  def _get_emotion_influence(self, emotion: str, positive: bool) -> float:
129
+ """
130
+ 감정과 상호작용의 긍/부정 여부에 따른 관계 변화량 결정
131
+ """
132
  base = 5.0 # 기본 영향력
133
 
134
  # 감정 종류별 base 수정
 
144
  # 정의되지 않은 감정 처리
145
  return 0.0
146
 
 
147
  def get_relationship_description(self, other_npc_name: str) -> str:
148
+ """
149
+ 상대방과의 현재 관계 설명 반환
150
+ """
151
  return self.relationships.describe_relationship(other_npc_name)
152
 
 
153
  def reflect_memory_on_relationship(self, other_npc_name:str):
154
+ """
155
+ 기억이 관계에 미치는 영향 반영
156
+ """
157
  # 최근 기억 + 최근 감정을 기반으로 관계 변화량 결정
158
  memories = self.memory_store.get_all_memories()
159
  dominant_emotion = self.emotion.get_dominant_emotion()
npc_social_network/npc/npc_behavior.py CHANGED
@@ -39,19 +39,51 @@ class BehaviorManager:
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}"
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  "skepticism": "raise_eyebrow", # 회의 : 의심스러운 듯 눈썹을 찌푸림
40
  }
41
 
42
+ def decide_sequence(self, composite_emotions):
43
+ """
44
+ 복합 감정 상태(composite_emotions: [(emotions, score), ...])에 따른 행동 시퀀스 결정
45
+ """
46
+ sequence = []
47
+ for emo, score in composite_emotions:
48
+ action = self.mapping.get(emo)
49
+ if action:
50
+ sequence.append((action, score)) # (행동, 감정 강도 형태로 저장)
51
+ return sequence
52
 
53
+ def perform_sequence(self, name, job, composite_emotion):
54
+ """
55
+ 행동 시퀀스를 텍스트로 구성하여 출력
56
+ """
57
+ sequence = self.decide_sequence(composite_emotion)
58
+ if not sequence:
59
+ return f"{name}은(는) 특별한 행동을 하지 않습니다."
60
+
61
+ # 행동 텍스트 템플릿
62
  behavior_lines = {
63
+ "jump": f"기뻐서 깡충 뜁니다.",
64
+ "shout": f"화가 나서 소리를 지릅니다!",
65
+ "run_away": f"무서워 도망갑니다!",
66
+ "sit_down": f"슬퍼서 주저앉습니다....",
67
+ "wave": f"손을 흔듭니다.",
68
+ "sigh": f"한숨을 쉽니다...",
69
+ "thank": f"감사한 마음을 표현합니다.",
70
+ "console": f"상대방을 따듯하게 위로합니다.",
71
+ "avoid": f"뒤로 물러서 피하려 합니다.",
72
+ "show_off": "자랑스러운 모습을 보여줍니다.",
73
+ # 나머지는 기본 fallback 사용
74
  }
75
+
76
+ # 행동 문장 생성
77
+ lines = []
78
+ for action, score in sequence:
79
+ msg = behavior_lines.get(action, f"{action} 행동을 합니다.")
80
+ lines.append(f"({round(score,1)}) {msg}")
81
+
82
+ # 직업 라인 추가
83
+ job_line = {
84
+ "farmer": "밭일도 해야 하겠군요.",
85
+ "blacksmith": "대장간 불도 지펴야죠.",
86
+ }.get(job, "오늘도 바쁜 하루네요.")
87
+
88
+ # 최종 출력 문장
89
+ return f"{name} 행동 시퀀스: \n" + "\n".join(lines) + f"\n{job_line}"
test.ipynb CHANGED
@@ -2881,10 +2881,19 @@
2881
  },
2882
  {
2883
  "cell_type": "code",
2884
- "execution_count": null,
2885
  "id": "53c994dd",
2886
  "metadata": {},
2887
- "outputs": [],
 
 
 
 
 
 
 
 
 
2888
  "source": [
2889
  "# 8단계 관계 + 감정 + 기억 연동\n",
2890
  "from npc_social_network.npc.npc_base import NPC\n",
@@ -2906,6 +2915,42 @@
2906
  "print(\"로다와의 관계 점수:\", npc1.relationships.get_relationship(\"로다\"))\n",
2907
  "print(\"로다와의 관계 설명:\", npc1.get_relationship_description(\"로다\"))"
2908
  ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2909
  }
2910
  ],
2911
  "metadata": {
 
2881
  },
2882
  {
2883
  "cell_type": "code",
2884
+ "execution_count": 3,
2885
  "id": "53c994dd",
2886
  "metadata": {},
2887
+ "outputs": [
2888
+ {
2889
+ "name": "stdout",
2890
+ "output_type": "stream",
2891
+ "text": [
2892
+ "로다와의 관계 점수: 9.5\n",
2893
+ "로다와의 관계 설명: 호감 있음\n"
2894
+ ]
2895
+ }
2896
+ ],
2897
  "source": [
2898
  "# 8단계 관계 + 감정 + 기억 연동\n",
2899
  "from npc_social_network.npc.npc_base import NPC\n",
 
2915
  "print(\"로다와의 관계 점수:\", npc1.relationships.get_relationship(\"로다\"))\n",
2916
  "print(\"로다와의 관계 설명:\", npc1.get_relationship_description(\"로다\"))"
2917
  ]
2918
+ },
2919
+ {
2920
+ "cell_type": "code",
2921
+ "execution_count": null,
2922
+ "id": "69387020",
2923
+ "metadata": {},
2924
+ "outputs": [
2925
+ {
2926
+ "ename": "AttributeError",
2927
+ "evalue": "'BehaviorManager' object has no attribute 'decide'",
2928
+ "output_type": "error",
2929
+ "traceback": [
2930
+ "\u001b[31m---------------------------------------------------------------------------\u001b[39m",
2931
+ "\u001b[31mAttributeError\u001b[39m Traceback (most recent call last)",
2932
+ "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[3]\u001b[39m\u001b[32m, line 12\u001b[39m\n\u001b[32m 9\u001b[39m npc.remember(\u001b[33m\"\u001b[39m\u001b[33m실망스러운 일 겪음\u001b[39m\u001b[33m\"\u001b[39m, importance=\u001b[32m8\u001b[39m, emotion=\u001b[33m\"\u001b[39m\u001b[33msadness\u001b[39m\u001b[33m\"\u001b[39m, strength=\u001b[32m1.8\u001b[39m)\n\u001b[32m 11\u001b[39m \u001b[38;5;66;03m# 복합 감정 기반 행동 시퀀스 출력\u001b[39;00m\n\u001b[32m---> \u001b[39m\u001b[32m12\u001b[39m \u001b[38;5;28mprint\u001b[39m(\u001b[43mnpc\u001b[49m\u001b[43m.\u001b[49m\u001b[43mgenerate_dialogue\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m)\n",
2933
+ "\u001b[36mFile \u001b[39m\u001b[32me:\\Git-Repository\\Portfolio\\npc_social_network\\npc\\npc_base.py:69\u001b[39m, in \u001b[36mNPC.generate_dialogue\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 65\u001b[39m \u001b[38;5;250m\u001b[39m\u001b[33;03m\"\"\"\u001b[39;00m\n\u001b[32m 66\u001b[39m \u001b[33;03m감정 상태와 직업에 따른 복합 행동 시퀀스 기반 대사 생성\u001b[39;00m\n\u001b[32m 67\u001b[39m \u001b[33;03m\"\"\"\u001b[39;00m\n\u001b[32m 68\u001b[39m composite = \u001b[38;5;28mself\u001b[39m.get_composite_emotion_state(top_n=\u001b[32m3\u001b[39m) \u001b[38;5;66;03m# 상위 3개 감정 사용\u001b[39;00m\n\u001b[32m---> \u001b[39m\u001b[32m69\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mbehavior\u001b[49m\u001b[43m.\u001b[49m\u001b[43mperform_sequence\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mname\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mjob\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcomposite\u001b[49m\u001b[43m)\u001b[49m\n",
2934
+ "\u001b[36mFile \u001b[39m\u001b[32me:\\Git-Repository\\Portfolio\\npc_social_network\\npc\\npc_behavior.py:57\u001b[39m, in \u001b[36mBehaviorManager.perform_sequence\u001b[39m\u001b[34m(self, name, job, composite_emotion)\u001b[39m\n\u001b[32m 53\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34mperform_sequence\u001b[39m(\u001b[38;5;28mself\u001b[39m, name, job, composite_emotion):\n\u001b[32m 54\u001b[39m \u001b[38;5;250m \u001b[39m\u001b[33;03m\"\"\"\u001b[39;00m\n\u001b[32m 55\u001b[39m \u001b[33;03m 행동 시퀀스를 텍스트로 구성하여 출력\u001b[39;00m\n\u001b[32m 56\u001b[39m \u001b[33;03m \"\"\"\u001b[39;00m\n\u001b[32m---> \u001b[39m\u001b[32m57\u001b[39m sequence = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mdecide\u001b[49m(composite_emotion)\n\u001b[32m 58\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m sequence:\n\u001b[32m 59\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mname\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m은(는) 특별한 행동을 하지 않습니다.\u001b[39m\u001b[33m\"\u001b[39m\n",
2935
+ "\u001b[31mAttributeError\u001b[39m: 'BehaviorManager' object has no attribute 'decide'"
2936
+ ]
2937
+ }
2938
+ ],
2939
+ "source": [
2940
+ "# 9단계 복합 감정 - 행동 시퀀스 구성\n",
2941
+ "from npc_social_network.npc.npc_base import NPC\n",
2942
+ "\n",
2943
+ "npc = NPC(name=\"아린\", job=\"farmer\", path=[(0,0)], image=None)\n",
2944
+ "\n",
2945
+ "# 여러 감정 기억 등록\n",
2946
+ "npc.remember(\"칭찬받아 기쁨\", importance=7, emotion=\"joy\", strength=2.0)\n",
2947
+ "npc.remember(\"동료가 위로함\", importance=8, emotion=\"gratitude\", strength=1.5)\n",
2948
+ "npc.remember(\"실망스러운 일 겪음\", importance=8, emotion=\"sadness\", strength=1.8)\n",
2949
+ "print(npc.get_composite_emotion_state())\n",
2950
+ "\n",
2951
+ "# 복합 감정 기반 행동 시퀀스 출력\n",
2952
+ "print(npc.generate_dialogue())"
2953
+ ]
2954
  }
2955
  ],
2956
  "metadata": {