-
Posts
2,953 -
Joined
-
Last visited
Content Type
Blogs
Forums
Store
Gallery
Videos
Blog Comments posted by Roland
-
-
Announcing Leadwerks Game Engine Enterprise Edition
in Ultra Software Company Blog
A group blog by The Ultra Software Team in General
No upgrade prices ?
-
Say Hello to Leadwerks 5 Shared Objects
in Ultra Software Company Blog
A group blog by The Ultra Software Team in General
I like what I see coming
-
3 Ways Leadwerks Game Engine 5 Makes Game Programming Easier
in Ultra Software Company Blog
A group blog by The Ultra Software Team in General
2 hours ago, thehankinator said:Ranged based for loops were added to the C++ standard in c++11, not a MSVC only thing. I've not seen the exact syntax that Roland used but this is how I do it in MSVC 2015 and GCC.
for (auto&& entity : list) { entity->SetPosition(1,2,3); }
( auto&& ensures you get a reference and no copies are made)
http://en.cppreference.com/w/cpp/language/range-for
https://stackoverflow.com/questions/26991393/what-does-auto-e-do-in-range-based-for-loops
Yes that is even better. Will change my loops to that
-
3 Ways Leadwerks Game Engine 5 Makes Game Programming Easier
in Ultra Software Company Blog
A group blog by The Ultra Software Team in General
Looks great
This one could be even more simple
for each( auto entity in list ) { entity->SetPosition(1,2,3); }
instead of
for (auto it = list.begin(); it!= list.end(); it++) { auto entity = *it; entity->SetPosition(1,2,3); }
-
21 minutes ago, mdgunn said:
I'd be interested too!
Great. All you who are interested just tell here
- 1
-
I'm going away for a roadtrip tomorrow and won't be back for three weeks. First thing when I'm back is to finish the LCS design editor and then start making some docs and tutorials. After that we will need some beta testers (you have already showed an interest in that). Anyone else is welcome. Will probably be in end of August if God and Thor is on our side. All this will be free when released.
- 2
-
So here is a small example of a game with a first person player with a camera. You can move the player forwards, backwards and turn him around. The camera will detect any items in front of the cursor within a limited range. If you left click on such an item when its detected it will go away, add health to the player and update a health meter showing current health.
Here is a screenshot of the example (here with two different kinds of items to pick and two meters, but besides that its the same)
Instead of diving into coding the player with all its operations to handle we won't in fact make any player coding. Instead we will divide things into components with their own responsibilities. The components is hold together by a runtime object created by LCS and is nothing you don't need to care about. So what is a component then. Its a LUA class that has Events and Actions and is completely isolated and independent of other components. This means it can easily be replaced with another component that behaves in another way. It can also without any changes be used in other projects.
So how would we go about this then. First of all we have some entity's in our scene.
- Health items to pick up (the boxes)
- Player (a simple invisible cylinder) including a FPS camera
- HealthMeter ( GUI showing current health)
Normally we the would have started making Scripts for those items above. But were not this time In fact none of the Entities in the scene will have a Script. Instead we are going to write Component scripts that can be used here or in some other game.
So what components could our player need then? That's up you depending on how you want to design things. Here is a suggested approach by me.
- Input component that handles user input
- Controller component that handles movement of the player and camera
- Health component that keeps track of current health
- Cam component which is the FPS camera
Further on the Health item needs a HealthItem component that reacts to a pickup and a HealtMeter component that serve as a GUI for current health. You don't need to write any Script code for the Entities in the Scene, only component needs some action by you. Each entity will be connected to a GameObject (which is generated in runtime and does not need any coding). This GameObject ties the components together and handles all things to get the communication going. The system is defined by supplying a setup written in JSON format. However you wont need to do that either as I'm currently working on a design application for setting things up and that will be available when we release the system.
Anyway.. so now when we know what components we need, its time to think a bit on what they should to and how they will interact with the surroundings. So lets get started. I won't go through every component but I think you get the idea
Input
Should handle input from the user and generate events accordingly. An event can be subscribed to by any other component and sends some information about the event that happened. All events starts with 'on'. A subscriber of an event must have an action that will be triggered by the event. All actions starts with 'do'.
Events
- onForward is sent when user presses 'W'
- onBackward is sent when user presses 'S'
- onClick is sent when user clicks left mouse button
Controller
The controller is responsible for moving the player
Events
- onMove is sent when the player moves (contains information about current position)
Actions
- doMoveForward moves the player forward
- doMoveBackward moves the player backward
- doTurn turns the player around its Y-axis (needs an angle)
Cam
Is simply the camera which is rotated by moving the mouse.
Events
onPick is sent if the doClick action is called when an pickable entity is in front of the camera. The entity is sent with the event.
onTurn is sent when the user rotates the camera by moving the mouse
Actions
doMove moves the camera to a given position
Here is a drawing showing our system now. The yellow parts are entities in the scene (map), the red ones are GameObjects created in runtime holding the components and finally the blue ones are the components which we have to write code for.
L
Lets have a look at the code for one of our components to see what it looks like
The Controller class must have following functions to be treated as a controller by the LCS system
- init called by LCS to create the controller
- attach called by LCS when its attached to its entity and GameObject
Besides those two, a component can (optionally) have following functions called in the same way as in normal Scripts. If one or more of those exist they are called automatically.
- update called on UpdateWorld
- updatePhysics called on UpdatePhysics
- draw called on Draw
- drawEach called on DrawEach
- postRender called on PostRender
- detach called on Detach
In the init function you can see how an Event is created. That is all needed to create an Event.
self.onMove = EventManager:create()
If you then look in the update function you can see how the event is sent by calling 'raise'
self.onMove:raise({Pos=newpos})
This event a contains a position. All event information are sent as tables. In this case a table member Pos set to the current position.
Further there are some actions that can respond to events from outside world. Lets look at one of the actions
function Controller:doMoveForward() self.move = self.moveSpeed end
The action is just a normal function that may or may not have an argument. It does not need to return anything. One exiting thing is that all Actions are automatically executed as coroutines which has really cool advantages. I wont go into that here but I thought its worth mentioning.
Here is a code snippet from the Cam component showing an action that can handle an onMove event that has an argument.
function Cam:doMove(args) self.camera:SetPosition(args.Pos,true) end
The args is the table sent with the event ( remember self.onMove:raise( {Pos=newpos} ) ). The args table will contain the member given when the event was raised.
Okay. Lets see how all this works then
In the diagram above you can see the components and the events in action.
-
User presses W and Input:onForward event is sent to the Controller:doMoveForward action which make the player move forward
-
The Controller raises the onMove event which is received by the Cam:DoMove action which moves the camera
-
User presses S and Input:onBackward event is sent to the Controller:doMoveBackward action which make the player move forward
-
The Controller raises the onMove event which is received by the Cam:DoMove action which moves the camera
-
User moves mouse left and the Cam:onTurn event is raised giving the angle of rotation the movement caused. This event is caught by the Controller:doTurn action which rotates the player in the same angle
-
User left clicks the mouse which raises the onClick event which is caught by the Cam:doClick action. If the camera now has an item directly in focus it raises an onPick event which then is caught by the entity's component HealtItem:doPicked.
-
In doPicked the HealthItem will hide it self and send a message back to the player telling that it needs to 'add.health' among with the amount of health.
-
The message will end up in the Health component (by magic ) and the health will be increased.
- As the health has changed the Health:onHealthChange event will be raised and is caught by the HealthMeter:doSetHealth action which will show the new health
The communication between Events and Actions is set as hookups. A hookup means that a component subscribes to an Event. Take our onMove/doMove example above. Setting up a hookup for this is simple
self.Controller.onMove:subscribe( self.Cam, self.Cam.doMove )
But that's done for you by the LCS system in the runtime GameComponent holding the Components for the player. How, you may wonder? This is done by creating a LCS project file (in JSON format) defining the GameObjects, Components and Hookups.
Going into this in detail goes a bit out of scope for this 'short' description, but I can show a cut from that file
{ "source": "Controller", "source_event": "Move", "destination": "Cam", "destination_action": "Move", "arguments": "", "filter": "", "post": "" }
This creates the hookup between the Controller and the Cam. This is only a simple example but you can add functions right here also for adding more arguments, making event filters and more.
Code creation. One nice feature is when adding a new component by defining it in the LCS project file, LCS will create the LUA file for the component with everything added. The only thing you will need is to add some code inside the Action functions.
As a last thing I will show how easy it is to take advantage of the actions which are coroutines (by magic). Here is how I raise the health level in the HealthMeter to be smooth
function HealthMeter:doSetHealth(args) local value = 1 if args.Health < self.health then value = -1 end local goal = Math:Round(Math:Clamp(args.Health, 0, self.max) * self.valueSize) while self.pixelValue ~= goal do self.pixelValue = self.pixelValue + value coroutine.yield() -- give system some time end self.power = args.Health -- done! end
Hope this have given you some more insight into the LCS system.
PS: Some details has been left out here to increase readability- 3
-
Fog in Leadwerks 4.4
in Ultra Software Company Blog
A group blog by The Ultra Software Team in General
Really wonderful to see.
-
1 hour ago, AggrorJorn said:
Very cool stuff Rick and Roland. I would also love to see more enthusiasm on such a communication framework. I think adding a basic diagram on the flow of the message and the structures it passed through, for instance a character with a health component, would aid in explaining how your solution works.
I can make one. Give me some hours
An FPS player that can pick health items and have a health meter
- 1
-
Shader Enhancements in Leadwerks 4.4
in Ultra Software Company Blog
A group blog by The Ultra Software Team in General
Great improvement and finally fog. Thanks
-
We integrated GameAnalytics.com into Leadwerks; You won't believe the shocking results
in Ultra Software Company Blog
A group blog by The Ultra Software Team in General
On 2017-06-09 at 8:43 PM, Josh said:There might be a way.
The Leadwerks 5 editor will be all C++. One reason I want to do this is to remove any possible ambiguity in the debugging. Once a month or so I get a mystery crash in the editor.
+ it can't handle coroutines
- 1
-
Website refresh and Leadwerks 5
in Ultra Software Company Blog
A group blog by The Ultra Software Team in General
Thanks for the update and I just say GO GO GO
-
Looks great and finally a descent code presentation
-
Error in C++ - ElseIf
int n = 2; if (n == 1) { Print("n equals 1"); } elseif(n == 2) { Print("n equals 2"); } elseif(n == 3) { Print("n equals 3"); }
should be
int n = 2; if (n == 1) { Print("n equals 1"); } else if(n == 2) { Print("n equals 2"); } else if(n == 3) { Print("n equals 3"); }
-
Looking forward to that Aggror. I also missed that component post that you mentioned. Really great stuff
-
This is the most interesting blog post I have read in years. I really get so many ideas on how to make my design better by reading this. I here by declare Rick my hero of the month if not the year. As some of you know I have been programming for many many years and because of that I have got stuck in old habits (like my player above) without even thinking if there's a better way to do it. Of course the component concept has come in my way now and then but in a more abstract and non-game format. Now Rick's words on this subject woke my interest as I know he's a clever guy and it was presented in a more hands-on format. I will absolutely adopt to this starting immediately and if I get drunk Rick is responsible
- 3
-
Yes. Thanks for the advice. I'm aware that events have a small time-penalty
-
Thanks Rick. You have really opened my eyes to this new concept. Also many thanks for taking the time to more or less type down a mini tutorial. Now with this new concept in mind I will start to break the player into components. I will keep you guys informed on how that goes, either here or in my blog. Reading this blog post by you Rick almost gave me religious experience, so thanks again
-
Aaah... Good questions Rick. Now I understand where you are going. Very very nice approach. To answer you question about the mouse events (and all other events) I can confirm that I'm an idiot :D. As its now events are caught by the player and used for controlling other components. That might be needed in some rare cases, but what you are saying is of course a much much better way of doing it. Lets say the MouseInput send events about mouse movement (onmousemove..). Those events could as an example be used to control the camera rotation. So instead of catching the mouse movement event in the Player code and rotate the camera (or call some Camera-object method), its better to have a Camera-Object that catches the MouseEvents directly. The player does not have to be involved in this. Am I on the right track here Rick??
Regarding my Player its like you Player was before doing the cleanup you are describing... which means some 1000 lines of code. To much to describe here and of course a BIG hint that I also have to make some cleanup by using Components. Thanks for you interesting view on this. I really appreciated it.
My player .cpp is far to big and complex to share here. However I can show the header file and as you see its to complex now. You can see the events handlers for various events (the methods starting with on.....). I will go through this as see what I can come up with
class Player : public GameItem, public GameTimerClient, public ToolBarListener, public GameActorClient, public AreaClient, public PickDetectorClient, public CollisionDetectorClient, public CarryListener { const float WalkSpeed = 1.5; const float MaxWalkDistace = 6.0; const float WalkDecay = 0.1; const float MouseSense = 15.0; const float MinPitch = -60.0; const float MaxPitch = 60.0; const float PitchSmooth = 4.0; const float PitchSpeed = 20.0; const float Turnsmooth = 4.0; const int HealthSpeed = 2; const int HealthAdd = 2; const int HealthDec = 1; const float DoubleClickMsec = 400; const float JumpForce = 8; const int StartDelay = 4; const float CameraFOV = 70; const float CameraMinRange = 0.5; const float CameraMaxRange = 500; const float EyeHeight = 1.8; const int MaxCarryWeight = 20; const float CarryDrop = 2; const float CarryMaxDiff = 0.5; // --- LUA UserInterface* _userinterface; std::string _pick_decal_material; std::string _pick_decal_red_material; // --- INTERNAL bool _is_started; long _startdelay; bool _firstTime; Leadwerks::Vec3 _start_pos; Leadwerks::Font* _bigFont; bool _wait_for_left_mouse_release; Leadwerks::Listener* _soundlistener; Leadwerks::Sound* _music; Leadwerks::Source* _soundsource; GameTimer _timer; Leadwerks::Entity* _pointed_at; Leadwerks::Entity* _socket; // --- Health int _cur_health; long _healthTimer; std::vector<int> _healthDec; // --- Messages to user MessageWindow _msgwin; // --- Health & Toolbar HealthBar _healthbar; ToolBar _toolbar; // --- Cursor Cursor _cursor; // --- Picking items PickDetector _pick_detector; // --- Colliding with things CollisionDetector _collision_detector; // --- Carry things GameItem* _carriedItem; Leadwerks::Vec3 _carryrotation; Leadwerks::Vec3 _carryposition; int _carry_collsion; float _lastMZ; void grabItem(GameItem* gi); void carryItem(); void dropItem(); // --- Tool Leadwerks::Pivot* _toolhandle; // --- FlashLight FlashLight _flashlight; // --- Camera Leadwerks::Camera* _camera; Leadwerks::Vec3 _camera_rot; float _cameraPitch; Leadwerks::Vec2 _mousediff; void _on_started(); void _pick_detection(); void _triggerhandler(const Leadwerks::PickInfo& pickinfo); void _checkpointhandler(const Leadwerks::PickInfo& pickinfo); // Mouse double click bool _leftclicked; long _lastclick; // Walking float _move; float _walkdistance; bool _walking; Leadwerks::Vec3 _walkstart; // Screen size int _scx; int _scy; void updateCam(); void updateMove(); void die(); protected: void onCollision(Leadwerks::Entity* e, const Leadwerks::Vec3& pos, const Leadwerks::Vec3& normal, float speed); void onUpdateWorld(); void onUpdatePhysics() {} void onPostRender(Leadwerks::Context* ct); public: Player(Leadwerks::Entity* entity); ~Player(); // --- GameItem void init(LuaBridge& lb); void onRestore(); void onactivated(); // --- GameTimerClient void onMsec100(); void onSec(); void onMinute(); // --- ToolbarClient void onToolbarFull(ToolBar* tb); void onToolbarAdded(ToolBar* tb, Tool* t); void onToolbarSelected(ToolBar* tb, Tool* t); void onToolbarUsing(ToolBar* tb, Tool* t); void onToolbarShown(ToolBar* tb); void onToolbarHidden(ToolBar* tb); void onToolbarSelectChange(ToolBar* tb, Tool* t); // --- HealthClient void onZeroHealth(Health* h); void onWarnHealth(Health* h); void onOkHealth(Health* h); void onFullHealth(Health* h); // --- GameActorClient void onActorAtStart(GameActor* a); void onActorAtEnd(GameActor* a); void onActorDone(GameActor* a); void onActorPicked(GameActor* a); // --- AreaClient void onAreaEnter(Area* a); void onAreaLeave(Area* a); // --- CarryClient void onCarryGrabbed(Carry* item); void onCarryDropped(Carry* item); // --- PickDetectorClient bool onPickedHealth(Health* item); bool onPickedTool(Tool* item); bool onPickedCarry(Carry* item); bool onPickedEnergy(ToolEnergy* item); bool onPickedActor(GameActor* item); bool onPointingAt(GameItem* ent); // --- CollisionDetectorClient bool onCollidedHealth(Health* item); bool onCollidedTool(Tool* item); bool onCollidedCarry(Carry* item); bool onCollidedEnergy(ToolEnergy* item); bool onCollidedActor(GameActor* item); bool onCollidedArea(Area* item); // Inherited via CarryListener virtual void onCarryCollision(Carry * item, const Leadwerks::Vec3 & pos) override; };
-
I'm probably missing something. You have a component that can act upon its own data by methods called from the outside world. The component can also send events to the outside world (the user of the component). How is this done? Either by callbacks or listeners that have a method that can handle (act on) such an event. Is there a third option? That sounds interesting. I'm using a combination of LUA and C++. The event-components are in C++ and uses inheritance, goes something like this (a simple fictional sample to illustrate how I'm doing it). Is there a better way to do this I'm certainly willing to adapt that into my code.
// inherits this to be a listener for MouseInput events class MouseEventListener { public: // called when use left clicks mouse virtual void onMouseLeftClick() = 0; // called when use right clicks mouse virtual void onMouseRightClick() = 0; }; // A mouse input component class MouseInput { vector<MouseEventListener*> _listeners; MouseInput() {} // register a new listener void register( MouseEventListener* listener ) { _listeners.push_back(listener); } // unregister a listener void unregister( MouseEventListener* listener ) { remove( _listeners.begin(), _listeners.end(), listener ); } // must be called frequently void update() { if( Window::GetCurrent()->MouseHit(0) ) { for each ( auto listener in _listeners ) { listener->onMouseLeftClick(); } } if( Window::GetCurrent()->MouseHit(1) ) { for each ( auto listener in _listeners ) { listener->onMouseRightClick(); } } } // reset the mouse void reset() { Window::GetCurrent()->FlushMouse(); } }; class Player: public MouseEventListener { // Mouse input component MouseInput _mouse; public: // MouseEventListener void onMouseLeftClick() { // handle left mouse clicks } void onMouseRightClick() { // handle right mouse clicks } // Player void init() { // start listening to mouse // you can stop doing this by // _mouse.unregister(this) _mouse.register(this); } void update() { _mouse.update(); } };
-
Very interesting reading. I really like your idea, especially with the events. Im using a similar approach by having some classes act as every generators where classes can register them self as event listeners. The listeners then inherits abstract event interfaces with methods like 'onMouseClick' ... If understood your system correctly we are more or less having the same approach.
-
My hair has fallen years ago and now I'm a proud baldhead ... ... what a gang of unconfident loosers
-
Rick said it
- 1
-
I use GIT although I'm alone. Perfect backup and great when I need to step back one bit and look what I did last time that caused some problem.
Announcing Leadwerks Game Engine Enterprise Edition
in Ultra Software Company Blog
A group blog by The Ultra Software Team in General
Posted
Aha Great. My wallet became really scared and jumped under my table. Now I can tell it that everything is OK