Programación gráfica para principiantes (Parte I): Aprendiendo CCanvas con Crazy Scalper
Introducción: El poder de aprender jugando
Usted ya programa EAs e indicadores en MQL5, pero al llegar a la parte gráfica con CCanvas suele aparecer el “pantallazo negro”: coordenadas contraintuitivas (Y aumenta hacia abajo), dudas sobre cómo crear un lienzo transparente ARGB, cómo actualizar la pantalla sin depender de ticks y cómo captar eventos de teclado. Sin esos conocimientos, las interfaces quedan estáticas, con parpadeos o con lógica dispersa de flags y timers.
En este artículo abordamos exactamente ese problema: construiremos un proyecto interactivo mínimo —Crazy Scalper— que se ejecuta sobre el gráfico de MetaTrader 5 y sirve como plantilla práctica. Aprenderá a crear correctamente la capa gráfica (CreateBitmapLabel con formato ARGB), a montar un bucle de actualización estable (EventSetMillisecondTimer), a dibujar con geometría vectorial, a implementar física básica y colisiones, y a procesar eventos de teclado. El objetivo es que, una vez completado, disponga de un armazón reutilizable para añadir paneles y elementos interactivos en sus propios EA.

El Paradigma del Lienzo: Entendiendo el Sistema de Coordenadas
Antes de escribir código debemos entender cómo "piensa" una pantalla de ordenador. Como traders estamos acostumbrados a los gráficos financieros: si el precio sube, la coordenada vertical Y aumenta. Sin embargo, en la programación gráfica tradicional y en CCanvas, las reglas cambian.
Imagine que su monitor es una hoja de papel cuadriculado. En CCanvas, el punto de origen —es decir, la coordenada (0,0)— no está abajo, sino en la esquina superior izquierda de la pantalla.
- A medida que nos movemos hacia la derecha, el valor de X aumenta.
- A medida que nos movemos hacia abajo, el valor de Y aumenta.
Este concepto es vital. Más adelante, cuando programemos la gravedad del cohete para que caiga, lo que haremos será sumar valores a su coordenada Y. Y para que vuele hacia arriba (un salto) vamos a restarle valores a su coordenada Y.
Preparando el Lienzo: El Cristal Transparente y el Canal Alpha
Para empezar a dibujar necesitamos crear el espacio de trabajo, en MQL5, primero incluimos la librería principal con #include <Canvas\Canvas.mqh> y creamos una instancia global llamada canvas.
El primer paso lógico dentro de nuestra función OnInit() es ocultar las típicas velas japonesas y la cuadrícula del terminal para dejar la pantalla totalmente limpia. Esto lo logramos desactivando la propiedad del gráfico mediante ChartSetInteger(0, CHART_SHOW, false). Ahora con el fondo limpio, obtenemos el ancho y alto actuales de la ventana del usuario usando píxeles.
Entonces viene el momento crítico: la creación del lienzo mediante la función CreateBitmapLabel. Piense en esta función como si estuviéramos colocando un cristal encima del gráfico de MetaTradrr 5, y sobre este cristal vamos a pintar.
Uno de los secretos para lograr gráficos modernos e integrados es el último parámetro de esta función, el formato de color. La mayoría de desarrolladores utilizan formatos de color opacos por defecto. Nosotros utilizaremos COLOR_FORMAT_ARGB_NORMALIZE.
¿Qué significa ARGB? Son las siglas de Alpha, Red, Green, Blue (Alpha, rojo, verde, azul). El canal Alpha es el encargado de la opacidad, y se mide con valores del 0 al 255:
- Un Alpha de 255 es un color totalmente sólido.
- Un Alpha de 0 significa que el color es totalmente invisible.
- Un Alpha intermedio (ej. 150) nos permite crear sombras, marcas de agua y en nuestro caso el fuego translúcido del propulsor de nuestro cohete.
Veamos cómo se traduce todo esto en el código de inicialización de nuestro proyecto:
//--- Include the standard MQL5 library for rendering graphics on the screen #include <Canvas\Canvas.mqh> //+------------------------------------------------------------------+ //| Global Variables for the Graphical Engine | //+------------------------------------------------------------------+ CCanvas canvas; // The main object that will act as our drawing canvas int g_chartWidth; // Will store the actual chart window width in pixels int g_chartHeight; // Will store the actual chart window height in pixels bool g_isInitialized = false; // Security flag to prevent timer execution before initialization //+------------------------------------------------------------------+ //| Game State Machine | //+------------------------------------------------------------------+ enum ENUM_GAME_STATE { START_SCREEN, // Initial screen waiting for user input PLAYING, // Active gameplay state GAME_OVER // Liquidation screen (Margin Call) }; ENUM_GAME_STATE g_gameState = START_SCREEN; //+------------------------------------------------------------------+ //| Physics and Movement Variables | //+------------------------------------------------------------------+ double g_playerY; // Current vertical position of the rocket double g_playerX; // Current horizontal position of the rocket double g_velocityY; // Current falling speed const double GRAVITY = 0.6; // Constant downward force applied every frame const double JUMP_STRENGTH = -9.5; // Upward force (negative Y) applied on key press int g_score = 0; // Current game score (Profit) int g_bestScore = 0; // All-Time High score //+------------------------------------------------------------------+ //| Obstacles (Japanese Candles) Structure and Variables | //+------------------------------------------------------------------+ struct CandlePipe { double x; // Horizontal position of the candle obstacle double gap_y; // Vertical center of the safe gap for the rocket bool passed; // Flag to avoid counting the same obstacle twice }; CandlePipe g_pipes[3]; // Array to hold a maximum of 3 obstacles on screen double g_pipeSpeed; // Current moving speed of the obstacles int g_pipeGap; // Vertical space between the upper and lower candles int g_pipeSpacing; // Horizontal distance between one obstacle and the next //--- Base difficulty constants const double BASE_PIPE_SPEED = 6.0; // Starting speed const int BASE_PIPE_GAP = 220; // Starting vertical gap const int BASE_PIPE_SPACING = 450; // Starting horizontal distance const int PIPE_WIDTH = 45; // Fixed width of the candlestick obstacles //--- Variable to animate the background grid (Parallax effect) double g_gridOffsetX = 0.0; //+------------------------------------------------------------------+ //| Cyberpunk Color Palette using Alpha Channel (ARGB) | //+------------------------------------------------------------------+ uint CLR_BG = ColorToARGB(C'15,17,20', 255); // Dark solid background (Alpha 255) uint CLR_GRID = ColorToARGB(C'25,28,32', 255); // Grid lines color uint CLR_ACCENT = ColorToARGB(C'0,240,240', 255); // Neon Cyan for details uint CLR_BULL = ColorToARGB(C'40,167,69', 255); // Institutional Green (Bull candle) uint CLR_BEAR = ColorToARGB(C'242,54,69', 255); // Institutional Red (Bear candle) uint CLR_ROCKET = ColorToARGB(C'255,160,0', 255); // Orange/Gold for the rocket body uint CLR_FIRECORE = ColorToARGB(C'255,255,100', 255); // Yellow for the engine fire //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Initialize the random number generator using the system tick count MathSrand(GetTickCount()); //--- Hide the standard MT5 candlestick chart to provide a clean visual space ChartSetInteger(0, CHART_SHOW, false); //--- Get the actual screen dimensions in pixels from the user's terminal g_chartWidth = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); g_chartHeight = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); //--- Create the canvas using COLOR_FORMAT_ARGB_NORMALIZE to support true Alpha transparency if(!canvas.CreateBitmapLabel(0, 0, "FlappyScreen", 0, 0, g_chartWidth, g_chartHeight, COLOR_FORMAT_ARGB_NORMALIZE)) { return(INIT_FAILED); } //--- Setup initial game variables ResetGame(); //--- Set a high-frequency timer to 12 milliseconds (approx. 83 Frames Per Second) EventSetMillisecondTimer(12); g_isInitialized = true; return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Stop the game loop EventKillTimer(); //--- Destroy the canvas to free up RAM canvas.Destroy(); //--- Restore the standard MT5 chart view ChartSetInteger(0, CHART_SHOW, true); } //+------------------------------------------------------------------+ //| Reset the game to initial state | //+------------------------------------------------------------------+ void ResetGame() { //--- Position the rocket vertically in the center and horizontally to the left g_playerX = g_chartWidth * 0.2; g_playerY = g_chartHeight / 2.0; g_velocityY = 0.0; g_score = 0; //--- Restore base difficulty parameters g_pipeSpeed = BASE_PIPE_SPEED; g_pipeGap = BASE_PIPE_GAP; g_pipeSpacing = BASE_PIPE_SPACING; //--- Generate the first 3 obstacles outside the right edge of the screen for(int i = 0; i < 3; i++) { g_pipes[i].x = g_chartWidth + (i * g_pipeSpacing); g_pipes[i].gap_y = (double)(MathRand() % (g_chartHeight - 300) + 150); g_pipes[i].passed = false; } }
La Máquina de Estados (State Machine)
Observe cómo en el código superior hemos declarado un enum llamado ENUM_GAME_STATE. Los programadores novatos suelen utilizar múltiples variables booleanas para controlar lo que ocurre en la pantalla. Esto genera errores lógicos muchas veces.
Al usar un enum creamos una máquina de estados finitos. Nuestro juego solo puede existir en uno de los tres estados: en la pantalla de inicio, en medio de una partida o en la pantalla de liquidación. Esto mantendrá nuestro código ordenado a medida que avancemos.
El Latido del Juego: El Bucle Principal (Game Loop)
En el trading algorítmico, los EAs suelen reaccionar a un nuevo tick (OnTick) o a la formación de una nueva vela. Sin embargo, un videojuego o un panel gráfico dinámico no puede esperar a que el mercado se mueva. Necesita actualizarse constantemente, incluso los fines de semana cuando el mercado está cerrado.
Para solucionar esto, en nuestra función de inicialización usamos EventSetMillisecondTimer(12). Esto le dice a MetaTrader 5: ejecuta la función OnTimer() cada 12 milisegundos. Si dividimos 1 segundo entre 12, obtenemos aproximadamente 83 fotogramas por segundo (FPS). Este es el latido de nuestro juego.
Dentro de OnTimer(), ocurre la magia. Todo motor gráfico (o la mayoría) sigue un ciclo de tres pasos en una fracción de segundo:
- Borrar: Se limpia el lienzo. Si no borráramos, los dibujos se amontonarían dejando una estela infinita en la pantalla.
- Calcular y dibujar: Evaluamos en qué estado de nuestra máquina de estados estamos (Inicio, Jugando, Game Over) y actualizamos las posiciones matemáticas de los objetos para luego dibujarlos.
- Actualizar pantalla: Finalmente llamamos a canvas.Update(). Esta es la orden que le dice a MetaTrader 5 que tome nuestro lienzo interno y lo muestre físicamente en el monitor del usuario.
//+------------------------------------------------------------------+ //| Timer function (Main Game Loop) | //+------------------------------------------------------------------+ void OnTimer() { //--- Prevent execution if initialization failed if(!g_isInitialized) { return; } //--- Check if the user resized the MT5 window int curr_w = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); int curr_h = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); if(curr_w != g_chartWidth || curr_h != g_chartHeight) { g_chartWidth = curr_w; g_chartHeight = curr_h; canvas.Resize(g_chartWidth, g_chartHeight); } //--- STEP 1: CLEAR THE SCREEN (Erase the previous frame to avoid infinite trails) canvas.Erase(CLR_BG); //--- Calculate the Parallax background offset if(g_gameState == PLAYING) { g_gridOffsetX -= (g_pipeSpeed * 0.5); // Move background slower than obstacles if(g_gridOffsetX <= -40.0) { g_gridOffsetX += 40.0; // Infinite loop reset } } //--- Draw the dynamic background grid for(int x = (int)g_gridOffsetX; x < g_chartWidth + 40; x += 40) { canvas.LineVertical(x, 0, g_chartHeight, CLR_GRID); } for(int y = 0; y < g_chartHeight; y += 40) { canvas.LineHorizontal(0, g_chartWidth, y, CLR_GRID); } //--- STEP 2: CALCULATE AND DRAW BASED ON CURRENT STATE if(g_gameState == START_SCREEN) { DrawStartScreen(); } else if(g_gameState == PLAYING) { UpdatePhysics(); DrawPipes(); DrawRocket(); DrawScore(); } else if(g_gameState == GAME_OVER) { DrawPipes(); DrawRocket(); DrawGameOver(); } //--- STEP 3: UPDATE DISPLAY (Send the rendered graphics to the monitor) canvas.Update(); }
Pintando con Matemáticas: Geometría Vectorial
Uno de los mayores obstáculos para los principiantes es pensar que para dibujar un cohete necesitan saber diseño gráfico, crear un archivo .png en Photoshop y cargarlo en MetaTrader. Aunque CCanvas permite cargar imágenes, nosotros vamos a usar Geometría Vectorial.
Esto significa que vamos a construir nuestros personajes y obstáculos uniendo figuras geométricas básicas mediante código. Las ventajas son inmensas: el código es más ligero, se escala perfectamente a cualquier tamaño de la pantalla sin pixelarse, y no necesitamos adjuntar archivos extra.
Vamos a desglosar cómo creamos nuestro cohete (la función DrawRocket). Nuestro cohete no es más que la suma de:
- Un rectángulo central que hace de fuselaje, dibujado con canvas.FillRectangle(). Le pasamos las coordenadas de la esquina superior izquierda y la inferior derecha.
- Un triángulo para la punta (el morro del cohete), creado con canvas.FillTriangle(). Esta función requiere tres puntos (X e Y para cada una de las 3 esquinas del triángulo).
- Dos triángulos adicionales más pequeños que actúan como alerones superior e inferior.
- Un círculo dibujado con canvas.FillCircle() en el centro del rectángulo para simular la ventana o escotilla.
Lo mejor de este enfoque es que todas las coordenadas de estos dibujos se calculan en base a player_x y player_y (la posición central del jugador). Así, si el jugador cae por la gravedad, el cohete entero se dibuja automáticamente más abajo en el siguiente milisegundo.
//+------------------------------------------------------------------+ //| Vector Engine: Draw the Rocket | //+------------------------------------------------------------------+ void DrawRocket() { int px = (int)g_playerX; int py = (int)g_playerY; //--- 1. Draw dynamic engine fire if the rocket is jumping (negative velocity) if(g_velocityY < 0.0) { int fire_len = (MathRand() % 15) + 15; uchar flicker_alpha = (uchar)((MathRand() % 100) + 100); uint clrFireOuter = ColorToARGB(C'255,80,30', flicker_alpha); //--- Transparent outer flame (Aura) canvas.FillTriangle(px - 10, py - 8, px - 10, py + 8, px - 10 - fire_len - 5, py, clrFireOuter); //--- Solid inner core flame canvas.FillTriangle(px - 10, py - 4, px - 10, py + 4, px - 10 - fire_len, py, CLR_FIRECORE); } //--- 2. Draw the main fuselage using basic geometry canvas.FillRectangle(px - 10, py - 6, px + 8, py + 6, CLR_ROCKET); canvas.FillTriangle(px + 8, py - 6, px + 8, py + 6, px + 18, py, CLR_ACCENT); //--- 3. Draw upper and lower aerodynamic fins canvas.FillTriangle(px - 10, py - 6, px - 2, py - 6, px - 10, py - 12, CLR_ACCENT); canvas.FillTriangle(px - 10, py + 6, px - 2, py + 6, px - 10, py + 12, CLR_ACCENT); //--- 4. Draw the cockpit window canvas.FillCircle(px, py, 3, CLR_BG); } //+------------------------------------------------------------------+ //| Vector Engine: Draw the Candles (Obstacles) | //+------------------------------------------------------------------+ void DrawPipes() { for(int i = 0; i < 3; i++) { int px = (int)g_pipes[i].x; int gap_top = (int)(g_pipes[i].gap_y - g_pipeGap / 2.0); int gap_bot = (int)(g_pipes[i].gap_y + g_pipeGap / 2.0); //--- BEAR CANDLE (Dropping from the ceiling) canvas.FillRectangle(px, 0, px + PIPE_WIDTH, gap_top, CLR_BEAR); canvas.LineVertical(px + PIPE_WIDTH / 2, gap_top, gap_top + 20, ColorToARGB(C'150,150,150', 255)); canvas.Rectangle(px, 0, px + PIPE_WIDTH, gap_top, ColorToARGB(clrBlack, 255)); //--- BULL CANDLE (Rising from the floor) canvas.FillRectangle(px, gap_bot, px + PIPE_WIDTH, g_chartHeight, CLR_BULL); canvas.LineVertical(px + PIPE_WIDTH / 2, gap_bot - 20, gap_bot, ColorToARGB(C'150,150,150', 255)); canvas.Rectangle(px, gap_bot, px + PIPE_WIDTH, g_chartHeight, ColorToARGB(clrBlack, 255)); } }
Las Leyes de la Física: Gravedad en Código
En el mundo real, si usted deja caer una pelota, esta no baja a una velocidad constante. Comienza a caer lentamente y acelera a medida que se acerca al suelo. Para que nuestro juego se sienta natural y fluido, debemos replicar esta aceleración, y hacerlo en código es sorprendentemente sencillo.
Solo necesitamos tres variables:
- player_y: La posición vertical actual del cohete.
- velocity_y: La velocidad actual a la que cae (o sube) el cohete.
- gravity: Una constante (en nuestro caso 0.6) que empuja todo hacia abajo.
En la función UpdatePhysics(), aplicamos la gravedad en dos sencillos pasos que ocurren 83 veces por segundo. Primero, le sumamos la gravedad a la velocidad (velocity_y += gravity). Segundo, le sumamos esta nueva velocidad a la posición del cohete (player_y += velocity_y).
Al hacer esto, la velocidad se hace cada vez más grande con cada fotograma, creando una curva de caída perfecta (una parábola) en lugar de un movimiento robótico en línea recta.
Detección de Colisiones: Las Cajas Invisibles
Si el cohete se mueve libremente, ¿cómo sabe MetaTrader 5 cuándo hemos chocado contra una vela japonesa? Para resolver esto utilizamos una técnica fundamental en el desarrollo de videojuegos llamada AABB (Axis-Aligned Bounding Box), o como se le conoce coloquialmente: cajas de colisión.
Piense en esto como dibujar cajas rectangulares invisibles alrededor de nuestros objetos. Para saber si el cohete se estrelló, el sistema evalúa dos condiciones:
- ¿Están superpuestos en el eje X (horizontal)? Verificamos si el borde derecho del cohete ha cruzado el borde izquierdo de la vela, y si el borde izquierdo del cohete aún no ha salido del borde derecho de la vela.
- ¿Están superpuestos en el eje Y (vertical)? Si el cohete está dentro de los límites horizontales de la vela, comprobamos si su altura toca la parte superior de la vela roja (Bear) o la parte inferior de la vela verde (Bull).
Si ambas condiciones son verdaderas al mismo tiempo... ¡Boom! Significa que las cajas invisibles se han tocado. En ese exacto milisegundo llamamos a la función TriggerGameOver(), reproducimos el doloroso sonido de desconexión de MetaTrader 5 con PlaySound("disconnect.wav") y mostramos el letrero de liquidación.

