简体   繁体   中英

Moving two objects in the same class on a JFrame in Java

I have a project I am working on in that I have to create two objects, I would like the big rectangle(BLUE) to move around the frame every time I press the arrow keys on my keyboard while the small rectangle(RED) is moving away, once the big square touches/tags the small rectangle, screen refreshes and I can move the big rectangle again to chase down the small rectangle. Below is my main class, and IT class where I have implemented my two shapes.

The goal is to have the two rectangles, small rectangle runs away in the frame every time the big rectangle comes close until it's tagged. Would also have to add some kind of score panel on the frame to show updated scores and a timer to count down when the player starts playing.

I need help having the two rectangles move differently and not on top of each other. I would like the second rectangle to move away every time the first rectangle comes close to it moving around the Frame

`

My class IT

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;

public class IT extends JPanel implements ActionListener, KeyListener {
    
    Timer shapeTimer = new Timer(5, this);

    public double xPos = 0, yPos = 0, movementX = 0, movementY = 0;

    public int rectSize = 50;
    public int rectSize2 = 35;

    public int windowWidth;
    int windowHeight;

    public int xBound;
    public int yBound;

    public IT(int w, int h){
        shapeTimer.start();

        addKeyListener(this);
        setFocusable(true);
        setFocusTraversalKeysEnabled(false);

        windowWidth = w;
        windowHeight = h;

        xBound = (windowWidth - rectSize);
        yBound = (windowHeight - rectSize);
    }
    public void paintComponent(Graphics g){
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g;

        Rectangle2D movableRect = new Rectangle2D.Double(xPos, yPos, rectSize, rectSize);
        
        g2.setColor(Color.BLUE);
        g2.draw(movableRect);
        
        g2.fill(movableRect);
        
        Rectangle2D movableRect2 = new Rectangle2D.Double(xPos, yPos, rectSize2, rectSize2);
        g2.setColor(Color.RED);
        g2.draw(movableRect2);
        
        g2.fill(movableRect2);
     
    }

    public void actionPerformed(ActionEvent e){
        repaint();

        xPos += movementX;
        yPos += movementY;
    }

    public void moveUp(){
        if (yPos == 0){
            movementY = 0;
            movementX = 0;
        }
        movementY = -0.5;
        movementX = 0;
    }

    public void moveDown(){
        if (yPos == yBound){
            movementY = 0;
            movementX = 0;              
        }
        movementY = 0.5;
        movementX = 0;
    }
    public void moveLeft()
    {        
        if (xPos == 0){
            movementY = 0;
            movementX = 0;              
        }         
        movementX = -0.5;
        movementY = 0;
    }

    public void moveRight(){
        if (xPos == xBound)
        {
            movementY = 0;
            movementX = 0;
        }
            
        movementX = 0.5;
        movementY = 0;
    }

    public void enlargeSquare(){
        rectSize++;
        
        rectSize2++;
    }

    public void shrinkSquare(){
        rectSize--;
        
        rectSize2--;
    }

    public void keyPressed(KeyEvent e){
        int keyCode = e.getKeyCode();

        if (keyCode == KeyEvent.VK_UP){
            moveUp();
        }

        if (keyCode == KeyEvent.VK_DOWN){
            moveDown();
        }

        if (keyCode == KeyEvent.VK_RIGHT){
            moveRight();
        }

        if (keyCode == KeyEvent.VK_LEFT){
            moveLeft();
        }

        if (keyCode == KeyEvent.VK_OPEN_BRACKET)
        {
            shrinkSquare();
        }

        if (keyCode == KeyEvent.VK_CLOSE_BRACKET)
        {
            enlargeSquare();
        }
    }


    public void keyTyped(KeyEvent e){

    }

    public void keyReleased(KeyEvent e){
       int keyCode = e.getKeyCode();

        if (keyCode == KeyEvent.VK_UP){
            movementX = 0;
            movementY = 0;
        }

        if (keyCode == KeyEvent.VK_DOWN){
            movementX = 0;
            movementY = 0;
        }

        if (keyCode == KeyEvent.VK_RIGHT){
            movementX = 0;
            movementY = 0;
        }

        if (keyCode == KeyEvent.VK_UP){
            movementX = 0;
            movementY = 0;
        }               
    }

}

` MainTester class

`

import javax.swing.*;

import javax.swing.JFrame;

public class MainTester {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        int frameWidth = 850;
        int frameHeight = 650;

        JFrame frmMain = new JFrame();
        frmMain.setSize(frameWidth, frameHeight);

        IT it = new IT(frameWidth, frameHeight);      
        frmMain.add(it);

        frmMain.setVisible(true);
        frmMain.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frmMain.setTitle("Tag Game");

    }

}

`

