我正在尝试开发一款飞扬的小鸟游戏。现在我面临着背景移动的问题。就我的代码而言,鸟在移动,然后背景也在移动。我的游戏世界的大小需要大于物理窗口游戏的大小。在这种类型中 在游戏中,世界应该看起来围绕玩家移动,而不是玩家移动 世界(当世界在玩家下方移动时,玩家将显得静止)。任何人都可以帮我将背景从右上方移动到左上方。这里有一朵云从右向左移动。这正在起作用。但背景图块没有发生移动。
import java.awt.event.KeyEvent;
import java.io.InputStream;
import java.util.ArrayList;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.Sequence;
import javax.sound.midi.Sequencer;
import java.awt.*;
import game2D.*;
@SuppressWarnings("serial")
public class Game extends GameCore
{
// Useful game constants
static int screenWidth = 612;
static int screenHeight = 484;
// Game constants
float lift = 0.005f;
float gravity = 0.0001f;
float fly = -0.04f;
float moveSpeed = 0.05f;
// Game state flags
boolean flap = false;
boolean moveRight = false;
boolean debug = true;
// Game resources
Animation landing;
Sprite player = null;
ArrayList<Sprite> clouds = new ArrayList<Sprite>();
ArrayList<Tile> collidedTiles = new ArrayList<Tile>();
TileMap tmap = new TileMap(); // Our tile map, note that we load it in init()
long total; // The score will be the total time elapsed since a crash
private boolean gameOver = false;
private Sound backgroundSound;
private Sound gameOverSound;
private Sequencer sequencer; // For playing MIDI music
/**
* The obligatory main method that creates
* an instance of our class and starts it running
*
* @param args The list of parameters this program might use (ignored)
*/
public static void main(String[] args) {
Game gct = new Game();
gct.init();
// Start in windowed mode with the given screen height and width
gct.run(false,screenWidth,screenHeight);
}
/**
* Initialise the class, e.g. set up variables, load images,
* create animations, register event handlers.
*
* This shows you the general principles but you should create specific
* methods for setting up your game that can be called again when you wish to
* restart the game (for example you may only want to load animations once
* but you could reset the positions of sprites each time you restart the game).
*/
public void init()
{
Sprite s; // Temporary reference to a sprite
// Load the tile map and print it out so we can check it is valid
tmap.loadMap("maps", "map.txt");
setSize(tmap.getPixelWidth()/4, tmap.getPixelHeight());
adjustWindowSize(); // Adjust window size to match game size
setVisible(true);
// Create a set of background sprites that we can
// rearrange to give the illusion of motion
landing = new Animation();
landing.loadAnimationFromSheet("images/landbird.png", 4, 1, 60);
// Initialise the player with an animation
player = new Sprite(landing);
// Load a single cloud animation
Animation ca = new Animation();
ca.addFrame(loadImage("images/cloud.png"), 1000);
// Create 3 clouds at random positions off the screen
// to the right
for (int c=0; c<3; c++)
{
s = new Sprite(ca);
s.setX(screenWidth + (int)(Math.random()*200.0f));
s.setY(30 + (int)(Math.random()*150.0f));
s.setVelocityX(-0.02f);
s.show();
clouds.add(s);
}
initialiseGame();
System.out.println(tmap);
try {
sequencer = MidiSystem.getSequencer();
sequencer.open();
InputStream is = getClass().getResourceAsStream("background.mid");
if (is != null) {
Sequence sequence = MidiSystem.getSequence(is);
sequencer.setSequence(sequence);
sequencer.start(); // Start playback
} else {
System.err.println("MIDI file not found");
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* You will probably want to put code to restart a game in
* a separate method so that you can call it when restarting
* the game when the player loses.
*/
public void initialiseGame()
{
total = 0;
player.setPosition(200,200);
player.setVelocity(0,0);
player.show();
gameOver=false;
// Start playing background music
if (sequencer != null && sequencer.isOpen()) {
sequencer.stop(); // Stop the music if it's currently playing
sequencer.setMicrosecondPosition(0); // Rewind the music to the beginning
sequencer.start(); // Start playing from the beginning
sequencer.setLoopCount(Sequencer.LOOP_CONTINUOUSLY);
}
}
/**
* Adjusts the size of the window to match the size of the game.
*/
private void adjustWindowSize() {
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
// Set the game size to match the tile map size
int gameWidth = tmap.getPixelWidth();
int gameHeight = tmap.getPixelHeight();
// Calculate the position to center the game on the screen
int x = (screenSize.width - gameWidth) / 2;
int y = (screenSize.height - gameHeight) / 2;
// Update the size of the window to match the game rectangle
setSize(gameWidth, gameHeight);
setLocation(x, y);
}
/**
* Draw the current state of the game. Note the sample use of
* debugging output that is drawn directly to the game screen.
*/
public void draw(Graphics2D g)
{
// Be careful about the order in which you draw objects - you
// should draw the background first, then work your way 'forward'
// First work out how much we need to shift the view in order to
// see where the player is. To do this, we adjust the offset so that
// it is relative to the player's position along with a shift
int xo = -(int)player.getX() + 200;
int yo = -(int)player.getY() + 200;
g.setColor(Color.white);
g.fillRect(0, 0, getWidth(), getHeight());
g.setFont(new Font("Arial", Font.BOLD, 10));
// Apply offsets to sprites then draw them
for (Sprite s: clouds)
{
s.setOffsets(xo,yo);
s.draw(g);
}
// Apply offsets to tile map and draw it
tmap.draw(g,xo,yo);
// Apply offsets to player and draw
player.setOffsets(xo, yo);
player.draw(g);
// Show score and status information
String msg = String.format("Score: %d", total/100);
g.setColor(Color.darkGray);
g.drawString(msg, getWidth() - 100, 50);
if (debug)
{
// When in debug mode, you could draw borders around objects
// and write messages to the screen with useful information.
// Try to avoid printing to the console since it will produce
// a lot of output and slow down your game.
tmap.drawBorder(g, xo, yo, Color.black);
g.setColor(Color.red);
player.drawBoundingBox(g);
g.drawString(String.format("Player: %.0f,%.0f", player.getX(),player.getY()),
getWidth() - 100, 70);
drawCollidedTiles(g, tmap, xo, yo);
}
}
public void drawCollidedTiles(Graphics2D g, TileMap map, int xOffset, int yOffset)
{
if (collidedTiles.size() > 0)
{
int tileWidth = map.getTileWidth();
int tileHeight = map.getTileHeight();
g.setColor(Color.blue);
for (Tile t : collidedTiles)
{
g.drawRect(t.getXC()+xOffset, t.getYC()+yOffset, tileWidth, tileHeight);
}
player.setPosition(player.getX(), player.getY());
showGameOver(g);
showRestartOption(g);
gameOver = true; // Set game over flag
}
}
public void showGameOver(Graphics2D g) {
g.setColor(Color.RED);
String gameOverMessage = "Game Over, Your score is "+total;
int x = (screenWidth - g.getFontMetrics().stringWidth(gameOverMessage)) / 2;
int y = screenHeight / 2;
g.drawString(gameOverMessage, x, y);
// Stop background music
if (sequencer != null && sequencer.isOpen()) {
sequencer.stop();
}
}
public void showRestartOption(Graphics2D g) {
g.setColor(Color.BLACK);
String restartMessage = "Press Enter to Restart";
int x = (screenWidth - g.getFontMetrics().stringWidth(restartMessage)) / 2;
int y = screenHeight / 2 + 30;
g.drawString(restartMessage, x, y);
}
/**
* Update any sprites and check for collisions
*
* @param elapsed The elapsed time between this call and the previous call of elapsed
*/
public void update(long elapsed) {
boolean playerMoved = false;
// Check if the player has reached the right edge of the screen
if (player.getX() + player.getWidth() >= tmap.getPixelWidth()) {
// Move player to the left side of the screen
player.setX(0);
// Repeat the background sprite by adjusting the x position of the clouds
for (Sprite s : clouds) {
s.setX(s.getX() - tmap.getPixelWidth()); // Move the cloud sprite back by the width of the map
s.update(elapsed);
}
}
// Update player's movement and track if it moves horizontally
if (flap || moveRight) {
playerMoved = true;
player.setVelocityY(player.getVelocityY() + (gravity * elapsed));
player.setAnimationSpeed(1.0f);
if (flap) {
player.setAnimationSpeed(1.8f);
player.setVelocityY(fly);
}
if (moveRight) {
player.setVelocityX(moveSpeed);
}
}
// Update total score based on player's movement
if (!gameOver && playerMoved) {
total += elapsed; // Increment the total score by the elapsed time
}
// Make adjustments to the speed of the sprite due to gravity
player.setVelocityY(player.getVelocityY() + (gravity * elapsed));
player.setAnimationSpeed(1.0f);
if (flap) {
player.setAnimationSpeed(1.8f);
player.setVelocityY(fly);
}
if (moveRight) {
player.setVelocityX(moveSpeed);
} else {
player.setVelocityX(0);
}
// Update the clouds
for (Sprite s : clouds) {
s.update(elapsed);
if (s.getX() + s.getWidth() < 0) {
// If the cloud has moved off the left side of the screen
// Move it to the right side of the screen
s.setX(screenWidth);
// Optionally, randomize the Y position to create variety
s.setY(30 + (int)(Math.random() * 150.0f));
}
}
// Update the player's animation and position
player.update(elapsed);
// Then check for any collisions that may have occurred
handleScreenEdge(player, tmap, elapsed);
checkTileCollision(player, tmap);
}
/**
* Checks and handles collisions with the edge of the screen. You should generally
* use tile map collisions to prevent the player leaving the game area. This method
* is only included as a temporary measure until you have properly developed your
* tile maps.
*
* @param s The Sprite to check collisions for
* @param tmap The tile map to check
* @param elapsed How much time has gone by since the last call
*/
public void handleScreenEdge(Sprite s, TileMap tmap, long elapsed)
{
// This method just checks if the sprite has gone off the bottom screen.
// Ideally you should use tile collision instead of this approach
float heightDifference = s.getY() + s.getHeight() - tmap.getPixelHeight();
if (heightDifference > 0)
{
// Put the player back on the map according to how far over they were
s.setY(tmap.getPixelHeight() - s.getHeight() - (int)(heightDifference));
// and make them bounce
s.setVelocityY(-s.getVelocityY()*0.75f);
}
float widthDifference = s.getX() + s.getWidth() - tmap.getPixelWidth();
if (widthDifference > 0)
{
// Put the player back on the map according to how far over they were
s.setX(tmap.getPixelWidth() - s.getWidth() - (int)(widthDifference));
// and make them bounce
s.setVelocityX(-s.getVelocityX()*0.75f);
}
}
/**
* Override of the keyPressed event defined in GameCore to catch our
* own events
*
* @param e The event that has been generated
*/
public void keyPressed(KeyEvent e)
{
int key = e.getKeyCode();
if (gameOver) { // Check if the game is over
if (key == KeyEvent.VK_ENTER) {
initialiseGame(); // Restart the game
}
return;
}
switch (key) {
case KeyEvent.VK_UP : flap = true; break;
case KeyEvent.VK_RIGHT : moveRight = true; break;
case KeyEvent.VK_S : Sound s = new Sound("sounds/caw.wav");
s.start();
break;
case KeyEvent.VK_ESCAPE : stop(); break;
case KeyEvent.VK_B : debug = !debug; break; // Flip the debug state
default : break;
}
}
/** Use the sample code in the lecture notes to properly detect
* a bounding box collision between sprites s1 and s2.
*
* @return true if a collision may have occurred, false if it has not.
*/
public boolean boundingBoxCollision(Sprite s1, Sprite s2)
{
return false;
}
/**
* Check and handles collisions with a tile map for the
* given sprite 's'. Initial functionality is limited...
*
* @param s The Sprite to check collisions for
* @param tmap The tile map to check
*/
public void checkTileCollision(Sprite s, TileMap tmap)
{
// Empty out our current set of collided tiles
collidedTiles.clear();
// Take a note of a sprite's current position
float sx = s.getX();
float sy = s.getY();
// Find out how wide and how tall a tile is
float tileWidth = tmap.getTileWidth();
float tileHeight = tmap.getTileHeight();
// Divide the sprite’s x coordinate by the width of a tile, to get
// the number of tiles across the x axis that the sprite is positioned at
int xtile = (int)(sx / tileWidth);
// The same applies to the y coordinate
int ytile = (int)(sy / tileHeight);
// What tile character is at the top left of the sprite s?
Tile tl = tmap.getTile(xtile, ytile);
if (tl != null && tl.getCharacter() != '.') // If it's not a dot (empty space), handle it
{
// Here we just stop the sprite.
s.stop();
collidedTiles.add(tl);
// You should move the sprite to a position that is not colliding
}
// We need to consider the other corners of the sprite
// The above looked at the top left position, let's look at the bottom left.
xtile = (int)(sx / tileWidth);
ytile = (int)((sy + s.getHeight())/ tileHeight);
Tile bl = tmap.getTile(xtile, ytile);
// If it's not empty space
if (bl != null && bl.getCharacter() != '.')
{
// Let's make the sprite bounce
s.setVelocityY(-s.getVelocityY()*0.6f); // Reverse velocity
collidedTiles.add(bl);
}
}
public void keyReleased(KeyEvent e) {
int key = e.getKeyCode();
switch (key)
{
case KeyEvent.VK_ESCAPE : stop(); break;
case KeyEvent.VK_UP : flap = false; break;
case KeyEvent.VK_RIGHT : moveRight = false; break;
default : break;
}
}
public void stop() {
// Stop background music
if (sequencer != null && sequencer.isOpen()) {
sequencer.stop();
}
}
}
import javax.swing.ImageIcon;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.util.HashMap;
import java.util.Map;
import java.io.*;
/**
* TileMap enables you to load a character based tile map from a text file. An
* example of the format for such a text file is given below:
10 5 32 32
// The first line should contain the width and height of the
// map and the width and height of each tile. A list of character to
// tile mappings is then provided where each character is preceded by a
// # character. The dot character always defaults to a blank space
// Note that the referenced files should be in the same directory as the
// tile map.
#b=orangeblock.png
#c=greencircle.png
#g=glasses.png
// The actual tile map is preceded by the #map line
#map
bbbbbbbbbb
b........b
b..g.....b
bccccccccb
bbbbbbbbbb
}
* @author David Cairns
*/
public class TileMap
{
private Tile [][] tmap; // The tile map grid, initially null
private int mapWidth=0; // The maps width in tiles
private int mapHeight=0; // The maps height in tiles
private int tileWidth=0; // The width of a tile in pixels
private int tileHeight=0; // The height of a tile in pixels
// imagemap contains a set of character to image mappings for
// quick loop up of the image associated with a given character.
private Map<String,Image> imagemap = new HashMap<String,Image>();
/**
* @return The map height in tiles
*/
public int getMapHeight() {
return mapHeight;
}
/**
* @return The map width in tiles
*/
public int getMapWidth() {
return mapWidth;
}
/**
* @return The height of a tile in pixels.
*/
public int getTileHeight() {
return tileHeight;
}
/**
* @return The width of a tile in pixels.
*/
public int getTileWidth() {
return tileWidth;
}
/**
* @return The map height in pixels
*/
public int getPixelHeight() {
return mapHeight * tileHeight;
}
/**
* @return The map width in pixels
*/
public int getPixelWidth() {
return mapWidth * tileWidth;
}
/**
* Loads a 'mapfile' that is contained in the given 'folder'. It is expected that
* the images associated with the map will also be in 'folder'.
*
* @param folder The folder the tile map and images are located in
* @param mapfile The name of the map file in the map folder
* @return true if the map loaded successfully, false otherwise
*/
public boolean loadMap(String folder, String mapfile)
{
// Create a full path to the tile map by sticking the folder and mapfile together
String path = folder + "/" + mapfile;
int row=0;
try
{
BufferedReader in = new BufferedReader(new FileReader(path));
String line="";
String trimmed="";
String [] vals;
// First we need to clear out the old image map
imagemap.clear();
// Read the first line of the tile map to find out
// the relevant dimensions of the map plus the tiles
line = in.readLine();
vals = line.split(" ");
// Check that we read 4 values
if (vals.length != 4)
{
System.err.println("Incorrect number of parameters in the TileMap header:" + vals.length);
in.close();
return false;
}
// Read in the map dimensions
mapWidth = Integer.parseInt(vals[0]);
mapHeight = Integer.parseInt(vals[1]);
tileWidth = Integer.parseInt(vals[2]);
tileHeight = Integer.parseInt(vals[3]);
// Now look for the character assignments
while ((line = in.readLine()) != null)
{
trimmed = line.trim();
// Skip the current line if it's a comment
if (trimmed.startsWith("//")) continue;
// Break out of the loop if we find the map
if (trimmed.startsWith("#map")) break;
if (trimmed.charAt(0) == '#') // Look for a character to image map
{
// Extract the character
String ch = "" + trimmed.charAt(1);
// and it's file name
String fileName = trimmed.substring(3,trimmed.length());
Image img = new ImageIcon(folder + "/" + fileName).getImage();
// Now add this character->image mapping to the map
if (img != null)
imagemap.put(ch,img);
else
System.err.println("Failed to load image '" + folder + "/" + fileName + "'");
}
}
// Check the map dimensione are at least > 0
if ((mapWidth > 0) && (mapHeight > 0))
{
tmap = new Tile[mapWidth][mapHeight];
}
else
{
System.err.println("Incorrect image map dimensions.");
trimmed = "";
}
// Now read in the tile map structure
if (trimmed.startsWith("#map"))
{
row=0;
while ((line = in.readLine()) != null)
{
if (line.trim().startsWith("//")) continue;
if (line.length() != mapWidth)
{
System.err.println("Incorrect line length in map");
System.err.println(row + " : " + line);
continue;
}
for (int col=0; col<mapWidth && col<line.length(); col++)
tmap[col][row] = new Tile(line.charAt(col),col*tileWidth,row*tileHeight);
row++;
if (row >= mapHeight) break;
}
}
in.close();
}
catch (Exception e)
{
System.err.println("Failed to read in tile map '" + path + "':" + e);
return false;
}
if (row != mapHeight)
{
System.err.println("Map failed to load. Incorrect rows in map");
return false;
}
return true;
}
/**
* Generate the tile map as a String so we can inspect its current state
*/
public String toString()
{
StringBuffer s = new StringBuffer();
for (int r=0; r<mapHeight; r++)
{
for (int c=0; c<mapWidth; c++)
s.append(tmap[c][r].getCharacter());
s.append('\n');
}
return s.toString();
}
/**
* Get the Image object associated with the tile at position 'x','y'
*
* @param x The x tile coordinate (in tiles, not pixels)
* @param y The y tile coordinate (in tiles, not pixels)
* @return The Image object associated with the tile at position 'x,y', null if blank or not found
*/
public Image getTileImage(int x, int y)
{
if (!valid(x,y)) return null;
Tile t = tmap[x][y];
if (t == null) return null;
char ch = t.getCharacter();
if (ch == '.') return null; // Blank space
return imagemap.get(ch + "");
}
/**
* Get the top left pixel x coordinate of a tile at position 'x,y' in the tile map
*
* @param x The x tile coordinate (in tiles, not pixels)
* @param y The y tile coordinate (in tiles, not pixels)
* @return The top left pixel x coordinate of a tile at position 'x,y' in the tile map
*/
public int getTileXC(int x, int y)
{
if (!valid(x,y)) return 0;
return tmap[x][y].getXC();
}
/**
* Get the top left pixel y coordinate of a tile at position 'x,y' in the tile map
*
* @param x The x tile coordinate (in tiles, not pixels)
* @param y The y tile coordinate (in tiles, not pixels)
* @return The top left pixel y coordinate of a tile at position 'x,y' in the tile map
*/
public int getTileYC(int x, int y)
{
if (!valid(x,y)) return 0;
return tmap[x][y].getYC();
}
/**
*
* @param x The x tile coordinate (in tiles, not pixels)
* @param y The y tile coordinate (in tiles, not pixels)
* @return true if tile coordinate 'x,y' is a valid position in the tile map
*/
public boolean valid(int x, int y)
{
return (x >= 0 && y >= 0 && x<mapWidth && y<mapHeight);
}
/**
* Sets the tile character at position 'x,y' to the value of 'ch'.
*
* @param ch The character to set the tile to.
* @param x The x tile coordinate (in tiles, not pixels)
* @param y The y tile coordinate (in tiles, not pixels)
* @return true if the character was correctly set
*/
public boolean setTileChar(char ch, int x, int y)
{
if (!valid(x,y)) return false;
tmap[x][y].setCharacter(ch);
return true;
}
/**
* Gets the tile character at position 'x,y'
*
* @param x The x tile coordinate (in tiles, not pixels)
* @param y The y tile coordinate (in tiles, not pixels)
* @return The character the tile is currently set to.
*/
public char getTileChar(int x, int y)
{
if (!valid(x,y)) return '?';
return tmap[x][y].getCharacter();
}
/**
* Gets the tile object at position 'x,y'
*
* @param x The x tile coordinate (in tiles, not pixels)
* @param y The y tile coordinate (in tiles, not pixels)
* @return The tile object at position 'x,y'.
*/
public Tile getTile(int x, int y)
{
if (!valid(x,y)) return null;
return tmap[x][y];
}
/**
* Draws the tile map to the graphics device pointed to by 'g'.
*
* @param g The graphics device to draw to
* @param xoff The xoffset to shift the tile map by
* @param yoff The yoffset to shift the tile map by
*/
public void draw(Graphics2D g, int xoff, int yoff)
{
if (g == null) return;
Image img=null;
Rectangle rect = (Rectangle)g.getClip();
int xc,yc;
for (int r=0; r<mapHeight; r++)
{
for (int c=0; c<mapWidth; c++)
{
img = getTileImage(c, r);
if (img == null) continue;
xc = xoff + c*tileWidth;
yc = yoff + r*tileHeight;
// Only draw the tile if it is on screen, otherwise go back round the loop
if (xc+tileWidth < 0 || xc >= rect.x + rect.width) continue;
if (yc+tileHeight < 0 || yc >= rect.y + rect.height) continue;
g.drawImage(img,xc,yc,null);
}
}
}
/**
* Draw a border around the tile map using the given colour and offsets.
* This may be useful for debugging purposes.
*
* @param g The graphics device to draw to
* @param xoff The xoffset to shift the tile map by
* @param yoff The yoffset to shift the tile map by
* @param colour The colour to draw the border with
*/
public void drawBorder(Graphics2D g, int xoff, int yoff, Color colour)
{
if (g == null) return;
g.setColor(colour);
g.drawRect(xoff, yoff, getPixelWidth(), getPixelHeight());
}
}
在你的Game类中创建一个固定间隔更新方法,比如FixedUpdate,还有另一个类,比如FixedUpdater,扩展TimerTask类。这个FixedUpdater只会在一个名为run的方法中调用固定更新方法。回到游戏类,在游戏开始时,将固定更新设置为定期运行。并生成您的第一个滚动背景精灵。使精灵具有与云相同或相似的类。确保左侧和右侧正确匹配,否则 2 个精灵之间的接缝将是清晰的。在FixedUpdate方法中,一旦旧的背景精灵完成滚动,就生成自定义背景精灵。下面是一些应该传达想法的粗略代码,但可能不适用于您的库。
import java.time.*;
public class FixedUpdater extends TimerTasK{
public void run(){
Game.FixedUpdate();
}
}
public class Game{
double elapsedTime = 0; // seconds
double fixedUpdateInterval = 1/30; // seconds
int imgSpeed = -100; // pixels per second
int worldWidth = 1000; // pixels
int worldHeight = 400; // pixels
int imgWidth = 3000; // pixels
int imgHeigt = 400; // pixels
public Game(){
Timer timer = new Timer();
timer.schedule(new FixedUpdater(), 0, 1 * 1000);
// runs the FixedUpdater's run method every 1 seconds
new Image(worldWidth * 1.1, 0, imgSpeed, 0, Sprite.background);
// (x, y, xspeed, yspeed, sprite to show);
/* assumes the 0,0 is top left of screen and the constructors
positions are for the top left corner of the image*/
}
public void FixedUpdate(){
elapsedTime += fixedUpdateInterval;
if(elapsedTime % (imgWidth / imgSpeed) = 0){
new Image(worldWidth * 1.1, 0, imgSpeed, 0, Sprite.background);
}
}
}
请小心确保图像滚动速度不会太快。在上面的代码中,如果屏幕长度为 1,单个像素的速度为 worldwidth / imgSpeed。在代码中,该值为 10,并且由于使用的单位是秒,因此背景上的单个像素将在 10 秒内穿过用户的屏幕。还要确保在精灵到达屏幕边缘时尊重它,以节省内存和处理能力。