//+------------------------------------------------------------------+ //| Physics and Collision Updates | //+------------------------------------------------------------------+ void UpdatePhysics() { //--- Apply gravity to the falling speed g_velocityY += GRAVITY; //--- Move the rocket along the Y axis g_playerY += g_velocityY; //--- Detect collision with the ceiling or the floor if(g_playerY < 0.0 || g_playerY > g_chartHeight) { TriggerGameOver(); return; } //--- Update obstacles and check for collisions for(int i = 0; i < 3; i++) { g_pipes[i].x -= g_pipeSpeed; //--- If the obstacle leaves the screen, recycle it to the right if(g_pipes[i].x < -PIPE_WIDTH) { g_pipes[i].x += g_pipeSpacing * 3.0; g_pipes[i].gap_y = (double)(MathRand() % (g_chartHeight - 300) + 150); // New random gap height g_pipes[i].passed = false; } //--- AABB Collision Detection (Hitbox evaluation) // Horizontal check if(g_playerX + 15.0 > g_pipes[i].x && g_playerX - 15.0 < g_pipes[i].x + PIPE_WIDTH) { //--- Vertical check (Did it hit the upper bear candle or lower bull candle?) if(g_playerY - 12.0 < g_pipes[i].gap_y - g_pipeGap / 2.0 || g_playerY + 12.0 > g_pipes[i].gap_y + g_pipeGap / 2.0) { TriggerGameOver(); return; } } //--- Score counter (Executed only once when passing an obstacle) if(!g_pipes[i].passed && g_playerX > g_pipes[i].x + PIPE_WIDTH) { g_score++; g_pipes[i].passed = true; PlaySound("ok.wav"); //--- DYNAMIC DIFFICULTY RAMP-UP (Triggered every 10 points) if(g_score % 10 == 0) { if(g_pipeSpeed < 12.0) { g_pipeSpeed += 1.5; // Increase market volatility (speed) } if(g_pipeGap > 110) { g_pipeGap -= 20; // Decrease safety gap } if(g_pipeSpacing > 250) { g_pipeSpacing -= 30; // Decrease distance between candles } } } } } //+------------------------------------------------------------------+ //| Trigger Game Over State | //+------------------------------------------------------------------+ void TriggerGameOver() { g_gameState = GAME_OVER; //--- Save the All-Time High score in memory if(g_score > g_bestScore) { g_bestScore = g_score; } //--- Play the classic MT5 disconnection sound as a Margin Call alert PlaySound("disconnect.wav"); }
Dificultad Dinámica: El Mercado se vuelve Volátil
Un buen minijuego, al igual que un buen mercado financiero, debe cambiar su volatilidad para no aburrir al usuario. No podemos dejar que la velocidad de las velas sea siempre la misma.
Para aumentar la dificultad progresivamente, utilizamos una de las herramientas más útiles de la programación: el Operador Módulo (%). El módulo nos devuelve el resto de una división.
Por ejemplo, usamos la condición if(score % 10 == 0). Esto significa que cada vez que nuestro Profit llegue a un múltiplo de 10 (10, 20, 30...), el juego ejecutará una rutina que hace tres cosas para complicarnos la vida:
- Aumenta la velocidad a la que se mueven las velas hacia la izquierda.
- Reduce el hueco vertical entre la vela roja y la verde.
- Reduce la distancia horizontal entre un obstáculo y el siguiente.
Tomando el Control: Captura de Teclado (Eventos)
Un juego no es nada sin interactividad. MetaTrader 5 proporciona una función nativa llamada OnChartEvent() que escucha todo lo que el usuario hace (clics del ratón, movimientos y teclas presionadas).
En nuestro caso, queremos escuchar el evento CHARTEVENT_KEYDOWN (Tecla presionada). Cada tecla en su teclado tiene un código numérico asociado (lparam). El número 32 corresponde a la barra espaciadora y el 38 a la flecha hacia arriba.
Cuando el sistema detecta una de estas teclas y el juego está en estado JUGANDO, impulsamos el cohete. ¿Cómo? Simplemente inyectando un valor negativo gigante a nuestra variable velocity_y (ej. -9.5).
Como recordará del primer apartado, en nuestra pantalla la coordenada 0 está arriba. Al aplicar una velocidad negativa, el cohete salta hacia arriba, superando momentáneamente la fuerza de la gravedad.

