Show Posts
|
|
Pages: [1]
|
|
2
|
Game Development / Newbie & Debugging Questions / Coordinates in a tiled world
|
on: 2010-06-11 10:02:35
|
|
Hello,
I've tried out a couple of coordinate systems in a tiled world. So far I'm not convinced that any of them is really what I'm looking for.
I'm working with a pacman clone (The level is tiled, but entities should be able to move freely). The level is tiled from a 2d array. Each tile is 16x16.
My first attempt was just to use x:y coords for both objects and tiles. (position and index in the tile-map). This resulted in pacman jumping from tile to tile and not moving smoothly... clearly not what I was looking for.
My second attempt was to add delta coords for objects. This allowed me to move the objects smoothly, but I feel using a delta is error prone. And should the major coord be changed at the start/end/middle of a move?
Now I'm thinking about using different coordinates for objects and scenery. And add a translation method. Does this sound like a good idea?
I would also like to have objects with larger than 1x1 tile sizes in the future. Does any of the proposed coordinate systems limit this?
Thanks!
|
|
|
|
|
4
|
Games Center / Featured Games / Re: Stickmen Wars
|
on: 2009-08-18 15:17:03
|
Great Gameplay. 2D RTS with all AI controlled units. Excellent!!! Randomly generated (?) maps also increase replay value. Me like  I like that the terrain affects what will be the best strategy (hills provide cover for archers/mortar and flat areas are good for infantry). I would like to see a sniper (slow, long-range infantry (100% to hit?)) and a flamethrower (slow, short-range splash-damage). Burning units could start to run back and ignite other units close by.
|
|
|
|
|
5
|
Game Development / Performance Tuning / Re: Quadtree for moving objects
|
on: 2009-07-22 13:57:58
|
Hi again, I made a reference implementation of my Quadtree : FlatQuadtree. Basically a wrapper for a single HashSet. The performance gain compared to my other Quadtrees doesn't seem to be as great as I had hoped.  With FlatQuadtree: 150 Boids @ 60hz With either Static/DynamicQuadtree: 190 Boids @ 60hz 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| import java.util.Collection; import java.util.HashSet;
public class FlatQuadtree implements Quadtree { private final Collection<Entity> boids = new HashSet<Entity>();
@Override public void add(Entity entity) { boids.add(entity); }
@Override public Collection<Entity> get() { final Collection<Entity> result = new HashSet<Entity>(); get(result); return result; }
@Override public void get(Collection<Entity> result) { result.addAll(boids); }
@Override public Collection<Entity> get(BoundingSquare bs) { final Collection<Entity> result = new HashSet<Entity>(); get(bs, result); return result; }
@Override public void get(BoundingSquare bs, Collection<Entity> result) { for (Entity boid : boids) { if (bs.contains(boid.getPos())) { result.add(boid); } } }
@Override public Collection<Entity> get(BoundingCircle bc) { final Collection<Entity> result = new HashSet<Entity>(); get(bc, result); return result; }
@Override public void get(BoundingCircle bc, Collection<Entity> result) { for (Entity boid : boids) { if (bc.contains(boid.getPos())) { result.add(boid); } } }
@Override public void lock(boolean lock) {}
@Override public int maxLevel() { return 1; }
@Override public void moveDoit(Entity entity, Point pos) { throw new RuntimeException(); }
@Override public boolean moveTest(Entity entity, Point pos) { return true; }
@Override public void remove(Entity entity) { boids.remove(entity); }
@Override public int size() { return boids.size(); } } |
I've also got a new question: Should it be possible for an object to be located in more than one BB? Right now all objects are limited to one BB. The add method return after the 1st successful add operation. But if I want to use Quadtrees for colission detection I assume that their location in the Quadtree should be based on their position *and* size and not just their position. Right?
|
|
|
|
|
6
|
Game Development / Performance Tuning / Re: Quadtree for moving objects
|
on: 2009-07-16 18:51:07
|
The reason your remove is slow, is that you remove from a Vector.
First you have to search the vector (on average you have to check 50% of the items), and them you have to remove the item (on average you have to shift 50% of the remaining items).
If you replace the Vector by a HashSet, you'd get far better results.
...adding will be a tad slower though...
Whoa! After changing Vector to HashSet the add time was reduced from ~17sec to ~200ms Remove time is reduced from ~13sec to ~4sec Search times are approximately doubled Odd... 
|
|
|
|
|
7
|
Game Development / Performance Tuning / Re: Quadtree for moving objects
|
on: 2009-07-16 18:35:15
|
WRT: Being a 2D array that was in reference to "even distributed" not the code, I should have been more clear.
WRT: Being overcomplex. Examples: Nodes is taking on two rolls, that of an interior node and a leaf. Since your only storing data in leaves the code and data size can be reduced by breaking in two. 'splitSize', 'joinSize' and 'bb' could be replaced by a reference to the tree and a chosen coordinate. (The extent is implicit from the overall size and the depth). This should speed up construction time.
splitSize and joinSize are now moved to the Quadtree class. Still available to the inner class. Thanks!  I will try to change the BB class into a set of static methods that will work with the "level" and one point. Not sure how readable the code will be however...
|
|
|
|
|
8
|
Game Development / Performance Tuning / Re: Quadtree for moving objects
|
on: 2009-07-16 15:30:07
|
That is exactly how a quadtree normally works - if you rebuild the quadtree from scratch every frame based on the data you're attempting to classify, which is all you have to do. So do that. You'll then find that the quadtree nodes end up being quite a lot of garbage, so you might want to implement a quadtree node factory that uses a pool. Cas  This sounds like something that will help during move (remove/add). Now I get odd profiling results. One of my tests (add 64k of Entities, remove them) generally executes in 30 sec. However every once in a while it takes almost a minute.
|
|
|
|
|
9
|
Game Development / Performance Tuning / Re: Quadtree for moving objects
|
on: 2009-07-16 15:18:59
|
Ah! But that's the whole point. Otherwise you just have a 2D array with an overly complex interface (and memory requirements). Surely there is some existing Java code you could look at.
How did my "on-demand tree" become a 2D array? Exactly why is there a point in having n levels of populated Quadtree if the parent can handle the request? Please try to be more specific about the parts that are "way over complex with lots of redundent data". I've tried to write this as straightforward as possible so that others can read it and help me improve it.
|
|
|
|
|
10
|
Game Development / Performance Tuning / Re: Quadtree for moving objects
|
on: 2009-07-16 14:20:40
|
Not sure what you mean by "evenly distributed", but the whole point of a quadtree is to localize queries. Skimming your code, it looks way over complex with lots of redundent data.
I tried to say that if most of my Boids fly around in the top right corner. It would be better to have a high resolution in that corner and a low resolution in the other corners. No need to generate the nodes where there are no Boids.
|
|
|
|
|
11
|
Game Development / Performance Tuning / Re: Quadtree for moving objects
|
on: 2009-07-16 13:53:53
|
Because my Boids fly in flocks and doesn't spread out evenly I think an evenly distributed Quadtree would be a waste. Enter the DynamicQuadtree: My DynamicQuadtree is in many ways identical to the StaticQuadtree. However it uses lazy-load to generate the lower layers. A new layer is generated when more then a specified number of Entities has been added to a Node. Many things improved directly: + Inital creation of a Quadtree is now very quick + Searching in an empty tree is now much quicker However: - Add and remove operations are basically unaffected  64k Entities take ~20 sec to add and ~10sek to remove Can anyone direct me in the way to improve this? Are there more parameters than bucket size and tree depth? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
| import java.util.Collection; import java.util.Vector;
public class DynamicQuadtree implements Quadtree { private final static int DEFAULT_SPLIT_SIZE = 5; private final static int DEFAULT_LEVELS = 5; private final Collection<Entity> entities = new Vector<Entity>(); private final Node head;
public DynamicQuadtree() { this(DEFAULT_SPLIT_SIZE, DEFAULT_LEVELS); } public DynamicQuadtree(int splitSize, int levels) { if (splitSize < 1) throw new IllegalArgumentException(); if (levels < 0) throw new IllegalArgumentException(); this.head = new Node(splitSize, levels, new BoundingBox(-256, -256, 256, 256)); }
@Override public synchronized int size() { return head.size(); } @Override public synchronized Collection<Entity> get() { return new Vector<Entity>(entities); } @Override public synchronized void get(BoundingBox bb, Collection<Entity> result) { head.get(bb, result); } @Override public synchronized void add(Entity entity) { if (entities.contains(entity)) throw new RuntimeException();
entities.add(entity); head.add(entity); } @Override public synchronized void remove(Entity entity) { if (!entities.contains(entity)) throw new RuntimeException();
entities.remove(entity); head.remove(entity); }
@Override public String toString() { return "[QT "+ head +"]"; }
private final class Node { private final int splitSize; private final int joinSize; private final int level; private final BoundingBox bb; private Collection<Entity> entities; private Node minmin, minmax, maxmin, maxmax;
public Node(int splitSize, int level, BoundingBox bb) { if (level < 0) throw new IllegalArgumentException();
this.splitSize = splitSize; this.joinSize = splitSize*2; this.level = level; this.bb = bb; this.entities = new Vector<Entity>(); }
public int size() { if (entities != null) { return entities.size(); } else { int sum = 0; sum += minmin.size(); sum += minmax.size(); sum += maxmin.size(); sum += maxmax.size(); return sum; } } public void get(Collection<Entity> result) { if (entities != null) { result.addAll(entities); } else { minmin.get(result); minmax.get(result); maxmin.get(result); maxmax.get(result); } } public void get(BoundingBox bb, Collection<Entity> result) { if (!this.bb.intersects(bb)) throw new RuntimeException();
if (entities != null) { for (Entity entity : entities) { if (bb.contains(entity.pos)) result.add(entity); } } else { if (minmin.bb.intersects(bb)) minmin.get(bb, result); if (minmax.bb.intersects(bb)) minmax.get(bb, result); if (maxmin.bb.intersects(bb)) maxmin.get(bb, result); if (maxmax.bb.intersects(bb)) maxmax.get(bb, result); } } public void add(Entity entity) { if (!this.bb.contains(entity.pos)) throw new RuntimeException();
if (entities != null) { entities.add(entity);
if (entities.size() >= splitSize && level > 0) { final Collection<Entity> tmpEntities = new Vector<Entity>(entities);
entities = null; final double dx = (bb.maxX - bb.minX)/2; final double dy = (bb.maxY - bb.minY)/2; minmin = new Node(splitSize, level-1, new BoundingBox(bb.minX, bb.minY, bb.maxX-dx, bb.maxY-dy)); minmax = new Node(splitSize, level-1, new BoundingBox(bb.minX, bb.minY+dy, bb.maxX-dx, bb.maxY)); maxmin = new Node(splitSize, level-1, new BoundingBox(bb.minX+dx, bb.minY, bb.maxX, bb.maxY-dy)); maxmax = new Node(splitSize, level-1, new BoundingBox(bb.minX+dx, bb.minY+dy, bb.maxX, bb.maxY));
for (Entity tmpEntity : tmpEntities) { add(tmpEntity); } } } else { if (minmin.bb.contains(entity.pos)) minmin.add(entity); if (minmax.bb.contains(entity.pos)) minmax.add(entity); if (maxmin.bb.contains(entity.pos)) maxmin.add(entity); if (maxmax.bb.contains(entity.pos)) maxmax.add(entity); } } public void remove(Entity entity) { if (!this.bb.contains(entity.pos)) throw new RuntimeException();
if (entities != null) { entities.remove(entity); } else { if (minmin.bb.contains(entity.pos)) minmin.remove(entity); if (minmax.bb.contains(entity.pos)) minmax.remove(entity); if (maxmin.bb.contains(entity.pos)) maxmin.remove(entity); if (maxmax.bb.contains(entity.pos)) maxmax.remove(entity);
int sum = 0; sum += minmin.size(); sum += minmax.size(); sum += maxmin.size(); sum += maxmax.size(); if (sum < joinSize) { entities = new Vector<Entity>(); minmin.get(entities); minmin = null; minmax.get(entities); minmax = null; maxmin.get(entities); maxmin = null; maxmax.get(entities); maxmax = null; } } }
public String toString() { StringBuffer sb = new StringBuffer(); if (entities != null) sb.append(entities); if (minmin != null) sb.append(minmin); if (minmax != null) sb.append(minmax); if (maxmin != null) sb.append(maxmin); if (maxmax != null) sb.append(maxmax); return "[Node "+ sb.toString() +"]"; } } } |
|
|
|
|
|
12
|
Game Development / Performance Tuning / Quadtree for moving objects
|
on: 2009-07-16 11:11:57
|
This is my first Quadtree implementation and I'm not too impressed with it  I started out because I wanted to speed up my Boids application ( http://www.red3d.com/cwr/boids/). 1) Boids move around 2) Boids adapt their movement based on their closest neigbours Without any form of spatial organisation I had a O(n^2) complexity for each update Enter the StaticQuadtree... My StaticQuadtree generates all lower levels of the tree on instantiation. A StaticQuadtree with 1 level is basically the same as my original Vector. After some performance tests I noticed that generating more than 5 levels starts to decrease performance. Especially when removing Boids from the Quadtree when they move and when searching in an area that contains several sub levels. Is that a normal number of levels for a Quadtree, or can this code be improved by multitudes after a few simple optimizations?  Also, am I correct in asuming that I have to remove and re-add all boids during update? Wouldn't it be easier (quicker even) to regenerate the Quadtree before each update? My first implementation: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
| import java.util.Collection; import java.util.Vector;
public class StaticQuadtree implements Quadtree { private final static int DEFAULT_LEVELS = 5; private final Collection<Entity> entities = new Vector<Entity>(); private final Node head;
public StaticQuadtree() { this(DEFAULT_LEVELS); } public StaticQuadtree(int levels) { if (levels < 0) throw new IllegalArgumentException(); head = new Node(levels, new BoundingBox(-256, -256, 256, 256)); }
@Override public synchronized int size() { return entities.size(); } @Override public synchronized Collection<Entity> get() { return new Vector<Entity>(entities); } @Override public synchronized void get(BoundingBox bb, Collection<Entity> result) { head.get(bb, result); } @Override public synchronized void add(Entity entity) { if (entities.contains(entity)) throw new RuntimeException();
entities.add(entity); head.add(entity); } @Override public synchronized void remove(Entity entity) { if (!entities.contains(entity)) throw new RuntimeException();
entities.remove(entity); head.remove(entity); }
@Override public String toString() { return "[QT "+ head +"]"; }
private final class Node { private final int level; private final BoundingBox bb; private final Collection<Entity> entities; private final Node minmin, minmax, maxmin, maxmax;
public Node(final int level, BoundingBox bb) { if (level < 0) throw new IllegalArgumentException(); this.level = level; this.bb = bb;
if (level > 0) { entities = null; final double dx = (bb.maxX - bb.minX)/2; final double dy = (bb.maxY - bb.minY)/2; minmin = new Node(level-1, new BoundingBox(bb.minX, bb.minY, bb.maxX-dx, bb.maxY-dy)); minmax = new Node(level-1, new BoundingBox(bb.minX, bb.minY+dy, bb.maxX-dx, bb.maxY)); maxmin = new Node(level-1, new BoundingBox(bb.minX+dx, bb.minY, bb.maxX, bb.maxY-dy)); maxmax = new Node(level-1, new BoundingBox(bb.minX+dx, bb.minY+dy, bb.maxX, bb.maxY)); } else { entities = new Vector<Entity>(); minmin = null; minmax = null; maxmin = null; maxmax = null; } }
public synchronized int size() { if (level == 0) { return entities.size(); } int sum = 0; sum += minmin.size(); sum += minmax.size(); sum += maxmin.size(); sum += maxmax.size(); return sum; } public synchronized void get(Collection<Entity> result) { if (level == 0) { result.addAll(entities); } else { minmin.get(result); minmax.get(result); maxmin.get(result); maxmax.get(result); } } public synchronized void get(BoundingBox bb, Collection<Entity> result) { if (!this.bb.intersects(bb)) throw new RuntimeException();
if (level == 0) { for (Entity entity : entities) { if (bb.contains(entity.pos)) { result.add(entity); } } } else { if (minmin.bb.intersects(bb)) minmin.get(bb, result); if (minmax.bb.intersects(bb)) minmax.get(bb, result); if (maxmin.bb.intersects(bb)) maxmin.get(bb, result); if (maxmax.bb.intersects(bb)) maxmax.get(bb, result); } } public synchronized void add(Entity entity) { if (!this.bb.contains(entity.pos)) throw new RuntimeException();
if (level == 0) { entities.add(entity); } else { if (minmin.bb.contains(entity.pos)) minmin.add(entity); if (minmax.bb.contains(entity.pos)) minmax.add(entity); if (maxmin.bb.contains(entity.pos)) maxmin.add(entity); if (maxmax.bb.contains(entity.pos)) maxmax.add(entity); } } public synchronized void remove(Entity entity) { if (!this.bb.contains(entity.pos)) throw new RuntimeException();
if (level == 0) { entities.remove(entity); } else { if (minmin.bb.contains(entity.pos)) minmin.remove(entity); if (minmax.bb.contains(entity.pos)) minmax.remove(entity); if (maxmin.bb.contains(entity.pos)) maxmin.remove(entity); if (maxmax.bb.contains(entity.pos)) maxmax.remove(entity); } }
@Override public String toString() { StringBuffer sb = new StringBuffer(); if (entities != null) sb.append(entities); if (minmin != null) sb.append(minmin); if (minmax != null) sb.append(minmax); if (maxmin != null) sb.append(maxmin); if (maxmax != null) sb.append(maxmax); return "[Node "+ sb.toString() +"]"; } } }
final class Entity { public Point pos; public Point vel;
public Entity(double x, double y) { this.pos = new Point(x, y); this.vel = new Point(0, 0); }
@Override public String toString() { return "[Entity "+ pos.x +":"+ pos.y +"]"; } }
final class Point { public final double x, y;
public Point(double x, double y) { this.x = x; this.y = y; } }
final class BoundingBox { public final double minX, minY; public final double maxX, maxY;
public BoundingBox(double minX, double minY, double maxX, double maxY) { if (minX >= maxX) throw new IllegalArgumentException(); if (minY >= maxY) throw new IllegalArgumentException();
this.minX = minX; this.minY = minY; this.maxX = maxX; this.maxY = maxY; }
public boolean contains(double x, double y) { return (minX < x && x <= maxX && minY < y && y <= maxY); } public boolean contains(Point that) { return contains(that.x, that.y); } public boolean intersects(BoundingBox that) { return (minX < that.maxX && that.minX <= maxX && minY < that.maxY && that.minY <= maxY); } } |
Stay tuned for my DynamicQuadtree update... 
|
|
|
|
|
13
|
Game Development / Game Play & Game Design / Re: MVC
|
on: 2009-06-18 12:50:36
|
Would this be a correct MVC design? The Model is now stupid The Controller is responsible for updating the Model The Viewer is observing the Controller instead of the Model <-- is this correct MVC design? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
| package rps;
import java.awt.BorderLayout; import java.awt.Font; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.Observable; import java.util.Observer;
import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.UIManager; import javax.swing.UnsupportedLookAndFeelException;
public class RPS2 {
private enum Hand { Rock, Paper, Scissor } private enum Player { Attacker, Defender }
public static void main(String[] args) { try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (UnsupportedLookAndFeelException e) {} catch (ClassNotFoundException e) {} catch (InstantiationException e) {} catch (IllegalAccessException e) {}
new RPS2(); }
private RPS2() { Model model = new Model(); Controller controller = new Controller(model); new View(controller, Player.Attacker); new View(controller, Player.Defender); }
private class Model { private int score; private Hand attacker; private Hand defender;
public void setAttacker(Hand hand) { this.attacker = hand; } public void setDefender(Hand hand) { this.defender = hand; }
@Override public String toString() { return "[Model]"; } }
private class Controller extends Observable { private final Model model;
public Controller(Model model) { this.model = model; }
public void setHand(Player player, Hand hand) { if (player == Player.Attacker) { model.setAttacker(hand); setChanged(); } if (player == Player.Defender) { model.setDefender(hand); setChanged(); } testVictory(); }
private void testVictory() { if (model.attacker == null) return; if (model.defender == null) return;
Player winner = null; if ((model.attacker == Hand.Rock && model.defender == Hand.Scissor) || (model.attacker == Hand.Paper && model.defender == Hand.Rock) || (model.attacker == Hand.Scissor && model.defender == Hand.Paper)) { model.score++; winner = Player.Attacker; } if ((model.defender == Hand.Rock && model.attacker == Hand.Scissor) || (model.defender == Hand.Paper && model.attacker == Hand.Rock) || (model.defender == Hand.Scissor && model.attacker == Hand.Paper)) { model.score--; winner = Player.Defender; } notifyObservers(winner);
model.attacker = null; model.defender = null; }
public int getScore(Player player) { if (player == Player.Attacker) return model.score; if (player == Player.Defender) return -model.score; return 0; } public Hand getHand(Player player) { if (player == Player.Attacker) return model.attacker; if (player == Player.Defender) return model.defender; return null; }
@Override public String toString() { return "[Controller]"; } }
private class View extends JFrame implements Observer { private static final long serialVersionUID = 1L; private static final String VICTORY = "Victory!"; private static final String DEFEAT = "Defeat!"; private static final String DRAW = "Draw!"; private final Controller model; private final Player player; private final JLabel score = new Label(); private final JLabel info = new Label();
public View(Controller controller, Player player) { super(player.toString()); this.model = controller; this.player = player; controller.addObserver(this);
updateScore(); add(score, BorderLayout.NORTH); add(new Panel()); add(info, BorderLayout.SOUTH);
setDefaultCloseOperation(EXIT_ON_CLOSE); setResizable(false); pack(); setLocationByPlatform(true); setVisible(true); }
private void updateScore() { this.score.setText("Score: "+ model.getScore(player)); } private void notifyWinner(Player player) { if (player == null) { info.setText(DRAW); } else if (player == this.player) { info.setText(VICTORY); } else { info.setText(DEFEAT); } }
@Override public void update(Observable o, Object note) { System.out.println(this +" "+ o +" "+ note); updateScore(); if (note == null || note instanceof Player) { notifyWinner((Player) note); } }
@Override public String toString() { return "[View]"; }
private class Panel extends JPanel { private static final long serialVersionUID = 1L; public Panel() { super(new GridLayout(1, 3)); add(new Button(Hand.Rock)); add(new Button(Hand.Paper)); add(new Button(Hand.Scissor)); }
private class Button extends JButton implements ActionListener { private static final long serialVersionUID = 1L; private final Hand hand;
public Button(Hand hand) { super(hand.toString()); this.hand = hand; addActionListener(this); }
@Override public void actionPerformed(ActionEvent ae) { View.this.model.setHand(player, hand); } } }
private class Label extends JLabel { private static final long serialVersionUID = 1L; public Label(String message) { super(message); setHorizontalAlignment(JLabel.CENTER); setFont(getFont().deriveFont(Font.BOLD)); } public Label() { this("..."); } } } } |
|
|
|
|
|
14
|
Game Development / Game Play & Game Design / Re: MVC
|
on: 2009-06-17 17:02:53
|
Here is an example of my apparently not so great MVC prowess... I couldn't find a place for a Controller in this Rock-Paper-Scissor game. Can anyone refactor the code below to use a Controller? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
| package rps;
import java.awt.BorderLayout; import java.awt.Font; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.Observable; import java.util.Observer;
import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.UIManager; import javax.swing.UnsupportedLookAndFeelException;
public class RPS {
private enum Hand { Rock, Paper, Scissor } private enum Player { Attacker, Defender }
public static void main(String[] args) { try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (UnsupportedLookAndFeelException e) {} catch (ClassNotFoundException e) {} catch (InstantiationException e) {} catch (IllegalAccessException e) {}
new RPS(); }
private RPS() { Model model = new Model(); new View(model, Player.Attacker); new View(model, Player.Defender); }
private class Model extends Observable { private int score; private Hand attacker; private Hand defender;
public void setAttacker(Hand hand) { this.attacker = hand; setChanged();
testVictory(); } public void setDefender(Hand hand) { this.defender = hand; setChanged();
testVictory(); }
public void testVictory() { if (attacker == null) return; if (defender == null) return;
if ((attacker == Hand.Rock && defender == Hand.Scissor) || (attacker == Hand.Paper && defender == Hand.Rock) || (attacker == Hand.Scissor && defender == Hand.Paper)) { score++; notifyObservers(Player.Attacker); } else if ((defender == Hand.Rock && attacker == Hand.Scissor) || (defender == Hand.Paper && attacker == Hand.Rock) || (defender == Hand.Scissor && attacker == Hand.Paper)) { score--; notifyObservers(Player.Defender); } else notifyObservers();
attacker = null; defender = null; }
@Override public String toString() { return "[Model]"; } }
private class View extends JFrame implements Observer { private static final long serialVersionUID = 1L; private static final String VICTORY = "Victory!"; private static final String DEFEAT = "Defeat!"; private static final String DRAW = "Draw!"; private final Model model; private final Player player; private final JLabel score = new Label(); private final JLabel info = new Label();
public View(Model model, Player player) { super(player.toString()); this.model = model; this.player = player; model.addObserver(this);
updateScore(); add(score, BorderLayout.NORTH); add(new Panel()); add(info, BorderLayout.SOUTH);
setDefaultCloseOperation(EXIT_ON_CLOSE); setResizable(false); pack(); setLocationByPlatform(true); setVisible(true); }
@Override public void update(Observable o, Object note) { updateScore(); if (note == null || note instanceof Player) { notifyWinner((Player) note); } } private void updateScore() { final int score = (player == Player.Attacker) ? model.score : -model.score; this.score.setText("Score: "+ score); } private void notifyWinner(Player player) { if (player == null) { info.setText(DRAW); } else if (player == this.player) { info.setText(VICTORY); } else { info.setText(DEFEAT); } }
private class Panel extends JPanel { private static final long serialVersionUID = 1L; public Panel() { super(new GridLayout(1, 3)); add(new Button(Hand.Rock)); add(new Button(Hand.Paper)); add(new Button(Hand.Scissor)); }
private class Button extends JButton implements ActionListener { private static final long serialVersionUID = 1L; private final Hand hand;
public Button(Hand hand) { super(hand.toString()); this.hand = hand; addActionListener(this); }
@Override public void actionPerformed(ActionEvent ae) { if (View.this.player == Player.Attacker) { View.this.model.setAttacker(hand); } else { View.this.model.setDefender(hand); } } } }
private class Label extends JLabel { private static final long serialVersionUID = 1L; public Label(String message) { super(message); setHorizontalAlignment(JLabel.CENTER); setFont(getFont().deriveFont(Font.BOLD)); } public Label() { this("..."); } } } } |
|
|
|
|
|
15
|
Game Development / Game Play & Game Design / Re: MVC
|
on: 2009-06-17 14:59:38
|
When discussing MVC, you really cannot leave out Controllers for the sake of simplicity.
The Model and View are separated by the Controller.
Now I'm lost. I thought Viewers registered as observers in the Model. Should the Views register as observers in the Controller?
|
|
|
|
|
16
|
Game Development / Game Play & Game Design / Re: MVC
|
on: 2009-06-17 11:56:39
|
Hi!
Sorry for my silly question but where are your controllers? Don't forget the C in MVC.
I left out the Controllers for simplicity.  I have other questions concerning the Controllers, but right now I'm focusing on the Model/Viewer part. Right now there is one abstract Controller class (with an abstract execute() method) and one concrete class for every "verb" in the game logic (move, attach etc.).
|
|
|
|
|
17
|
Game Development / Game Play & Game Design / MVC
|
on: 2009-06-17 10:32:40
|
Hello, There are many topics regarding MVC. I like the following a lot. http://www.java-gaming.org/topics/mvc-pattern/3685/view.htmlHowever there are still a couple of issues that I feel uncertain of. Should the Views register as observers to every Model entity or just the Model singleton? - In a game like Ludo (static and limited number of pieces) I would assume that each piece should be observed by the viewers. Each move would then notify the Views. - In an arcade game (Model entities are created and destroyed) I have registered the Views in the Model singleton only, and read all entities from the Model on notification. This works ok because all entities are updated in the Model every tick. In essence; all notifications are collated into a single notification. - In a RTS (Views do not observe the entire Model, and different Views observe different parts of the Model) I'm a bit lost. I would prefer if the Views are notified when something "interesting" occurs in their View.
|
|
|
|
|
|
Add your game by posting it in the WIP section,
or publish it in Showcase.
The first screenshot will be displayed as a thumbnail.
|
|