|
|
| Looking for a Game Tutorial |
|
Neil Walker
Member #210
April 2000
|
Gabriel Campos said: I dont understood why J said to use a virtual void draw(); I haven't read the thread, but he's probably referring to polymorphism, i.e. loose coupling is king. It means you can have lots of sub-classes but still have call draw() called correctly when using the base class, e.g. assuming Sprite1 and Sprite2 were inherited from Object, then: void MyGreatFunction(Object* a) if not using virtual then Object::draw would be called. If using virtual then the correct ones are. The drawback is a slight overhead in determining the correct method to call. Neil. wii:0356-1384-6687-2022, kart:3308-4806-6002. XBOX:chucklepie |
|
bamccaig
Member #7,536
July 2006
|
I think that J-Gamer starting giving one suggestion and ended up giving a better one. Ignore the part about the draw method. Game objects generally shouldn't know how to draw themselves (they should have no concept of a screen, etc.). A third-party, like the Renderer concept should know how to draw all objects. It's the getSprite or getFrame method that you'll want to be virtual (and likely abstract AKA pure virtual) in the base class. -- acc.js | al4anim - Allegro 4 Animation library | Allegro 5 VS/NuGet Guide | Allegro.cc Mockup | Allegro.cc <code> Tag | Allegro 4 Timer Example (w/ Semaphores) | Allegro 5 "Winpkg" (MSVC readme) | Bambot | Blog | C++ STL Container Flowchart | Castopulence Software | Check Return Values | Derail? | Is This A Discussion? Flow Chart | Filesystem Hierarchy Standard | Clean Code Talks - Global State and Singletons | How To Use Header Files | GNU/Linux (Debian, Fedora, Gentoo) | rot (rot13, rot47, rotN) | Streaming |
|
J-Gamer
Member #12,491
January 2011
|
bamccaig said: I think that J-Gamer starting giving one suggestion and ended up giving a better one.
Did I? bamccaig said: Ignore the part about the draw method. I always have a draw() method in each game object. Those functions mostly take (BITMAP* buffer,float x,float y) as parameters. I do it this way, because e.g. my spacecraft has to be drawn in different ways according to a frame and mode variable within the spaceship class. " There are plenty of wonderful ideas in The Bible, but God isn't one of them." - Derezo |
|
james_lohr
Member #1,947
February 2002
|
Well, at least you were able to identify that your code looks ugly. That's an important step. Now you just need a decade or so of experience and you might be able to write some decent code.
|
|
Gabriel Campos
Member #12,034
June 2010
|
So lets see if I understand . . . I must have a class cObject, a class cRenderer . . . 1class cObject{ // Create a cObject class that will manager all drawable objects
2};
3
4class cRenderer{ // Renderer class
5 BITMAP *Buffer;
6
7void Render(cObject * object, cCamera *camera){
8 masked_blit(object->getSprite(), Buffer, object->getFrame()*object->getW(), Object->getStance()*object->getH(), object->getX - camera->getX, object->getY - camera->getY(), object->getW(), object->getH());
9}
10};
11
12
13int main(){
14 std::vector<cObject *>Objects;
15 for(int i = 0; i < Objects.length(); i++){
16 Renderer.Render(Objects[i], &Camera);
17 }
18};
19END_OF_MAIN();
Now my last doubt(I think) . .
|
|
J-Gamer
Member #12,491
January 2011
|
Gabriel Campos said: And, where I put the virtual draw?? You're mixing up two concepts here... You can do it by putting a virtual draw function in the cObject class, and making all classes like player,hud,enemy,... members of it with their own non-virtual draw that draws them. OR, you can have a cRenderer class draw function that checks all Objects for their type and draws them accordingly. In the latter, you have all drawing code in the cRenderer class, whereas in the former, you have the corresponding drawing code specified in each of the classes. " There are plenty of wonderful ideas in The Bible, but God isn't one of them." - Derezo |
|
bamccaig
Member #7,536
July 2006
|
J-Gamer said: I do it this way, because e.g. my spacecraft has to be drawn in different ways according to a frame and mode variable within the spaceship class.
Out of curiosity, what kinds of things vary based on the "frame" and "mode"? -- acc.js | al4anim - Allegro 4 Animation library | Allegro 5 VS/NuGet Guide | Allegro.cc Mockup | Allegro.cc <code> Tag | Allegro 4 Timer Example (w/ Semaphores) | Allegro 5 "Winpkg" (MSVC readme) | Bambot | Blog | C++ STL Container Flowchart | Castopulence Software | Check Return Values | Derail? | Is This A Discussion? Flow Chart | Filesystem Hierarchy Standard | Clean Code Talks - Global State and Singletons | How To Use Header Files | GNU/Linux (Debian, Fedora, Gentoo) | rot (rot13, rot47, rotN) | Streaming |
|
J-Gamer
Member #12,491
January 2011
|
Most of the time: which of the bitmaps in the different sequences are drawn. I have e.g.: a flying mode, propulsion mode, explosion mode, vanish mode, etc. " There are plenty of wonderful ideas in The Bible, but God isn't one of them." - Derezo |
|
bamccaig
Member #7,536
July 2006
|
I think you should be able to accomplish all of those with an animation class and possibly a separate interface for spaceship position vs. spaceship sprite position (if those aren't always the same as the position). The getSprite(gameTime) or getFrame(gameTime) method should return an appropriate sprite for the given mode/animation/game time. That way the actual drawing can be handled by a single rendering class and the differences in what are drawn get returned by the individual objects (preferably managed by another class under the surface, like an animation class, if non-trivial). This way your objects don't need to know anything about the drawing surface, or how to be drawn at all. They only store the data that is their state (including the visual and audio representation) and separate service objects, like our renderer, determine what to do with that data.
-- acc.js | al4anim - Allegro 4 Animation library | Allegro 5 VS/NuGet Guide | Allegro.cc Mockup | Allegro.cc <code> Tag | Allegro 4 Timer Example (w/ Semaphores) | Allegro 5 "Winpkg" (MSVC readme) | Bambot | Blog | C++ STL Container Flowchart | Castopulence Software | Check Return Values | Derail? | Is This A Discussion? Flow Chart | Filesystem Hierarchy Standard | Clean Code Talks - Global State and Singletons | How To Use Header Files | GNU/Linux (Debian, Fedora, Gentoo) | rot (rot13, rot47, rotN) | Streaming |
|
Neil Walker
Member #210
April 2000
|
I remember about 15 years ago when I first got started into game coding (at that point I thought there was something magical and complicated about how you display moving things, not knowing it was just a case of blanking the screen and drawing stuff elsewhere), I wondered whether when a player picked up an item and moved with it, whether I had to have a graphic with the player and the item and thought this will take a lot of graphics Neil. wii:0356-1384-6687-2022, kart:3308-4806-6002. XBOX:chucklepie |
|
Thomas Fjellstrom
Member #476
June 2000
|
/me thinks bamccaig is falling into the OO/C++ trap of overcomplicated, overengineered patterned designs... -- |
|
bamccaig
Member #7,536
July 2006
|
That might be what it sounds like, but in actuality it's simplifying the design instead of complicating it. Instead of making 20 different redundant calls to blit or al_draw_bitmap or whatever, you make one (or a handful) and everything else just works the same way. Your objects have a visual state and another external class knows how to draw that state (but knows nothing of specific types, like spaceships or moonmen or whathaveyou). It's actually a lot simpler of a design, but it requires a bit of forethought. -- acc.js | al4anim - Allegro 4 Animation library | Allegro 5 VS/NuGet Guide | Allegro.cc Mockup | Allegro.cc <code> Tag | Allegro 4 Timer Example (w/ Semaphores) | Allegro 5 "Winpkg" (MSVC readme) | Bambot | Blog | C++ STL Container Flowchart | Castopulence Software | Check Return Values | Derail? | Is This A Discussion? Flow Chart | Filesystem Hierarchy Standard | Clean Code Talks - Global State and Singletons | How To Use Header Files | GNU/Linux (Debian, Fedora, Gentoo) | rot (rot13, rot47, rotN) | Streaming |
|
Thomas Fjellstrom
Member #476
June 2000
|
bamccaig said: That might be what it sounds like, but in actuality it's simplifying the design instead of complicating it. Depends. Every post you come up with more and more complicated patterns to add, for a BEGINNER. And no, it isn't simpler. -- |
|
bamccaig
Member #7,536
July 2006
|
It's more complicated language-wise because you need to understand polymorphism, but the solution is simpler than redundantly duplicating the same basic logic over and over again. IIRC, the OP said he was knowledgeable in C++. At least, he seems comfortable enough to be introduced to/reminded of polymorphism and its benefits. -- acc.js | al4anim - Allegro 4 Animation library | Allegro 5 VS/NuGet Guide | Allegro.cc Mockup | Allegro.cc <code> Tag | Allegro 4 Timer Example (w/ Semaphores) | Allegro 5 "Winpkg" (MSVC readme) | Bambot | Blog | C++ STL Container Flowchart | Castopulence Software | Check Return Values | Derail? | Is This A Discussion? Flow Chart | Filesystem Hierarchy Standard | Clean Code Talks - Global State and Singletons | How To Use Header Files | GNU/Linux (Debian, Fedora, Gentoo) | rot (rot13, rot47, rotN) | Streaming |
|
Thomas Fjellstrom
Member #476
June 2000
|
bamccaig said: It's more complicated language-wise because you need to understand polymorphism And a crap ton of new "design patterns". I'm not against a little polymprphosm, but springing a bunch of fancy design patterns on a newbie is silly in the extreme. Quote: At least, he seems comfortable enough to be introduced to/reminded of polymorphism and its benefits. Just my oppinion, but if you haven't been introduced to Polymorphism, you aren't comfortable with C++, and you probably aren't ready for most of the fancy design patterns you are suggesting -- |
|
Edgar Reynaldo
Major Reynaldo
May 2007
|
Thomas Fjellstrom said: And a crap ton of new "design patterns". I'm not against a little polymprphosm, but springing a bunch of fancy design patterns on a newbie is silly in the extreme. You don't need design patterns to use polymorphism, and there aren't any design patterns being used here... All it is is inheritance, to replicate an interface for different classes, so they can be used interchangeably through a base class pointer. 1#include <allegro.h>
2
3class ObjectBase {
4protected :
5 BITMAP* sprite;
6 int x;
7 int y;
8public :
9 ObjectBase(BITMAP* image , int xpos , int ypos) : sprite(image) , x(xpos) , y(ypos) {}
10 virtual void DrawOn(BITMAP* bmp , int offsetx , int offsety) const {
11 ASSERT(bmp);
12 draw_sprite(bmp , sprite , xpos + offsetx , ypos + offsety);
13 }
14};
15
16class Player : public ObjectBase {
17// ...
18};
19
20class Camera {
21protected :
22 BITMAP* background;
23 int cx;
24 int cy;
25 int cw;
26 int ch;
27public :
28 Camera(BITMAP* bg , int xpos , int ypos , int width , int height) :
29 background(bg) , cx(xpos) , cy(ypos) , cw(width) , ch(height) {}
30 void SetPos(int xpos , int ypos) {
31 //... adjust xpos and ypos to make sure the camera doesn't go 'offworld'
32 cx = xpos;
33 cy = ypos;
34 }
35 void DrawBackground(BITMAP* bmp , int x , int y) {
36 ASSERT(bmp);
37 blit(background , buffer , cx , cy , x , y , cw , ch);
38 }
39 int X() {return cx;}
40 int Y() {return cy;}
41}
42
43class World {
44protected :
45 vector<ObjectBase*> objects;
46public :
47 void DrawOn(BITMAP* bmp , const Camera& cam) {
48 ASSERT(bmp);
49 cam.DrawBackground(bmp , 0 , 0);
50 int camx = cam.X();
51 int camy = cam.Y();
52 for (int i = 0 ; i < objects.size() ; ++i) {
53 objects->DrawOn(bmp , -camx , -camy);
54 }
55 }
56};
My Website! | EAGLE GUI Library Demos | My Deviant Art Gallery | Spiraloid Preview | A4 FontMaker | Skyline! (Missile Defense) Eagle and Allegro 5 binaries | Older Allegro 4 and 5 binaries | Allegro 5 compile guide |
|
Thomas Fjellstrom
Member #476
June 2000
|
Edgar Reynaldo said: You don't need design patterns to use polymorphism, and there aren't any design patterns being used here... I was talking about some of the stuff bamccaig was proposing. Fancy adapters and MVC... -- |
|
Neil Walker
Member #210
April 2000
|
Good use of polymorphism as I said above helps you with loose coupling. What's worse - having a bit of thought before you write your code, or turning your code into spaghetti within a few weeks with either 30 case statements in a method or 10 methods that do the same thing but slightly different and having to remember what they all are? I agree design patterns can be bad in the wrong hands and they do over-complicate things, but where it's relevant it makes things better. For example, most people end up coding using the Strategy, Adapter and Facade pattern without even realising it. Neil. wii:0356-1384-6687-2022, kart:3308-4806-6002. XBOX:chucklepie |
|
Gabriel Campos
Member #12,034
June 2010
|
Ok, lets some code: 1class cObject{ // This class will handle all objects in the game
2 private:
3 BITMAP *Buffer;
4 public:
5 virtual void Draw(cObject *Object, cCamera *Camera){
6 // here comes a little doubt.. what to put here?
7 // Maybe this? masked_blit(Object->GetSprite(), Buffer, Object->GetFrame() * Object->GetW(), Object->GetStance() * Object->GetH(), Object->Getx(), Object->Gety(), Object->GetW(), Object->GetH());
8}
9
10 void UpdateScreen(){
11 draw_sprite(screen, Buffer, 0, 0);
12}
13};
14
15.............................................................................
16
17class cPlayer : public cObject{
18 private:
19 int x, y, W, H ..... // or maybe put this variables in the cObject class, because is common in all objects
20 BITMAP *Sprite;
21 public:
22 void Draw(){ // Here I declare the Draw in Normal?? This Way?
23 masked_blit(Sprite, Buffer, Frame * W, Stance * H, x, y, W, H);
24}
25};
26
27.............................................................................
28
29class cCamera{
30 int x, y....//and others definitions
31};
32
33..............................................................................
34
35#include <vector>
36
37int main(){
38 std::vector<cObject *>Objects;
39
40for(int i=0; i < Objects.lenght(); i++){
41 Objects[i].Draw(&Player, &Camera);
42}
43END_OF_MAIN();
Please, help me...I am learning a lot here...
|
|
jhetfield21
Member #12,379
November 2010
|
if you have sprites then a good idea is to let the renderer class handle it.just make a spriteholder class(basically a class with a map/list/vector<string,Sprite*> of all the Sprites in your level or even the whole game though it's not advised) and whenever you instantiate a sprite you register it(insert it) in the sprite holder('s map/list/vector).so in the game loop the first thing you do is call the renderer and have it blit everything visible on the screen and afterward do every other calculation to change the state of the game,player,character etc so that in the next loop the renderer has all the updated info on the sprites including the new destination point and possibly another frame of the same film. i'm currently taking a course in my uni for 2D bitmapped games and this is the way we are doing it.it's simple enough imo.then again it might not fit in this situation,i didn't really see the code but it's a good concept to know.and you don't need to implement any design patterns.
|
|
Audric
Member #907
January 2001
|
This is my take: 1class cSprite{ // This class will handle all moving bitmap objects in the game
2 cSprite()
3 {
4 Sprite=NULL; // Default: invisible (and non-crashing :) )
5 }
6 public:
7 BITMAP *Sprite; // This points to an already allocated bitmap, or NULL when the object is invisible.
8 int X, Y; // Position in pixels, in "world" coordinates.
9 int W, H; // Any other members that all your cSprites need).
10 virtual void LogicUpdate(int tick); // Called once per game tick (assuming fixed-step timing). Has to be implemented by each derived class.
11}
12
13 void Draw(cCamera *cam, BITMAP *target_buffer){
14 if (Sprite==NULL)
15 return; // To make an object invisible, set its Sprite to NULL;
16 draw_sprite(target_buffer, Sprite, X - cam->Offset_X, Y - cam->Offset_Y);
17}
18
19};
20
21.............................................................................
22
23class cPlayer : public cObject{
24 public:
25
26 cPlayer()
27 {
28 // Initial position
29 X=100;
30 Y=100;
31 }
32
33 void LogicUpdate(int tick) // Logic for player, not very interactive here.
34 {
35 Sprite = Player_sprite[10 + ((tick/6) % 4)]; // Changes the image once every 6 ticks, and uses sprites 10 to 13 for an array.
36 X = (tick % 200) + 100; // Moves from 100 to 300, 1 pixel per tick, then warp back and repeat.
37 }
38};
39
40..............................................................................
41
42#include <vector>
43
44int main(){
45 std::vector<cSprite *> Sprites;
46
47// Code for redrawing all screen:
48// Erase all here
49...
50// Draw all sprites
51for(int i=0; i < Objects.length(); i++){
52 Objects[i].Draw(&Camera, screen);
53}
54
55....
56
57// Code for the logic of *** all the game*** :
58for(int i=0; i < Objects.length(); i++){
59 Objects[i].LogicUpdate(current_frame);
60}
61
62
63END_OF_MAIN();
I didn't want to make this too complex, but note that the sprites will be displayed in the order of the collection (vector) : last ones are "above". |
|
jhetfield21
Member #12,379
November 2010
|
you have to have priority given one way or the other if you want your characters to be able to go behind graphics as well. and principally Audric's solution is the same as what i said.
|
|
J-Gamer
Member #12,491
January 2011
|
Neil Walker said: The drawback is a slight overhead in determining the correct method to call. ehh... I didn't know virtual functions cause overhead... BTW, what is overhead exactly(srr for the noobish question) @bamccaig: " There are plenty of wonderful ideas in The Bible, but God isn't one of them." - Derezo |
|
jhetfield21
Member #12,379
November 2010
|
overhead is a term to describe the excess work that the CPU or any other part of the pc has to do when given a difficult task.for example if you call too many functions then you get an overhead in the call stack.you make too many records of function calls when you could have made less to attain the same goal.another example of overhead is polling.before interrupts where implemented the CPU had to poll the hard disk to see if a transaction had ended thus having to queue many other processes and perform context switches all the time just to make sure the transaction of a data transfer was completed.then interrupts came and then DMA.when a data transfer needs to be done it is approved and started and then DMA manages it until it's finished without having the CPU involved. And virtual functions cause a little overhead because every object uses late binding to determine which function to use,that of the super class or the derived.but i don't think it's such an overhead to even be mentioned.i mean with today's hardware and all the object oriented programs in c++ lying around and very often well known ones at that you can see it doesn't pose a problem.
|
|
Arthur Kalliokoski
Second in Command
February 2005
|
I'd say overhead is best described by the computer doing at runtime with brute force what you were too lazy to do with your brain at program design time. They all watch too much MSNBC... they get ideas. |
|
|
|