Use key bindings , seriously, this is going to solve a swagger of issues related to KeyListener .

Decouple and seperate your logic. Each "entity" should be a self contained unit of work. In your case, they should contain information about their color, location and size at a minimum.

For simplicity, I started out with something which could just be painted...

public interface Entity {
    public void paint(Graphics2D g2d);
}

Now, you could have a lot of different interface s which reflect this which can be painted, moved, controlled, represents effects or what ever you need - then your classes should implement the interface s they need. Then, your engine just deals with the "concepts" it wants to - need all "paintable" entities, need all "movable" entities, etc, when it needs to.

Next I created a concept of a "player". A player is a "paintable" entity, but which can be controlled by the player in some way, so it takes the current state of the "input"s and updates itself based on those states (more about that to come)

public static class PlayerEntity implements Entity {
    
   protected static final int DELTA = 2;
    
    private Rectangle bounds = new Rectangle(0, 0, 35, 35);
    private Color fillColor;

    public PlayerEntity(Color fillColor, Point location) {
        this.fillColor = fillColor;
        this.bounds.setLocation(location);
    }

    public Color getFillColor() {
        return fillColor;
    }

    public Rectangle getBounds() {
        return bounds;
    }
    
    public Point getCenter() {
        return new Point((int)getBounds().getCenterX(), (int)getBounds().getCenterY());
    }
    
    public void update(Set<PlayerAction> actions, Dimension size) {
        Rectangle currentBounds = getBounds();
        int x = currentBounds.x;
        int y = currentBounds.y;
        
        if (actions.contains(PlayerAction.UP)) {
            y -= DELTA;
        }
        if (actions.contains(PlayerAction.DOWN)) {
            y += DELTA;
        }
        if (actions.contains(PlayerAction.LEFT)) {
            x -= DELTA;
        }
        if (actions.contains(PlayerAction.RIGHT)) {
            x += DELTA;
        }
        
        if (y < 0) {
            y = 0;
        }
        if (y + currentBounds.height > size.height) {
            y = size.height - currentBounds.height;
        }
        if (x < 0) {
            x = 0;
        }
        if (x + currentBounds.width > size.width) {
            x = size.width - currentBounds.width;
        }
        
        getBounds().setLocation(x, y);
    }

    @Override
    public void paint(Graphics2D g2d) {
        g2d.setColor(getFillColor());
        g2d.fill(getBounds());
    }
    
}

For reference, PlayerAction represents all the valid actions which can be performed by the player, for simplicity sake, I've just stuck to movement:

public enum PlayerAction {
    UP, DOWN, LEFT, RIGHT;
}

Next I created a "monster" entity, in this case, the "monster" will always try and follow the player, this implementation is loosely based on Java: Move image towards mouse position

public static class MonsterEntity implements Entity {
    protected static final int DELTA = 1;

    private Rectangle bounds = new Rectangle(0, 0, 15, 15);
    private Color fillColor;

    public MonsterEntity(Color fillColor, Point location) {
        this.fillColor = fillColor;
        this.bounds.setLocation(location);
    }

    public Color getFillColor() {
        return fillColor;
    }

    public Rectangle getBounds() {
        return bounds;
    }
            
    public Point getCenter() {
        return new Point((int)getBounds().getCenterX(), (int)getBounds().getCenterY());
    }

    public void moveTowards(Point target) {
        Rectangle bounds = getBounds();
        Point center = getCenter();
        int xDelta = target.x < center.x ? -DELTA : DELTA;
        int yDelta = target.y < center.y ? -DELTA : DELTA;
        
        getBounds().setLocation(bounds.x + xDelta, bounds.y + yDelta);
    }

    @Override
    public void paint(Graphics2D g2d) {
        g2d.setColor(getFillColor());
        g2d.fill(getBounds());
    }
}

The monster is always trying to move it's center to the target location (which will eventually be the center of the player)

Now, this is where things become complicated.

We need:

  • A renderable surface onto which we can paint the player and monster(s)
  • Input bindings
  • A "game loop" to update the state of the entities and schedule repaints

For simplicity, I started with a JPanel , made use of Swing Timer and the key bindings API.

public class MainPane extends JPanel {
    
    // This represents the "input bindings", these represent
    // abstract actions which can be applied to the player
    // or game state.
    private enum InputKey {
        PRESSED_UP, PRESSED_DOWN, PRESSED_LEFT, PRESSED_RIGHT,
        RELEASED_UP, RELEASED_DOWN, RELEASED_LEFT, RELEASED_RIGHT;
        
