419 lines
14 KiB
C#
419 lines
14 KiB
C#
/*******************************************************************************************
|
|
*
|
|
* raylib [core] example - automation events
|
|
*
|
|
* Example complexity rating: [★★★☆] 3/4
|
|
*
|
|
* Example originally created with raylib 5.0, last time updated with raylib 5.0
|
|
*
|
|
* Example based on 2d_camera_platformer example by arvyy (@arvyy)
|
|
*
|
|
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
|
|
* BSD-like license that allows static linking with closed source software
|
|
*
|
|
* Copyright (c) 2023-2025 Ramon Santamaria (@raysan5)
|
|
*
|
|
********************************************************************************************/
|
|
|
|
namespace Examples.Core;
|
|
|
|
public partial class AutomationEvents : IExample
|
|
{
|
|
private const int GRAVITY = 400;
|
|
private const float PLAYER_JUMP_SPD = 350.0f;
|
|
private const float PLAYER_HOR_SPD = 200.0f;
|
|
|
|
private const int MAX_ENVIRONMENT_ELEMENTS = 5;
|
|
|
|
private const int screenWidth = 800;
|
|
private const int screenHeight = 450;
|
|
|
|
public string Name => "Core / Automation Events";
|
|
|
|
public string Title => "raylib [core] example - automation events";
|
|
|
|
private struct Player
|
|
{
|
|
public Vector2 Position;
|
|
public float Speed;
|
|
public bool CanJump;
|
|
}
|
|
|
|
private struct EnvElement
|
|
{
|
|
public Rectangle Rect;
|
|
public int Blocking;
|
|
public Color Color;
|
|
}
|
|
|
|
private Player player;
|
|
private EnvElement[] envElements;
|
|
private Camera2D camera;
|
|
|
|
private AutomationEventList aelist;
|
|
private bool eventRecording;
|
|
private bool eventPlaying;
|
|
|
|
private uint frameCounter;
|
|
private uint playFrameCounter;
|
|
private uint currentPlayFrame;
|
|
|
|
public unsafe void Init()
|
|
{
|
|
// Define player
|
|
player = new Player();
|
|
player.Position = new Vector2(400, 280);
|
|
player.Speed = 0;
|
|
player.CanJump = false;
|
|
|
|
// Define environment elements (platforms)
|
|
envElements = new EnvElement[MAX_ENVIRONMENT_ELEMENTS]
|
|
{
|
|
new EnvElement { Rect = new Rectangle(0, 0, 1000, 400), Blocking = 0, Color = Color.LightGray },
|
|
new EnvElement { Rect = new Rectangle(0, 400, 1000, 200), Blocking = 1, Color = Color.Gray },
|
|
new EnvElement { Rect = new Rectangle(300, 200, 400, 10), Blocking = 1, Color = Color.Gray },
|
|
new EnvElement { Rect = new Rectangle(250, 300, 100, 10), Blocking = 1, Color = Color.Gray },
|
|
new EnvElement { Rect = new Rectangle(650, 300, 100, 10), Blocking = 1, Color = Color.Gray }
|
|
};
|
|
|
|
// Define camera
|
|
camera = new Camera2D();
|
|
camera.Target = player.Position;
|
|
camera.Offset = new Vector2(screenWidth / 2.0f, screenHeight / 2.0f);
|
|
camera.Rotation = 0.0f;
|
|
camera.Zoom = 1.0f;
|
|
|
|
// Automation events
|
|
aelist = LoadAutomationEventList((sbyte*)null); // Initialize list of automation events to record new events
|
|
SetAutomationEventList(ref aelist);
|
|
eventRecording = false;
|
|
eventPlaying = false;
|
|
|
|
frameCounter = 0;
|
|
playFrameCounter = 0;
|
|
currentPlayFrame = 0;
|
|
}
|
|
|
|
public unsafe void Update()
|
|
{
|
|
// Update
|
|
//----------------------------------------------------------------------------------
|
|
float deltaTime = 0.015f;//GetFrameTime();
|
|
|
|
// Dropped files logic
|
|
//----------------------------------------------------------------------------------
|
|
#if BROWSER
|
|
// NOTE: drag-and-drop event-list loading (.txt/.rae) is not supported in the browser host.
|
|
#else
|
|
if (IsFileDropped())
|
|
{
|
|
FilePathList droppedFiles = LoadDroppedFiles();
|
|
|
|
// Supports loading .rgs style files (text or binary) and .png style palette images
|
|
if (IsFileExtension(droppedFiles[0], ".txt;.rae"))
|
|
{
|
|
UnloadAutomationEventList(aelist);
|
|
aelist = LoadAutomationEventList(droppedFiles[0]);
|
|
|
|
eventRecording = false;
|
|
|
|
// Reset scene state to play
|
|
eventPlaying = true;
|
|
playFrameCounter = 0;
|
|
currentPlayFrame = 0;
|
|
|
|
player.Position = new Vector2(400, 280);
|
|
player.Speed = 0;
|
|
player.CanJump = false;
|
|
|
|
camera.Target = player.Position;
|
|
camera.Offset = new Vector2(screenWidth / 2.0f, screenHeight / 2.0f);
|
|
camera.Rotation = 0.0f;
|
|
camera.Zoom = 1.0f;
|
|
}
|
|
|
|
UnloadDroppedFiles(droppedFiles); // Unload filepaths from memory
|
|
}
|
|
#endif
|
|
//----------------------------------------------------------------------------------
|
|
|
|
// Update player
|
|
//----------------------------------------------------------------------------------
|
|
if (IsKeyDown(KeyboardKey.Left))
|
|
{
|
|
player.Position.X -= PLAYER_HOR_SPD * deltaTime;
|
|
}
|
|
|
|
if (IsKeyDown(KeyboardKey.Right))
|
|
{
|
|
player.Position.X += PLAYER_HOR_SPD * deltaTime;
|
|
}
|
|
|
|
if (IsKeyDown(KeyboardKey.Space) && player.CanJump)
|
|
{
|
|
player.Speed = -PLAYER_JUMP_SPD;
|
|
player.CanJump = false;
|
|
}
|
|
|
|
int hitObstacle = 0;
|
|
for (int i = 0; i < MAX_ENVIRONMENT_ELEMENTS; i++)
|
|
{
|
|
EnvElement element = envElements[i];
|
|
if (element.Blocking != 0 &&
|
|
element.Rect.X <= player.Position.X &&
|
|
element.Rect.X + element.Rect.Width >= player.Position.X &&
|
|
element.Rect.Y >= player.Position.Y &&
|
|
element.Rect.Y <= player.Position.Y + player.Speed * deltaTime)
|
|
{
|
|
hitObstacle = 1;
|
|
player.Speed = 0.0f;
|
|
player.Position.Y = element.Rect.Y;
|
|
}
|
|
}
|
|
|
|
if (hitObstacle == 0)
|
|
{
|
|
player.Position.Y += player.Speed * deltaTime;
|
|
player.Speed += GRAVITY * deltaTime;
|
|
player.CanJump = false;
|
|
}
|
|
else
|
|
{
|
|
player.CanJump = true;
|
|
}
|
|
|
|
if (IsKeyPressed(KeyboardKey.R))
|
|
{
|
|
// Reset game state
|
|
player.Position = new Vector2(400, 280);
|
|
player.Speed = 0;
|
|
player.CanJump = false;
|
|
|
|
camera.Target = player.Position;
|
|
camera.Offset = new Vector2(screenWidth / 2.0f, screenHeight / 2.0f);
|
|
camera.Rotation = 0.0f;
|
|
camera.Zoom = 1.0f;
|
|
}
|
|
//----------------------------------------------------------------------------------
|
|
|
|
// Events playing
|
|
// NOTE: Logic must be before Camera update because it depends on mouse-wheel value,
|
|
// that can be set by the played event... but some other inputs could be affected
|
|
//----------------------------------------------------------------------------------
|
|
if (eventPlaying)
|
|
{
|
|
// NOTE: Multiple events could be executed in a single frame
|
|
while (playFrameCounter == aelist.Events[currentPlayFrame].Frame)
|
|
{
|
|
PlayAutomationEvent(aelist.Events[currentPlayFrame]);
|
|
currentPlayFrame++;
|
|
|
|
if (currentPlayFrame == aelist.Count)
|
|
{
|
|
eventPlaying = false;
|
|
currentPlayFrame = 0;
|
|
playFrameCounter = 0;
|
|
|
|
TraceLog(TraceLogLevel.Info, "FINISH PLAYING!");
|
|
break;
|
|
}
|
|
}
|
|
|
|
playFrameCounter++;
|
|
}
|
|
//----------------------------------------------------------------------------------
|
|
|
|
// Update camera
|
|
//----------------------------------------------------------------------------------
|
|
camera.Target = player.Position;
|
|
camera.Offset = new Vector2(screenWidth / 2.0f, screenHeight / 2.0f);
|
|
float minX = 1000, minY = 1000, maxX = -1000, maxY = -1000;
|
|
|
|
// WARNING: On event replay, mouse-wheel internal value is set
|
|
camera.Zoom += ((float)GetMouseWheelMove() * 0.05f);
|
|
if (camera.Zoom > 3.0f)
|
|
{
|
|
camera.Zoom = 3.0f;
|
|
}
|
|
else if (camera.Zoom < 0.25f)
|
|
{
|
|
camera.Zoom = 0.25f;
|
|
}
|
|
|
|
for (int i = 0; i < MAX_ENVIRONMENT_ELEMENTS; i++)
|
|
{
|
|
EnvElement element = envElements[i];
|
|
minX = MathF.Min(element.Rect.X, minX);
|
|
maxX = MathF.Max(element.Rect.X + element.Rect.Width, maxX);
|
|
minY = MathF.Min(element.Rect.Y, minY);
|
|
maxY = MathF.Max(element.Rect.Y + element.Rect.Height, maxY);
|
|
}
|
|
|
|
Vector2 max = GetWorldToScreen2D(new Vector2(maxX, maxY), camera);
|
|
Vector2 min = GetWorldToScreen2D(new Vector2(minX, minY), camera);
|
|
|
|
if (max.X < screenWidth)
|
|
{
|
|
camera.Offset.X = screenWidth - (max.X - (float)screenWidth / 2);
|
|
}
|
|
|
|
if (max.Y < screenHeight)
|
|
{
|
|
camera.Offset.Y = screenHeight - (max.Y - (float)screenHeight / 2);
|
|
}
|
|
|
|
if (min.X > 0)
|
|
{
|
|
camera.Offset.X = (float)screenWidth / 2 - min.X;
|
|
}
|
|
|
|
if (min.Y > 0)
|
|
{
|
|
camera.Offset.Y = (float)screenHeight / 2 - min.Y;
|
|
}
|
|
//----------------------------------------------------------------------------------
|
|
|
|
// Events management
|
|
if (IsKeyPressed(KeyboardKey.S)) // Toggle events recording
|
|
{
|
|
if (!eventPlaying)
|
|
{
|
|
if (eventRecording)
|
|
{
|
|
StopAutomationEventRecording();
|
|
eventRecording = false;
|
|
|
|
ExportAutomationEventList(aelist, "automation.rae");
|
|
|
|
TraceLog(TraceLogLevel.Info, $"RECORDED FRAMES: {aelist.Count}");
|
|
}
|
|
else
|
|
{
|
|
SetAutomationEventBaseFrame(180);
|
|
StartAutomationEventRecording();
|
|
eventRecording = true;
|
|
}
|
|
}
|
|
}
|
|
else if (IsKeyPressed(KeyboardKey.A)) // Toggle events playing (WARNING: Starts next frame)
|
|
{
|
|
if (!eventRecording && (aelist.Count > 0))
|
|
{
|
|
// Reset scene state to play
|
|
eventPlaying = true;
|
|
playFrameCounter = 0;
|
|
currentPlayFrame = 0;
|
|
|
|
player.Position = new Vector2(400, 280);
|
|
player.Speed = 0;
|
|
player.CanJump = false;
|
|
|
|
camera.Target = player.Position;
|
|
camera.Offset = new Vector2(screenWidth / 2.0f, screenHeight / 2.0f);
|
|
camera.Rotation = 0.0f;
|
|
camera.Zoom = 1.0f;
|
|
}
|
|
}
|
|
|
|
if (eventRecording || eventPlaying)
|
|
{
|
|
frameCounter++;
|
|
}
|
|
else
|
|
{
|
|
frameCounter = 0;
|
|
}
|
|
//----------------------------------------------------------------------------------
|
|
|
|
// Draw
|
|
//----------------------------------------------------------------------------------
|
|
BeginDrawing();
|
|
|
|
ClearBackground(Color.LightGray);
|
|
|
|
BeginMode2D(camera);
|
|
|
|
// Draw environment elements
|
|
for (int i = 0; i < MAX_ENVIRONMENT_ELEMENTS; i++)
|
|
{
|
|
DrawRectangleRec(envElements[i].Rect, envElements[i].Color);
|
|
}
|
|
|
|
// Draw player rectangle
|
|
DrawRectangleRec(new Rectangle(player.Position.X - 20, player.Position.Y - 40, 40, 40), Color.Red);
|
|
|
|
EndMode2D();
|
|
|
|
// Draw game controls
|
|
DrawRectangle(10, 10, 290, 145, Fade(Color.SkyBlue, 0.5f));
|
|
DrawRectangleLines(10, 10, 290, 145, Fade(Color.Blue, 0.8f));
|
|
|
|
DrawText("Controls:", 20, 20, 10, Color.Black);
|
|
DrawText("- RIGHT | LEFT: Player movement", 30, 40, 10, Color.DarkGray);
|
|
DrawText("- SPACE: Player jump", 30, 60, 10, Color.DarkGray);
|
|
DrawText("- R: Reset game state", 30, 80, 10, Color.DarkGray);
|
|
|
|
DrawText("- S: START/STOP RECORDING INPUT EVENTS", 30, 110, 10, Color.Black);
|
|
DrawText("- A: REPLAY LAST RECORDED INPUT EVENTS", 30, 130, 10, Color.Black);
|
|
|
|
// Draw automation events recording indicator
|
|
if (eventRecording)
|
|
{
|
|
DrawRectangle(10, 160, 290, 30, Fade(Color.Red, 0.3f));
|
|
DrawRectangleLines(10, 160, 290, 30, Fade(Color.Maroon, 0.8f));
|
|
DrawCircle(30, 175, 10, Color.Maroon);
|
|
|
|
if (((frameCounter / 15) % 2) == 1)
|
|
{
|
|
DrawText($"RECORDING EVENTS... [{aelist.Count}]", 50, 170, 10, Color.Maroon);
|
|
}
|
|
}
|
|
else if (eventPlaying)
|
|
{
|
|
DrawRectangle(10, 160, 290, 30, Fade(Color.Lime, 0.3f));
|
|
DrawRectangleLines(10, 160, 290, 30, Fade(Color.DarkGreen, 0.8f));
|
|
DrawTriangle(new Vector2(20, 155 + 10), new Vector2(20, 155 + 30), new Vector2(40, 155 + 20), Color.DarkGreen);
|
|
|
|
if (((frameCounter / 15) % 2) == 1)
|
|
{
|
|
DrawText($"PLAYING RECORDED EVENTS... [{currentPlayFrame}]", 50, 170, 10, Color.DarkGreen);
|
|
}
|
|
}
|
|
|
|
EndDrawing();
|
|
//----------------------------------------------------------------------------------
|
|
}
|
|
|
|
public void Unload()
|
|
{
|
|
UnloadAutomationEventList(aelist);
|
|
}
|
|
|
|
public static int Main()
|
|
{
|
|
// Initialization
|
|
//--------------------------------------------------------------------------------------
|
|
InitWindow(screenWidth, screenHeight, "raylib [core] example - automation events");
|
|
|
|
var game = new AutomationEvents();
|
|
game.Init();
|
|
|
|
SetTargetFPS(60);
|
|
//--------------------------------------------------------------------------------------
|
|
|
|
// Main game loop
|
|
while (!WindowShouldClose())
|
|
{
|
|
game.Update();
|
|
}
|
|
|
|
game.Unload();
|
|
|
|
// De-Initialization
|
|
//--------------------------------------------------------------------------------------
|
|
CloseWindow(); // Close window and OpenGL context
|
|
//--------------------------------------------------------------------------------------
|
|
|
|
return 0;
|
|
}
|
|
}
|