Search code examples
visual-studiounity-game-enginegame-physics

How to Position Objects With Same Distances


I am coding a game in Unity that number of your soldiers are increasing/decreasing by some triggers. I want to position my soldier objects like a full circle, so they will always be near each other(like same distances) even if their number is increasing or decreasing. How can I manage this?


Solution

  • You can start with some simple relatively ordered distribution of positions and by applying a dynamical system approach/gradient decent type iteration, you can let the positions converge to a much more structured pattern. I wrote such implementation in python, it is in vectorized form, but I also added an equivalent function with for loops, to illustrate the structure of the function. The final ordered pattern is inspired by the stable equilibrium position that a bunch of discs of same radius r would form if they are hold by springs, one for every two of them. To ease up the computations, I squared the spring tensions, thus avoiding square roots, so not exactly like the typical physics model, but close to it.

    import numpy as np
    import matplotlib.pyplot as plt
    
    def Grad(pos, r):
        Dq = - pos[:, :, np.newaxis] + pos[:, np.newaxis, :] 
        D = Dq[0,:,:]*Dq[0,:,:] + Dq[1,:,:]*Dq[1,:,:] + np.identity(Dq.shape[1]) 
        Dq = (1 - r**2 / D) * Dq
        return - Dq.sum(axis=2)
    
    def Grad_flow(q_, r, step):
        Q = q_
        n_iter = 0
        while True:
            n_iter = n_iter + 1 # you can count the number of iterations needed to reach the equilibrium
            Q_prev = Q
            Q = Q - step * Grad(Q, r) 
            if np.sum(np.abs((Q.T).dot(Q) - (Q_prev.T).dot(Q_prev))) < 1e-5:
                return Q
    
    '''
    Test:
    '''
    
    p = np.array([[-3, 3], [-1, 3], [1,3], [3,3],
                  [-3, 1], [-1, 1], [1,1], [3,1],
                  [-3,-1], [-1,-1], [1,-1], [3,-1],
                  [-3,-3], [-1, -3], [1, -3], [3,-3], 
                  [-2, 1], [-1,2],[2,-2], [-2,-2], 
                  [2,2], [2,0]]).T
    r = 0.5
    step = 0.01
    q = Grad_flow(p, r, step)
    
    '''
    Plot:
    '''
    
    fig, axs = plt.subplots(1,1)
    axs.set_aspect('equal')
    
    axs.plot(q[0,:], q[1,:], 'ro')
    axs.plot(p[0,:], p[1,:], 'bo')
    
    plt.grid()
    plt.show()
    

    You start from the blue positions and you make them converge to the red positions:

    enter image description here

    Here is the loop version of the Grad function:

    def Grad(pos, r):
        grad = np.zeros(pos.shape, dtype=float)
        for i in range(pos.shape[1]):
            for j in range(pos.shape[1]):
                if not i==j:
                    d_pos_0 = pos[0, i] - pos[0, j]
                    d_pos_1 = pos[1, i] - pos[1, j]
                    m = d_pos_0*d_pos_0 + d_pos_1*d_pos_1
                    m = 1 - r*r / m
                    grad[0, i] = grad[0, i]  +  m * d_pos_0
                    grad[1, i] = grad[1, i]  +  m * d_pos_1
        return grad
    

    Of course, all of this is a bit heuristic and I cannot promise full generality, so you have to play and select the parameters r which is half-distance between positions, iteration step-size step, the initial position p and so on.