        public KeyStroke getKeyStroke() {
            switch (this) {
                case PRESSED_UP: return KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, false);
                case PRESSED_DOWN: return KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, false);
                case PRESSED_LEFT: return KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false);
                case PRESSED_RIGHT: return KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false);
                case RELEASED_UP: return KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, true);
                case RELEASED_DOWN: return KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, true);
                case RELEASED_LEFT: return KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, true);
                case RELEASED_RIGHT: return KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, true);
            }
            return null;
        }
    }
    
    private PlayerEntity playerEntity;
    private MonsterEntity monsterEntity;
    private Timer timer;
    
    private Set<PlayerAction> actions = new HashSet<PlayerAction>();
    
    public MainPane() {
        InputMap inputMap = getInputMap(WHEN_IN_FOCUSED_WINDOW);
        ActionMap actionMap = getActionMap();
        
        inputMap.put(InputKey.PRESSED_UP.getKeyStroke(), InputKey.PRESSED_UP);
        inputMap.put(InputKey.PRESSED_DOWN.getKeyStroke(), InputKey.PRESSED_DOWN);
        inputMap.put(InputKey.PRESSED_LEFT.getKeyStroke(), InputKey.PRESSED_LEFT);
        inputMap.put(InputKey.PRESSED_RIGHT.getKeyStroke(), InputKey.PRESSED_RIGHT);
        inputMap.put(InputKey.RELEASED_UP.getKeyStroke(), InputKey.RELEASED_UP);
        inputMap.put(InputKey.RELEASED_DOWN.getKeyStroke(), InputKey.RELEASED_DOWN);
        inputMap.put(InputKey.RELEASED_LEFT.getKeyStroke(), InputKey.RELEASED_LEFT);
        inputMap.put(InputKey.RELEASED_RIGHT.getKeyStroke(), InputKey.RELEASED_RIGHT);
        
        actionMap.put(InputKey.PRESSED_UP, new MoveAction(actions, PlayerAction.UP, true));
        actionMap.put(InputKey.PRESSED_DOWN, new MoveAction(actions, PlayerAction.DOWN, true));
        actionMap.put(InputKey.PRESSED_LEFT, new MoveAction(actions, PlayerAction.LEFT, true));
        actionMap.put(InputKey.PRESSED_RIGHT, new MoveAction(actions, PlayerAction.RIGHT, true));
        actionMap.put(InputKey.RELEASED_UP, new MoveAction(actions, PlayerAction.UP, false));
        actionMap.put(InputKey.RELEASED_DOWN, new MoveAction(actions, PlayerAction.DOWN, false));
        actionMap.put(InputKey.RELEASED_LEFT, new MoveAction(actions, PlayerAction.LEFT, false));
        actionMap.put(InputKey.RELEASED_RIGHT, new MoveAction(actions, PlayerAction.RIGHT, false));
        
        Dimension size = getPreferredSize();
        Point center = new Point((size.width - 35) / 2, (size.height - 35) / 2);
        
        playerEntity = new PlayerEntity(Color.BLUE, center);
        monsterEntity = new MonsterEntity(Color.RED, new Point(size.width - 15, size.height - 15));
    }

    @Override
    public void addNotify() {
        super.addNotify();
        if (timer != null) {
            timer.stop();
        }
        timer = new Timer(5, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                performTick();
            }
        });
        timer.start();
    }

    @Override
    public void removeNotify() {
        super.removeNotify();
        if (timer != null) {
            timer.stop();
        }
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(400, 400);
    }
    
    protected void performTick() {
        playerEntity.update(actions, getSize());
        monsterEntity.moveTowards(playerEntity.getCenter());
        repaint();
    }
    
    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g.create();
        playerEntity.paint(g2d);
        monsterEntity.paint(g2d);
        g2d.dispose();
    }        
}

Movement (or input) is controlled through the key bindings API, this triggers a MoveAction which updates a centralised state repository (which is then passed to the PlayerEntity so it apply the state accordingly).

For simplicity, I've only used a single Action , but you could make a couple, one representing "press/activate" or "release/deactivate"

public class MoveAction extends AbstractAction {
    
    private Set<PlayerAction> actions;
    private PlayerAction action;
    private boolean activate;

    public MoveAction(Set<PlayerAction> directions, PlayerAction direction, boolean activate) {
        this.actions = directions;
        this.action = direction;
        this.activate = activate;
    }
    
