Need feedback on code design for maps of an RPG

Started by
7 comments, last by Nicholas Kong 10 years, 3 months ago

Code is in Java. Screen0 class updates and renders a quest giver npc. Screen1 class updates and renders 9 pigs. Only one instance of each class is created when the user enters the main menu state of the game. <-Let me know if this is actually allowed or if I need to change it somehow. Only one map is update and drawn on screen therefore only things on that one map also gets update and render which is performed in the Game class. The logic of update and render itself are in written in each map since each map is derived as a Sprite class, a class that can update and render.

The game is 2D top-down RPG. The wall collision code is in each map class (the code that needs to set the camera coordinates).

It seems I have more control over the maps doing it this way, but it might just be me.

Camera speed controls how fast everything around the main character moves since everything around the main character is drawn relative to the camera. The main character is locked at the centered of the screen even if its action state is running. The set on the camera coordinate in the wall collision code is to make the camera not move on the character collides with the wall so it would make "appear" that link is "actually" colliding with the wall.

Screen1 class handles drawing the loot dropped by the pigs and erases all the loot when the pigs are all killed. When pigs all get killed and when the main character goes to a different screen, the pigs are back to full health.

The two maps uses the same map texture so they share the same directory for that texture.


package com.nicholaskong.Game.MapScreen;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.util.ArrayList;
import com.nicholaskong.Game.Animation;
import com.nicholaskong.Game.Camera;
import com.nicholaskong.Game.MapTracker;
import com.nicholaskong.Game.Sprite;
import com.nicholaskong.Game.Dialogue.Dialogue;
import com.nicholaskong.Game.DialogueSystem.NPC;
import com.nicholaskong.Game.GameCharacters.Soldier;
import com.nicholaskong.Game.GameCharacters.Link;
 
public class Screen0 extends Sprite
{
private Animation anim;
private NPC soldier;
private static String[] animations = {"src/screen/screen0.png"};
private Rectangle leftRectangle;
private ArrayList<NPC> npcList;
private Rectangle rightRectangle;
private Rectangle topRectangle;
private Rectangle downRectangle;
 
private static final int frameInterval = 0;
 
 
public Screen0(double x, double y) {
              super(x, y);
              anim = new Animation(animations,frameInterval);
              Dialogue dialogue = new Dialogue();
              soldier = new NPC(new Soldier(getWidth() - (getWidth() * .66)  , (getHeight() - (getHeight() * 1.2))  ),dialogue);
              npcList = new ArrayList<>();
 
              MapTracker.SCREEN = 0;
              npcList.add(soldier);
}
 
public int getX()
{
return super.getX();
}
 
public int getY()
{
return super.getY();
}
 
public int getWidth()
{
return anim.getWidth();
}
 
public int getHeight()
{
return anim.getHeight();
}
 
public Rectangle getLeftRectangle()
{
return leftRectangle;
}
 
public Rectangle getRightRectangle()
{
return rightRectangle;
}
 
public Rectangle getTopRectangle()
{
return topRectangle;
}
 
public Rectangle getDownRectangle()
{
return downRectangle;
}
 
public void update(Camera camera, Link link) {
 
             int rectWidth = 50;
             int rectHeight = 50;
             // left
             leftRectangle = new Rectangle(((int)(getWidth() * -.04)) - camera.getX(),getY() - camera.getY(),rectWidth,getHeight());
             // right
             rightRectangle = new Rectangle(((int)(getWidth())) - camera.getX(),getY() - camera.getY(),rectWidth,getHeight());
             // top
             topRectangle = new Rectangle((int)(getWidth() *  0)- camera.getX(),(int) ((getHeight() * -0.06) - camera.getY()) ,getWidth(),rectHeight);
             // down
             downRectangle = new Rectangle((int)(getWidth() * 0)- camera.getX(),getHeight()  - camera.getY() ,getWidth(),rectHeight);
 
 
 
             if(link.getRectangle().intersects(leftRectangle))
             {
                       MapTracker.SCREEN = 1;
             }
             // right wall collision
             else if(link.getX() + link.getIdleRightWidth() > ((int)(getWidth())) - camera.getX())
             {
                       camera.setX(camera.getX() - camera.getSpeed());
             }
             // top wall collision
             else if(link.getY() < (int) ((getHeight() * -0.06) - camera.getY()) + topRectangle.getHeight() - 10 )
             { 
                       camera.setY(camera.getY() + camera.getSpeed());
             }
             // down wall collision
             else if( (link.getY() + link.getIdleDownHeight() > downRectangle.getY()))
             { 
                       camera.setY(camera.getY() - camera.getSpeed());
             }
 
 
 
}
 
public ArrayList<NPC> getNPCsInMap()
{
return npcList;
}
 
public void draw(Graphics2D g2D, Camera camera)
{
                    int rectWidth = 50;
                    g2D.setColor(Color.BLUE);
 
 
                    g2D.drawImage(anim.getImage(),super.getX() - camera.getX(),super.getY()- camera.getY(),null);
                    //left
                    g2D.fillRect(((int)(getWidth() * -.04))- camera.getX(),getY() - camera.getY(),rectWidth , getHeight());
 
 
                    // draw all npcs on the map
                    for(int i = 0; i < npcList.size();i++)
                    {
                               npcList.get(i).draw(g2D, camera);
                    }
 
 
}
 
@Override
public void update(Camera camera) {
// TODO Auto-generated method stub
 
}
 
 
 
}


