import ursina from ursina import * from ursina.prefabs.first_person_controller import FirstPersonController import math app = Ursina() window.fps_counter.enabled = True window.fps_limit = 0 tile_size = 20 tile_half = tile_size / 2 max_distance = 75 normal_speed = 20 sprint_speed = 30 acceleration = 40.0 deceleration = 60.0 enemy_acceleration = 40.0 enemy_velocity = Vec2(0, 0) # Gravity settings: player's gravity and enemy's gravity is 0.9 times that. gravity = 10.0 enemy_gravity = gravity * 0.9 enemy_vertical_velocity = 0.0 floor_tiles = [] paused = False sensitivity = 100 velocity_x = 0 velocity_z = 0 pause_menu = Entity(parent=camera.ui, model='quad', scale=(0.5, 0.5), color=color.gray, enabled=False) Text('Sensitivity', parent=pause_menu, position=(-0.06, 0.3)) sensitivity_slider = Slider(min=10, max=200, default=sensitivity, step=1, parent=pause_menu, position=(-.235, 0.2)) def toggle_pause(): global paused paused = not paused pause_menu.enabled = paused mouse.locked = not paused player.enabled = not paused application.time_scale = 0 if paused else 1 def create_floor(): if paused: return global floor_tiles x_start = int((player.x - max_distance) // tile_size) * tile_size z_start = int((player.z - max_distance) // tile_size) * tile_size x_end = int((player.x + max_distance) // tile_size) * tile_size + tile_size z_end = int((player.z + max_distance) // tile_size) * tile_size + tile_size for x in range(x_start, x_end, tile_size): for z in range(z_start, z_end, tile_size): if not any(abs(x - tile.x) < tile_size and abs(z - tile.z) < tile_size for tile in floor_tiles): tile = Entity(model='plane', texture='assets/grass.jpg', scale=(tile_size, 1, tile_size), position=(x, 0, z)) tile.collider = 'box' floor_tiles.append(tile) def remove_floor(): if paused: return global floor_tiles for tile in floor_tiles[:]: if abs(tile.x - player.x) > max_distance or abs(tile.z - player.z) > max_distance: floor_tiles.remove(tile) destroy(tile) def vector_approach(current: Vec2, target: Vec2, delta: float) -> Vec2: diff = target - current diff_len = diff.length() if diff_len < delta or diff_len == 0: return target return current + diff.normalized() * delta def approach_scalar(current: float, target: float, delta: float) -> float: if current < target: return min(current + delta, target) elif current > target: return max(current - delta, target) return current def update(): global velocity_x, velocity_z, enemy_velocity, enemy_vertical_velocity if paused: return sensitivity_val = sensitivity_slider.value / 2.5 player.mouse_sensitivity = Vec2(sensitivity_val, sensitivity_val) create_floor() remove_floor() angle = player.rotation_y rad = math.radians(angle) forward = Vec2(math.sin(rad), math.cos(rad)) right = Vec2(math.sin(rad + math.pi/2), math.cos(rad + math.pi/2)) move_dir = Vec2(0, 0) if held_keys['w']: move_dir += forward if held_keys['s'] and not held_keys['w']: move_dir -= forward * 0.5 if held_keys['a'] and not held_keys['w']: move_dir -= right * 0.5 if held_keys['d'] and not held_keys['w']: move_dir += right * 0.5 if move_dir != Vec2(0, 0): move_dir = move_dir.normalized() target_speed = sprint_speed if held_keys['shift'] else normal_speed target_velocity = move_dir * target_speed current_velocity = Vec2(velocity_x, velocity_z) new_velocity = vector_approach(current_velocity, target_velocity, acceleration * time.dt) velocity_x, velocity_z = new_velocity.x, new_velocity.y if move_dir == Vec2(0, 0): new_velocity = vector_approach(new_velocity, Vec2(0, 0), deceleration * time.dt) velocity_x, velocity_z = new_velocity.x, new_velocity.y player.x += velocity_x * time.dt player.z += velocity_z * time.dt direction_to_player = (player.position - red_cylinder.position).normalized() target_rotation_y = math.degrees(math.atan2(direction_to_player.x, direction_to_player.z)) red_cylinder.rotation_y = target_rotation_y enemy_target_velocity = Vec2(red_cylinder.forward.x, red_cylinder.forward.z).normalized() * (sprint_speed + 10) enemy_velocity = vector_approach(enemy_velocity, enemy_target_velocity, enemy_acceleration * time.dt) red_cylinder.x += enemy_velocity.x * time.dt red_cylinder.z += enemy_velocity.y * time.dt # Apply gravity to the enemy: decrease its vertical speed and update its y-position. enemy_vertical_velocity -= enemy_gravity * time.dt red_cylinder.y += enemy_vertical_velocity * time.dt if red_cylinder.y < 1: red_cylinder.y = 1 enemy_vertical_velocity = 0 speed_display.text = f"{math.sqrt(velocity_x ** 2 + velocity_z ** 2):.1f}" distance_to_player = (player.position - red_cylinder.position).length() distance_display.text = f"{distance_to_player:.2f}" if distance_to_player <= 1.1: respawn() def input(key): if key == 'escape': toggle_pause() if not paused: mouse.locked = True player.enabled = True def respawn(): player.position = Vec3(1, 1, 1) global velocity_x, velocity_z, enemy_vertical_velocity velocity_x = 0 velocity_z = 0 enemy_vertical_velocity = 0 sky = Sky() player = FirstPersonController(position=(1, 1, 1), speed=0) red_cylinder = Entity(model=Cylinder(6, start=-.5), color=Vec4(0.5, 0, 0, 1), position=(50, 1, 50), scale=(1, 1.5, 1), collider='box') red_cylinder.on_collision = lambda: respawn() enemy_acceleration = 40.0 small_direction_cylinder = Entity( parent=red_cylinder, model=Cylinder(6, start=-0.5), color=Vec4(0.5, 0.5, 0, 1), scale=(0.1, 0.3, 0.1), position=(0, 0, 0.50), rotation=Vec3(0, 0, -90), inherit_rotation=False ) speed_display = Text(text="0.0", position=(-0.85, -0.45), scale=2, color=color.white) distance_display = Text(text="0.0", position=(0.6, -0.45), scale=2, color=color.white) app.run()