    @Override
    public void actionPerformed(ActionEvent e) {
        if (activate) {
            actions.add(action);
        } else {
            actions.remove(action);
        }
    }
    
}

See How to Use Actions for more details about actions.

But why follow this workflow?!

  1. It decouples and decentralises a lot of the workflows. In fact, if you really wanted to, you could also seperate out the Timer and "paint" workflows to seperate classes, further decoupling the classes.
  2. Key bindings solve all the issues related to KeyListener weirdness. It also decouples the input - want to add touch controls/buttons, no worries, it's done via Action s. Want to add joystick/controllers, no worries, it's done via Action s.

Want more monsters?

Change:

private MonsterEntity monsterEntity;

to:

private List<MonsterEntity> monsterEntitys = new ArrayList<>(32);

Change:

monsterEntity = new MonsterEntity(Color.RED, new Point(size.width - 15, size.height - 15));

to:

monsterEntitys.add(new MonsterEntity(Color.RED, new Point(size.width - 15, size.height - 15)));
monsterEntitys.add(new MonsterEntity(Color.RED, new Point(0, 0)));
monsterEntitys.add(new MonsterEntity(Color.RED, new Point(size.width - 15, 0)));
monsterEntitys.add(new MonsterEntity(Color.RED, new Point(0, size.height - 15)));

Change:

monsterEntity.paint(g2d);

to:

for (MonsterEntity entity : monsterEntitys) {
    entity.paint(g2d);
}

And now you have more monsters! Have fun with that!

Runnable example...

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.HashSet;
import java.util.Set;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;

public class Main {
    public static void main(String[] args) {
        new Main();
    }

    public Main() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new MainPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public enum PlayerAction {
        UP, DOWN, LEFT, RIGHT;
    }

    public class MainPane extends JPanel {

        private enum InputKey {
            PRESSED_UP, PRESSED_DOWN, PRESSED_LEFT, PRESSED_RIGHT,
            RELEASED_UP, RELEASED_DOWN, RELEASED_LEFT, RELEASED_RIGHT;

