feat: add gameplay enhancements and visual improvements

Backend:
- Add weather system with 6 weather types and transition probabilities
- Add day/night cycle (dawn, day, dusk, night) with phase modifiers
- Add mood system for agents (happy, neutral, sad, anxious)
- Add new commands: heal, talk, encourage, revive
- Add agent social interaction system with relationships
- Add casual mode with auto-revive and reduced decay rates

Frontend (Web):
- Add world state display (weather, time of day)
- Add mood bar to agent cards
- Add new action buttons for heal, encourage, talk, revive
- Handle new event types from server

Unity Client:
- Add EnvironmentManager with dynamic sky gradient and island scene
- Add WeatherEffects with rain, sun rays, fog, and heat particles
- Add SceneBootstrap for automatic visual system initialization
- Improve AgentVisual with better character sprites and animations
- Add breathing and bobbing idle animations
- Add character shadows
- Improve UI panels with rounded corners and borders
- Improve SpeechBubble with rounded corners and proper tail
- Add support for all new server events and commands

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
empty
2026-01-01 15:25:15 +08:00
parent 8264fe2be3
commit 6c66764cce
18 changed files with 3418 additions and 313 deletions

View File

@@ -39,6 +39,12 @@ namespace TheIsland.Visual
[SerializeField] private Color hpLowColor = new Color(0.9f, 0.3f, 0.3f);
[SerializeField] private Color energyHighColor = new Color(1f, 0.8f, 0.2f);
[SerializeField] private Color energyLowColor = new Color(1f, 0.5f, 0.1f);
[Header("Mood Colors")]
[SerializeField] private Color moodHappyColor = new Color(0.3f, 0.9f, 0.5f);
[SerializeField] private Color moodNeutralColor = new Color(0.98f, 0.75f, 0.15f);
[SerializeField] private Color moodSadColor = new Color(0.4f, 0.65f, 0.98f);
[SerializeField] private Color moodAnxiousColor = new Color(0.97f, 0.53f, 0.53f);
#endregion
#region References
@@ -48,8 +54,11 @@ namespace TheIsland.Visual
private TextMeshProUGUI _personalityLabel;
private Image _hpBarFill;
private Image _energyBarFill;
private Image _moodBarFill;
private TextMeshProUGUI _hpText;
private TextMeshProUGUI _energyText;
private TextMeshProUGUI _moodText;
private TextMeshProUGUI _moodEmoji;
private GameObject _deathOverlay;
private SpeechBubble _speechBubble;
private Billboard _spriteBillboard;
@@ -61,6 +70,12 @@ namespace TheIsland.Visual
private int _agentId;
private AgentData _currentData;
private Coroutine _speechCoroutine;
// Animation state
private float _idleAnimTimer;
private float _breathScale = 1f;
private Vector3 _originalSpriteScale;
private float _bobOffset;
#endregion
#region Properties
@@ -76,6 +91,33 @@ namespace TheIsland.Visual
CreateVisuals();
}
private void Update()
{
if (!IsAlive) return;
// Idle breathing animation
_idleAnimTimer += Time.deltaTime;
_breathScale = 1f + Mathf.Sin(_idleAnimTimer * 2f) * 0.02f;
// Gentle bobbing
_bobOffset = Mathf.Sin(_idleAnimTimer * 1.5f) * 0.05f;
if (_spriteRenderer != null && _originalSpriteScale != Vector3.zero)
{
// Apply breathing scale
_spriteRenderer.transform.localScale = new Vector3(
_originalSpriteScale.x * _breathScale,
_originalSpriteScale.y * _breathScale,
_originalSpriteScale.z
);
// Apply bobbing
var pos = _spriteRenderer.transform.localPosition;
pos.y = 1f + _bobOffset;
_spriteRenderer.transform.localPosition = pos;
}
}
private void OnMouseDown()
{
if (!IsAlive)
@@ -162,8 +204,63 @@ namespace TheIsland.Visual
RegeneratePlaceholderSprite();
}
// Store original scale for animation
_originalSpriteScale = spriteObj.transform.localScale;
// Add billboard
_spriteBillboard = spriteObj.AddComponent<Billboard>();
// Add shadow
CreateShadow(spriteObj.transform);
}
private void CreateShadow(Transform spriteTransform)
{
var shadowObj = new GameObject("Shadow");
shadowObj.transform.SetParent(transform);
shadowObj.transform.localPosition = new Vector3(0, 0.01f, 0);
shadowObj.transform.localRotation = Quaternion.Euler(90, 0, 0);
shadowObj.transform.localScale = new Vector3(1.2f, 0.6f, 1f);
var shadowRenderer = shadowObj.AddComponent<SpriteRenderer>();
shadowRenderer.sprite = CreateShadowSprite();
shadowRenderer.sortingOrder = sortingOrder - 1;
shadowRenderer.color = new Color(0, 0, 0, 0.3f);
}
private Sprite CreateShadowSprite()
{
int size = 32;
Texture2D tex = new Texture2D(size, size);
tex.filterMode = FilterMode.Bilinear;
Vector2 center = new Vector2(size / 2f, size / 2f);
Color[] pixels = new Color[size * size];
for (int y = 0; y < size; y++)
{
for (int x = 0; x < size; x++)
{
float dx = (x - center.x) / (size * 0.4f);
float dy = (y - center.y) / (size * 0.4f);
float dist = dx * dx + dy * dy;
if (dist < 1)
{
float alpha = Mathf.Clamp01(1 - dist) * 0.5f;
pixels[y * size + x] = new Color(0, 0, 0, alpha);
}
else
{
pixels[y * size + x] = Color.clear;
}
}
}
tex.SetPixels(pixels);
tex.Apply();
return Sprite.Create(tex, new Rect(0, 0, size, size), new Vector2(0.5f, 0.5f), 100f);
}
private void RegeneratePlaceholderSprite()
@@ -183,7 +280,7 @@ namespace TheIsland.Visual
private Texture2D CreatePlaceholderTexture(int width, int height)
{
Texture2D texture = new Texture2D(width, height, TextureFormat.RGBA32, false);
texture.filterMode = FilterMode.Point;
texture.filterMode = FilterMode.Bilinear;
// Clear to transparent
Color[] pixels = new Color[width * height];
@@ -192,30 +289,193 @@ namespace TheIsland.Visual
pixels[i] = Color.clear;
}
// Draw simple character shape
Vector2 center = new Vector2(width / 2f, height / 2f);
// Body (ellipse)
DrawEllipse(pixels, width, height, center + Vector2.down * 8, 14, 20, placeholderBodyColor);
// Create highlight and shadow colors
Color highlight = Color.Lerp(placeholderBodyColor, Color.white, 0.3f);
Color shadow = Color.Lerp(placeholderBodyColor, Color.black, 0.3f);
Color skinTone = new Color(0.95f, 0.8f, 0.7f);
Color skinShadow = new Color(0.85f, 0.65f, 0.55f);
// Head (circle)
DrawCircle(pixels, width, height, center + Vector2.up * 12, 12, placeholderBodyColor);
// Body (ellipse with shading)
Vector2 bodyCenter = center + Vector2.down * 6;
DrawShadedEllipse(pixels, width, height, bodyCenter, 16, 22, placeholderBodyColor, highlight, shadow);
// Outline
DrawCircleOutline(pixels, width, height, center + Vector2.up * 12, 12, placeholderOutlineColor, 2);
DrawEllipseOutline(pixels, width, height, center + Vector2.down * 8, 14, 20, placeholderOutlineColor, 2);
// Head (circle with skin tone)
Vector2 headCenter = center + Vector2.up * 14;
DrawShadedCircle(pixels, width, height, headCenter, 13, skinTone, Color.Lerp(skinTone, Color.white, 0.2f), skinShadow);
// Hair (top of head)
Color hairColor = placeholderOutlineColor;
DrawHair(pixels, width, height, headCenter, 13, hairColor);
// Eyes
DrawCircle(pixels, width, height, center + new Vector2(-4, 14), 2, Color.white);
DrawCircle(pixels, width, height, center + new Vector2(4, 14), 2, Color.white);
DrawCircle(pixels, width, height, center + new Vector2(-4, 14), 1, Color.black);
DrawCircle(pixels, width, height, center + new Vector2(4, 14), 1, Color.black);
DrawCircle(pixels, width, height, headCenter + new Vector2(-4, -1), 3, Color.white);
DrawCircle(pixels, width, height, headCenter + new Vector2(4, -1), 3, Color.white);
DrawCircle(pixels, width, height, headCenter + new Vector2(-4, -1), 1.5f, new Color(0.2f, 0.15f, 0.1f));
DrawCircle(pixels, width, height, headCenter + new Vector2(4, -1), 1.5f, new Color(0.2f, 0.15f, 0.1f));
// Eye highlights
DrawCircle(pixels, width, height, headCenter + new Vector2(-3, 0), 0.8f, Color.white);
DrawCircle(pixels, width, height, headCenter + new Vector2(5, 0), 0.8f, Color.white);
// Mouth (smile)
DrawSmile(pixels, width, height, headCenter + Vector2.down * 5, 4);
// Blush
DrawCircle(pixels, width, height, headCenter + new Vector2(-7, -3), 2, new Color(1f, 0.6f, 0.6f, 0.4f));
DrawCircle(pixels, width, height, headCenter + new Vector2(7, -3), 2, new Color(1f, 0.6f, 0.6f, 0.4f));
// Arms
DrawArm(pixels, width, height, bodyCenter + new Vector2(-14, 5), -30, skinTone);
DrawArm(pixels, width, height, bodyCenter + new Vector2(14, 5), 30, skinTone);
// Legs
DrawLeg(pixels, width, height, bodyCenter + new Vector2(-6, -20), placeholderBodyColor);
DrawLeg(pixels, width, height, bodyCenter + new Vector2(6, -20), placeholderBodyColor);
// Outline
AddOutline(pixels, width, height, placeholderOutlineColor);
texture.SetPixels(pixels);
texture.Apply();
return texture;
}
private void DrawShadedCircle(Color[] pixels, int width, int height, Vector2 center, float radius, Color baseColor, Color highlight, Color shadow)
{
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
float dist = Vector2.Distance(new Vector2(x, y), center);
if (dist <= radius)
{
// Shading based on position relative to light source (top-left)
float dx = (x - center.x) / radius;
float dy = (y - center.y) / radius;
float shade = (-dx * 0.3f + dy * 0.7f) * 0.5f + 0.5f;
Color color = Color.Lerp(highlight, shadow, shade);
color = Color.Lerp(color, baseColor, 0.5f);
pixels[y * width + x] = color;
}
}
}
}
private void DrawShadedEllipse(Color[] pixels, int width, int height, Vector2 center, float rx, float ry, Color baseColor, Color highlight, Color shadow)
{
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
float dx = (x - center.x) / rx;
float dy = (y - center.y) / ry;
if (dx * dx + dy * dy <= 1)
{
float shade = (-dx * 0.3f + dy * 0.5f) * 0.5f + 0.5f;
Color color = Color.Lerp(highlight, shadow, shade);
color = Color.Lerp(color, baseColor, 0.5f);
pixels[y * width + x] = color;
}
}
}
}
private void DrawHair(Color[] pixels, int width, int height, Vector2 headCenter, float headRadius, Color hairColor)
{
// Draw hair on top half of head
for (int y = (int)(headCenter.y); y < height; y++)
{
for (int x = 0; x < width; x++)
{
float dist = Vector2.Distance(new Vector2(x, y), headCenter);
if (dist <= headRadius + 2 && dist >= headRadius - 4 && y > headCenter.y - 2)
{
float noise = Mathf.PerlinNoise(x * 0.3f, y * 0.3f);
if (noise > 0.3f)
{
pixels[y * width + x] = Color.Lerp(hairColor, hairColor * 0.7f, noise);
}
}
}
}
}
private void DrawSmile(Color[] pixels, int width, int height, Vector2 center, float smileWidth)
{
Color mouthColor = new Color(0.8f, 0.4f, 0.4f);
for (int x = (int)(center.x - smileWidth); x <= (int)(center.x + smileWidth); x++)
{
float t = (x - center.x + smileWidth) / (smileWidth * 2);
int y = (int)(center.y - Mathf.Sin(t * Mathf.PI) * 2);
if (x >= 0 && x < width && y >= 0 && y < height)
{
pixels[y * width + x] = mouthColor;
if (y > 0) pixels[(y - 1) * width + x] = mouthColor;
}
}
}
private void DrawArm(Color[] pixels, int width, int height, Vector2 start, float angle, Color skinColor)
{
float rad = angle * Mathf.Deg2Rad;
int length = 10;
for (int i = 0; i < length; i++)
{
int x = (int)(start.x + Mathf.Sin(rad) * i);
int y = (int)(start.y - Mathf.Cos(rad) * i);
DrawCircle(pixels, width, height, new Vector2(x, y), 2, skinColor);
}
}
private void DrawLeg(Color[] pixels, int width, int height, Vector2 start, Color clothColor)
{
for (int i = 0; i < 8; i++)
{
int x = (int)start.x;
int y = (int)(start.y - i);
if (y >= 0 && y < height)
{
DrawCircle(pixels, width, height, new Vector2(x, y), 3, clothColor);
}
}
// Shoe
DrawCircle(pixels, width, height, start + Vector2.down * 8, 4, new Color(0.3f, 0.2f, 0.15f));
}
private void AddOutline(Color[] pixels, int width, int height, Color outlineColor)
{
Color[] newPixels = (Color[])pixels.Clone();
for (int y = 1; y < height - 1; y++)
{
for (int x = 1; x < width - 1; x++)
{
if (pixels[y * width + x].a < 0.1f)
{
// Check neighbors
bool hasNeighbor = false;
for (int dy = -1; dy <= 1; dy++)
{
for (int dx = -1; dx <= 1; dx++)
{
if (pixels[(y + dy) * width + (x + dx)].a > 0.5f)
{
hasNeighbor = true;
break;
}
}
if (hasNeighbor) break;
}
if (hasNeighbor)
{
newPixels[y * width + x] = outlineColor;
}
}
}
}
System.Array.Copy(newPixels, pixels, pixels.Length);
}
private void DrawCircle(Color[] pixels, int width, int height, Vector2 center, float radius, Color color)
{
for (int y = 0; y < height; y++)
@@ -294,31 +554,35 @@ namespace TheIsland.Visual
_uiCanvas.sortingOrder = sortingOrder + 1;
var canvasRect = canvasObj.GetComponent<RectTransform>();
canvasRect.sizeDelta = new Vector2(400, 150);
canvasRect.sizeDelta = new Vector2(400, 180);
// Add billboard to canvas (configured for UI - full facing)
_uiBillboard = canvasObj.AddComponent<Billboard>();
_uiBillboard.ConfigureForUI();
// Create UI panel
var panel = CreateUIPanel(canvasObj.transform, new Vector2(350, 120));
// Create UI panel (increased height for mood bar)
var panel = CreateUIPanel(canvasObj.transform, new Vector2(350, 150));
// Name label
_nameLabel = CreateUIText(panel.transform, "NameLabel", "Agent", 36, Color.white, FontStyles.Bold);
SetRectPosition(_nameLabel.rectTransform, 0, 45, 320, 45);
SetRectPosition(_nameLabel.rectTransform, 0, 60, 320, 45);
// Personality label
_personalityLabel = CreateUIText(panel.transform, "PersonalityLabel", "(Personality)", 20,
new Color(0.8f, 0.8f, 0.8f), FontStyles.Italic);
SetRectPosition(_personalityLabel.rectTransform, 0, 15, 320, 25);
SetRectPosition(_personalityLabel.rectTransform, 0, 30, 320, 25);
// HP Bar
var hpBar = CreateProgressBar(panel.transform, "HPBar", "HP", hpHighColor, out _hpBarFill, out _hpText);
SetRectPosition(hpBar, 0, -15, 280, 24);
SetRectPosition(hpBar, 0, 0, 280, 24);
// Energy Bar
var energyBar = CreateProgressBar(panel.transform, "EnergyBar", "Energy", energyHighColor, out _energyBarFill, out _energyText);
SetRectPosition(energyBar, 0, -45, 280, 24);
SetRectPosition(energyBar, 0, -30, 280, 24);
// Mood Bar
var moodBar = CreateProgressBar(panel.transform, "MoodBar", "Mood", moodNeutralColor, out _moodBarFill, out _moodText);
SetRectPosition(moodBar, 0, -60, 280, 24);
// Death overlay
_deathOverlay = CreateDeathOverlay(panel.transform);
@@ -338,11 +602,79 @@ namespace TheIsland.Visual
rect.anchoredPosition = Vector2.zero;
var bg = panel.AddComponent<Image>();
bg.color = new Color(0, 0, 0, 0.6f);
bg.sprite = CreateRoundedRectSprite(32, 32, 8);
bg.type = Image.Type.Sliced;
bg.color = new Color(0.1f, 0.12f, 0.18f, 0.85f);
// Add subtle border
var borderObj = new GameObject("Border");
borderObj.transform.SetParent(panel.transform);
borderObj.transform.localPosition = Vector3.zero;
borderObj.transform.localRotation = Quaternion.identity;
borderObj.transform.localScale = Vector3.one;
var borderRect = borderObj.AddComponent<RectTransform>();
borderRect.anchorMin = Vector2.zero;
borderRect.anchorMax = Vector2.one;
borderRect.offsetMin = new Vector2(-2, -2);
borderRect.offsetMax = new Vector2(2, 2);
borderRect.SetAsFirstSibling();
var borderImg = borderObj.AddComponent<Image>();
borderImg.sprite = CreateRoundedRectSprite(32, 32, 8);
borderImg.type = Image.Type.Sliced;
borderImg.color = new Color(0.3f, 0.35f, 0.45f, 0.5f);
return panel;
}
private Sprite CreateRoundedRectSprite(int width, int height, int radius)
{
Texture2D tex = new Texture2D(width, height);
tex.filterMode = FilterMode.Bilinear;
Color[] pixels = new Color[width * height];
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
bool inRect = true;
// Check corners for rounding
if (x < radius && y < radius)
{
// Bottom-left corner
inRect = Vector2.Distance(new Vector2(x, y), new Vector2(radius, radius)) <= radius;
}
else if (x >= width - radius && y < radius)
{
// Bottom-right corner
inRect = Vector2.Distance(new Vector2(x, y), new Vector2(width - radius - 1, radius)) <= radius;
}
else if (x < radius && y >= height - radius)
{
// Top-left corner
inRect = Vector2.Distance(new Vector2(x, y), new Vector2(radius, height - radius - 1)) <= radius;
}
else if (x >= width - radius && y >= height - radius)
{
// Top-right corner
inRect = Vector2.Distance(new Vector2(x, y), new Vector2(width - radius - 1, height - radius - 1)) <= radius;
}
pixels[y * width + x] = inRect ? Color.white : Color.clear;
}
}
tex.SetPixels(pixels);
tex.Apply();
// Create 9-sliced sprite
return Sprite.Create(tex, new Rect(0, 0, width, height), new Vector2(0.5f, 0.5f), 100f,
0, SpriteMeshType.FullRect, new Vector4(radius, radius, radius, radius));
}
private TextMeshProUGUI CreateUIText(Transform parent, string name, string text,
float fontSize, Color color, FontStyles style = FontStyles.Normal)
{
@@ -510,6 +842,26 @@ namespace TheIsland.Visual
_energyText.text = $"Energy: {data.energy}";
}
// Update Mood bar
float moodPercent = data.mood / 100f;
if (_moodBarFill != null)
{
_moodBarFill.rectTransform.anchorMax = new Vector2(moodPercent, 1);
_moodBarFill.color = GetMoodColor(data.mood_state);
}
if (_moodText != null)
{
string moodIndicator = GetMoodEmoji(data.mood_state);
string moodLabel = data.mood_state switch
{
"happy" => "Happy",
"sad" => "Sad",
"anxious" => "Anxious",
_ => "Neutral"
};
_moodText.text = $"{moodIndicator} {moodLabel}: {data.mood}";
}
// Update death state
if (!data.IsAlive)
{
@@ -521,6 +873,29 @@ namespace TheIsland.Visual
}
}
private Color GetMoodColor(string moodState)
{
return moodState switch
{
"happy" => moodHappyColor,
"sad" => moodSadColor,
"anxious" => moodAnxiousColor,
_ => moodNeutralColor
};
}
private string GetMoodEmoji(string moodState)
{
// Use text symbols instead of emoji for font compatibility
return moodState switch
{
"happy" => "<color=#5AE65A>+</color>",
"sad" => "<color=#6AA8FF>-</color>",
"anxious" => "<color=#FF7777>!</color>",
_ => "<color=#FFD700>~</color>"
};
}
private void OnDeath()
{
if (_deathOverlay != null) _deathOverlay.SetActive(true);

View File

@@ -54,10 +54,17 @@ namespace TheIsland.Core
private int _currentTick;
private int _currentDay;
private int _nextSpawnIndex;
// World state
private string _currentTimeOfDay = "day";
private string _currentWeather = "Sunny";
#endregion
#region Properties
public int PlayerGold => _playerGold;
public string CurrentTimeOfDay => _currentTimeOfDay;
public string CurrentWeather => _currentWeather;
public int CurrentDay => _currentDay;
public int AliveAgentCount
{
get
@@ -135,6 +142,16 @@ namespace TheIsland.Core
network.OnTick += HandleTick;
network.OnSystemMessage += HandleSystemMessage;
network.OnUserUpdate += HandleUserUpdate;
// New phase events
network.OnWeatherChange += HandleWeatherChange;
network.OnPhaseChange += HandlePhaseChange;
network.OnDayChange += HandleDayChange;
network.OnHeal += HandleHeal;
network.OnEncourage += HandleEncourage;
network.OnTalk += HandleTalk;
network.OnRevive += HandleRevive;
network.OnSocialInteraction += HandleSocialInteraction;
}
private void UnsubscribeFromNetworkEvents()
@@ -151,6 +168,16 @@ namespace TheIsland.Core
network.OnTick -= HandleTick;
network.OnSystemMessage -= HandleSystemMessage;
network.OnUserUpdate -= HandleUserUpdate;
// New phase events
network.OnWeatherChange -= HandleWeatherChange;
network.OnPhaseChange -= HandlePhaseChange;
network.OnDayChange -= HandleDayChange;
network.OnHeal -= HandleHeal;
network.OnEncourage -= HandleEncourage;
network.OnTalk -= HandleTalk;
network.OnRevive -= HandleRevive;
network.OnSocialInteraction -= HandleSocialInteraction;
}
#endregion
@@ -215,7 +242,17 @@ namespace TheIsland.Core
{
if (tickInfo == null) return;
tickInfo.text = $"Day {_currentDay} | Tick {_currentTick} | Alive: {AliveAgentCount}";
// Format time of day nicely
string timeDisplay = _currentTimeOfDay switch
{
"dawn" => "Dawn",
"day" => "Day",
"dusk" => "Dusk",
"night" => "Night",
_ => "Day"
};
tickInfo.text = $"Day {_currentDay} | {timeDisplay} | {_currentWeather} | Tick {_currentTick} | Alive: {AliveAgentCount}";
}
private void UpdateGoldDisplay()
@@ -324,6 +361,17 @@ namespace TheIsland.Core
{
_currentTick = data.tick;
_currentDay = data.day;
// Update weather and time of day from tick data
if (!string.IsNullOrEmpty(data.time_of_day))
{
_currentTimeOfDay = data.time_of_day;
}
if (!string.IsNullOrEmpty(data.weather))
{
_currentWeather = data.weather;
}
UpdateTickInfo();
}
@@ -341,6 +389,102 @@ namespace TheIsland.Core
UpdateGoldDisplay();
}
}
private void HandleWeatherChange(WeatherChangeData data)
{
Debug.Log($"[GameManager] Weather changed: {data.old_weather} -> {data.new_weather}");
_currentWeather = data.new_weather;
ShowNotification($"Weather: {data.new_weather}");
UpdateTickInfo();
}
private void HandlePhaseChange(PhaseChangeData data)
{
Debug.Log($"[GameManager] Phase changed: {data.old_phase} -> {data.new_phase}");
_currentTimeOfDay = data.new_phase;
ShowNotification($"The {data.new_phase} begins...");
UpdateTickInfo();
}
private void HandleDayChange(DayChangeData data)
{
Debug.Log($"[GameManager] New day: {data.day}");
_currentDay = data.day;
ShowNotification($"Day {data.day} begins!");
UpdateTickInfo();
}
private void HandleHeal(HealEventData data)
{
Debug.Log($"[GameManager] Heal event: {data.message}");
// Update gold if this was our action
if (data.user == NetworkManager.Instance.Username)
{
_playerGold = data.user_gold;
UpdateGoldDisplay();
}
ShowNotification(data.message);
}
private void HandleEncourage(EncourageEventData data)
{
Debug.Log($"[GameManager] Encourage event: {data.message}");
// Update gold if this was our action
if (data.user == NetworkManager.Instance.Username)
{
_playerGold = data.user_gold;
UpdateGoldDisplay();
}
ShowNotification(data.message);
}
private void HandleTalk(TalkEventData data)
{
Debug.Log($"[GameManager] Talk event: {data.agent_name} responds about '{data.topic}'");
// Show the agent's speech response
if (_agentVisuals.TryGetValue(GetAgentIdByName(data.agent_name), out AgentVisual agentVisual))
{
agentVisual.ShowSpeech(data.response);
}
else if (_agentUIs.TryGetValue(GetAgentIdByName(data.agent_name), out AgentUI agentUI))
{
agentUI.ShowSpeech(data.response);
}
}
private void HandleRevive(ReviveEventData data)
{
Debug.Log($"[GameManager] Revive event: {data.message}");
// Update gold if this was our action
if (data.user == NetworkManager.Instance.Username)
{
_playerGold = data.user_gold;
UpdateGoldDisplay();
}
ShowNotification(data.message);
}
private void HandleSocialInteraction(SocialInteractionData data)
{
Debug.Log($"[GameManager] Social: {data.initiator_name} -> {data.target_name} ({data.interaction_type})");
// Show dialogue from initiator
if (_agentVisuals.TryGetValue(data.initiator_id, out AgentVisual initiatorVisual))
{
initiatorVisual.ShowSpeech(data.dialogue);
}
else if (_agentUIs.TryGetValue(data.initiator_id, out AgentUI initiatorUI))
{
initiatorUI.ShowSpeech(data.dialogue);
}
}
#endregion
#region Agent Management
@@ -429,6 +573,41 @@ namespace TheIsland.Core
}
return null;
}
/// <summary>
/// Get agent ID by name (searches all agent systems).
/// </summary>
private int GetAgentIdByName(string name)
{
// Check AgentVisual first (newest system)
foreach (var kvp in _agentVisuals)
{
if (kvp.Value.CurrentData?.name == name)
{
return kvp.Key;
}
}
// Check AgentUI
foreach (var kvp in _agentUIs)
{
if (kvp.Value.CurrentData?.name == name)
{
return kvp.Key;
}
}
// Check AgentController (legacy)
foreach (var kvp in _agents)
{
if (kvp.Value.CurrentData?.name == name)
{
return kvp.Key;
}
}
return -1;
}
#endregion
#region UI Actions