package com.nicholaskong.Game.MapScreen;
 
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.util.ArrayList;
import com.nicholaskong.Game.ActionState;
import com.nicholaskong.Game.Animation;
import com.nicholaskong.Game.Camera;
import com.nicholaskong.Game.Collidable;
import com.nicholaskong.Game.MapTracker;
import com.nicholaskong.Game.Sprite;
import com.nicholaskong.Game.DialogueSystem.NPC;
import com.nicholaskong.Game.GameCharacters.Link;
import com.nicholaskong.Game.Monsters.Pig;
 
public class Screen1 extends Sprite
{
private Animation anim;
private static String[] animations = {"src/screen/screen0.png"};
private Rectangle rightRectangle;
private ArrayList<Collidable> monsterList;
private ArrayList<Collidable> projectileList;
private static final int frameInterval = 0;
private int numOfMonsters = 9;
private double[] widthData = {1.3,1.31,1.32,1.33,1.34,1.35,1.36,1.37,1.38 };
private double[] heightData = {0.90,0.83,0.76,0.69,0.62,0.55,0.48,0.41,0.34 };
private int[] behaviorData = {10,280,190,370,100,460,90,210,137 };
private ArrayList<Collidable> lootList;
private Rectangle leftRectangle;
private Rectangle topRectangle;
private Rectangle bottomRectangle;
 
public Screen1(double x, double y) {
super(x, y);
anim = new Animation(animations,frameInterval); 
monsterList = new ArrayList<>();
lootList = new ArrayList<>();
 
 
for(int i = 0; i < numOfMonsters;i++)
{
Pig pig = new Pig(getWidth() - (getWidth() * widthData[i]),getHeight() - (getHeight() * heightData[i]));
pig.setBehaviorCounter(behaviorData[i]);
monsterList.add(pig);
}
 
 
 
 
projectileList = new ArrayList<>();
 
 
rightRectangle = new Rectangle(0,0,(int)( .50 * getWidth()),getHeight());
 
}
 
public int getX()
{
return super.getX();
}
 
public int getY()
{
return super.getY();
}
 
public int getWidth()
{
return anim.getWidth();
}
 
public int getHeight()
{
return anim.getHeight();
}
 
public Rectangle getRightRectangle()
{
return rightRectangle;
}
 
public Rectangle getTopRectangle()
{
return topRectangle;
 
}
 
public ArrayList<NPC> getNPCsInMap()
{
return npcList;
}
 
public void update(Camera camera, Link link) {
 
int rectWidth = 50;
int rectHeight = 50;
 
rightRectangle = new Rectangle((int)(getWidth() * .16 ) - camera.getX(),getY() - camera.getY(),50,getHeight());
leftRectangle = new Rectangle((int)((getX())- camera.getX() - rectWidth),getY() - camera.getY(),rectWidth , getHeight());
topRectangle = new Rectangle((int)((getX())- camera.getX()),getY() - camera.getY() - rectHeight,getWidth() , rectHeight);
bottomRectangle = new Rectangle((int)((getX())- camera.getX()),getY() - camera.getY() + getHeight(),getWidth() , rectHeight);
 
 
if(link.getRectangle().intersects(rightRectangle))
{
MapTracker.SCREEN = 0;
 
}
else if(link.getX() <= leftRectangle.getX() + leftRectangle.getWidth())
{
camera.setX(camera.getX() + camera.getSpeed());
}
else if(link.getY() <= topRectangle.getY() + topRectangle.getHeight() - 5)
{
camera.setY(camera.getY() + camera.getSpeed());
}
else if(link.getY() + link.getIdleDownHeight() > bottomRectangle.getY())
{
camera.setY(camera.getY() - camera.getSpeed());
 
}
 
if(numOfMonsters == 0 && MapTracker.SCREEN == 0)
{
lootList.removeAll(lootList);
numOfMonsters = 9;
 
for(int i = 0; i < numOfMonsters;i++)
{
Collidable mob = monsterList.get(i);
mob.setLife(mob.getMaxLife());
mob.setState(ActionState.RUNNING);
 
}
}
 
for(int i = 0; i < monsterList.size();i++)
{
Collidable mob = monsterList.get(i);
 
if(mob.getLife() == 0)
{
lootList.add(mob.getGold());
 
 
link.addExp(link.getExp() + mob.getExp());
numOfMonsters--;
 
}
 
if(mob.getLife() >= 0)
{
mob.update(camera);
}
 
 
}
 
 
}
 
public ArrayList<Collidable> getMonstersOnMap()
{
return monsterList;
}
 
public void draw(Graphics2D g2D, Camera camera)
{
int rectWidth = 50;
 
g2D.setColor(Color.BLUE);
g2D.drawImage(anim.getImage(),getX() - camera.getX(),getY()- camera.getY(),null);
// right
g2D.fillRect((int)((getWidth() * .16 )- camera.getX()),getY() - camera.getY(),rectWidth , getHeight());


 
 
 
// draw and update all monsters on the map
for(int i = 0; i < monsterList.size();i++)
{
Collidable mob  = monsterList.get(i);
mob.draw(g2D, camera);
 
}
 
// draw all the loots
for(int i = 0; i < lootList.size();i++)
{
Collidable loot = lootList.get(i);
 
loot.update(camera);
 
loot.draw(g2D, camera);
 
}
 
 

 
 
 
 
}
 
 
public void update(Camera camera) {
// TODO Auto-generated method stub
 
}
 
public ArrayList<Collidable> getLootsOnMap()
{
return lootList;
}
 
 
 
public ArrayList<Collidable> getProjectilesInMap()
{
return projectileList;
}
 
 
}
Advertisement