            public KeyStroke getKeyStroke() {
                switch (this) {
                    case PRESSED_UP: return KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, false);
                    case PRESSED_DOWN: return KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, false);
                    case PRESSED_LEFT: return KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false);
                    case PRESSED_RIGHT: return KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false);
                    case RELEASED_UP: return KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, true);
                    case RELEASED_DOWN: return KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, true);
                    case RELEASED_LEFT: return KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, true);
                    case RELEASED_RIGHT: return KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, true);
                }
                return null;
            }
        }

        private PlayerEntity playerEntity;
        private MonsterEntity monsterEntity;
        private Timer timer;

        private Set<PlayerAction> actions = new HashSet<PlayerAction>();

        public MainPane() {
            InputMap inputMap = getInputMap(WHEN_IN_FOCUSED_WINDOW);
            ActionMap actionMap = getActionMap();

            inputMap.put(InputKey.PRESSED_UP.getKeyStroke(), InputKey.PRESSED_UP);
            inputMap.put(InputKey.PRESSED_DOWN.getKeyStroke(), InputKey.PRESSED_DOWN);
            inputMap.put(InputKey.PRESSED_LEFT.getKeyStroke(), InputKey.PRESSED_LEFT);
            inputMap.put(InputKey.PRESSED_RIGHT.getKeyStroke(), InputKey.PRESSED_RIGHT);
            inputMap.put(InputKey.RELEASED_UP.getKeyStroke(), InputKey.RELEASED_UP);
            inputMap.put(InputKey.RELEASED_DOWN.getKeyStroke(), InputKey.RELEASED_DOWN);
            inputMap.put(InputKey.RELEASED_LEFT.getKeyStroke(), InputKey.RELEASED_LEFT);
            inputMap.put(InputKey.RELEASED_RIGHT.getKeyStroke(), InputKey.RELEASED_RIGHT);

            actionMap.put(InputKey.PRESSED_UP, new MoveAction(actions, PlayerAction.UP, true));
            actionMap.put(InputKey.PRESSED_DOWN, new MoveAction(actions, PlayerAction.DOWN, true));
            actionMap.put(InputKey.PRESSED_LEFT, new MoveAction(actions, PlayerAction.LEFT, true));
            actionMap.put(InputKey.PRESSED_RIGHT, new MoveAction(actions, PlayerAction.RIGHT, true));
            actionMap.put(InputKey.RELEASED_UP, new MoveAction(actions, PlayerAction.UP, false));
            actionMap.put(InputKey.RELEASED_DOWN, new MoveAction(actions, PlayerAction.DOWN, false));
            actionMap.put(InputKey.RELEASED_LEFT, new MoveAction(actions, PlayerAction.LEFT, false));
            actionMap.put(InputKey.RELEASED_RIGHT, new MoveAction(actions, PlayerAction.RIGHT, false));

            Dimension size = getPreferredSize();
            Point center = new Point((size.width - 35) / 2, (size.height - 35) / 2);

            playerEntity = new PlayerEntity(Color.BLUE, center);
            monsterEntity = new MonsterEntity(Color.RED, new Point(size.width - 15, size.height - 15));
        }

        @Override
        public void addNotify() {
            super.addNotify();
            if (timer != null) {
                timer.stop();
            }
            timer = new Timer(5, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    performTick();
                }
            });
            timer.start();
        }

        @Override
        public void removeNotify() {
            super.removeNotify();
            if (timer != null) {
                timer.stop();
            }
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(400, 400);
        }

        protected void performTick() {
            playerEntity.update(actions, getSize());
            monsterEntity.moveTowards(playerEntity.getCenter());
            repaint();
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            playerEntity.paint(g2d);
            monsterEntity.paint(g2d);
            g2d.dispose();
        }        
    }

    public class MoveAction extends AbstractAction {

        private Set<PlayerAction> actions;
        private PlayerAction action;
        private boolean activate;

        public MoveAction(Set<PlayerAction> directions, PlayerAction direction, boolean activate) {
            this.actions = directions;
            this.action = direction;
            this.activate = activate;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            if (activate) {
                actions.add(action);
            } else {
                actions.remove(action);
            }
        }

    }

    public interface Entity {
        public void paint(Graphics2D g2d);
    }

    public static class MonsterEntity implements Entity {
        protected static final int DELTA = 1;

        private Rectangle bounds = new Rectangle(0, 0, 15, 15);
        private Color fillColor;

        public MonsterEntity(Color fillColor, Point location) {
            this.fillColor = fillColor;
            this.bounds.setLocation(location);
        }

        public Color getFillColor() {
            return fillColor;
        }

        public Rectangle getBounds() {
            return bounds;
        }

        public Point getCenter() {
            return new Point((int)getBounds().getCenterX(), (int)getBounds().getCenterY());
        }

        public void moveTowards(Point target) {
            Rectangle bounds = getBounds();
            Point center = getCenter();
            int xDelta = target.x < center.x ? -DELTA : DELTA;
            int yDelta = target.y < center.y ? -DELTA : DELTA;

            getBounds().setLocation(bounds.x + xDelta, bounds.y + yDelta);
        }

        @Override
        public void paint(Graphics2D g2d) {
            g2d.setColor(getFillColor());
            g2d.fill(getBounds());
        }
    }

    public static class PlayerEntity implements Entity {

       protected static final int DELTA = 2;

        private Rectangle bounds = new Rectangle(0, 0, 35, 35);
        private Color fillColor;

        public PlayerEntity(Color fillColor, Point location) {
            this.fillColor = fillColor;
            this.bounds.setLocation(location);
        }

        public Color getFillColor() {
            return fillColor;
        }

        public Rectangle getBounds() {
            return bounds;
        }

        public Point getCenter() {
            return new Point((int)getBounds().getCenterX(), (int)getBounds().getCenterY());
        }

        public void update(Set<PlayerAction> actions, Dimension size) {
            Rectangle currentBounds = getBounds();
            int x = currentBounds.x;
            int y = currentBounds.y;

            if (actions.contains(PlayerAction.UP)) {
                y -= DELTA;
            }
            if (actions.contains(PlayerAction.DOWN)) {
                y += DELTA;
            }
            if (actions.contains(PlayerAction.LEFT)) {
                x -= DELTA;
            }
            if (actions.contains(PlayerAction.RIGHT)) {
                x += DELTA;
            }

            if (y < 0) {
                y = 0;
            }
            if (y + currentBounds.height > size.height) {
                y = size.height - currentBounds.height;
            }
            if (x < 0) {
                x = 0;
            }
            if (x + currentBounds.width > size.width) {
                x = size.width - currentBounds.width;
            }

            getBounds().setLocation(x, y);
        }

        @Override
        public void paint(Graphics2D g2d) {
            g2d.setColor(getFillColor());
            g2d.fill(getBounds());
        }

    }
}

Other considerations...

Right now the speed/delta is actually really high. I would consider making use of the Shape API, using things like Point2D and Rectangle2D which provide double based properties instead of int , which would give away to reduce the delta values and slow down the entities.

See Working with Geometry for some more details

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM