OpenClaw Godot Plugin에 기여해 주셔서 감사합니다!
| 영역 | 설명 | 난이도 |
|---|---|---|
| 🐛 버그 수정 | 이슈 해결, 안정성 개선 | ⭐ |
| 📝 문서화 | README, 예제, 번역 | ⭐ |
| 🔧 새 도구 추가 | tools.gd에 기능 추가 | ⭐⭐ |
| ⚡ 성능 개선 | 최적화, 메모리 관리 | ⭐⭐⭐ |
| 🏗️ 아키텍처 | 핵심 구조 변경 | ⭐⭐⭐ |
GitHub에서 good first issue 라벨 확인:
https://github.com/TomLeeLive/openclaw-godot-plugin/labels/good%20first%20issue
# 1. GitHub에서 Fork
# 2. Clone
git clone https://github.com/YOUR_USERNAME/openclaw-godot-plugin.git
cd openclaw-godot-plugin
# 3. 원본 저장소 추가
git remote add upstream https://github.com/TomLeeLive/openclaw-godot-plugin.git# 테스트 Godot 프로젝트 생성
mkdir -p ~/godot-dev
cp -r addons ~/godot-dev/
# Gateway 확장 설치
cp -r OpenClawPlugin~/* ~/.openclaw/extensions/godot/
openclaw gateway restart# 기능 개발
git checkout -b feature/audio-tools
# 버그 수정
git checkout -b fix/connection-timeout
# 문서
git checkout -b docs/korean-translation## Feature Request: animation.play tool
### 설명
AnimationPlayer 노드를 제어하는 도구
### 사용 사례
- AI가 캐릭터 애니메이션 테스트
- 시네마틱 시퀀스 미리보기
### 제안 API
animation.play {path: "Player/AnimationPlayer", animation: "walk"}
animation.stop {path: "Player/AnimationPlayer"}
animation.list {path: "Player/AnimationPlayer"}addons/openclaw/tools.gd:
# TOOLS 배열에 추가
var TOOLS = [
# ... 기존 도구 ...
{
"name": "animation.play",
"description": "Play an animation on AnimationPlayer",
"inputSchema": {
"type": "object",
"properties": {
"path": {"type": "string", "description": "Path to AnimationPlayer node"},
"animation": {"type": "string", "description": "Animation name to play"},
"speed": {"type": "number", "description": "Playback speed (default: 1.0)"}
},
"required": ["path", "animation"]
}
},
{
"name": "animation.stop",
"description": "Stop animation on AnimationPlayer",
"inputSchema": {
"type": "object",
"properties": {
"path": {"type": "string", "description": "Path to AnimationPlayer node"}
},
"required": ["path"]
}
},
{
"name": "animation.list",
"description": "List available animations",
"inputSchema": {
"type": "object",
"properties": {
"path": {"type": "string", "description": "Path to AnimationPlayer node"}
},
"required": ["path"]
}
}
]
# execute 함수에 핸들러 추가
func execute(tool_name: String, args: Dictionary) -> Dictionary:
match tool_name:
# ... 기존 케이스 ...
"animation.play": return _animation_play(args)
"animation.stop": return _animation_stop(args)
"animation.list": return _animation_list(args)
# 구현
func _animation_play(args: Dictionary) -> Dictionary:
var path = args.get("path", "")
var animation = args.get("animation", "")
var speed = args.get("speed", 1.0)
var node = _find_node_by_path(path)
if node == null:
return {"success": false, "error": "Node not found: " + path}
if not node is AnimationPlayer:
return {"success": false, "error": "Node is not AnimationPlayer: " + path}
var player = node as AnimationPlayer
if not player.has_animation(animation):
return {"success": false, "error": "Animation not found: " + animation}
player.speed_scale = speed
player.play(animation)
return {
"success": true,
"animation": animation,
"duration": player.get_animation(animation).length,
"speed": speed
}
func _animation_stop(args: Dictionary) -> Dictionary:
var path = args.get("path", "")
var node = _find_node_by_path(path)
if node == null:
return {"success": false, "error": "Node not found: " + path}
if not node is AnimationPlayer:
return {"success": false, "error": "Node is not AnimationPlayer"}
(node as AnimationPlayer).stop()
return {"success": true}
func _animation_list(args: Dictionary) -> Dictionary:
var path = args.get("path", "")
var node = _find_node_by_path(path)
if node == null:
return {"success": false, "error": "Node not found: " + path}
if not node is AnimationPlayer:
return {"success": false, "error": "Node is not AnimationPlayer"}
var player = node as AnimationPlayer
var animations = []
for anim_name in player.get_animation_list():
var anim = player.get_animation(anim_name)
animations.append({
"name": anim_name,
"duration": anim.length,
"loop": anim.loop_mode != Animation.LOOP_NONE
})
return {
"success": true,
"animations": animations,
"current": player.current_animation
}# test_tools.gd에 추가
func _test_animation_tools():
_section("Animation Tools")
# 테스트 씬 준비
tools.execute("scene.create", {"rootType": "Node2D", "name": "AnimTest"})
# AnimationPlayer 생성 (수동으로 해야 함)
# 또는 기존 프로젝트의 AnimationPlayer 사용
# animation.list
var list = tools.execute("animation.list", {
"path": "AnimationPlayer" # 실제 경로로 변경
})
# _assert(list.success, "animation.list works")
# animation.play
# animation.stopREADME.md에 추가:
### Animation Tools (3) - NEW
| Tool | Description |
|------|-------------|
| `animation.play` | Play animation on AnimationPlayer |
| `animation.stop` | Stop current animation |
| `animation.list` | List available animations |## Bug: Connection timeout after 60 seconds of inactivity
### 재현 단계
1. 플러그인 활성화
2. 60초 동안 아무 명령 없음
3. 이후 명령 실행 시 타임아웃
### 예상 동작
연결이 유지되어야 함
### 환경
- Godot 4.6
- OpenClaw 2026.2.3connection_manager.gd:
# 문제: heartbeat 간격이 너무 김
const HEARTBEAT_INTERVAL = 60.0 # 60초 (너무 김)
# 수정: 30초로 단축
const HEARTBEAT_INTERVAL = 30.0 # 30초
# 추가: 재연결 로직 개선
func _on_heartbeat_timeout():
if not is_connected_flag:
# 재연결 시도
_reconnect()
return
_send_heartbeat()
func _reconnect():
print("[OpenClaw] Attempting to reconnect...")
_register()# 70초 대기 후 명령 실행
sleep 70
openclaw godot execute editor.getState
# 성공해야 함## 변경 사항
<!-- 무엇을 변경했는지 설명 -->
AnimationPlayer를 제어하는 3개의 새 도구 추가
## 관련 이슈
Fixes #42
## 변경 유형
- [x] 새 기능 (New feature)
- [ ] 버그 수정 (Bug fix)
- [ ] 문서 (Documentation)
- [ ] 리팩토링 (Refactoring)
## 테스트
- [x] Godot 4.6에서 테스트 완료
- [x] 기존 도구 동작 확인
- [x] 새 도구 정상 작동 확인
## 체크리스트
- [x] 코드 스타일 준수
- [x] 문서 업데이트
- [x] 테스트 추가/업데이트
- [ ] Breaking change 없음
## 스크린샷 (해당 시)
<!-- UI 변경 시 스크린샷 첨부 --># 기능 추가
feat(tools): add animation.play, animation.stop, animation.list tools
# 버그 수정
fix(connection): reduce heartbeat interval to 30s to prevent timeout
# 문서
docs(readme): add animation tools to feature list
# 리팩토링
refactor(tools): extract common node finding logic
# 스타일/포맷
style(tools): apply consistent indentation<type>(<scope>): <subject>
[optional body]
[optional footer]
Types: feat, fix, docs, style, refactor, test, chore
## 버그 설명
<!-- 무엇이 잘못되었는지 명확하게 설명 -->
## 재현 단계
1. '...' 이동
2. '...' 클릭
3. '...' 스크롤
4. 오류 발생
## 예상 동작
<!-- 정상적으로 어떻게 동작해야 하는지 -->
## 실제 동작
<!-- 실제로 어떻게 동작하는지 -->
## 환경
- OS: macOS 15.3
- Godot: 4.6-stable
- OpenClaw: 2026.2.3
- Plugin version: 1.1.0
## 로그
[OpenClaw] Error: ...
## 스크린샷
<!-- 해당 시 첨부 -->
## 기능 설명
<!-- 원하는 기능 설명 -->
## 사용 사례
<!-- 이 기능이 필요한 이유 -->
## 제안 API
<!-- 가능하다면 API 형태 제안 -->
```gdscript
animation.play {path: "...", animation: "walk"}
---
## 코드 리뷰 프로세스
### 리뷰어 체크리스트
- [ ] 코드가 목적에 맞게 동작하는가?
- [ ] 테스트가 충분한가?
- [ ] 문서가 업데이트되었는가?
- [ ] 코드 스타일이 일관적인가?
- [ ] 에러 처리가 적절한가?
- [ ] 성능 문제가 없는가?
- [ ] 보안 문제가 없는가?
### 리뷰 코멘트 예시
```markdown
# 긍정적
✅ LGTM! 깔끔한 구현입니다.
# 제안
💡 여기서 `is` 대신 `is_instance_of()`를 사용하면 더 안전합니다.
# 요청
⚠️ 이 경우 null 체크가 필요합니다.
# 질문
❓ 이 상수값의 근거가 있나요?
- 🤝 존중하는 태도로 소통
- 🌍 다양한 배경과 경험 존중
- 📚 건설적인 피드백 제공
- 🚫 차별, 괴롭힘 금지
- GitHub Issues: 버그, 기능 요청
- GitHub Discussions: 질문, 아이디어
- Discord: OpenClaw Community
- 이슈: 1-3일 내 응답
- PR: 1주일 내 첫 리뷰
- 긴급 버그: 가능한 빨리
기여하신 코드는 MIT 라이선스 하에 배포됩니다.
모든 기여에 감사드립니다. 작은 오타 수정도 큰 도움이 됩니다!
질문이 있으시면 언제든 이슈를 열어주세요.
문서 업데이트: 2026-02-08