//+------------------------------------------------------------------+ //| Chart Event Handler (Keyboard Inputs) | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Check if the event is a keyboard key press if(id == CHARTEVENT_KEYDOWN) { //--- Check if the pressed key is SPACE (32) or UP ARROW (38) if(lparam == 32 || lparam == 38) { if(g_gameState == START_SCREEN) { ResetGame(); g_gameState = PLAYING; } else { if(g_gameState == PLAYING) { //--- Apply upward thrust fighting gravity g_velocityY = JUMP_STRENGTH; } else { if(g_gameState == GAME_OVER) { g_gameState = START_SCREEN; } } } } } }
Conclusión: El Primer Paso de un Gran Proyecto
Hemos recorrido los fundamentos necesarios para pasar de la confusión inicial a un prototipo funcional sobre MetaTrader 5. Concretamente, implementamos y entendimos: el sistema de coordenadas de CCanvas, la creación de un lienzo transparente con ARGB, un game loop independiente de ticks mediante EventSetMillisecondTimer, una máquina de estados (start → playing → game over), la renderización vectorial de objetos (rectángulos, triángulos, círculos), física simple (gravedad y salto), detección de colisiones AABB y captura de teclado con OnChartEvent.
Resultado práctico: dispone de un prototipo compilable —Crazy Scalper— que
- dibuja y actualiza la escena sin depender del mercado,
- soporta transparencias y efecto de paralaje,
- entrega un esqueleto listo para integrar UI interactivas en sus EAs (canvas + ciclo + eventos + estado + colisiones).
Esto es solo la base. En próximos artículos abordaremos persistencia (.bin) para guardar récords, refactorización orientada a objetos para ampliar avatares y enemigos, y optimización/UX para reducir consumo y mejorar ergonomía visual. ¿Cuál de estas mejoras le gustaría que tratemos primero?
Puede descargar el código fuente completo de esta primera versión en el archivo adjunto. ¡Compile, juegue, rompa el código para ver qué sucede y experimente creando sus propias reglas!
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.
Redes neuronales en el trading: Segmentación periódica adaptativa (Final)
Creación de un Panel de administración de operaciones en MQL5 (Parte XII): Integración de una calculadora de valores Forex
De novato a experto: Noticias animadas utilizando MQL5 (IV) Análisis de mercado sobre modelos de IA alojados localmente
Análisis de las brechas temporales de precios en MQL5 (Parte II): Creamos un mapa de calor de la distribución de liquidez a lo largo del tiempo
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso