在落沙模拟中,如何让沙堆“塌陷”?

问题描述 投票:0回答:1

我正在尝试制作一个相当准确的落沙模拟,但我似乎无法弄清楚如何使沙堆自然倒塌。 它的基本工作原理是,当用户按下鼠标按钮时,一大块沙子会掉下来,当碰到底行时,它会以一种随机的方式堆积起来。但它创建了一个完美的金字塔,我不知道如何解决这个问题。

import pygame as pg
from pygame.locals import *
import time
import random
import numpy as np

pg.init()

# Setup
screenW = 600
screenH = 600

screen = pg.display.set_mode((screenW, screenH))
clock = pg.time.Clock()

bg_color = (85, 35, 65)
black = (0, 0, 0)
white = (255, 255, 255)

# Grid
cell_size = 5
cols = screenW // cell_size
rows = screenH // cell_size
grid = np.zeros((cols, rows), dtype=int)
rect_coords = [] # Keeps track of where the sand is


# \--- CODE SIMPLIFICATION FUNCTIONS ---/
# -> Enhance code clarity and streamlining through functions <-

"""
-> Returns the index of the given coordinates (x, y) in the rect_coords list <-

    Parameters:
    - x (int): The x-coordinate.
    - y (int): The y-coordinate.

    Returns:
    - int: The index of the coordinates in the rect_coords list.
"""
def index(x, y):
    for i, (col, row) in enumerate(rect_coords):
        if col == x and row == y:
            return i

"""

-> Move a cell in the grid to a new position <-

    Note:
        - The grid is a two-dimensional array.

    Args:
        col (int): The column index of the cell to be moved.
        row (int): The row index of the cell to be moved.
        new_col (int): The new column index for the cell.

"""
def cellMovement(col, row, new_col):
    grid[col, row] = 0
    del rect_coords[index(col, row)]
    grid[new_col, row+1] = 1
    rect_coords.append((new_col, row+1))


def randomChoice(values):
    return random.choice(values)

"""

-> Decorator that limits the rate at which a function can be invoked <-

"""
def rate_limit(interval):
    def decorator(func):
        last_invocation = 0

        def wrapper(*args, **kwargs):
            nonlocal last_invocation
            elapsed = time.time() - last_invocation
            if elapsed >= interval:
                last_invocation = time.time()
                return func(*args, **kwargs)
        return wrapper
    return decorator


"""

-> Moves the sand particles in the grid <-

    -> This method iterates through the coordinates of the sand particles in the grid and checks if there is sand below each one. 
    -> The sand particles fall down one row at a time, and if there is an empty cell below, the sand particle moves to that cell.
    -> If the cell below has sand and the diagonal is empty, the sand particle may randomly move to the left or right (depending on which is available).

    Note:
        - The sand particles are represented by the value 1 in the grid.
        - rect_coords is a list containing tuples representing the coordinates of particles in the format (column, row).

"""
def moveSand():
    for _, (i, j) in enumerate(rect_coords):
        if grid[i, j] == 1:
            if j+1 <= rows-1:
                below = grid[i, j+1];
                if below == 0: # Checking if there's already sand below
                    cellMovement(i, j, i)
                else:
                    rand = randomChoice([-1, 1])

                    belowA = grid[i+rand, j+1] if i+rand in range(cols) else 1
                    belowB = grid[i-rand, j+1] if i-rand in range(cols) else 1
                    if belowA == 0:
                        cellMovement(i, j, i+rand)
                    elif belowB == 0:
                        cellMovement(i, j, i-rand)


"""

-> Draws the sand on the screen <-

    -> This function calls the moveSand() function to update the sand positions,
    and then draws each sand rectangle on the screen using pygame's draw.rect() function.
    -> Also by iterating through the rect_coords list (which keeps track of the occupied cells)
    there's an increase in performance due to optimizing the search process and minimizing redundant computations

"""
def drawSand():
    moveSand()
    for _, (x, y) in enumerate(rect_coords):
        pg.draw.rect(screen, white, ((x * cell_size, y * cell_size), (cell_size, cell_size)))


"""

-> Spawns sand particles on the grid based on the mouse position <-

    -> The sand particles are created within a specific radius around the mouse position,
    and if a cell, within the radius, is empty, a sand particle is created in it.

    Note:
        - 

"""
@rate_limit(interval=0.05) # the bigger the interval the less frequency the sand will spawn
def summonSand():
    # The numbers should be around the cell_size value (5), to give an extra flailing/flaming effect without exagerating
    # Assigning 1 creates a reminiscent feel of a dying flame rekindling
    # Opt for odd numbers to avoid redundancy; even numbers yield the same result due to floor division "//" halving them
    radius = randomChoice([3, 5, 7, 9])
    sand_radius = radius // 2

    mouse_state = pg.mouse.get_pressed()
    if mouse_state[0]:
        mouseX, mouseY = pg.mouse.get_pos()
        for i in range(-sand_radius, sand_radius, 1):
            for j in range(-sand_radius, sand_radius, 1):
                if randomChoice([True, False]):
                    col = (mouseX // cell_size) + i
                    row = (mouseY // cell_size) + j
                    if col in range(cols) and row in range(rows):
                        if grid[col, row] == 0:
                            grid[col, row] = 1 # setting the cell state to 1
                            rect_coords.append((col, row)) # tracking the current coorfinate the particle



# Main loop
running = True
while running:
    for event in pg.event.get():
        if event.type == QUIT:
            running = False

    screen.fill(black)
    summonSand()
    drawSand()
    pg.display.flip()
    clock.tick(60)
python pygame simulation simulator
1个回答
0
投票

我前段时间在YouTube视频中看到了这个问题的答案。 用我自己的话来说,答案是,当沙子像素下落并撞击其下方的固体物体时,它会将其垂直速度转换为水平速度,并且不要忘记施加地面摩擦力。这将使沙子在与固体碰撞时散开。还有更多可以实现的方法,如参考视频所示: https://www.youtube.com/watch?v=5Ka3tbbT-9E&t=822s

在实现它时,我还会存储每帧的沙块速度,而不是重新计算它,因为这意味着您可以存储以前的状态信息。

© www.soinside.com 2019 - 2024. All rights reserved.