View File

@@ -43,6 +43,11 @@ namespace TheIsland.Models
public int energy;
public string inventory;
// Mood system (Phase 3)
public int mood;
public string mood_state; // "happy", "neutral", "sad", "anxious"
public string social_tendency; // "introvert", "extrovert", "neutral"
public bool IsAlive => status == "Alive";
}
@@ -99,6 +104,8 @@ namespace TheIsland.Models
public int tick;
public int day;
public int alive_agents;
public string time_of_day; // "dawn", "day", "dusk", "night"
public string weather; // "Sunny", "Cloudy", "Rainy", etc.
}
/// <summary>
@@ -129,6 +136,108 @@ namespace TheIsland.Models
public int day_count;
public string weather;
public int resource_level;
public int current_tick_in_day;
public string time_of_day; // "dawn", "day", "dusk", "night"
}
/// <summary>
/// Weather change event data.
/// </summary>
[Serializable]
public class WeatherChangeData
{
public string old_weather;
public string new_weather;
public string message;
}
/// <summary>
/// Phase change event data (day/night cycle).
/// </summary>
[Serializable]
public class PhaseChangeData
{
public string old_phase;
public string new_phase;
public int day;
public string message;
}
/// <summary>
/// Day change event data.
/// </summary>
[Serializable]
public class DayChangeData
{
public int day;
public string message;
}
/// <summary>
/// Heal event data.
/// </summary>
[Serializable]
public class HealEventData
{
public string user;
public string agent_name;
public int hp_restored;
public int agent_hp;
public int user_gold;
public string message;
}
/// <summary>
/// Encourage event data.
/// </summary>
[Serializable]
public class EncourageEventData
{
public string user;
public string agent_name;
public int mood_boost;
public int agent_mood;
public int user_gold;
public string message;
}
/// <summary>
/// Talk event data.
/// </summary>
[Serializable]
public class TalkEventData
{
public string user;
public string agent_name;
public string topic;
public string response;
}
/// <summary>
/// Revive event data.
/// </summary>
[Serializable]
public class ReviveEventData
{
public string user;
public string agent_name;
public int user_gold;
public string message;
}
/// <summary>
/// Social interaction event data.
/// </summary>
[Serializable]
public class SocialInteractionData
{
public int initiator_id;
public string initiator_name;
public int target_id;
public string target_name;
public string interaction_type; // "chat", "share_food", "help", "argue", "comfort"
public string relationship_type; // "stranger", "friend", "rival", etc.
public string dialogue;
}
/// <summary>
@@ -164,5 +273,25 @@ namespace TheIsland.Models
public const string USER_UPDATE = "user_update";
public const string WORLD_UPDATE = "world_update";
public const string CHECK = "check";
// Day/Night cycle (Phase 2)
public const string TIME_UPDATE = "time_update";
public const string PHASE_CHANGE = "phase_change";
public const string DAY_CHANGE = "day_change";
// Weather system (Phase 3)
public const string WEATHER_CHANGE = "weather_change";
public const string MOOD_UPDATE = "mood_update";
// New commands (Phase 4)
public const string HEAL = "heal";
public const string TALK = "talk";
public const string ENCOURAGE = "encourage";
public const string REVIVE = "revive";
// Social system (Phase 5)
public const string SOCIAL_INTERACTION = "social_interaction";
public const string RELATIONSHIP_CHANGE = "relationship_change";
public const string AUTO_REVIVE = "auto_revive";
}
}