It seems these Screen# classes are some kind of God Classes... lot of coupling here. They know pretty evertything of everyone, which is simpler at an early stage of the development, but becomes a mess fast.

Anyway, if you are happy with this design, go on, but you could at least make a ScreenBase class from which every Screen inherits. A lot more Javaish, isn't it?

It seems these Screen# classes are some kind of God Classes... lot of coupling here. They know pretty evertything of everyone, which is simpler at an early stage of the development, but becomes a mess fast.
Anyway, if you are happy with this design, go on, but you could at least make a ScreenBase class from which every Screen inherits. A lot more Javaish, isn't it?

It is written in Java. Thanks for the response. Looks like I need to separate the systems so they don't know about the other systems.

I hate dealing with a mess. I will be sure to restructure this code base to prevent the mess.

Code is written in Java which explains it being Javaish.

Yes, I recognized Java syntax. My "Javaish" was referred to a more structured code rather than the one you have now. It was a joke: Java (imo) tends to over-use the oop paradigm, with a proliferation of classes even for a simple task like a button click handler, while your code is totally in the opposite direction ;)


public int getX()
{
return super.getX();
}

This is neither necessary nor proper style. The geX() method inherited will be visible as long as it's public.


int rectWidth = 50;
             int rectHeight = 50;
             // left
             leftRectangle = new Rectangle(((int)(getWidth() * -.04)) - camera.getX(),getY() - camera.getY(),rectWidth,getHeight());
             // right
             rightRectangle = new Rectangle(((int)(getWidth())) - camera.getX(),getY() - camera.getY(),rectWidth,getHeight());
             // top
             topRectangle = new Rectangle((int)(getWidth() *  0)- camera.getX(),(int) ((getHeight() * -0.06) - camera.getY()) ,getWidth(),rectHeight);
             // down
             downRectangle = new Rectangle((int)(getWidth() * 0)- camera.getX(),getHeight()  - camera.getY() ,getWidth(),rectHeight);
 
 
 
             if(link.getRectangle().intersects(leftRectangle))
             {
                       MapTracker.SCREEN = 1;
             }
             // right wall collision
             else if(link.getX() + link.getIdleRightWidth() > ((int)(getWidth())) - camera.getX())
             {
                       camera.setX(camera.getX() - camera.getSpeed());
             }
             // top wall collision
             else if(link.getY() < (int) ((getHeight() * -0.06) - camera.getY()) + topRectangle.getHeight() - 10 )
             { 
                       camera.setY(camera.getY() + camera.getSpeed());
             }
             // down wall collision
             else if( (link.getY() + link.getIdleDownHeight() > downRectangle.getY()))
             { 
                       camera.setY(camera.getY() - camera.getSpeed());
             }

