Jump to content

Roland

Members
  • Posts

    2,953
  • Joined

  • Last visited

Blog Comments posted by Roland

  1. 5 hours ago, Josh said:

    This is really only meant for business customers who have been asking for this.  I don't recommend indie developers buy this.

    No.  The purpose of this product is to allow companies to install Leadwerks on their networks.

    Aha :D Great. My wallet became really scared and jumped under my table. Now I can tell it that everything is OK :D

  2. 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. 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.

    • Upvote 2
  4. 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)

    lcssample.jpg.903c7d59c66eb7543e35e3e7246ab5eb.jpg

    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 :o 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.

    ed.PNG.2f1b7a23177a99948e6a0487dc7d78e0.PNG

    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.

    lcs1.PNG.fd945ffbf9bd178de727df90a5ae8f8a.PNGL

     Lets have a look at the code for one of our components to see what it looks like

    controller.thumb.PNG.509a17b410340949c64f511445fb86cf.PNG

    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

    lcs2.PNG.86ad986915b451162b817be3be26bcb4.PNG

    In the diagram above you can see the components and the events in action.

    1. User presses W and Input:onForward event is sent to the Controller:doMoveForward action which make the player move forward
       
    2. The Controller raises the onMove event which is received  by the Cam:DoMove action which moves the camera
       
    3. User presses S and Input:onBackward event is sent to the Controller:doMoveBackward action which make the player move forward
       
    4. The Controller raises the onMove event which is received  by the Cam:DoMove action which moves the camera
       
    5. 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
       
    6. 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.
       
    7. 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.
       
    8. The message will end up in the Health component (by magic :) ) and the health will be increased. 
       
    9. 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 

    • Upvote 3
  5. 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 

    • Upvote 1
  6. 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 :)

    • Upvote 3
  7. 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

  8. 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 :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;
    };
    

  9. 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();
       }
    };
    
    
    

  10. 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.

    [[Template blog/front/global/commentTableHeader is throwing an error. This theme may be out of date. Run the support tool in the AdminCP to restore the default theme.]]

    My hair has fallen years ago and now I'm a proud baldhead ... smile.png ... what a gang of unconfident loosers

×
×
  • Create New...