Trong thế giới game và ứng dụng tương tác hiện đại, **NPC (Non-Player Character)** không còn đơn thuần là những nhân vật cứng nhắc với các đoạn hội thoại lặp đi lặp lại. Với sự bùng nổ của các mô hình ngôn ngữ lớn (LLM), việc tạo ra những NPC có khả năng thể hiện cảm xúc chân thực, phản ứng linh hoạt theo ngữ cảnh đã trở thành hiện thực. Bài viết này sẽ hướng dẫn bạn xây dựng hệ thống **emotion simulation for NPC** từ cơ bản đến nâng cao, tích hợp API LLM với chi phí tối ưu.
---
1. Bối cảnh thực chiến: Dự án game RPG thương mại điện tử
Tôi bắt đầu tìm hiểu về NPC emotion computation khi làm việc cho một dự án **game RPG kết hợp thương mại điện tử** — nơi người chơi tương tác với các nhân vật thương nhân, thợ rèn, và NPC phục vụ nhiệm vụ. Yêu cầu đặt ra là:
- NPC phải nhớ lịch sử tương tác với người chơi
- Cảm xúc thay đổi theo hành động người chơi (giúp đỡ, lừa dối, chiến đấu)
- Phản hồi ngữ cảnh, không lặp lại câu trả lời
- Chi phí API LLM phải dưới **$50/tháng** cho 10,000 người dùng active
Sau khi thử nghiệm nhiều giải pháp, tôi xây dựng được hệ thống sử dụng **HolySheep AI** với chi phí thực tế chỉ **$12/tháng** — tiết kiệm **85%** so với việc dùng OpenAI trực tiếp.
---
2. Kiến trúc hệ thống Emotion Simulation
2.1. Sơ đồ tổng quan
┌─────────────────────────────────────────────────────────┐
│ GAME CLIENT │
│ ┌─────────┐ ┌──────────┐ ┌────────────────────┐ │
│ │ Player │ │ NPC │ │ Emotion State │ │
│ │ Action │──│ Renderer │──│ Tracker │ │
│ └─────────┘ └──────────┘ └────────────────────┘ │
└──────────────────────┬──────────────────────────────────┘
│ HTTP/WebSocket
▼
┌─────────────────────────────────────────────────────────┐
│ BACKEND SERVER │
│ ┌─────────────┐ ┌──────────────┐ ┌───────────────┐ │
│ │ Conversation│ │ Emotion │ │ RAG │ │
│ │ Manager │──│ Engine │──│ Knowledge │ │
│ └─────────────┘ └──────────────┘ └───────────────┘ │
└──────────────────────┬──────────────────────────────────┘
│ API Call
▼
┌─────────────────────────────────────────────────────────┐
│ HOLYSHEEP AI API (LLM) │
│ base_url: https://api.holysheep.ai/v1 │
│ Models: DeepSeek V3.2, GPT-4.1, Claude Sonnet 4.5 │
└─────────────────────────────────────────────────────────┘
2.2. Emotion State Model
Mỗi NPC cần theo dõi một **trạng thái cảm xúc đa chiều**. Tôi sử dụng model sau:
from pydantic import BaseModel, Field
from typing import Dict, List, Optional
from datetime import datetime
from enum import Enum
class EmotionType(str, Enum):
JOY = "joy"
SADNESS = "sadness"
ANGER = "anger"
FEAR = "fear"
SURPRISE = "surprise"
TRUST = "trust"
DISGUST = "disgust"
ANTICIPATION = "anticipation"
class EmotionState(BaseModel):
"""Trạng thái cảm xúc của NPC"""
npc_id: str
emotions: Dict[EmotionType, float] = Field(
default_factory=lambda: {
EmotionType.JOY: 0.5,
EmotionType.SADNESS: 0.0,
EmotionType.ANGER: 0.0,
EmotionType.FEAR: 0.0,
EmotionType.TRUST: 0.5,
}
)
mood: float = Field(default=0.5, ge=-1.0, le=1.0) # -1: very negative, +1: very positive
arousal: float = Field(default=0.5, ge=0.0, le=1.0) # 0: calm, 1: excited
# Memory traces
recent_interactions: List[Dict] = Field(default_factory=list)
trust_score: float = Field(default=0.5, ge=0.0, le=1.0)
familiarity: float = Field(default=0.0, ge=0.0, le=1.0) # How well NPC knows player
# Context
current_location: str = "village_square"
time_of_day: str = "morning"
weather: str = "clear"
last_updated: datetime = Field(default_factory=datetime.now)
---
3. Triển khai Core Engine
3.1. Cài đặt và cấu hình
# Cài đặt các thư viện cần thiết
pip install openai>=1.0.0 pydantic>=2.0 redis>=5.0 httpx>=0.25
Hoặc sử dụng Poetry
poetry add openai pydantic redis httpx
3.2. HolySheep AI Integration — Triển khai Emotion Engine
import os
from openai import OpenAI
from typing import List, Dict, Optional
from dataclasses import dataclass, field
import json
import httpx
============ CẤU HÌNH HOLYSHEEP AI ============
Đăng ký tài khoản tại: https://www.holysheep.ai/register
Nhận tín dụng miễn phí khi đăng ký!
HOLYSHEEP_API_KEY = os.getenv("HOLYSHEEP_API_KEY", "YOUR_HOLYSHEEP_API_KEY")
HOLYSHEEP_BASE_URL = "https://api.holysheep.ai/v1"
@dataclass
class NPCEmotionConfig:
"""Cấu hình cho NPC emotion simulation"""
# Chọn model phù hợp với budget
# DeepSeek V3.2: $0.42/MTok - Giá rẻ nhất, hiệu năng tốt
# Gemini 2.5 Flash: $2.50/MTok - Cân bằng chi phí
# GPT-4.1: $8/MTok - Chất lượng cao nhất
primary_model: str = "deepseek/deepseek-chat-v3-240529"
fallback_model: str = "google/gemini-2.5-flash"
temperature: float = 0.8
max_tokens: int = 500
emotion_update_interval: int = 3 # Update emotion sau mỗi N interactions
@dataclass
class InteractionContext:
"""Ngữ cảnh tương tác"""
player_id: str
npc_id: str
player_action: str
player_message: str
game_state: Dict
time_pressure: bool = False
has_combat: bool = False
class HolySheepEmotionLLM:
"""
LLM Client sử dụng HolySheep AI cho NPC emotion computation.
Chi phí tiết kiệm 85%+ so với OpenAI trực tiếp.
"""
def __init__(self, config: Optional[NPCEmotionConfig] = None):
self.config = config or NPCEmotionConfig()
# Khởi tạo client với base_url của HolySheep
self.client = OpenAI(
api_key=HOLYSHEEP_API_KEY,
base_url=HOLYSHEEP_BASE_URL,
http_client=httpx.Client(timeout=30.0)
)
def generate_emotion_response(
self,
emotion_state: EmotionState,
context: InteractionContext,
conversation_history: List[Dict]
) -> Dict:
"""
Sinh phản hồi với emotion simulation.
Returns:
{
"response": str,
"emotion_changes": Dict[str, float],
"animation_trigger": str,
"new_mood": float
}
"""
# Xây dựng system prompt với emotion context
system_prompt = self._build_emotion_system_prompt(emotion_state)
# Xây dựng messages với conversation history
messages = [{"role": "system", "content": system_prompt}]
for msg in conversation_history[-10:]: # Giới hạn context window
messages.append({
"role": msg.get("role", "user"),
"content": msg["content"]
})
# Thêm player message hiện tại
messages.append({
"role": "user",
"content": f"Người chơi: {context.player_message}"
})
try:
response = self.client.chat.completions.create(
model=self.config.primary_model,
messages=messages,
temperature=self.config.temperature,
max_tokens=self.config.max_tokens,
extra_body={
"response_format": {
"type": "json_object",
"schema": {
"type": "object",
"properties": {
"response": {"type": "string"},
"emotion_changes": {
"type": "object",
"properties": {
"joy": {"type": "number"},
"sadness": {"type": "number"},
"anger": {"type": "number"},
"fear": {"type": "number"},
"trust": {"type": "number"}
}
},
"animation": {"type": "string"},
"new_mood": {"type": "number"}
},
"required": ["response", "emotion_changes", "animation", "new_mood"]
}
}
}
)
result = json.loads(response.choices[0].message.content)
# Log chi phí để theo dõi budget
usage = response.usage
estimated_cost = (usage.prompt_tokens * 0.42 + usage.completion_tokens * 0.42) / 1_000_000
print(f"📊 Token usage: {usage.prompt_tokens} prompt + {usage.completion_tokens} completion")
print(f"💰 Estimated cost: ${estimated_cost:.6f}")
return result
except Exception as e:
print(f"⚠️ LLM call failed: {e}")
return self._fallback_response(emotion_state)
def _build_emotion_system_prompt(self, state: EmotionState) -> str:
"""Xây dựng system prompt với emotional context"""
# Tính dominant emotion
dominant = max(state.emotions.items(), key=lambda x: x[1])
prompt = f"""Bạn là một NPC trong game RPG với khả năng thể hiện cảm xúc chân thực.
Bạn có trạng thái cảm xúc hiện tại:
- Tâm trạng chung: {state.mood:.1f}/10 (âm = tiêu cực, dương = tích cực)
- Mức độ kích động: {state.arousal:.1f}/10
- Cảm xúc chủ đạo: {dominant[0].value} ({dominant[1]:.1f}/10)
- Mức độ tin tưởng người chơi: {state.trust_score:.1f}/10
- Quen thuộc với người chơi: {state.familiarity:.1f}/10
Cảm xúc ảnh hưởng đến cách bạn nói chuyện:
- Nếu anger cao: Có thể nói gắt gỏng, đe dọa
- Nếu joy cao: Nói vui vẻ, hào phóng
- Nếu trust cao: Chia sẻ thông tin cá nhân, giúp đỡ nhiều hơn
- Nếu fear cao: Do dự, cảnh giác
- Nếu familiarity thấp: Thận trọng, giữ khoảng cách
Trả lời JSON với format:
{{
"response": "Câu trả lời của bạn (dạng chat, tự nhiên)",
"emotion_changes": {{"joy": +/-0.0-0.3, "anger": +/-0.0-0.3, ...}},
"animation": "Tên animation phù hợp (smile/angry/fear/surprise/cry/thinking)",
"new_mood": -1.0 đến 1.0
}}
QUAN TRỌNG:
1. Luôn trả về JSON hợp lệ
2. emotion_changes là delta (thay đổi), không phải giá trị tuyệt đối
3. Chỉ thay đổi 1-3 emotions mỗi lần tương tác
4. Response phải phù hợp với personality và current emotions"""
return prompt
def _fallback_response(self, state: EmotionState) -> Dict:
"""Fallback khi LLM call fail"""
return {
"response": "Hmm... ta đang suy nghĩ...",
"emotion_changes": {"thinking": 0.1},
"animation": "thinking",
"new_mood": state.mood
}
---
4. Emotion State Manager — Quản lý trạng thái cảm xúc
```python
from typing import Dict, Optional
import redis
import json
from datetime import datetime, timedelta
class EmotionStateManager:
"""
Quản lý trạng thái cảm xúc của tất cả NPC trong game.
Sử dụng Redis để cache và sync giữa các server instances.
"""
def __init__(self, redis_url: str = "redis://localhost:6379/0"):
self.redis = redis.from_url(redis_url, decode_responses=True)
self.emotion_key_prefix = "npc:emotion:"
self.history_key_prefix = "npc:history:"
# Emotion decay rate (mỗi 5 phút)
self.decay_rate = {
"anger": 0.02,
"fear": 0.03,
"joy": 0.01,
"sadness": 0.02,
"surprise": 0.05, # Surprise decay nhanh nhất
}
def get_state(self, npc_id: str) -> Optional[EmotionState]:
"""Lấy trạng thái cảm xúc hiện tại của NPC"""
key = f"{self.emotion_key_prefix}{npc_id}"
data = self.redis.get(key)
if data:
state_dict = json.loads(data)
return EmotionState(**state_dict)
return None
def update_state(
self,
npc_id: str,
emotion_changes: Dict[str, float],
player_id: str,
interaction_type: str
) -> EmotionState:
"""
Cập nhật trạng thái cảm xúc sau tương tác.
Áp dụng các rule về emotional dynamics.
"""
state = self.get_state(npc_id) or EmotionState(npc_id=npc_id)
# 1. Apply emotion changes với clamping
for emotion, delta in emotion_changes.items():
if emotion in state.emotions:
current = state.emotions[EmotionType(emotion)]
new_value = max(0.0, min(1.0, current + delta))
state.emotions[EmotionType(emotion)] = new_value
# 2. Recalculate mood và arousal
positive_emotions = state.emotions.get(EmotionType.JOY, 0)
negative_emotions = (
state.emotions.get(EmotionType.ANGER, 0) +
state.emotions.get(EmotionType.SADNESS, 0) +
state.emotions.get(EmotionType.FEAR, 0) * 0.5
)
state.mood = (positive_emotions - negative_emotions) / 2
# Arousal = max của các emotional intensities
state.arousal = max(state.emotions.values())
# 3. Trust adjustments based on interaction type
trust_delta = self._calculate_trust_change(interaction_type, emotion_changes)
state.trust_score = max(0.0, min(1.0, state.trust_score + trust_delta))
# 4. Update familiarity
state.familiarity = min(1.0, state.familiarity + 0.02)
# 5. Record interaction
state.recent_interactions.append({
"timestamp": datetime.now().isoformat(),
"player_id": player_id,
"type": interaction_type,
"mood_after": state.mood
})
# Giới hạn history
if len(state.recent_interactions) > 50:
state.recent_interactions = state.recent_interactions[-50:]
state.last_updated = datetime.now()
# 6. Persist to Redis
self._save_state(state)
return state
def _calculate_trust_change(
self,
interaction_type: str,
emotion_changes: Dict
) -> float:
"""Tính toán thay đổi trust dựa trên loại tương tác"""
trust_map = {
"give_gift": 0.15,
"complete_quest": 0.10,
"help": 0.08,
"trade_fair": 0.05,
"small_talk": 0.02,
"betray": -0.25,
"attack": -0.20,
"steal": -0.30,
"lie": -0.15,
"ignore": -0.05,
}
base_change = trust_map.get(interaction_type, 0.0)
# Bonus nếu có positive emotion displayed
if emotion_changes.get("joy", 0) > 0.1:
base_change += 0.05
# Penalty nếu có negative emotion displayed
if emotion_changes.get("anger", 0) > 0.1:
base_change -= 0.10
return base_change
def apply_decay(self, npc_id: str) -> EmotionState:
"""Áp dụng emotional decay theo thời gian"""
state = self.get_state(npc_id)
if not state:
return None
# Tính thời gian kể từ lần update cuối
time_delta = datetime.now() - state.last_updated
decay_periods = time_delta.total_seconds() / 300 # 5 phút
for emotion, rate in self.decay_rate.items():
if emotion in state.emotions:
state.emotions[EmotionType(emotion)] = max(
0.0,
state.emotions[EmotionType(emotion)] - rate * decay_periods
)
# Gentle mood recovery
if state.mood < 0:
state.mood = min(0, state.mood + 0.01 * decay_periods)
elif state.mood > 0:
state.mood = max(0, state.mood - 0.01 * decay_periods)
state.last_updated = datetime.now()
self._save_state(state)
return state
def _save_state(self, state: EmotionState):
"""Lưu state vào Redis"""
key = f"{self.emotion
Tài nguyên liên quan
Bài viết liên quan