Try not to create objects when it's not necessary. Why not just check if the player's x, y coordinates are within the camera frustum, if not shift the Scene.

Try using a node based scene heiarchy it will be much more flexible


/**
 *
 * @author William Gervasio
 *
 */

import java.util.ArrayList;
import java.util.List;

import core.util.Node;
import core.util.math.Vector2;


public abstract class SceneNode extends Node<SceneNode> {

    private Vector2 translation;
    private List<IBehavior> behaviors = new ArrayList<IBehavior>();
    private float sceneTime;

    public SceneNode() {
        this(new Vector2());
    }

    public SceneNode(Vector2 translation) {
        this.translation = translation;
    }

    public void render(int delta) {
        for (Node<SceneNode> node : this.getSubnodes()) {
            ((SceneNode) node).render(delta);
        }
    }

    public void renderDebug(int delta) {
        for (Node<SceneNode> node : this.getSubnodes()) {
            ((SceneNode) node).renderDebug(delta);
        }
    }

    public void update(int delta) {
        this.sceneTime += delta;

        for (IBehavior behavior : behaviors) {
            behavior.act(this);
        }

        for (Node<SceneNode> node : this.getSubnodes()) {
            ((SceneNode) node).update(delta);
        }
    }

    public SceneNode setTranslation(Vector2 translation) {
        return setTranslation(translation.x, translation.y);
    }

    public SceneNode setTranslation(float x, float y) {
        for (Node<SceneNode> node : this.getSubnodes()) {
            Vector2 offset = ((SceneNode) node).getTranslation().sub(x, y);
            ((SceneNode) node).setTranslation(offset.add(x, y));
        }
        translation.set(x, y);

        return this;
    }

    public SceneNode translate(Vector2 translation) {
        return translate(translation.x, translation.y);
    }

    public SceneNode translate(float x, float y) {
        for (Node<SceneNode> node : this.getSubnodes()) {
            ((SceneNode) node).getTranslation().add(x, y);
        }
        translation.add(x, y);

        return this;
    }

    public Vector2 getTranslation() {
        return translation;
    }

    public float getX() {
        return translation.x;
    }

    public float getY() {
        return translation.y;
    }

    public float getSceneTime() {
        return sceneTime;
    }


/**
 *
 * @author William Gervasio
 *
 */

import java.util.ArrayList;
import java.util.List;

public class Node<T> implements Cloneable {

    private List<Node<T>> subnodes;

    public Node() {
        this(new ArrayList<Node<T>>());
    }

    public Node(List<Node<T>> subnodes) {
        this.subnodes = subnodes;
    }

    public void attach(Node<T> node) {
        subnodes.add(node);
    }

    public void clearNodes() {
        subnodes.clear();
    }

    public void detach(Node<T> node) {
        subnodes.remove(node);
    }

    public void deepDetach(T node) {
        subnodes.remove(node);
        for (Node<T> i : subnodes) {
            i.deepDetach(node);
        }
    }

    public boolean hasNode(Node<T> node) {
        return subnodes.contains(node);
    }

    public List<Node<T>> getSubnodes() {
        return subnodes;
    }

    public void setSubnodes(List<Node<T>> subnodes) {
        this.subnodes = subnodes;
    }



Try not to create objects when it's not necessary. Why not just check if the player's x, y coordinates are within the camera frustum, if not shift the Scene.

I need the rectangles which are the bounding box for the walls in the map. Without those, I cannot do collision.


Try not to create objects when it's not necessary. Why not just check if the player's x, y coordinates are within the camera frustum, if not shift the Scene.

I need the rectangles which are the bounding box for the walls in the map. Without those, I cannot do collision.

Try translating the boxes instead of creating new objects.


Try translating the boxes instead of creating new objects.

Would that not be optimizing? I don't think I should optimize if the solution works.


This is neither necessary nor proper style. The geX() method inherited will be visible as long as it's public.

Ah true. I see where you are getting at. It sure looks redundant too.

This topic is closed to new replies.

Advertisement