any ideas on RPG map scrolling?

presently when a key is pressed my character changes x/y coords based on vx/vy. I’m sure this has been discussed before, so what’s the word on having a map that’s larger than the viewable region (640*480 in my case) that scrolls opposite the direction that the hero walks?
ex., when i walk left, the screen scrolls right while i stay in the middle. When I hit the edge of the map, however, my character finishes the distance by actually moving left instead of the screen moving right.

I could do something like:
if (hero.x >= image.getWidth()-viewableWidth/2)
hero.x+=vx;
else
image.x-=vx;

but that seems iffy, and i suck at java and consistenly find other ideas better than my own. I’m thinking this wont’ be too great because I do a lot of comparing x,y coords between my sprites and hero, as well as checking for obstructions. If my hero’s x,y coords are always in the middle except when he reaches the edge of the map, then that comparison won’t do. Any takers?

or how about this: can i check and modify the hero’s x,y coords relative to the map rather than the applet?

You want to move your hero and the background, or you want to move just the background in the opposite way (so that the hero remains in the middle of the screen) ?

If you want the latter, where only the background should move, I have some classes that do just that. I could mail them to you if you like. Just send me a PM or something. It’s not very convenient pasting the entire thing here.

i would suggest you avoid tying the movement and the view of the world together so closely… it prevents you from easily doing things like cut scenes where the camera would move… (cause you’d have to think about moving your character and all the other characters and objects that might happen to be on screen at the same time)