View File

@@ -56,6 +56,17 @@ namespace TheIsland.Network
public event Action<TickData> OnTick;
public event Action<SystemEventData> OnSystemMessage;
public event Action<UserUpdateData> OnUserUpdate;
// New Phase events
public event Action<WeatherChangeData> OnWeatherChange;
public event Action<PhaseChangeData> OnPhaseChange;
public event Action<DayChangeData> OnDayChange;
public event Action<HealEventData> OnHeal;
public event Action<EncourageEventData> OnEncourage;
public event Action<TalkEventData> OnTalk;
public event Action<ReviveEventData> OnRevive;
public event Action<SocialInteractionData> OnSocialInteraction;
public event Action<WorldStateData> OnWorldUpdate;
#endregion
#region Private Fields
@@ -286,6 +297,52 @@ namespace TheIsland.Network
OnUserUpdate?.Invoke(userData);
break;
case EventTypes.WORLD_UPDATE:
var worldData = JsonUtility.FromJson<WorldStateData>(dataJson);
OnWorldUpdate?.Invoke(worldData);
break;
case EventTypes.WEATHER_CHANGE:
var weatherData = JsonUtility.FromJson<WeatherChangeData>(dataJson);
OnWeatherChange?.Invoke(weatherData);
break;
case EventTypes.PHASE_CHANGE:
var phaseData = JsonUtility.FromJson<PhaseChangeData>(dataJson);
OnPhaseChange?.Invoke(phaseData);
break;
case EventTypes.DAY_CHANGE:
var dayData = JsonUtility.FromJson<DayChangeData>(dataJson);
OnDayChange?.Invoke(dayData);
break;
case EventTypes.HEAL:
var healData = JsonUtility.FromJson<HealEventData>(dataJson);
OnHeal?.Invoke(healData);
break;
case EventTypes.ENCOURAGE:
var encourageData = JsonUtility.FromJson<EncourageEventData>(dataJson);
OnEncourage?.Invoke(encourageData);
break;
case EventTypes.TALK:
var talkData = JsonUtility.FromJson<TalkEventData>(dataJson);
OnTalk?.Invoke(talkData);
break;
case EventTypes.REVIVE:
case EventTypes.AUTO_REVIVE:
var reviveData = JsonUtility.FromJson<ReviveEventData>(dataJson);
OnRevive?.Invoke(reviveData);
break;
case EventTypes.SOCIAL_INTERACTION:
var socialData = JsonUtility.FromJson<SocialInteractionData>(dataJson);
OnSocialInteraction?.Invoke(socialData);
break;
case EventTypes.COMMENT:
// Comments can be logged but typically not displayed in 3D
Debug.Log($"[Chat] {json}");
@@ -423,6 +480,29 @@ namespace TheIsland.Network
SendCommand($"feed {agentName}");
}
public void HealAgent(string agentName)
{
SendCommand($"heal {agentName}");
}
public void EncourageAgent(string agentName)
{
SendCommand($"encourage {agentName}");
}
public void TalkToAgent(string agentName, string topic = "")
{
string cmd = string.IsNullOrEmpty(topic)
? $"talk {agentName}"
: $"talk {agentName} {topic}";
SendCommand(cmd);
}
public void ReviveAgent(string agentName)
{
SendCommand($"revive {agentName}");
}
public void CheckStatus()
{
SendCommand("check");

View File

@@ -84,6 +84,9 @@ namespace TheIsland.Visual
// Add CanvasGroup for fading
_canvasGroup = gameObject.AddComponent<CanvasGroup>();
// Create rounded rect sprite for bubble
Sprite roundedSprite = CreateRoundedBubbleSprite(32, 32, 10);
// Create outline (slightly larger background)
var outlineObj = new GameObject("Outline");
outlineObj.transform.SetParent(transform);
@@ -92,6 +95,8 @@ namespace TheIsland.Visual
outlineObj.transform.localScale = Vector3.one;
_bubbleOutline = outlineObj.AddComponent<Image>();
_bubbleOutline.sprite = roundedSprite;
_bubbleOutline.type = Image.Type.Sliced;
_bubbleOutline.color = outlineColor;
var outlineRect = outlineObj.GetComponent<RectTransform>();
outlineRect.anchorMin = Vector2.zero;
@@ -107,6 +112,8 @@ namespace TheIsland.Visual
bgObj.transform.localScale = Vector3.one;
_bubbleBackground = bgObj.AddComponent<Image>();
_bubbleBackground.sprite = roundedSprite;
_bubbleBackground.type = Image.Type.Sliced;
_bubbleBackground.color = bubbleColor;
var bgRect = bgObj.GetComponent<RectTransform>();
bgRect.anchorMin = Vector2.zero;
@@ -153,17 +160,88 @@ namespace TheIsland.Visual
tailRect.anchoredPosition = new Vector2(0, 0);
tailRect.sizeDelta = new Vector2(24, 16);
// Create a simple triangle using UI Image with a sprite
// For now, use a simple downward-pointing shape
// Create triangle sprite for tail
var tailImage = tail.AddComponent<Image>();
tailImage.sprite = CreateTriangleSprite(24, 16);
tailImage.color = bubbleColor;
// Note: For a proper triangle, you'd use a custom sprite.
// This creates a simple rectangle as placeholder.
// In production, replace with a triangle sprite.
return tail;
}
private Sprite CreateRoundedBubbleSprite(int width, int height, int radius)
{
Texture2D tex = new Texture2D(width, height);
tex.filterMode = FilterMode.Bilinear;
Color[] pixels = new Color[width * height];
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
bool inRect = true;
// Check corners for rounding
if (x < radius && y < radius)
{
inRect = Vector2.Distance(new Vector2(x, y), new Vector2(radius, radius)) <= radius;
}
else if (x >= width - radius && y < radius)
{
inRect = Vector2.Distance(new Vector2(x, y), new Vector2(width - radius - 1, radius)) <= radius;
}
else if (x < radius && y >= height - radius)
{
inRect = Vector2.Distance(new Vector2(x, y), new Vector2(radius, height - radius - 1)) <= radius;
}
else if (x >= width - radius && y >= height - radius)
{
inRect = Vector2.Distance(new Vector2(x, y), new Vector2(width - radius - 1, height - radius - 1)) <= radius;
}
pixels[y * width + x] = inRect ? Color.white : Color.clear;
}
}
tex.SetPixels(pixels);
tex.Apply();
return Sprite.Create(tex, new Rect(0, 0, width, height), new Vector2(0.5f, 0.5f), 100f,
0, SpriteMeshType.FullRect, new Vector4(radius, radius, radius, radius));
}
private Sprite CreateTriangleSprite(int width, int height)
{
Texture2D tex = new Texture2D(width, height);
tex.filterMode = FilterMode.Bilinear;
Color[] pixels = new Color[width * height];
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
// Triangle pointing down
float t = (float)y / height;
float halfWidth = (width / 2f) * (1 - t);
float center = width / 2f;
if (x >= center - halfWidth && x <= center + halfWidth)
{
pixels[y * width + x] = Color.white;
}
else
{
pixels[y * width + x] = Color.clear;
}
}
}
tex.SetPixels(pixels);
tex.Apply();
return Sprite.Create(tex, new Rect(0, 0, width, height), new Vector2(0.5f, 1f), 100f);
}
#endregion
#region Public API

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ba6aed8ea8f684710867429092622258
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,627 @@
using UnityEngine;
using TheIsland.Core;
using TheIsland.Network;
using TheIsland.Models;
namespace TheIsland.Visual
{
/// <summary>
/// Manages the island environment visuals including sky, ground, water, and lighting.
/// Creates a beautiful dynamic background that responds to time of day and weather.
/// </summary>
public class EnvironmentManager : MonoBehaviour
{
#region Singleton
private static EnvironmentManager _instance;
public static EnvironmentManager Instance => _instance;
#endregion
#region Sky Colors by Time of Day
[Header("Dawn Colors")]
[SerializeField] private Color dawnSkyTop = new Color(0.98f, 0.65f, 0.45f);
[SerializeField] private Color dawnSkyBottom = new Color(1f, 0.85f, 0.6f);
[SerializeField] private Color dawnAmbient = new Color(1f, 0.8f, 0.6f);
[Header("Day Colors")]
[SerializeField] private Color daySkyTop = new Color(0.4f, 0.7f, 1f);
[SerializeField] private Color daySkyBottom = new Color(0.7f, 0.9f, 1f);
[SerializeField] private Color dayAmbient = new Color(1f, 1f, 0.95f);
[Header("Dusk Colors")]
[SerializeField] private Color duskSkyTop = new Color(0.3f, 0.2f, 0.5f);
[SerializeField] private Color duskSkyBottom = new Color(1f, 0.5f, 0.3f);
[SerializeField] private Color duskAmbient = new Color(1f, 0.6f, 0.4f);
[Header("Night Colors")]
[SerializeField] private Color nightSkyTop = new Color(0.05f, 0.05f, 0.15f);
[SerializeField] private Color nightSkyBottom = new Color(0.1f, 0.15f, 0.3f);
[SerializeField] private Color nightAmbient = new Color(0.3f, 0.35f, 0.5f);
#endregion
#region Weather Modifiers
[Header("Weather Color Modifiers")]
[SerializeField] private Color cloudyTint = new Color(0.7f, 0.7f, 0.75f);
[SerializeField] private Color rainyTint = new Color(0.5f, 0.55f, 0.6f);
[SerializeField] private Color stormyTint = new Color(0.35f, 0.35f, 0.4f);
[SerializeField] private Color foggyTint = new Color(0.8f, 0.8f, 0.85f);
[SerializeField] private Color hotTint = new Color(1.1f, 0.95f, 0.85f);
#endregion
#region Ground & Water
[Header("Ground Settings")]
[SerializeField] private Color sandColor = new Color(0.95f, 0.87f, 0.7f);
[SerializeField] private Color sandDarkColor = new Color(0.8f, 0.7f, 0.5f);
[Header("Water Settings")]
[SerializeField] private Color waterShallowColor = new Color(0.3f, 0.8f, 0.9f, 0.8f);
[SerializeField] private Color waterDeepColor = new Color(0.1f, 0.4f, 0.6f, 0.9f);
[SerializeField] private float waveSpeed = 0.5f;
[SerializeField] private float waveAmplitude = 0.1f;
#endregion
#region References
private Camera _mainCamera;
private Material _skyMaterial;
private GameObject _groundPlane;
private GameObject _waterPlane;
private Material _groundMaterial;
private Material _waterMaterial;
private Light _mainLight;
// Current state
private string _currentTimeOfDay = "day";
private string _currentWeather = "Sunny";
private float _transitionProgress = 1f;
private Color _targetSkyTop, _targetSkyBottom;
private Color _currentSkyTop, _currentSkyBottom;
#endregion
#region Unity Lifecycle
private void Awake()
{
if (_instance != null && _instance != this)
{
Destroy(gameObject);
return;
}
_instance = this;
_mainCamera = Camera.main;
CreateEnvironment();
}
private void Start()
{
// Subscribe to network events
var network = NetworkManager.Instance;
if (network != null)
{
network.OnPhaseChange += HandlePhaseChange;
network.OnWeatherChange += HandleWeatherChange;
network.OnTick += HandleTick;
}
// Set initial sky
UpdateSkyColors();
}
private void Update()
{
// Smooth sky transition
if (_transitionProgress < 1f)
{
_transitionProgress += Time.deltaTime * 0.5f;
_currentSkyTop = Color.Lerp(_currentSkyTop, _targetSkyTop, _transitionProgress);
_currentSkyBottom = Color.Lerp(_currentSkyBottom, _targetSkyBottom, _transitionProgress);
UpdateSkyMaterial();
}
// Animate water
AnimateWater();
}
private void OnDestroy()
{
var network = NetworkManager.Instance;
if (network != null)
{
network.OnPhaseChange -= HandlePhaseChange;
network.OnWeatherChange -= HandleWeatherChange;
network.OnTick -= HandleTick;
}
}
#endregion
#region Environment Creation
private void CreateEnvironment()
{
CreateSky();
CreateGround();
CreateWater();
CreateLighting();
CreateDecorations();
}
private void CreateSky()
{
// Create a gradient sky using a camera background shader
_skyMaterial = new Material(Shader.Find("Unlit/Color"));
// Create sky quad that fills the background
var skyObj = GameObject.CreatePrimitive(PrimitiveType.Quad);
skyObj.name = "SkyBackground";
skyObj.transform.SetParent(transform);
skyObj.transform.position = new Vector3(0, 5, 20);
skyObj.transform.localScale = new Vector3(60, 30, 1);
// Remove collider
Destroy(skyObj.GetComponent<Collider>());
// Create gradient material
_skyMaterial = CreateGradientMaterial();
skyObj.GetComponent<Renderer>().material = _skyMaterial;
skyObj.GetComponent<Renderer>().sortingOrder = -100;
// Set initial colors
_currentSkyTop = daySkyTop;
_currentSkyBottom = daySkyBottom;
_targetSkyTop = daySkyTop;
_targetSkyBottom = daySkyBottom;
UpdateSkyMaterial();
}
private Material CreateGradientMaterial()
{
// Create a simple shader for vertical gradient
string shaderCode = @"
Shader ""Custom/SkyGradient"" {
Properties {
_TopColor (""Top Color"", Color) = (0.4, 0.7, 1, 1)
_BottomColor (""Bottom Color"", Color) = (0.7, 0.9, 1, 1)
}
SubShader {
Tags { ""Queue""=""Background"" ""RenderType""=""Opaque"" }
Pass {
ZWrite Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include ""UnityCG.cginc""
fixed4 _TopColor;
fixed4 _BottomColor;
struct v2f {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};
v2f vert(appdata_base v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
return o;
}
fixed4 frag(v2f i) : SV_Target {
return lerp(_BottomColor, _TopColor, i.uv.y);
}
ENDCG
}
}
}";
// Since we can't create shaders at runtime easily, use a texture-based approach
return CreateGradientTextureMaterial();
}
private Material CreateGradientTextureMaterial()
{
// Create gradient texture
Texture2D gradientTex = new Texture2D(1, 256);
gradientTex.wrapMode = TextureWrapMode.Clamp;
for (int y = 0; y < 256; y++)
{
float t = y / 255f;
Color color = Color.Lerp(_currentSkyBottom, _currentSkyTop, t);
gradientTex.SetPixel(0, y, color);
}
gradientTex.Apply();
Material mat = new Material(Shader.Find("Unlit/Texture"));
mat.mainTexture = gradientTex;
return mat;
}
private void UpdateSkyMaterial()
{
if (_skyMaterial == null || _skyMaterial.mainTexture == null) return;
Texture2D tex = (Texture2D)_skyMaterial.mainTexture;
for (int y = 0; y < 256; y++)
{
float t = y / 255f;
Color color = Color.Lerp(_currentSkyBottom, _currentSkyTop, t);
tex.SetPixel(0, y, color);
}
tex.Apply();
}
private void CreateGround()
{
// Create sandy beach ground
_groundPlane = GameObject.CreatePrimitive(PrimitiveType.Quad);
_groundPlane.name = "GroundPlane";
_groundPlane.transform.SetParent(transform);
_groundPlane.transform.position = new Vector3(0, -0.5f, 5);
_groundPlane.transform.rotation = Quaternion.Euler(90, 0, 0);
_groundPlane.transform.localScale = new Vector3(40, 20, 1);
// Create sand texture
_groundMaterial = new Material(Shader.Find("Unlit/Texture"));
_groundMaterial.mainTexture = CreateSandTexture();
_groundPlane.GetComponent<Renderer>().material = _groundMaterial;
_groundPlane.GetComponent<Renderer>().sortingOrder = -50;
// Remove collider (we don't need physics)
Destroy(_groundPlane.GetComponent<Collider>());
}
private Texture2D CreateSandTexture()
{
int size = 128;
Texture2D tex = new Texture2D(size, size);
tex.filterMode = FilterMode.Bilinear;
for (int y = 0; y < size; y++)
{
for (int x = 0; x < size; x++)
{
// Create sandy noise pattern
float noise = Mathf.PerlinNoise(x * 0.1f, y * 0.1f) * 0.3f;
float detail = Mathf.PerlinNoise(x * 0.3f, y * 0.3f) * 0.1f;
Color baseColor = Color.Lerp(sandDarkColor, sandColor, 0.5f + noise + detail);
// Add some sparkle/grain
if (Random.value > 0.95f)
{
baseColor = Color.Lerp(baseColor, Color.white, 0.3f);
}
tex.SetPixel(x, y, baseColor);
}
}
tex.Apply();
return tex;
}
private void CreateWater()
{
// Create water plane at the horizon
_waterPlane = GameObject.CreatePrimitive(PrimitiveType.Quad);
_waterPlane.name = "WaterPlane";
_waterPlane.transform.SetParent(transform);
_waterPlane.transform.position = new Vector3(0, -0.3f, 12);
_waterPlane.transform.rotation = Quaternion.Euler(90, 0, 0);
_waterPlane.transform.localScale = new Vector3(60, 15, 1);
// Create water material
_waterMaterial = new Material(Shader.Find("Unlit/Transparent"));
_waterMaterial.mainTexture = CreateWaterTexture();
_waterPlane.GetComponent<Renderer>().material = _waterMaterial;
_waterPlane.GetComponent<Renderer>().sortingOrder = -40;
Destroy(_waterPlane.GetComponent<Collider>());
}
private Texture2D CreateWaterTexture()
{
int size = 128;
Texture2D tex = new Texture2D(size, size);
tex.filterMode = FilterMode.Bilinear;
tex.wrapMode = TextureWrapMode.Repeat;
for (int y = 0; y < size; y++)
{
for (int x = 0; x < size; x++)
{
float t = (float)y / size;
Color baseColor = Color.Lerp(waterShallowColor, waterDeepColor, t);
// Add wave highlights
float wave = Mathf.Sin(x * 0.2f + y * 0.1f) * 0.5f + 0.5f;
baseColor = Color.Lerp(baseColor, Color.white, wave * 0.1f);
tex.SetPixel(x, y, baseColor);
}
}
tex.Apply();
return tex;
}
private void AnimateWater()
{
if (_waterMaterial == null) return;
// Simple UV scrolling for wave effect
float offset = Time.time * waveSpeed * 0.1f;
_waterMaterial.mainTextureOffset = new Vector2(offset, offset * 0.5f);
}
private void CreateLighting()
{
// Find or create main directional light
_mainLight = FindFirstObjectByType<Light>();
if (_mainLight == null)
{
var lightObj = new GameObject("MainLight");
lightObj.transform.SetParent(transform);
_mainLight = lightObj.AddComponent<Light>();
_mainLight.type = LightType.Directional;
}
_mainLight.transform.rotation = Quaternion.Euler(50, -30, 0);
_mainLight.intensity = 1f;
_mainLight.color = dayAmbient;
// Set ambient light
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Flat;
RenderSettings.ambientLight = dayAmbient;
}
private void CreateDecorations()
{
// Create palm tree silhouettes
CreatePalmTree(new Vector3(-8, 0, 8), 2.5f);
CreatePalmTree(new Vector3(-10, 0, 10), 3f);
CreatePalmTree(new Vector3(9, 0, 7), 2.2f);
CreatePalmTree(new Vector3(11, 0, 9), 2.8f);
// Create rocks
CreateRock(new Vector3(-5, 0, 4), 0.5f);
CreateRock(new Vector3(6, 0, 5), 0.7f);
CreateRock(new Vector3(-7, 0, 6), 0.4f);
}
private void CreatePalmTree(Vector3 position, float scale)
{
var treeObj = new GameObject("PalmTree");
treeObj.transform.SetParent(transform);
treeObj.transform.position = position;
// Create trunk (stretched capsule-ish shape using sprite)
var trunkSprite = new GameObject("Trunk");
trunkSprite.transform.SetParent(treeObj.transform);
trunkSprite.transform.localPosition = new Vector3(0, scale * 0.5f, 0);
var trunkRenderer = trunkSprite.AddComponent<SpriteRenderer>();
trunkRenderer.sprite = CreateTreeSprite();
trunkRenderer.sortingOrder = -20;
trunkSprite.transform.localScale = new Vector3(scale * 0.5f, scale, 1);
}
private Sprite CreateTreeSprite()
{
int width = 64;
int height = 128;
Texture2D tex = new Texture2D(width, height);
Color trunk = new Color(0.4f, 0.25f, 0.15f);
Color trunkDark = new Color(0.3f, 0.18f, 0.1f);
Color leaf = new Color(0.2f, 0.5f, 0.2f);
Color leafBright = new Color(0.3f, 0.65f, 0.25f);
// Clear
Color[] pixels = new Color[width * height];
for (int i = 0; i < pixels.Length; i++) pixels[i] = Color.clear;
// Draw trunk
int trunkWidth = 8;
int trunkStart = width / 2 - trunkWidth / 2;
for (int y = 0; y < height * 0.6f; y++)
{
for (int x = trunkStart; x < trunkStart + trunkWidth; x++)
{
float noise = Mathf.PerlinNoise(x * 0.2f, y * 0.1f);
pixels[y * width + x] = Color.Lerp(trunkDark, trunk, noise);
}
}
// Draw palm fronds
DrawPalmFronds(pixels, width, height, leaf, leafBright);
tex.SetPixels(pixels);
tex.Apply();
tex.filterMode = FilterMode.Point;
return Sprite.Create(tex, new Rect(0, 0, width, height), new Vector2(0.5f, 0));
}
private void DrawPalmFronds(Color[] pixels, int width, int height, Color leaf, Color leafBright)
{
Vector2 center = new Vector2(width / 2, height * 0.65f);
// Draw several fronds
float[] angles = { -60, -30, 0, 30, 60, -80, 80 };
foreach (float angle in angles)
{
DrawFrond(pixels, width, height, center, angle, leaf, leafBright);
}
}
private void DrawFrond(Color[] pixels, int width, int height, Vector2 start, float angle, Color leaf, Color leafBright)
{
float rad = angle * Mathf.Deg2Rad;
int length = 35;
for (int i = 0; i < length; i++)
{
float t = i / (float)length;
float droop = t * t * 15; // Fronds droop more at the end
int x = (int)(start.x + Mathf.Sin(rad) * i);
int y = (int)(start.y + Mathf.Cos(rad) * i - droop);
// Draw thick frond
for (int dx = -2; dx <= 2; dx++)
{
for (int dy = -1; dy <= 1; dy++)
{
int px = x + dx;
int py = y + dy;
if (px >= 0 && px < width && py >= 0 && py < height)
{
float brightness = Mathf.PerlinNoise(px * 0.1f, py * 0.1f);
pixels[py * width + px] = Color.Lerp(leaf, leafBright, brightness);
}
}
}
}
}
private void CreateRock(Vector3 position, float scale)
{
var rockObj = new GameObject("Rock");
rockObj.transform.SetParent(transform);
rockObj.transform.position = position;
var rockRenderer = rockObj.AddComponent<SpriteRenderer>();
rockRenderer.sprite = CreateRockSprite();
rockRenderer.sortingOrder = -15;
rockObj.transform.localScale = Vector3.one * scale;
}
private Sprite CreateRockSprite()
{
int size = 32;
Texture2D tex = new Texture2D(size, size);
Color rockDark = new Color(0.3f, 0.3f, 0.35f);
Color rockLight = new Color(0.5f, 0.5f, 0.55f);
Color[] pixels = new Color[size * size];
for (int i = 0; i < pixels.Length; i++) pixels[i] = Color.clear;
// Draw rock shape
Vector2 center = new Vector2(size / 2, size / 3);
for (int y = 0; y < size; y++)
{
for (int x = 0; x < size; x++)
{
float dx = (x - center.x) / (size * 0.4f);
float dy = (y - center.y) / (size * 0.3f);
float dist = dx * dx + dy * dy;
if (dist < 1 && y < size * 0.7f)
{
float noise = Mathf.PerlinNoise(x * 0.2f, y * 0.2f);
pixels[y * size + x] = Color.Lerp(rockDark, rockLight, noise);
}
}
}
tex.SetPixels(pixels);
tex.Apply();
tex.filterMode = FilterMode.Point;
return Sprite.Create(tex, new Rect(0, 0, size, size), new Vector2(0.5f, 0));
}
#endregion
#region Event Handlers
private void HandlePhaseChange(PhaseChangeData data)
{
_currentTimeOfDay = data.new_phase;
UpdateSkyColors();
}
private void HandleWeatherChange(WeatherChangeData data)
{
_currentWeather = data.new_weather;
UpdateSkyColors();
}
private void HandleTick(TickData data)
{
if (!string.IsNullOrEmpty(data.time_of_day) && data.time_of_day != _currentTimeOfDay)
{
_currentTimeOfDay = data.time_of_day;
UpdateSkyColors();
}
if (!string.IsNullOrEmpty(data.weather) && data.weather != _currentWeather)
{
_currentWeather = data.weather;
UpdateSkyColors();
}
}
private void UpdateSkyColors()
{
// Get base colors for time of day
Color baseTop, baseBottom, ambient;
switch (_currentTimeOfDay)
{
case "dawn":
baseTop = dawnSkyTop;
baseBottom = dawnSkyBottom;
ambient = dawnAmbient;
break;
case "dusk":
baseTop = duskSkyTop;
baseBottom = duskSkyBottom;
ambient = duskAmbient;
break;
case "night":
baseTop = nightSkyTop;
baseBottom = nightSkyBottom;
ambient = nightAmbient;
break;
default: // day
baseTop = daySkyTop;
baseBottom = daySkyBottom;
ambient = dayAmbient;
break;
}
// Apply weather tint
Color weatherTint = Color.white;
switch (_currentWeather)
{
case "Cloudy": weatherTint = cloudyTint; break;
case "Rainy": weatherTint = rainyTint; break;
case "Stormy": weatherTint = stormyTint; break;
case "Foggy": weatherTint = foggyTint; break;
case "Hot": weatherTint = hotTint; break;
}
_targetSkyTop = baseTop * weatherTint;
_targetSkyBottom = baseBottom * weatherTint;
_transitionProgress = 0f;
// Update lighting
if (_mainLight != null)
{
_mainLight.color = ambient * weatherTint;
_mainLight.intensity = _currentTimeOfDay == "night" ? 0.3f : 1f;
}
RenderSettings.ambientLight = ambient * weatherTint * 0.8f;
}
#endregion
#region Public API
/// <summary>
/// Force update the environment to specific conditions.
/// </summary>
public void SetEnvironment(string timeOfDay, string weather)
{
_currentTimeOfDay = timeOfDay;
_currentWeather = weather;
UpdateSkyColors();
}
#endregion
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 6aa9102a04d7544619ec0187e065eda9

View File

@@ -0,0 +1,95 @@
using UnityEngine;
namespace TheIsland.Visual
{
/// <summary>
/// Bootstraps the scene with all visual components.
/// Attach this to an empty GameObject in your scene to automatically
/// create the environment, weather effects, and other visual systems.
/// </summary>
public class SceneBootstrap : MonoBehaviour
{
[Header("Auto-Create Components")]
[SerializeField] private bool createEnvironment = true;
[SerializeField] private bool createWeatherEffects = true;
[Header("Camera Settings")]
[SerializeField] private bool configureCamera = true;
[SerializeField] private Vector3 cameraPosition = new Vector3(0, 3, -8);
[SerializeField] private Vector3 cameraRotation = new Vector3(15, 0, 0);
[SerializeField] private float cameraFieldOfView = 60f;
private void Awake()
{
// Configure camera
if (configureCamera)
{
ConfigureMainCamera();
}
// Create environment
if (createEnvironment && EnvironmentManager.Instance == null)
{
CreateEnvironmentManager();
}
// Create weather effects
if (createWeatherEffects && WeatherEffects.Instance == null)
{
CreateWeatherEffects();
}
Debug.Log("[SceneBootstrap] Visual systems initialized");
}
private void ConfigureMainCamera()
{
Camera mainCamera = Camera.main;
if (mainCamera == null)
{
var camObj = new GameObject("Main Camera");
mainCamera = camObj.AddComponent<Camera>();
camObj.AddComponent<AudioListener>();
camObj.tag = "MainCamera";
}
mainCamera.transform.position = cameraPosition;
mainCamera.transform.rotation = Quaternion.Euler(cameraRotation);
mainCamera.fieldOfView = cameraFieldOfView;
mainCamera.clearFlags = CameraClearFlags.SolidColor;
mainCamera.backgroundColor = new Color(0.4f, 0.6f, 0.9f); // Fallback sky color
Debug.Log("[SceneBootstrap] Camera configured");
}
private void CreateEnvironmentManager()
{
var envObj = new GameObject("EnvironmentManager");
envObj.AddComponent<EnvironmentManager>();
Debug.Log("[SceneBootstrap] EnvironmentManager created");
}
private void CreateWeatherEffects()
{
var weatherObj = new GameObject("WeatherEffects");
weatherObj.AddComponent<WeatherEffects>();
Debug.Log("[SceneBootstrap] WeatherEffects created");
}
/// <summary>
/// Call this to manually refresh all visual systems.
/// </summary>
public void RefreshVisuals()
{
if (EnvironmentManager.Instance != null)
{
EnvironmentManager.Instance.SetEnvironment("day", "Sunny");
}
if (WeatherEffects.Instance != null)
{
WeatherEffects.Instance.SetWeather("Sunny");
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 35dc7c6201b284023b5ab113ffab8add

View File

@@ -0,0 +1,473 @@
using UnityEngine;
using TheIsland.Network;
using TheIsland.Models;
namespace TheIsland.Visual
{
/// <summary>
/// Creates and manages weather particle effects.
/// Responds to weather changes from the server.
/// </summary>
public class WeatherEffects : MonoBehaviour
{
#region Singleton
private static WeatherEffects _instance;
public static WeatherEffects Instance => _instance;
#endregion
#region Configuration
[Header("Rain Settings")]
[SerializeField] private int rainParticleCount = 500;
[SerializeField] private int stormParticleCount = 1000;
[SerializeField] private Color rainColor = new Color(0.7f, 0.8f, 0.9f, 0.6f);
[Header("Sun Settings")]
[SerializeField] private int sunRayCount = 50;
[SerializeField] private Color sunRayColor = new Color(1f, 0.95f, 0.8f, 0.3f);
[Header("Fog Settings")]
[SerializeField] private Color fogColor = new Color(0.85f, 0.85f, 0.9f, 0.5f);
[Header("Hot Weather Settings")]
[SerializeField] private int heatWaveCount = 30;
[SerializeField] private Color heatColor = new Color(1f, 0.9f, 0.7f, 0.2f);
#endregion
#region References
private ParticleSystem _rainSystem;
private ParticleSystem _sunRaySystem;
private ParticleSystem _fogSystem;
private ParticleSystem _heatSystem;
private ParticleSystem _cloudSystem;
private string _currentWeather = "Sunny";
#endregion
#region Unity Lifecycle
private void Awake()
{
if (_instance != null && _instance != this)
{
Destroy(gameObject);
return;
}
_instance = this;
CreateAllEffects();
}
private void Start()
{
var network = NetworkManager.Instance;
if (network != null)
{
network.OnWeatherChange += HandleWeatherChange;
network.OnTick += HandleTick;
}
// Start with sunny weather
SetWeather("Sunny");
}
private void OnDestroy()
{
var network = NetworkManager.Instance;
if (network != null)
{
network.OnWeatherChange -= HandleWeatherChange;
network.OnTick -= HandleTick;
}
}
#endregion
#region Effect Creation
private void CreateAllEffects()
{
CreateRainEffect();
CreateSunRayEffect();
CreateFogEffect();
CreateHeatEffect();
CreateCloudEffect();
}
private void CreateRainEffect()
{
var rainObj = new GameObject("RainEffect");
rainObj.transform.SetParent(transform);
rainObj.transform.position = new Vector3(0, 10, 5);
_rainSystem = rainObj.AddComponent<ParticleSystem>();
var main = _rainSystem.main;
main.maxParticles = stormParticleCount;
main.startLifetime = 1.5f;
main.startSpeed = 15f;
main.startSize = 0.05f;
main.startColor = rainColor;
main.simulationSpace = ParticleSystemSimulationSpace.World;
main.gravityModifier = 1.5f;
var emission = _rainSystem.emission;
emission.rateOverTime = rainParticleCount;
var shape = _rainSystem.shape;
shape.shapeType = ParticleSystemShapeType.Box;
shape.scale = new Vector3(25, 0.1f, 15);
// Renderer settings
var renderer = rainObj.GetComponent<ParticleSystemRenderer>();
renderer.material = CreateParticleMaterial(rainColor);
renderer.sortingOrder = 50;
// Start stopped
_rainSystem.Stop();
}
private void CreateSunRayEffect()
{
var sunObj = new GameObject("SunRayEffect");
sunObj.transform.SetParent(transform);
sunObj.transform.position = new Vector3(5, 8, 10);
sunObj.transform.rotation = Quaternion.Euler(45, -30, 0);
_sunRaySystem = sunObj.AddComponent<ParticleSystem>();
var main = _sunRaySystem.main;
main.maxParticles = sunRayCount;
main.startLifetime = 3f;
main.startSpeed = 0.5f;
main.startSize = new ParticleSystem.MinMaxCurve(0.5f, 2f);
main.startColor = sunRayColor;
main.simulationSpace = ParticleSystemSimulationSpace.World;
var emission = _sunRaySystem.emission;
emission.rateOverTime = 10;
var shape = _sunRaySystem.shape;
shape.shapeType = ParticleSystemShapeType.Cone;
shape.angle = 15;
shape.radius = 3;
var colorOverLifetime = _sunRaySystem.colorOverLifetime;
colorOverLifetime.enabled = true;
Gradient gradient = new Gradient();
gradient.SetKeys(
new GradientColorKey[] { new GradientColorKey(Color.white, 0), new GradientColorKey(Color.white, 1) },
new GradientAlphaKey[] { new GradientAlphaKey(0, 0), new GradientAlphaKey(0.3f, 0.3f), new GradientAlphaKey(0, 1) }
);
colorOverLifetime.color = gradient;
var sizeOverLifetime = _sunRaySystem.sizeOverLifetime;
sizeOverLifetime.enabled = true;
sizeOverLifetime.size = new ParticleSystem.MinMaxCurve(1f, new AnimationCurve(
new Keyframe(0, 0.5f), new Keyframe(0.5f, 1f), new Keyframe(1, 1.5f)));
var renderer = sunObj.GetComponent<ParticleSystemRenderer>();
renderer.material = CreateSunRayMaterial();
renderer.sortingOrder = 40;
_sunRaySystem.Stop();
}
private void CreateFogEffect()
{
var fogObj = new GameObject("FogEffect");
fogObj.transform.SetParent(transform);
fogObj.transform.position = new Vector3(0, 1, 5);
_fogSystem = fogObj.AddComponent<ParticleSystem>();
var main = _fogSystem.main;
main.maxParticles = 100;
main.startLifetime = 8f;
main.startSpeed = 0.3f;
main.startSize = new ParticleSystem.MinMaxCurve(3f, 6f);
main.startColor = fogColor;
main.simulationSpace = ParticleSystemSimulationSpace.World;
var emission = _fogSystem.emission;
emission.rateOverTime = 5;
var shape = _fogSystem.shape;
shape.shapeType = ParticleSystemShapeType.Box;
shape.scale = new Vector3(30, 2, 15);
var velocityOverLifetime = _fogSystem.velocityOverLifetime;
velocityOverLifetime.enabled = true;
velocityOverLifetime.x = new ParticleSystem.MinMaxCurve(-0.2f, 0.2f);
velocityOverLifetime.y = new ParticleSystem.MinMaxCurve(0.05f, 0.1f);
var colorOverLifetime = _fogSystem.colorOverLifetime;
colorOverLifetime.enabled = true;
Gradient gradient = new Gradient();
gradient.SetKeys(
new GradientColorKey[] { new GradientColorKey(Color.white, 0), new GradientColorKey(Color.white, 1) },
new GradientAlphaKey[] { new GradientAlphaKey(0, 0), new GradientAlphaKey(0.5f, 0.3f), new GradientAlphaKey(0.5f, 0.7f), new GradientAlphaKey(0, 1) }
);
colorOverLifetime.color = gradient;
var renderer = fogObj.GetComponent<ParticleSystemRenderer>();
renderer.material = CreateFogMaterial();
renderer.sortingOrder = 30;
_fogSystem.Stop();
}
private void CreateHeatEffect()
{
var heatObj = new GameObject("HeatEffect");
heatObj.transform.SetParent(transform);
heatObj.transform.position = new Vector3(0, 0, 5);
_heatSystem = heatObj.AddComponent<ParticleSystem>();
var main = _heatSystem.main;
main.maxParticles = heatWaveCount;
main.startLifetime = 4f;
main.startSpeed = 0.8f;
main.startSize = new ParticleSystem.MinMaxCurve(1f, 3f);
main.startColor = heatColor;
main.simulationSpace = ParticleSystemSimulationSpace.World;
var emission = _heatSystem.emission;
emission.rateOverTime = 8;
var shape = _heatSystem.shape;
shape.shapeType = ParticleSystemShapeType.Box;
shape.scale = new Vector3(20, 0.1f, 10);
var velocityOverLifetime = _heatSystem.velocityOverLifetime;
velocityOverLifetime.enabled = true;
velocityOverLifetime.y = 1f;
velocityOverLifetime.x = new ParticleSystem.MinMaxCurve(-0.3f, 0.3f);
var colorOverLifetime = _heatSystem.colorOverLifetime;
colorOverLifetime.enabled = true;
Gradient gradient = new Gradient();
gradient.SetKeys(
new GradientColorKey[] { new GradientColorKey(heatColor, 0), new GradientColorKey(heatColor, 1) },
new GradientAlphaKey[] { new GradientAlphaKey(0, 0), new GradientAlphaKey(0.2f, 0.3f), new GradientAlphaKey(0, 1) }
);
colorOverLifetime.color = gradient;
var renderer = heatObj.GetComponent<ParticleSystemRenderer>();
renderer.material = CreateHeatMaterial();
renderer.sortingOrder = 35;
_heatSystem.Stop();
}
private void CreateCloudEffect()
{
var cloudObj = new GameObject("CloudEffect");
cloudObj.transform.SetParent(transform);
cloudObj.transform.position = new Vector3(0, 8, 15);
_cloudSystem = cloudObj.AddComponent<ParticleSystem>();
var main = _cloudSystem.main;
main.maxParticles = 30;
main.startLifetime = 20f;
main.startSpeed = 0.2f;
main.startSize = new ParticleSystem.MinMaxCurve(5f, 10f);
main.startColor = new Color(1, 1, 1, 0.7f);
main.simulationSpace = ParticleSystemSimulationSpace.World;
var emission = _cloudSystem.emission;
emission.rateOverTime = 1;
var shape = _cloudSystem.shape;
shape.shapeType = ParticleSystemShapeType.Box;
shape.scale = new Vector3(40, 2, 5);
var velocityOverLifetime = _cloudSystem.velocityOverLifetime;
velocityOverLifetime.enabled = true;
velocityOverLifetime.x = 0.3f;
var renderer = cloudObj.GetComponent<ParticleSystemRenderer>();
renderer.material = CreateCloudMaterial();
renderer.sortingOrder = 25;
_cloudSystem.Stop();
}
#endregion
#region Material Creation
private Material CreateParticleMaterial(Color color)
{
Material mat = new Material(Shader.Find("Particles/Standard Unlit"));
mat.SetColor("_Color", color);
mat.SetFloat("_Mode", 2); // Fade mode
// Create simple white texture
Texture2D tex = new Texture2D(8, 8);
for (int i = 0; i < 64; i++) tex.SetPixel(i % 8, i / 8, Color.white);
tex.Apply();
mat.mainTexture = tex;
return mat;
}
private Material CreateSunRayMaterial()
{
Material mat = new Material(Shader.Find("Particles/Standard Unlit"));
mat.SetColor("_Color", sunRayColor);
mat.SetFloat("_Mode", 1); // Additive
// Create soft gradient texture
Texture2D tex = new Texture2D(32, 32);
Vector2 center = new Vector2(16, 16);
for (int y = 0; y < 32; y++)
{
for (int x = 0; x < 32; x++)
{
float dist = Vector2.Distance(new Vector2(x, y), center) / 16f;
float alpha = Mathf.Clamp01(1 - dist);
tex.SetPixel(x, y, new Color(1, 1, 1, alpha * alpha));
}
}
tex.Apply();
mat.mainTexture = tex;
return mat;
}
private Material CreateFogMaterial()
{
Material mat = new Material(Shader.Find("Particles/Standard Unlit"));
mat.SetColor("_Color", fogColor);
mat.SetFloat("_Mode", 2); // Fade
// Create soft cloud texture
Texture2D tex = new Texture2D(64, 64);
for (int y = 0; y < 64; y++)
{
for (int x = 0; x < 64; x++)
{
float noise = Mathf.PerlinNoise(x * 0.1f, y * 0.1f);
float dist = Vector2.Distance(new Vector2(x, y), new Vector2(32, 32)) / 32f;
float alpha = Mathf.Clamp01((1 - dist) * noise);
tex.SetPixel(x, y, new Color(1, 1, 1, alpha));
}
}
tex.Apply();
mat.mainTexture = tex;
return mat;
}
private Material CreateHeatMaterial()
{
Material mat = new Material(Shader.Find("Particles/Standard Unlit"));
mat.SetColor("_Color", heatColor);
mat.SetFloat("_Mode", 1); // Additive
// Create wavy heat texture
Texture2D tex = new Texture2D(32, 64);
for (int y = 0; y < 64; y++)
{
for (int x = 0; x < 32; x++)
{
float wave = Mathf.Sin((x + y * 0.3f) * 0.3f) * 0.5f + 0.5f;
float fade = 1 - Mathf.Abs(x - 16) / 16f;
float alpha = wave * fade * (1 - y / 64f);
tex.SetPixel(x, y, new Color(1, 1, 1, alpha * 0.3f));
}
}
tex.Apply();
mat.mainTexture = tex;
return mat;
}
private Material CreateCloudMaterial()
{
Material mat = new Material(Shader.Find("Particles/Standard Unlit"));
mat.SetColor("_Color", Color.white);
mat.SetFloat("_Mode", 2); // Fade
// Create fluffy cloud texture
Texture2D tex = new Texture2D(64, 64);
for (int y = 0; y < 64; y++)
{
for (int x = 0; x < 64; x++)
{
float noise1 = Mathf.PerlinNoise(x * 0.08f, y * 0.08f);
float noise2 = Mathf.PerlinNoise(x * 0.15f + 100, y * 0.15f + 100) * 0.5f;
float dist = Vector2.Distance(new Vector2(x, y), new Vector2(32, 32)) / 32f;
float alpha = Mathf.Clamp01((noise1 + noise2) * (1 - dist * dist));
tex.SetPixel(x, y, new Color(1, 1, 1, alpha * 0.8f));
}
}
tex.Apply();
tex.filterMode = FilterMode.Bilinear;
mat.mainTexture = tex;
return mat;
}
#endregion
#region Weather Control
private void HandleWeatherChange(WeatherChangeData data)
{
SetWeather(data.new_weather);
}
private void HandleTick(TickData data)
{
if (!string.IsNullOrEmpty(data.weather) && data.weather != _currentWeather)
{
SetWeather(data.weather);
}
}
public void SetWeather(string weather)
{
_currentWeather = weather;
// Stop all effects first
_rainSystem?.Stop();
_sunRaySystem?.Stop();
_fogSystem?.Stop();
_heatSystem?.Stop();
_cloudSystem?.Stop();
// Enable appropriate effects
switch (weather)
{
case "Sunny":
_sunRaySystem?.Play();
break;
case "Cloudy":
_cloudSystem?.Play();
break;
case "Rainy":
_rainSystem?.Play();
var rainMain = _rainSystem.main;
var rainEmission = _rainSystem.emission;
rainEmission.rateOverTime = rainParticleCount;
_cloudSystem?.Play();
break;
case "Stormy":
_rainSystem?.Play();
var stormMain = _rainSystem.main;
var stormEmission = _rainSystem.emission;
stormEmission.rateOverTime = stormParticleCount;
stormMain.startSpeed = 20f;
_cloudSystem?.Play();
// Could add lightning flashes here
break;
case "Foggy":
_fogSystem?.Play();
break;
case "Hot":
_heatSystem?.Play();
_sunRaySystem?.Play();
break;
}
Debug.Log($"[WeatherEffects] Weather set to: {weather}");
}
#endregion
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d309e5fa265df414cba2779d11a0ed3c