import pygame
import random
import math
[docs]
class BaseEntity:
"""Base class for all entities, handling smooth movement and rendering."""
def __init__(self, x, y, asset, speed=50.0, move_interval=3.0):
# Logical grid position
# Pixel position for smooth rendering, initialized later
# Movement state
[docs]
self.speed = speed # Pixels per second
# Decision-making timer
[docs]
self.move_timer = random.uniform(0.5, move_interval)
[docs]
self.move_interval = move_interval
# Asset
[docs]
self.rect = self.asset.get_rect()
[docs]
def update(self, dt, isometric_map):
"""Update entity state, including movement."""
if self.is_moving:
self._move_towards_target(dt, isometric_map)
else:
self._idle_behavior(dt, isometric_map)
def _idle_behavior(self, dt, isometric_map):
"""Handle behavior when not moving, like deciding where to go next."""
self.move_timer -= dt
if self.move_timer <= 0:
self._find_new_target(isometric_map)
self.move_timer = random.uniform(1.0, self.move_interval)
def _find_new_target(self, isometric_map):
"""Find a valid, walkable, adjacent tile to move to."""
possible_moves = []
# Check N, S, E, W neighbors
for dx, dy in [(0, 1), (0, -1), (1, 0), (-1, 0)]:
new_x, new_y = self.x + dx, self.y + dy
if isometric_map.is_valid_position(new_x, new_y):
possible_moves.append((new_x, new_y))
if possible_moves:
self.target_pos = random.choice(possible_moves)
self.is_moving = True
def _move_towards_target(self, dt, isometric_map):
"""Interpolate pixel position towards the target tile."""
if not self.target_pos:
self.is_moving = False
return
target_pixel_x, target_pixel_y = isometric_map.world_to_screen(self.target_pos[0], self.target_pos[1])
dx = target_pixel_x - self.pixel_x
dy = target_pixel_y - self.pixel_y
distance = math.sqrt(dx**2 + dy**2)
if distance < self.speed * dt:
self.x, self.y = self.target_pos
self.pixel_x, self.pixel_y = target_pixel_x, target_pixel_y
self.is_moving = False
self.target_pos = None
else:
self.pixel_x += (dx / distance) * self.speed * dt
self.pixel_y += (dy / distance) * self.speed * dt
def _step_towards_target(self, dt, speed):
"""Move (self.x, self.y) toward (self.target_x, self.target_y).
Returns (dx, dy, distance) measured before the move, or (0, 0, 0) when
already at the target.
"""
dx = self.target_x - self.x
dy = self.target_y - self.y
distance = math.sqrt(dx * dx + dy * dy)
if distance > 0.1:
move_speed = speed * dt
if distance > move_speed:
self.x += (dx / distance) * move_speed
self.y += (dy / distance) * move_speed
else:
self.x = self.target_x
self.y = self.target_y
return dx, dy, distance
return 0, 0, 0
[docs]
def get_screen_position(self, isometric_map):
"""Get screen position for rendering."""
return isometric_map.world_to_screen(self.x, self.y)
[docs]
def render(self, screen, isometric_map):
"""Render the entity on the screen."""
if not self.is_moving:
# Snap to grid position if idle
self.pixel_x, self.pixel_y = isometric_map.world_to_screen(self.x, self.y)
self.rect.center = (self.pixel_x, self.pixel_y)
screen.blit(self.asset, self.rect)