what i usually do is have something like screenX, and screenY which might be the centre or the top left corner of the viewable area, draw your tiles at their location but shifted according to screenX and screenY (so eg if screenX is 500 and screenY is 20, and your tile to draw is at 1600, 40… you would draw it at (1600 - 500), (40 - 20).

same for your characters… this way you can set the location of the camera to anywhere you feel like by adjusting screenX and screenY without having to worry about it later… basically, don’t tie the location of everything to the player…

also it’s always good to avoid drawing tiles that aren’t actually in the viewable area… give it a little overlap though, such as the size of your largest tile…

-Judd

or what about instead of shifting your images relative to the screenx/y, drawing all of your images to the main layer1 image and displaying a portion of that.
ex., all the characters and your main hero have free reign as far as x,y coords are concerned. you actually move x, y coords when you walk, but the camera follows you. So how would I be able to draw everything to one image (draw my sprites, trees etc, and hero to the tileLayer image)? If i could do that, then I could just throw in a boolean cameraFollows and have it always set to hero.x-screen.width/2, hero.y-screen.height/2. I would only paint one Image, the main image, which would be modified in the Map class by adding its own images.

success! if anyone’s interested in doing this, here’s my code:

import java.awt.*;
import java.awt.image.*;
import java.applet.Applet;
import java.util.ArrayList;

public class Room
{
      int width, height, appWidth, appHeight;
      String music="Music/Tsz_02.mid";
      boolean[][] grid;
      Applet applet; Graphics g;
      ArrayList sprites=new ArrayList();
      Sprite thisSprite;
      Hero hero;
      BufferedImage bimage; Graphics2D canvas, g2d;
      Image layer1;
      
      public Room(Image tile, Applet applet, Graphics g)
      {
            this.applet=applet;
            layer1=tile;
            width=layer1.getWidth(applet); height=layer1.getHeight(applet);
            appWidth=applet.bounds().width; appHeight=applet.bounds().height;
            
            this.g=g;
            g2d = (Graphics2D)g;
            bimage = g2d.getDeviceConfiguration().createCompatibleImage(width, height, Transparency.BITMASK);
            canvas = bimage.createGraphics();
            
            grid=new boolean[width][height];
            for (int j=0; j<width-1; j++) //set the borders as obstructions
                {grid[j][0]=true; grid[j][height-1]=true;}
          for (int j=0; j<height-1; j++)
                {grid[0][j]=true; grid[width-1][j]=true;}
            
      }
      
      public void update(int spriteNumber)
      {
            for (int i=0; i<sprites.size(); i++)
            {
                  thisSprite=(Sprite)sprites.get(i);
                  if (spriteNumber!=i)
                        thisSprite.move(sprites, i);
            }
            
      }      
      
      boolean cameraFollow=true;
      int clipX, clipY, clipWidth, clipHeight;
      public BufferedImage paint()
      {
            canvas.drawImage(layer1,0,0, null);
            canvas.drawImage(hero.image,hero.x, hero.y, null);
            for (int i=0; i<sprites.size(); i++)
                  {thisSprite=(Sprite)sprites.get(i);
                   canvas.drawImage(thisSprite.image,thisSprite.x, thisSprite.y, null);}
            
            // Set the viewable portion of the canvas to the hero's coords
        if (cameraFollow){
            clipX=hero.x-320; clipY=hero.y-240; clipWidth=640; clipHeight=480;
            if (hero.x-320<=0)             clipX=0;
            else if (hero.x+320>=width)    clipX=width-640;
            if (hero.y-240<=0)             clipY=0;
            else if (hero.y+240>=height)   clipY=height-480;
          }
          
          return bimage.getSubimage(clipX, clipY, clipWidth, clipHeight);
      }
                  
}      

for some reason this code is freakishly slow, but that might be in one of my other classes… otherwise, if someone sees some inefficiencies, please feel free to tell!

the boolean[][] grid sets up the obstructable regions. I do my collision testing by first checking sprite bounding boxes to see if they intersect (which isn’t too tough on the cpu) then checking boolean values on the map. Layer2 goes through and sets all pixels that aren’t transparent to true, leaving transprent pixels as false.

that’s because you are rendering absolutely everything to a giant image and then only picking a small portion of it out to display. the easiest way is quite often the least efficient way in programming…

the best option is to have a buffered image only the size of the display you actually want to see, then only drawing what should be visible in the frame onto it.

This way requires some extra math, but that’s what you have to deal with all the time in game programming.

this is my mario style game’s level rendering code…

note that if the tile is too far left, it is not painted…
if it’s too far right (i allow 16 tiles width) it is not painted…
the game doesn’t allow vertical scrolling so it’s a little basic, but it works.


      public void offsetPaint(Graphics g, int offset)
      {
            int x = 0, y = 0;
            while(true)//stop painting when we hit the void.
            {
                  if (y >= 14)
                  {
                        y = 0;
                        x++;
                  }
                  //as long as the tile isn't null
                  if (tiles[x][y] == null) break;
                  //as long as it's within the left visible bounds (2 off the edge)
                  if (x >= offset/32)
                  //same for the right bounds... and if this was a verical scroller
                  //it would be the same for top and bottom...
                  if (x <= offset/32+16)
                  
                  g.drawImage(tiles[x][y].img, x*32-offset, y*32, null);
                  y++;
            }
      }

in this case there’s only one offset, which is calculated as (player.x - 100) pixels, but of course i could adjust the offset however i liked at any time without a problem.

in a rpg you’d need a y-offset as well.

this is how i do this issue in the constructor i give all the currents objects in the world(im going to work on it to only give all obvjects in an given area) but then at drawing time ((i use a volatileimage for double buffering thats hardware accelerated) i ignore all objects that are out of the field and draw the rest to the volatile image my player is always in the middle the player has coordinates in the world and everything else is drawn relative to the player (player is drawn at 300,300 on the canvas but in the world hes at 0,0 when an object is in the world is -40,40 its drawn at 260,340 and if hes in the world at 318,48 its not drawn bceause its too far away. i tested it with over a million random object within an area of 500000 pixels(it was drawing like 1000 objects on the screen and it ran pretty smooth)

package lima.Games.GUI;

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import lima.Games.objects.*;
import java.awt.image.VolatileImage;

public class DrawArea extends Canvas{
      
      int i = 0,fps = 0;
      
      private Players player;
      private NPC[] npc;
      private StaticObjects[] sobject;

      private VolatileImage volatileImg;
      int[] playerpos;
      
      public DrawArea(Players player, NPC[] npc, StaticObjects[] sobject){

            this.player = player;
            this.npc = npc;
            this.sobject = sobject;
            setSize(600,600);
            
            
      }

      public void paint(Graphics g){

            createBackBuffer();

              do {//creating the buffer and draw everything if there is some kind of problem and the image is lost do it again

                    GraphicsConfiguration gc = this.getGraphicsConfiguration();

                  int valCode = volatileImg.validate(gc);
                  if(valCode==VolatileImage.IMAGE_INCOMPATIBLE){
                  createBackBuffer(); // recreate the hardware accelerated image.

            }

            Graphics offscreenGraphics = volatileImg.getGraphics();
            //offscreenGraphics
            offscreenGraphics.clearRect(0,0,600,600); //clear  the offscreen image

            offscreenGraphics.drawString(fps+" fps", 20, 20);

            offscreenGraphics.drawString(player.getPos()[0]+","+player.getPos()[1]+": "+player.getName(),(300)-10,(300)+20);
            offscreenGraphics.drawRect((300)-10,(300)-10,20,20, true);


            for(int i = 1; i < npc.length; i++){

                  if(getX(npc[i])-10 < 0 || getY(npc[i])-10 < 0){//if the x or y is to far to the west or north dont draw it
                  }else if(getX(npc[i])+10 > 600 || getY(npc[i])+10 > 600){//same goes for here but then for east and south
                  }else{

                        offscreenGraphics.drawString(npc[i].getPos()[0]+","+npc[i].getPos()[1]+": "+npc[i].getName(),getX(npc[i])-10,getY(npc[i])+20);
                        offscreenGraphics.drawRect(getX(npc[i])-10,getY(npc[i])-10,20,20,true);
                  }
            }


            for(int i = 1; i < sobject.length; i++){
                  if(getX(sobject[i])-10 < 0 || getY(sobject[i])-10 < 0){
                  }else if(getX(sobject[i])+10 > 600 || getY(sobject[i])+10 > 600){
                  }else{

                        offscreenGraphics.drawString(sobject[i].getPos()[0]+","+sobject[i].getPos()[1]+": "+sobject[i].getName(),getX(sobject[i])-10,getY(sobject[i])+20);
                        offscreenGraphics.drawRect(getX(sobject[i])-10,getY(sobject[i])-10,20,20,true);
                  }
            }

            g.drawImage(volatileImg,0,0,this); 
            } while(volatileImg.contentsLost());
      }
      
      public void Update(Players player, NPC[] npc, StaticObjects[] sobject, int fps){

            this.player = player;
            this.npc = npc;
            this.sobject = sobject;
            this.fps = fps;
            repaint();
      }
      
      public void update(Graphics g)
    {
            paint(g);
    }
      
      private void createBackBuffer() {
            GraphicsConfiguration gc = getGraphicsConfiguration();
            volatileImg = gc.createCompatibleVolatileImage(getWidth(), getHeight());
      }
      
      public int getX(Objects thing){//this retrieves the x coordinates to draw the object relative to the player
            int thisx = thing.getPos()[0];
            thisx = thisx - player.getPos()[0];
            thisx = 300+thisx;
            return thisx;
      }
      
      public int getY(Objects thing){//sme as getX but then for the y coordinates
            int thisy = thing.getPos()[1];
            thisy = thisy - player.getPos()[1];
            thisy = 300+thisy;
            return thisy;
      }
}