{"id":1228,"date":"2025-09-29T18:34:21","date_gmt":"2025-09-29T18:34:21","guid":{"rendered":"https:\/\/runonur.xyz\/?p=1228"},"modified":"2025-09-29T18:48:29","modified_gmt":"2025-09-29T18:48:29","slug":"game-development-meets-control-theory-implementing-pi-control-in-love","status":"publish","type":"post","link":"https:\/\/runonur.xyz\/index.php\/2025\/09\/29\/game-development-meets-control-theory-implementing-pi-control-in-love\/","title":{"rendered":"Game Development Meets Control Theory: Implementing PI Control in L\u00d6VE"},"content":{"rendered":"\n<p>When developing games, I often look for tools that allow <strong>fast prototyping, simple syntax, and strong community support<\/strong>. That\u2019s why I chose the <strong>Lua programming language<\/strong> together with the <strong>L\u00d6VE framework<\/strong>\u2014a lightweight, open\u2011source 2D game engine. It\u2019s particularly well\u2011suited for experimenting with game mechanics.<\/p>\n\n\n\n<p><strong>L\u00d6VE Framework Basics<\/strong><\/p>\n\n\n\n<p>In L\u00d6VE, the game loop is structured around three main functions:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>love.load() \u2192 runs once at the start, initializing the game.<\/li>\n\n\n\n<li>love.update(dt) \u2192 updates the game state every frame, where dt is the delta time.<\/li>\n\n\n\n<li>love.draw() \u2192 renders the updated state to the screen.<\/li>\n<\/ul>\n\n\n\n<p>This simple structure makes it easy to integrate control algorithms directly into the update cycle.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"356\" height=\"396\" src=\"https:\/\/runonur.xyz\/wp-content\/uploads\/2025\/09\/image-2.png\" alt=\"\u201cScreenshot of a Lua script in L\u00d6VE showing the basic game loop with load, update, and draw functions.\u201d\" class=\"wp-image-1232\" srcset=\"https:\/\/runonur.xyz\/wp-content\/uploads\/2025\/09\/image-2.png 356w, https:\/\/runonur.xyz\/wp-content\/uploads\/2025\/09\/image-2-270x300.png 270w\" sizes=\"auto, (max-width: 356px) 100vw, 356px\" \/><figcaption class=\"wp-element-caption\">\u201cScreenshot of a Lua script in L\u00d6VE showing the basic game loop with load, update, and draw functions.\u201d<\/figcaption><\/figure><\/div>\n\n\n<p>This minimal template is where I later integrated the PI controller logic. Notice the line at the top:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"246\" height=\"29\" src=\"https:\/\/runonur.xyz\/wp-content\/uploads\/2025\/09\/image5.png\" alt=\"\u201cThis disables output buffering, which ensures that print statements appear immediately in the console\u2014very handy for debugging.\" class=\"wp-image-1233\"\/><figcaption class=\"wp-element-caption\">\u201cThis disables output buffering, which ensures that print statements appear immediately in the console\u2014very handy for debugging.<\/figcaption><\/figure>\n\n\n\n<p>One of the classic game types I\u2019ve always been fascinated by is the <strong>\u201cbricks breaker\u201d (or breakout\/arkanoid\u2011style) game<\/strong>. Using this as a foundation, I built a prototype and then decided to take it a step further: I implemented a <strong>PI controller algorithm<\/strong> to automatically control the paddle.<\/p>\n\n\n\n<p><strong>Why PI Control in a Game?<\/strong><\/p>\n\n\n\n<p>In industrial automation, <strong>PID controllers<\/strong> are widely used to regulate systems with feedback loops. They combine three effects:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Proportional (P):<\/strong> reacts to the current error.<\/li>\n\n\n\n<li><strong>Integral (I):<\/strong> accounts for accumulated error over time.<\/li>\n\n\n\n<li><strong>Derivative (D):<\/strong> predicts future error trends.<\/li>\n<\/ul>\n\n\n\n<p>For this project, I focused on the <strong>PI controller<\/strong> (proportional + integral). My interest in PID control goes back to earlier experiments with Arduino boards and smart materials, where I used it for actuator position control. Bringing that concept into a game felt like a fun crossover between engineering and gameplay.<\/p>\n\n\n\n<p><strong>The Game Prototype<\/strong><\/p>\n\n\n\n<p>The layout includes:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Rows of bricks at the top of the screen.<\/li>\n\n\n\n<li>A paddle at the bottom, which is normally player\u2011controlled.<\/li>\n\n\n\n<li>A ball that bounces around the playfield.<\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"768\" height=\"597\" src=\"https:\/\/runonur.xyz\/wp-content\/uploads\/2025\/09\/image2.png\" alt=\"\u201cScreenshot of a breakout style game built with Lua and L\u00d6VE, showing bricks arranged in rows, a paddle at the bottom, and a ball in play.\u201d\" class=\"wp-image-1235\" srcset=\"https:\/\/runonur.xyz\/wp-content\/uploads\/2025\/09\/image2.png 768w, https:\/\/runonur.xyz\/wp-content\/uploads\/2025\/09\/image2-300x233.png 300w\" sizes=\"auto, (max-width: 768px) 100vw, 768px\" \/><figcaption class=\"wp-element-caption\">\u201cScreenshot of a breakout style game built with Lua and L\u00d6VE, showing bricks arranged in rows, a paddle at the bottom, and a ball in play.\u201d<\/figcaption><\/figure>\n\n\n\n<p><strong>Implementing the PI Algorithm<\/strong><\/p>\n\n\n\n<p>The PI controller was applied to the <strong>paddle\u2019s horizontal movement<\/strong>. The algorithm works by:<\/p>\n\n\n\n<ol start=\"1\" class=\"wp-block-list\">\n<li>Calculating the <strong>error<\/strong> between the ball\u2019s x\u2011position and the paddle\u2019s x\u2011position.<\/li>\n\n\n\n<li>Using <strong>Kp<\/strong> (proportional gain) and <strong>Ti<\/strong> (integral time constant) to compute the control signal.<\/li>\n\n\n\n<li>Integrating the error over time with a trapezoidal integration function (trapez_integral).<\/li>\n\n\n\n<li>Updating the paddle\u2019s velocity based on the control output.<\/li>\n<\/ol>\n\n\n\n<p>Instead of manually moving the paddle, the algorithm automatically adjusts its position to follow the ball. The result is a paddle that feels almost \u201cintelligent,\u201d smoothly tracking the ball\u2019s trajectory.<\/p>\n\n\n\n<p>Here\u2019s the function I used to update the paddle\u2019s position automatically. It runs inside the love.update(dt) loop:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"768\" height=\"568\" src=\"https:\/\/runonur.xyz\/wp-content\/uploads\/2025\/09\/img3.png\" alt=\"&quot;Paddle Moving&quot;\" class=\"wp-image-1238\" srcset=\"https:\/\/runonur.xyz\/wp-content\/uploads\/2025\/09\/img3.png 768w, https:\/\/runonur.xyz\/wp-content\/uploads\/2025\/09\/img3-300x222.png 300w\" sizes=\"auto, (max-width: 768px) 100vw, 768px\" \/><figcaption class=\"wp-element-caption\">&#8220;Paddle Moving&#8221;<\/figcaption><\/figure>\n\n\n\n<p><strong>How It Works<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Error calculation:<\/strong> The difference between the ball\u2019s x\u2011position and the paddle\u2019s center.<\/li>\n\n\n\n<li><strong>Integration:<\/strong> Errors are accumulated over time using a trapezoidal method (trapez_integral).<\/li>\n\n\n\n<li><strong>Control law:<\/strong> The paddle\u2019s velocity is computed as a combination of proportional and integral terms.<\/li>\n\n\n\n<li><strong>Update step:<\/strong> The paddle\u2019s position is adjusted each frame, making it smoothly track the ball.<\/li>\n<\/ul>\n\n\n\n<p>By tuning <strong>Kp<\/strong> and <strong>Ti<\/strong>, you can change how aggressively or smoothly the paddle follows the ball.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>A higher <strong>Kp<\/strong> makes it react faster but can overshoot.<\/li>\n\n\n\n<li>A smaller <strong>Ti<\/strong> increases the effect of accumulated error, improving accuracy but risking oscillation.<\/li>\n<\/ul>\n\n\n\n<p>Even with just proportional control, the paddle follows the ball reasonably well. But adding the integral term makes the movement <strong>more precise and stable<\/strong>, reducing steady\u2011state error. Since the PI controller was sufficient, I didn\u2019t include the derivative term.<\/p>\n\n\n\n<p>The resulting equation for paddle velocity is essentially: <\/p>\n\n\n<p>$$<br \/>\nKp \\cdot \\left( e(t) + \\frac{1}{Ti} \\int_{0}^{t} e(t)\\, dt \\right)<br \/>\n$$<\/p>\n\n\n\n<p>where:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Kp = proportional gain<\/li>\n\n\n\n<li>Ti = integral time constant<\/li>\n\n\n\n<li>e(t) = error between ball and paddle positions<\/li>\n<\/ul>\n\n\n\n<p><strong>Numerical Integration with the Trapezoidal Rule<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>To implement the integral part of the PI controller, I used a <strong>trapezoidal integration function<\/strong>. This method approximates the area under the error curve by summing trapezoids between successive time steps. It\u2019s simple, efficient, and works well in real\u2011time applications like games.<\/li>\n<\/ul>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Here\u2019s the Lua function:<\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"749\" height=\"219\" src=\"https:\/\/runonur.xyz\/wp-content\/uploads\/2025\/09\/img4.png\" alt=\"&quot;Integral Code&quot;\" class=\"wp-image-1241\" srcset=\"https:\/\/runonur.xyz\/wp-content\/uploads\/2025\/09\/img4.png 749w, https:\/\/runonur.xyz\/wp-content\/uploads\/2025\/09\/img4-300x88.png 300w\" sizes=\"auto, (max-width: 749px) 100vw, 749px\" \/><figcaption class=\"wp-element-caption\">&#8220;Integral Code&#8221;<\/figcaption><\/figure>\n\n\n\n<p><strong>How It Works<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Inputs:<\/strong>\n<ul class=\"wp-block-list\">\n<li>fonk \u2192 a table of error values over time.<\/li>\n\n\n\n<li>t \u2192 a table of corresponding time values.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Loop:<\/strong> For each time step, it calculates the trapezoid area between two points.<\/li>\n\n\n\n<li><strong>Output:<\/strong> Returns the accumulated integral of the error signal.<\/li>\n<\/ul>\n\n\n\n<p>This integral is then used in the PI control law to adjust the paddle\u2019s velocity, ensuring it doesn\u2019t just react to the ball\u2019s current position but also accounts for <strong>past errors<\/strong>.<\/p>\n\n\n\n<p>The outcome: a paddle that automatically moves toward the ball with smooth, responsive behavior. It was both effective and fun to implement!<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"600\" height=\"367\" src=\"https:\/\/runonur.xyz\/wp-content\/uploads\/2025\/09\/tugla-kirici-2.gif\" alt=\"\u201cLua code implementing a PI controller in L\u00d6VE, showing proportional and integral terms used to automatically move the paddle toward the ball.\u201d\" class=\"wp-image-1242\"\/><figcaption class=\"wp-element-caption\">\u201cLua code implementing a PI controller in L\u00d6VE, showing proportional and integral terms used to automatically move the paddle toward the ball.\u201d<\/figcaption><\/figure>\n\n\n\n<p><strong>Final Thoughts<\/strong><\/p>\n\n\n\n<p>By combining <strong>game development with control theory<\/strong>, I ended up with a paddle that intelligently tracks the ball using a PI controller. It\u2019s a small but exciting example of how engineering concepts can enrich gameplay mechanics. And honestly\u2014it was just plain fun to see control theory come alive in a simple arcade game!<\/p>\n\n\n\n<p><strong>Resources I Used<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>L\u00d6VE Arkanoid Tutorial \u2013 step\u2011by\u2011step guide to building a breakout clone.<\/li>\n\n\n\n<li>Sheepolution\u2019s L\u00d6VE Tutorials \u2013 beginner\u2011friendly learning material.<\/li>\n\n\n\n<li>ZeroBrane Studio \u2013 the Lua IDE I used for development.<\/li>\n\n\n\n<li><em>Automatic Control: System Dynamics and Control Systems<\/em> by Prof. Dr. \u0130brahim Y\u00fcksel \u2013 for deeper theoretical background.<\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><img loading=\"lazy\" decoding=\"async\" width=\"340\" height=\"171\" src=\"https:\/\/runonur.xyz\/wp-content\/uploads\/2023\/02\/cropped-logotest.png\" alt=\"\" class=\"wp-image-1171\" style=\"width:174px;height:auto\" srcset=\"https:\/\/runonur.xyz\/wp-content\/uploads\/2023\/02\/cropped-logotest.png 340w, https:\/\/runonur.xyz\/wp-content\/uploads\/2023\/02\/cropped-logotest-300x151.png 300w\" sizes=\"auto, (max-width: 340px) 100vw, 340px\" \/><figcaption class=\"wp-element-caption\">RUNONUR<\/figcaption><\/figure>\n","protected":false},"excerpt":{"rendered":"<p>When developing games, I often look for tools that allow fast prototyping, simple syntax, and strong community support. That\u2019s why I chose the Lua programming language together with the L\u00d6VE framework\u2014a lightweight, open\u2011source 2D game engine. It\u2019s particularly well\u2011suited for experimenting with game mechanics. L\u00d6VE Framework Basics In L\u00d6VE, the game loop is structured around [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-1228","post","type-post","status-publish","format-standard","hentry","category-game-development"],"_links":{"self":[{"href":"https:\/\/runonur.xyz\/index.php\/wp-json\/wp\/v2\/posts\/1228","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/runonur.xyz\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/runonur.xyz\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/runonur.xyz\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/runonur.xyz\/index.php\/wp-json\/wp\/v2\/comments?post=1228"}],"version-history":[{"count":10,"href":"https:\/\/runonur.xyz\/index.php\/wp-json\/wp\/v2\/posts\/1228\/revisions"}],"predecessor-version":[{"id":1250,"href":"https:\/\/runonur.xyz\/index.php\/wp-json\/wp\/v2\/posts\/1228\/revisions\/1250"}],"wp:attachment":[{"href":"https:\/\/runonur.xyz\/index.php\/wp-json\/wp\/v2\/media?parent=1228"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/runonur.xyz\/index.php\/wp-json\/wp\/v2\/categories?post=1228"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/runonur.xyz\/index.php\/wp-json\/wp\/v2\/tags?post=1228"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}