I've been working with my own game engine for awhile now and I've been trying to get the input and controls to flow much more like a AAA FPS game, or at least a decent indie one. I've posted several topics in the past on this issue about making a smooth camera, and at the time of posting them, I had been satisfied with the results. Now however, I feel that it is not quite smooth enough, and I've switched the whole engine to SDL so I have control over the loop. (previously I was using GLUT). After changing everything to SDL, the mouse is smooth as butter, but the camera movement (walking) is still stutters and looks bad. I've implemented the last of Dwitter's game loop, the one with interpolation, and here is the relevant code:
int main (int argc, char** argv)
{
arg1 = argc;
arg2 = argv;
engineInit();
//the loop has to stay here
//kill all extra threads so they don't cause problems after we quit
//gameloop
SDL_Event event;
bool running = true;
const int TPS = 20;
const int SKIP_TICKS = 1000 / TPS;
const int MAX_FRAMESKIP = 5;
int loops;
long lastSec = 0;
long nextGameTick = SDL_GetTicks();
while (running)
{
while (SDL_PollEvent(&event)) {
//do crap with events
switch (event.type)
{
int x,y,button;
case SDL_QUIT:
SDL_GL_DeleteContext(glContext);
SDL_DestroyWindow(window);
SDL_Quit();
cout << "The window has been closed.\n";
running = false;
break;
case SDL_MOUSEMOTION:
SDL_GetMouseState(&x, &y);
passiveMouse(x,y);
break;
case SDL_MOUSEBUTTONDOWN :
button = SDL_GetMouseState(&x, &y);
mouseFunc(button,1,x,y);
break;
case SDL_KEYDOWN:
keyboardDownFunc(event.key.keysym.sym);
break;
case SDL_KEYUP:
keyboardUpFunc(event.key.keysym.sym);
break;
default:
break;
}
}
loops = 0;
while (SDL_GetTicks()> nextGameTick && loops < MAX_FRAMESKIP) {
nextGameTick+=SKIP_TICKS;
loops++;
TickHandler.tps++;
TickHandler.onTick();
int tickTime = int(SDL_GetTicks()/1000);
if (tickTime > lastSec+1)
{
TickHandler.tps = 0;
lastSec = tickTime;
}
}
TickHandler.interpolation = double(SDL_GetTicks() + SKIP_TICKS - nextGameTick )
/ double( SKIP_TICKS );
TickHandler.onRender();
render();
}
Console.consoleActivated = false;
SDL_GL_DeleteContext(glContext);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
TickHandler.onRender() calls a few interpolated functions and here is the one that controls movement of the camera:
void renderTick(float intp)
{
if (cameraPlayer == true)
{
Physics.pos3 = -camy;
Physics.collisions();
Input.applyGravity();
if (Input.walking == true)
Input.moveCameraFirstPerson(1*intp);
else
{
roll = 0;
Input.change = false;
}
}
}
And here is the move camera first person:
void inputs::moveCameraFirstPerson(float speed)
{
speed = speed*walkspeed;
float radx = ((yaw+addedAngle)*MPI/180);
camx -= (sinf(radx)/10)*speed;
camz += (cosf(radx)/10)*speed;
Physics.pos1 = -camx;
Physics.pos2 = -camz;
if (Physics.collided == true)
{float radx = ((yaw+Input.addedAngle)*3.1415926535/180);
camx += (sinf(radx)/20)*speed;
camz -= (cosf(radx)/20)*speed;
Physics.collided = false;
}
Client.x = camx;
Client.z = camz;
Client.y = camy;
Projectile.x = camx;
Projectile.z = camz;
}
I'd love if I could get this all sorted out, any help or references?
Related
I have been trying to continually print the PWM output of pin 3 inside the switch statement condition but it only prints once. Can I continually print it in serial monitor until it meets the second conditon? or use a while loop? or a if else ?
Here is my code I also have a code with a similar function but it uses if else but still it only prints once
void loop() {
// if there's any serial available, read it:
while (Serial.available() > 0) {
int InitVal = Serial.parseInt();
int red = Serial.parseInt();
switch(InitVal) {
case 1:
if (Serial.read() == '\n') {
analogWrite(redPin, red);
Serial.println(red);
Serial.write(red);
}
break;
case 0:
analogWrite(redPin, 0);
Serial.println(0);
Serial.write(0);
break;
}
}
}
I'am planning to inter-phase this with a GUI . A GUI sends ascii to the arduino reads it then sends the output value to the GUI.
Example
1.GUI sends [1,123] : 1 = the trigger point for the switch statement ; 123 = PWM value.
Arduino receives instructions and it prints out the pwm value
GUI receives pwm value and displays it
Revised code: Stuck at the last while loop maybe i could use a threading function in arduino so that the last while loop would be satisfied/dissatisfied?
void loop() {
int InitVal = 0;
// if there's any serial available, read it:
while (Serial.available() > 0) {
int InitVal = Serial.parseInt();
int red = Serial.parseInt();
switch(InitVal) {
case 1:
if (Serial.read() == '\n') {
InitVal = 1;
//analogWrite(redPin, red);
//Serial.println(red);
// Serial.write(red);
}
break;
case 0:
InitVal = 0;
//analogWrite(redPin, 0);
//Serial.println(0);
//Serial.write(0);
break;
}
if (InitVal) /* when enabled, blink leds */ {
delay(20);
while (InitVal == 1) /* loop forever */{
Serial.println(red);
Serial.write(red);
delay(20);
}
}
}
}
I discarded Serial.parseInt() function, removed the switch statments and followed #Arno Bozo advise on serial listening while following this tutorial on http://forum.arduino.cc/index.php?topic=396450.0
I came up with what I want and here is the code
const int redPin = 3;
const byte numChars = 32;
char receivedChars[numChars];
char tempChars[numChars]; // temporary array for use when parsing
// variables to hold the parsed data
boolean newData = false;
int InitVal = 0; // change to init value or red
int red = 0;
void setup() {
// initialize serial:
Serial.begin(9600);
// make the pins outputs:
pinMode(redPin, OUTPUT);
}
void loop() {
recvWithStartEndMarkers();
if (newData == true) {
strcpy(tempChars, receivedChars);
// this temporary copy is necessary to protect the original data
// because strtok() used in parseData() replaces the commas with \0
parseData();
One();
newData = false;
}
else {
Zero();
}
}
///////////////////// ///////////////////// /////////////////////
void recvWithStartEndMarkers() {
static boolean recvInProgress = false;
static byte ndx = 0;
char startMarker = '<';
char endMarker = '>';
char rc;
while (Serial.available() > 0 && newData == false) {
rc = Serial.read();
if (recvInProgress == true) {
if (rc != endMarker) {
receivedChars[ndx] = rc;
ndx++;
if (ndx >= numChars) {
ndx = numChars - 1;
}
}
else {
receivedChars[ndx] = '\0'; // terminate the string
recvInProgress = false;
ndx = 0;
newData = true;
}
}
else if (rc == startMarker) {
recvInProgress = true;
}
}
}
///////////////////// ///////////////////// /////////////////////
void parseData() { // split the data into its parts
char * strtokIndx; // this is used by strtok() as an index
strtokIndx = strtok(tempChars,","); // get the first part - the string
InitVal = atoi(strtokIndx); // copy it to messageFromPC
strtokIndx = strtok(NULL, ","); // this continues where the previous call left off
red = atoi(strtokIndx); // convert this part to an integer
}
///////////////////// ///////////////////// /////////////////////
void One() {
if (InitVal == 0){
delay(20);
Serial.println(0);
delay(20);
}
}
///////////////////// ///////////////////// /////////////////////
void Zero() {
if (InitVal == 1){
delay(20);
Serial.println(red);
delay(20);
}
}
In Summary the code works like this
1.In serial monitor send this <1,123> : 1 = the trigger point for the switch statement ; 123 = PWM value.
Arduino receives instructions and it prints out the pwm value
If you send <0,123> it prints a zero once
I post a refined code here. The architecture may be reused for serial treatment. I have written it as an example for people I meet and who are learning with arduino.
I have made comments and explanation of ways to avoid delay. Here it is used to print current value of pwm every 1s, without stopping with a delay(1000).
#include <Arduino.h>
// with schedule(f,i) , the function f() will be called every i ms
// schedule(f,i) lines are put in loop() function
// f is of type void f(void)
#define schedule(f,i) {static unsigned long l=0;unsigned long c=millis();if((unsigned long)(c-l)>=i){l=c;f();}}
const int ledPin = 13;
void setup() {
Serial.begin(9600);
pinMode(ledPin, OUTPUT);
}
boolean newCommandHasArrived=false, newParsedCommand=false;
String personalSerialBuffer=""; // char[] would be better; but String are so convenient
enum ECommand {ecmdNoPwm=0, ecmdPwm=1, ecmdBad=10 };
ECommand cmd=ecmdNoPwm;
int cmdArg=0;
boolean readSerialBuffer(String &personalSerialBuffer);
boolean parseCommand(String &apersonalSerialBuffer, ECommand &acmd, int &acmdArg);
void executeCommand(ECommand acmd, int &acmdArg);
void printCurrentValue() {Serial.println(String("cval:") + cmdArg);}
void loop() {
// transfer serial buffer in personal buffer
newCommandHasArrived = readSerialBuffer(personalSerialBuffer);
if (newCommandHasArrived) {
newCommandHasArrived = false;
newParsedCommand = parseCommand(personalSerialBuffer, cmd, cmdArg);
}
if (newParsedCommand) {
newParsedCommand = false;
executeCommand(cmd, cmdArg);
}
// I print current value every 1000ms
//delay(1000); // you can often use delay without pb, but it is a bad usage
// Here I provide you with a quick way to execute a task every 1000ms
{
const unsigned long delayBetweenExecution=1000;
static unsigned long lastTime=0;
unsigned long current = millis();
// note that C++ says that overflow on unsigned is well defined
// it calculates modulo arithmetic
if ((unsigned long)(millis() - lastTime) >= delayBetweenExecution) {
lastTime = current;
Serial.println(String("cval:") + cmdArg);
}
}
// We can make it shorter thanks to a macro:
// but you have to define a void function(void) that uses only global variable
// because it has no argument :
// void printCurrentValue() {Serial.print(String("cval:") + cmdArg);}
//schedule(printCurrentValue, 1000);
}
boolean readSerialBuffer(String &personalSerialBuffer) {
if (Serial.available() > 0) {
personalSerialBuffer.concat(Serial.readString());
}
// the frame is considered finished, if it ends with \n
if (personalSerialBuffer.endsWith("\n"))
return true;
else
return false;
}
boolean parseCommand(String &apersonalSerialBuffer, ECommand &acmd, int &acmdArg) {
// format [ 1, 123]\n
// I omit [ then I read first int : 1
// Note: I cannot detect if no int is found because it will return 0 that is a valid cmd
int readCmd = apersonalSerialBuffer.substring(1).toInt();
// conversion readCmd to acmd
switch (readCmd) {
case 0:
acmd = ecmdNoPwm; break;
case 1:
acmd = ecmdPwm; break;
default:
Serial.println(String("new command unknown: ") +
apersonalSerialBuffer);
apersonalSerialBuffer = "";
return false;
}
// find beginning of 2nd part, separated by ','
int sepPos = apersonalSerialBuffer.indexOf(',');
// no ',' : indexOf returns -1
if (sepPos == -1) {
Serial.println(String("new command could not be parsed: ") +
apersonalSerialBuffer);
apersonalSerialBuffer = "";
return false;
}
// Note: I cannot detect if no int is found because it will return 0 that is a valid cmd
acmdArg = apersonalSerialBuffer.substring(sepPos+1).toInt();
// All is fine
// I have to reset buffer before leaving
apersonalSerialBuffer = "";
return true;
}
void executeCommand(ECommand acmd, int &acmdArg) {
switch(acmd) {
case ecmdNoPwm:
// I erase acmdArg
acmdArg = 0;
analogWrite(ledPin, acmdArg);
Serial.println("cmd no pwm");
break;
case ecmdPwm:
analogWrite(ledPin, acmdArg);
Serial.print("cmd pwm:"); Serial.println(acmdArg);
break;
default:
analogWrite(ledPin, 0);
Serial.println("Bad cmd");
}
}
Context: I am currently trying to practice my C skills a little bit with the SDL 2.0.7 and SDL2_image-2.0.2.
Problem: I get an error message during the execution of my program "IMG_Load: Couldn't open xxx.png". The error seems stupid as it is very explicit: "i can't find the image", but as the image is in the appropriate folder... I think I need a fresh eye to spot the stupid mistake.
Platform: Windows 10
IDE: Visual Studio 2017
Steps done to solve the problem:
1) Tried to reduce my code lenght/functionalities to its minimum. Result: Error is still here.
2) I created a new project and copy/pasted the simplified code. Result: On the new project, there is no error, everything is working fine.
3) I compared project's options and the folder. To me they are the same:
It shouldn't be useful but just in case, here is my:
Code sample:
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <SDL.h>
#include <SDL_image.h>
#include <SDL_render.h>
#include "SDL_timer.h"
int main(int argc, char *argv[])
{
printf("argc = %d\n", argc);
for (int i = 0; i < argc; ++i)
{
printf("argv[ %d ] = %s\n", i, argv[i]);
}
SDL_Window* pWindow = NULL;
SDL_Renderer* pRenderer = NULL;
SDL_Texture* pTexture = NULL;
SDL_Surface* pLoadedSurface = NULL;
SDL_Rect* tileClipsArray = NULL;
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER))
{
fprintf(stderr, "Erreur d'initialisation de la SDL : %s\n", SDL_GetError());
}
//Initialize PNG loading
int imgFlags = IMG_INIT_PNG;
if (!(IMG_Init(imgFlags) & imgFlags))
{
printf("IMG_Load: %s\n", IMG_GetError());
}
pWindow = SDL_CreateWindow("TestLoadingImage",
SDL_WINDOWPOS_CENTERED, // initial X position.
SDL_WINDOWPOS_CENTERED, // Initial Y position.
640, // Width, in pixels.
480, // Height, in pixels.
SDL_WINDOW_OPENGL); // Window flags
assert(NULL != pWindow);
//Create renderer for the window
pRenderer = SDL_CreateRenderer(pWindow,
-1, // Index of the rendering driver to initialize, -1 to initialize the first one supporting the requested flags.
SDL_RENDERER_ACCELERATED
| SDL_RENDERER_PRESENTVSYNC); // RendererFlags
assert(NULL != pRenderer);
//Initialize renderer color
SDL_SetRenderDrawColor(pRenderer, 0xFF, 0xFF, 0xFF, 0xFF);
pLoadedSurface = IMG_Load("GroundTiles.png");
if (NULL == pLoadedSurface)
{
printf("IMG_Load: %s\n", IMG_GetError());
assert(NULL != pLoadedSurface);
}
//Create texture from surface pixels
pTexture = SDL_CreateTextureFromSurface(pRenderer, pLoadedSurface);
assert(NULL != pTexture);
//Get image dimensions
const int textureWidth = pLoadedSurface->w;
const int textureHeight = pLoadedSurface->h;
const int tileClipWidth = 128;
const int tileClipHeight = 128;
const int nbLines = textureHeight / tileClipHeight;
const int nbColumns = textureWidth / tileClipWidth;
const int nbTileClips = nbLines + nbColumns;
tileClipsArray = malloc(nbTileClips * sizeof(SDL_Rect));
int tileClipIndex = 0;
for (int tileClipLineIndex = 0; tileClipLineIndex < nbLines; ++tileClipLineIndex)
{
for (int tileClipColumnIndex = 0; tileClipColumnIndex < nbColumns; ++tileClipColumnIndex)
{
tileClipsArray[tileClipIndex].x = tileClipColumnIndex * tileClipWidth;
tileClipsArray[tileClipIndex].y = tileClipLineIndex * tileClipHeight;
tileClipsArray[tileClipIndex].w = tileClipWidth;
tileClipsArray[tileClipIndex].h = tileClipHeight;
++tileClipIndex;
}
}
//Get rid of old loaded surface
SDL_FreeSurface(pLoadedSurface);
pLoadedSurface = NULL;
int canLoop = 1;
SDL_Event event;
int lastUpdate = SDL_GetTicks();
int now = 0;
int timeToSpendPerClip = 5000;
int timeSpentwithThisClip = 0;
int clipToUse = 0;
while (canLoop)
{
now = SDL_GetTicks();
if (now - lastUpdate > 16)
{
timeSpentwithThisClip += now - lastUpdate;
lastUpdate = now;
// We are processing all the events received this frame.
while (SDL_PollEvent(&event))
{
// We need to know what kind of event we are dealing with.
switch (event.type)
{
case SDL_QUIT:
canLoop = 0;
break;
}
}
SDL_RenderClear(pRenderer);
if (timeSpentwithThisClip > timeToSpendPerClip)
{
clipToUse = rand() % 4;
timeSpentwithThisClip = 0;
}
// Set rendering space and render to screen.
SDL_Rect renderQuad;
renderQuad.x = 50;
renderQuad.y = 50;
renderQuad.w = tileClipsArray[clipToUse].w;
renderQuad.h = tileClipsArray[clipToUse].h;
SDL_RenderCopyEx(pRenderer, pTexture, &tileClipsArray[clipToUse], &renderQuad, 0.0, NULL, SDL_FLIP_NONE);
SDL_RenderPresent(pRenderer);
}
}
SDL_DestroyTexture(pTexture);
free(tileClipsArray);
SDL_DestroyRenderer(pRenderer);
pRenderer = NULL;
SDL_DestroyWindow(pWindow);
pWindow = NULL;
IMG_Quit();
SDL_Quit();
return EXIT_SUCCESS;
}
I'm probably going to copy/paste all my files from the project 1 into the project 2, but I would like to understand my mistake!
I use C and SDL 2.0, but i have a problem, this(when you click on the window's "x" it quits):
void function(SDL_Surface *screen) {
SDL_Event event;
bool quit=false;
while (!quit) {
SDL_WaitEvent(&event);
switch (event.type) {
case...
case SDL_QUIT:
quit = true;
break;
}
}
This is working, but not well. If this is in a function like this, it quits only to the main(), so I need to click on "x" again to quit from the whole program.
How can I solve it? (I want to quit from the whole program everytime, does not matter if it's in a function or not).
As already mentioned in comments you, most probably, have multiple event handling loops, which is usually incorrect design. You game general layout should be something like:
int main(int argc, char* argv[]) {
// do initialize stuff
bool run = true;
SDL_Event evt;
// game loop
while (run) {
// process OS events
while(SDL_PollEvent(&evt) != 0) {
switch (evt.type) {
case SDL_QUIT:
run = false;
break;
}
}
update();
render();
}
// clean up
SDL_Quit();
return 0;
}
In my SDL 2.0 based application, I would like to handle both Control + and Control =.
I understand that I could handle the SDL_KEYDOWN event and look for the SDLK_EQUALS keycode in combination with KEYMODE_CTRL. And even check for KEYMOD_SHIFT' to distinguish between+and=`. However, this is not portable and breaks on keyboards where those symbols are mapped to different keys.
Another thing I have tried is to enable SDL_StartTextInput() and then listen to SDL_TEXTINPUT events. However that only works for printable characters. It ignores control sequences completely.
What is the correct the way to do this? I see SDL 1.2 actually had a unicode field in the SDL_Keysym structure. That would definitely make this a lot easier for me. Does anyone know why that was removed and what the equivalent in SDL 2.0 would be?
Here is an example how you can get unicode input as SDL_TEXTINPUT but the rest as SDL_KEYDOWN:
#include "SDL.h"
#include <stdio.h>
int main(int argc, char *argv[]) {
int done = 0;
SDL_Init(SDL_INIT_VIDEO);
SDL_Window *w = SDL_CreateWindow("foo", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
640, 480, 0);
int lctrl = 0, rctrl = 0;
SDL_StartTextInput();
while (!done) {
SDL_Event event;
while(SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_QUIT:
done = 1;
break;
case SDL_TEXTINPUT: {
int ctrl_state = lctrl || rctrl;
printf("%s, ctrl %s\n", event.text.text, (ctrl_state) ? "pressed" : "released");
} break;
case SDL_KEYDOWN:
if(event.key.keysym.sym == SDLK_RCTRL) { rctrl = 1; }
else if(event.key.keysym.sym == SDLK_LCTRL) { lctrl = 1; }
break;
case SDL_KEYUP:
if(event.key.keysym.sym == SDLK_RCTRL) { rctrl = 0; }
else if(event.key.keysym.sym == SDLK_LCTRL) { lctrl = 0; }
break;
}
}
SDL_UpdateWindowSurface(w);
}
SDL_Quit();
return 0;
}
To simplify things, it ignores SDL_TEXTEDITING, which may (or not) be what you want. Also SDL_GetKeyboardState can be used instead of manually processing events and accumulating modifier keys flags, with the same result.
I've just a strange problem. I'm looking to put sounds in my game. It's a game with two phases. One of rpg and one of fight. In rpg you can launch a fight when you walk on a monster. In my code i use 3 times SDL_mixer to play music (in the menu and in the rpg and in the fight). It works in the first two cases, but when i launch a fight there is no music. The music is loaded, and Mix_playingMusic returns true, but I can't hear any music when a fight is launched. I use the same music than in the rpg and menu. The code about SDL_Mixer is the same everywhere.
Part of the code of the fight :
{
SDL_Event events;
Mix_Music *fightMusic;
enum actual_player actual = PLAYER;
int go = 1;
int action;
int monsterId = 0;
Uint32 t = SDL_GetTicks(), nt;
fightMusic = Mix_LoadMUS("data/Music/rpg.mp3"); // we loading the music
if(fightMusic == NULL){
printf("error loading fightMusic\n");
}
Mix_VolumeMusic(MIX_MAX_VOLUME);
if(Mix_PlayMusic(fightMusic, -1)<0){
printf("error playing fightMusic \n");
}
if (Mix_PlayingMusic() ){
printf("Music it's playing \n");
}
while(go){
if(isFinished(fightSdl->fight))
go = 0;
nt = SDL_GetTicks();
while (SDL_PollEvent(&events)){
action = inputKeyboard(events);
if(action == QUIT){
return -1;
go = 0;
}
if(action == -2){
clearBufferGame(fightSdl->buffer, CHARACTER);
}
if(actual == PLAYER) {
switch(action){
case MOVE_DOWNWARD:
addActionToBufferGame(fightSdl->buffer, DOWN);
break;
case MOVE_UPWARD:
addActionToBufferGame(fightSdl->buffer, UP);
break;
case MOVE_LEFT:
addActionToBufferGame(fightSdl->buffer, LEFT);
break;
case MOVE_RIGHT:
addActionToBufferGame(fightSdl->buffer, RIGHT);
break;
case ATTACK_1:
actionAttackSdl(fightSdl, ATTACK_1);
break;
case ATTACK_2:
if(Mix_PlayChannel(-1,fireSound,0)<0){
printf("error playing fire sound");
}
actionAttackSdl(fightSdl, ATTACK_2);
break;
case ATTACK_3:
actionAttackSdl(fightSdl, ATTACK_3);
break;
case NOTHING:
actual = ENNEMY;
break;
case -1:
setIsIdleCharacSdl(fightSdl->characSdl,1);
}
}
}
if (nt-t>300){
if(actual == ENNEMY){
hitNRunSdl(fightSdl, monsterId);
if(getMpEnnemyFight(fightSdl->fight) == 3 && getMpEnnemyFight(fightSdl->fight) == 3)
monsterId++;
if(monsterId >= fightSdl->nbMstrSdl){
actual = PLAYER;
setApPlayerFight(fightSdl->fight, 3);
setMpPlayerFight(fightSdl->fight, 3);
monsterId = 0;
}
}
t = nt;
}
// updatePlayerDisplay(fightSdl);
// updateMonsterDisplay(fightSdl);
updatePlayerActionFight(fightSdl);
updateSpellDisplay(fightSdl);
updateDisplay(fightSdl);
drawGame(fightSdl);
// SDL_RenderPresent(fightSdl->renderer);
}
if(getLifePointsCharacter(getCharacterFight(fightSdl->fight)) >= 0)
return 1;
return 0;
}
EDIT : i've added a printf("%s", Mix_GetError) and there is no error in the menu and rpg but in the fight it was print : Invalid audio device ID
So,
I found a solution to solve my problem. Apparently the return value of SDL_OpenAudio is always success or failure, and not a device ID, which means you can only have one device open at a time with this function. I just closed the audio before re-opning it when launching fight, and it works.