Compare commits

3 Commits

Author SHA1 Message Date
Gus
df79adba2e Name update 2025-07-29 10:28:13 -03:00
Gus
df8e4eeecb Correct a bug on the time calculation 2025-07-23 17:45:18 -03:00
Gus
2674d42376 Update README.md 2025-07-22 23:39:22 -03:00
3 changed files with 191 additions and 76 deletions

View File

@@ -1,6 +1,6 @@
# Virtua_Pet # Virtua_Pet
This is a tiny little project I made to get my own Virtual Pet. I hope you like it as much as I do. This is a tiny little project I made to get my own Virtual Pet. I hope you like it as much as I do.
The code can receive some optimiztion, so feel free to change it as you like. The code was written in the Arduino IDE and can receive some optimiztion, so feel free to change it as you like.
All the bitmap sprites are on the bitmap.h file. All the bitmap sprites are on the bitmap.h file.
I would be glade to see your project too if you choose to make one pet by yourself. I would be glade to see your project too if you choose to make one pet by yourself.
Thank you! Thank you!

BIN
schematics.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

View File

@@ -11,6 +11,8 @@ HARDWARE:
- 1.3" OLED I2C Screen 128x64; - 1.3" OLED I2C Screen 128x64;
- 4 push buttons (with 1n4148 diode to prevent debounce); - 4 push buttons (with 1n4148 diode to prevent debounce);
NOTE: Disable the USB serial communication for more ROM space
by Gus Costa by Gus Costa
(GitHub: gusocosta) (GitHub: gusocosta)
(Reddit: gu-ocosta) (Reddit: gu-ocosta)
@@ -19,6 +21,8 @@ by Gus Costa
#include <GyverOLED.h> #include <GyverOLED.h>
#include "bitmaps.h" #include "bitmaps.h"
//Choose your screen protocol
//GyverOLED<SSD1306_128x64> oled;
GyverOLED<SSH1106_128x64> oled; GyverOLED<SSH1106_128x64> oled;
//Peripheral constants //Peripheral constants
@@ -28,6 +32,7 @@ const uint8_t bt_up = PA11;
const uint8_t bt_left = PA12; const uint8_t bt_left = PA12;
//Time vars and const //Time vars and const
bool timer_rolling = false;
const uint8_t check_pet_time = 5; //in sec const uint8_t check_pet_time = 5; //in sec
uint32_t draw_tick = 0; uint32_t draw_tick = 0;
uint8_t draw_tick_max = 100; uint8_t draw_tick_max = 100;
@@ -75,16 +80,23 @@ uint8_t pet_mood = 0; //0: normal, 1: happy, 2: sleep, 3:dead
uint8_t pet_doing = 0; //0: nothing, 1: eating tomato, 2: eating carrot, 3: eating mushroom, 4: eating chicken, 5: eating candy, 6: cleaning, 7: petting, 8: sleeping, 9: dead uint8_t pet_doing = 0; //0: nothing, 1: eating tomato, 2: eating carrot, 3: eating mushroom, 4: eating chicken, 5: eating candy, 6: cleaning, 7: petting, 8: sleeping, 9: dead
bool pet_alive = true; bool pet_alive = true;
bool pet_sleeping = false; bool pet_sleeping = false;
bool pet_poop = false; bool pet_poop = true;
uint8_t pet_ani = 0; uint8_t pet_ani = 0;
uint8_t food_check[5] = {0,0,0,0,0}; uint8_t food_check[5] = {0,0,0,0,0}; //0: tomato, 1: carrot, 2: mushroom, 3: chicken, 4: candy
bool happy_fail = false; //vars to check for evolution bool happy_fail = false; //vars to check for evolution
bool health_fail = false; //vars to check for evolution bool health_fail = false; //vars to check for evolution
//Name vars and consts
char ascii_alphabet[63] = {}; //all the alphabet. 26 Uppercases, 26 lowercases, 10 numbers and underscore
char name_letters_array[8] = {95,95,95,95,95,95,95,95}; //the characters that compose the pet's name
String pet_name = "";
uint8_t ascii_letter_count[8] = {62,62,62,62,62,62,62,62};
uint8_t name_letters_indicator = 0; //0 to 7 for the letters, 8 for the "Next" button
//UI vars and consts //UI vars and consts
uint32_t rng_comp = 0; uint32_t rng_comp = 0;
int rng_dir = 1; int rng_dir = 1;
uint8_t screen = 1; //0: name, 1: main, 2: status uint8_t screen = 0; //0: name, 1: main, 2: status
uint8_t sel = 0; uint8_t sel = 0;
uint8_t food_sel = 0; //0: tomato, 1: carrot, 2: mushroom, 3: chicken, 4: candy uint8_t food_sel = 0; //0: tomato, 1: carrot, 2: mushroom, 3: chicken, 4: candy
const uint8_t bottom_bar_size = 5; const uint8_t bottom_bar_size = 5;
@@ -118,47 +130,43 @@ const unsigned char* trex_up[1][2] = {{trex_up1, trex_up2}};
const unsigned char* trex_eyes[3][2] = {{trex_eye_n1, trex_eye_n2},{trex_eye_h1, trex_eye_h2},{trex_eye_s1, trex_eye_s2}}; const unsigned char* trex_eyes[3][2] = {{trex_eye_n1, trex_eye_n2},{trex_eye_h1, trex_eye_h2},{trex_eye_s1, trex_eye_s2}};
void setup() { void setup() {
Serial.begin(9600); delay(10);
pinMode(bt_left, INPUT_PULLUP); pinMode(bt_left, INPUT_PULLUP);
pinMode(bt_right, INPUT_PULLUP); pinMode(bt_right, INPUT_PULLUP);
pinMode(bt_up, INPUT_PULLUP); pinMode(bt_up, INPUT_PULLUP);
pinMode(bt_down, INPUT_PULLUP); pinMode(bt_down, INPUT_PULLUP);
//pinMode(buzz, OUTPUT);
oled.init(); oled.init();
oled.clear(); oled.clear();
bottom_bar_gen(); bottom_bar_gen();
age_gen(); age_gen();
alphabet_gen();
rng_comp = int(millis()+analogRead(PA0)); rng_comp = int(millis()+analogRead(PA0));
if (rng_comp % 2 == 0){pet_sex = 1;} if (rng_comp % 2 == 0){pet_sex = 1;}
} }
// Generates the bottom bar //========================================================================
void bottom_bar_gen(){ //============================ Name Functions ============================
bottom_bar_sel_size = 127 / bottom_bar_size; //========================================================================
for(uint8_t i = 0; i < bottom_bar_size; i++){
bottom_bar_pos[i] = (i* bottom_bar_sel_size) + ((bottom_bar_sel_size - 8)/2); void alphabet_gen(){
uint8_t char_count = 65;
for (uint8_t i=0; i<26; i++){ //Uppercase
ascii_alphabet[i] = char_count + i;
} }
char_count = 97;
for (uint8_t i=0; i<26; i++){ //Lowercase
ascii_alphabet[26 + i] = char_count + i;
}
char_count = 48;
for (uint8_t i=0; i<10; i++){ //numbers
ascii_alphabet[52 + i] = char_count + i;
}
ascii_alphabet[62] = 95; //add space
} }
// Make the age string on the status screen
void age_gen(){
if (pet_alive == true){
if (act_time[3]!=0){
pet_age = String(act_time[3]) + "D";
}
else if (act_time[2]!=0){
pet_age = String(act_time[2]) + "H";
}
else if (act_time[1]!=0){
pet_age = String(act_time[1]) + "m";
}
else{
pet_age = "0m";
}
}
}
//======================================================================== //========================================================================
//============================= Time Function ============================ //============================= Time Function ============================
//======================================================================== //========================================================================
@@ -201,22 +209,25 @@ void time_check(){
if (act_time[1] >= 60){ //check hours if (act_time[1] >= 60){ //check hours
act_time[1] = 0; act_time[1] = 0;
act_time[2] += 1; act_time[2] += 1;
if (act_time[1] >= 24){ //check days }
act_time[2] = 0;
act_time[3] += 1; if (act_time[2] >= 24){ //check days
if (pet_lvl == 1){ act_time[2] = 0;
evolve_ani = true; act_time[3] += 1;
} if (pet_lvl == 1){
} evolve_ani = true;
} }
}
} }
} }
} }
//========================================================================
//================================ Buttons ===============================
//========================================================================
void check_bt(){ void check_bt(){
//========================================================================
//=============================== Button Up ============================== //=============================== Button Up ==============================
//========================================================================
if (digitalRead(bt_up) == LOW){ if (digitalRead(bt_up) == LOW){
rng_comp += int(buttom_cooldown_millis - millis()) * rng_dir; rng_comp += int(buttom_cooldown_millis - millis()) * rng_dir;
rng_dir *= -1; rng_dir *= -1;
@@ -224,8 +235,30 @@ void check_bt(){
if(millis() > buttom_cooldown_millis + buttom_cooldown){ //debounce if(millis() > buttom_cooldown_millis + buttom_cooldown){ //debounce
buttom_cooldown_millis = millis(); buttom_cooldown_millis = millis();
//Name Screen
if (screen == 0){
if(name_letters_indicator !=8){
ascii_letter_count[name_letters_indicator] = func_select(ascii_letter_count[name_letters_indicator], 62, 1);
name_letters_array[name_letters_indicator] = ascii_alphabet[ascii_letter_count[name_letters_indicator]];
}
else{
uint8_t letter_count = 0;
for(uint8_t i =0; i < 8; i++){ //Counting the written letters
if(name_letters_array[7-i] != 95){
letter_count = 7-i;
break;
}
}
for(uint8_t i =0; i <= letter_count; i++){ //Saving the name
pet_name += name_letters_array[i];
}
timer_rolling = true;
screen = 1;
}
}
//Main Screen //Main Screen
if (screen == 1){ else if (screen == 1){
if (pet_alive == true && doing_tick < millis()){ if (pet_alive == true && doing_tick < millis()){
if (sel == 0){ //Eating if (sel == 0){ //Eating
if (pet.hungry < 100){ if (pet.hungry < 100){
@@ -238,7 +271,7 @@ void check_bt(){
pet.happy += pet_eating_vars[pet_species][food_sel][3]; pet.happy += pet_eating_vars[pet_species][food_sel][3];
pet.sleep += pet_eating_vars[pet_species][food_sel][4]; pet.sleep += pet_eating_vars[pet_species][food_sel][4];
food_check[food_sel] += 1; food_check[food_sel] += 1; //0: tomato, 1: carrot, 2: mushroom, 3: chicken, 4: candy
pet_mood = 1; pet_mood = 1;
pet_sleeping = false; pet_sleeping = false;
} }
@@ -292,9 +325,7 @@ void check_bt(){
} }
} }
//========================================================================
//============================== Button Down ============================= //============================== Button Down =============================
//========================================================================
if (digitalRead(bt_down) == LOW){ if (digitalRead(bt_down) == LOW){
rng_comp += int(buttom_cooldown_millis - millis()) * rng_dir; rng_comp += int(buttom_cooldown_millis - millis()) * rng_dir;
rng_dir *= -1; rng_dir *= -1;
@@ -302,7 +333,16 @@ void check_bt(){
if(millis() > buttom_cooldown_millis + buttom_cooldown){ //debounce if(millis() > buttom_cooldown_millis + buttom_cooldown){ //debounce
buttom_cooldown_millis = millis(); buttom_cooldown_millis = millis();
if (screen == 1){ //Name Screen
if (screen == 0){
if(name_letters_indicator !=8){
ascii_letter_count[name_letters_indicator] = func_select(ascii_letter_count[name_letters_indicator], 62, 0);
name_letters_array[name_letters_indicator] = ascii_alphabet[ascii_letter_count[name_letters_indicator]];
}
}
//Main Screen
else if (screen == 1){
if (pet_alive == true && doing_tick < millis()){ if (pet_alive == true && doing_tick < millis()){
if (sel == 0){ //Eating if (sel == 0){ //Eating
food_sel = func_select(food_sel, 4, 1); food_sel = func_select(food_sel, 4, 1);
@@ -311,20 +351,27 @@ void check_bt(){
} }
} }
//Status Screen
else if (screen == 2){
screen = 1;
}
} }
} }
//========================================================================
//============================== Button Left ============================= //============================== Button Left =============================
//========================================================================
if (digitalRead(bt_left) == LOW){ if (digitalRead(bt_left) == LOW){
rng_comp += int(buttom_cooldown_millis - millis()) * rng_dir; rng_comp += int(buttom_cooldown_millis - millis()) * rng_dir;
rng_dir *= -1; rng_dir *= -1;
if(millis() > buttom_cooldown_millis + buttom_cooldown){ //debounce if(millis() > buttom_cooldown_millis + buttom_cooldown){ //debounce
buttom_cooldown_millis = millis(); buttom_cooldown_millis = millis();
//Name Screen
if (screen == 0){
name_letters_indicator = func_select(name_letters_indicator, 8, 0);
}
//Main Screen //Main Screen
if (screen == 1){ else if (screen == 1){
sel = func_select(sel, bottom_bar_size-1, 0); sel = func_select(sel, bottom_bar_size-1, 0);
} }
@@ -335,29 +382,32 @@ void check_bt(){
} }
} }
//========================================================================
//============================= Button Right ============================= //============================= Button Right =============================
//========================================================================
if (digitalRead(bt_right) == LOW){ if (digitalRead(bt_right) == LOW){
rng_comp += int(buttom_cooldown_millis - millis()) * rng_dir; rng_comp += int(buttom_cooldown_millis - millis()) * rng_dir;
rng_dir *= -1; rng_dir *= -1;
if(millis() > buttom_cooldown_millis + buttom_cooldown){ //debounce if(millis() > buttom_cooldown_millis + buttom_cooldown){ //debounce
buttom_cooldown_millis = millis(); buttom_cooldown_millis = millis();
//Name Screen
if (screen == 0){
name_letters_indicator = func_select(name_letters_indicator, 8, 1);
}
//Main Screen //Main Screen
if (screen == 1){ else if (screen == 1){
sel = func_select(sel, bottom_bar_size - 1, 1); sel = func_select(sel, bottom_bar_size - 1, 1);
} }
//Status Screen //Status Screen
else if (screen == 2){ else if (screen == 2){
screen = 1;
} }
} }
} }
} }
uint8_t func_select(uint8_t var, uint8_t max, uint8_t dir){ uint8_t func_select(uint8_t var, uint8_t max, uint8_t dir){ //dir 0: negative, 1:positive
if(dir > 0){ if(dir > 0){
if(var < max){var += 1;} if(var < max){var += 1;}
else{var = 0;} else{var = 0;}
@@ -437,7 +487,7 @@ void check_pet(){
if (pet.health <= 50){ if (pet.health <= 50){
health_fail = true; health_fail = true;
} }
if (pet.happy <= 70){ if (pet.happy <= 50){
happy_fail = true; happy_fail = true;
} }
@@ -451,11 +501,11 @@ void evolve(){
break; break;
case 1: case 1:
pet_species = 2; //evolve to Bunny pet_species = 2; //evolve to Bunny
if (happy_fail == false && food_check[0] == 0 && food_check[1] == 0 && food_check[2] == 0){ //evolve to T-Rex if (happy_fail == false && health_fail == false && food_check[0] == 0 && food_check[1] == 0 && food_check[2] == 0){ //(only eats chicken or candy) - food_check: 0: tomato, 1: carrot, 2: mushroom, 3: chicken, 4: candy
pet_species = 4; pet_species = 4; //evolve to T-Rex
} }
else if (health_fail == false && food_check[0] > 0 && food_check[1] > 0 && food_check[2] > 0 && food_check[3] == 0){ //evolve to Dino else if (health_fail == false && food_check[0] > 0 && food_check[1] > 0 && food_check[2] > 0 && food_check[3] == 0){ //(eats every vegetable. can eat candy too) - food_check: 0: tomato, 1: carrot, 2: mushroom, 3: chicken, 4: candy
pet_species = 3; pet_species = 3; //evolve to Dino
} }
break; break;
} }
@@ -470,6 +520,10 @@ void clamp_pet(){
if(pet.sleep > 200){pet.sleep = 200;} if(pet.sleep > 200){pet.sleep = 200;}
} }
//========================================================================
//================================ Misc ==================================
//========================================================================
void ani_check(){ void ani_check(){
if (pet_mood == 0) { if (pet_mood == 0) {
pet_ani = ani_tick % 2; pet_ani = ani_tick % 2;
@@ -485,6 +539,32 @@ void ani_check(){
} }
} }
// Generates the bottom bar
void bottom_bar_gen(){
bottom_bar_sel_size = 127 / bottom_bar_size;
for(uint8_t i = 0; i < bottom_bar_size; i++){
bottom_bar_pos[i] = (i* bottom_bar_sel_size) + ((bottom_bar_sel_size - 8)/2);
}
}
// Make the age string on the status screen
void age_gen(){
if (pet_alive == true){
if (act_time[3]!=0){
pet_age = String(act_time[3]) + "D";
}
else if (act_time[2]!=0){
pet_age = String(act_time[2]) + "H";
}
else if (act_time[1]!=0){
pet_age = String(act_time[1]) + "m";
}
else{
pet_age = "0m";
}
}
}
void resize(const uint8_t* bmp, uint16_t w, uint16_t h, int16_t x0, int16_t y0, uint8_t scale) { void resize(const uint8_t* bmp, uint16_t w, uint16_t h, int16_t x0, int16_t y0, uint8_t scale) {
if (!scale) return; // avoid divide-by-zero if (!scale) return; // avoid divide-by-zero
@@ -541,6 +621,30 @@ void draw(){
oled.setCursorXY(0, 0); oled.setCursorXY(0, 0);
oled.line(0, 10, 127, 10); oled.line(0, 10, 127, 10);
//========================================================================
//===============================Name Screen==============================
//========================================================================
if (screen == 0){
draw_sex(1, 16);
oled.setCursor(16,3);
oled.setScale(2);
draw_letter_scroll();
oled.setScale(1);
if (name_letters_indicator == 8){
oled.invertText(true);
}
oled.setCursorXY(91,55);
oled.print("Start!");
oled.invertText(false);
oled.rect(89,53,127,63,2);
if (name_letters_indicator == 8){
oled.line(90,54,90,63);
oled.line(90,54,127,54);
}
}
//======================================================================== //========================================================================
//===============================Main Screen============================== //===============================Main Screen==============================
//======================================================================== //========================================================================
@@ -562,7 +666,7 @@ void draw(){
} }
if (pet_poop == true){ if (pet_poop == true){
resize(icon_poop, 8, 8, 112, 32, 2); //bmp, w, h, x0, y0, scale resize(icon_poop, 8, 8, 104, 32, 2); //bmp, w, h, x0, y0, scale
} }
//draw bottom bar //draw bottom bar
@@ -580,6 +684,10 @@ void draw(){
oled.drawBitmap(bottom_bar_pos[i], 55, bottom_bar_icons[i-1], 8, 8, 0, invert_sprite); oled.drawBitmap(bottom_bar_pos[i], 55, bottom_bar_icons[i-1], 8, 8, 0, invert_sprite);
} }
} }
if (evolve_ani == true){
draw_explosion();
}
} }
//======================================================================== //========================================================================
@@ -587,14 +695,15 @@ void draw(){
//======================================================================== //========================================================================
else if(screen == 2){ else if(screen == 2){
//Draw Infos //Draw Infos
if (pet_sex == 0) {oled.drawBitmap(1, 16, icon_fem, 8, 8);} draw_sex(1, 16);
else {oled.drawBitmap(1, 16, icon_male, 8, 8);}
oled.setCursorXY(12, 16); oled.setCursorXY(12, 16);
oled.print(pet_all_species[pet_species]); oled.print(pet_name);
oled.setCursorXY(1, 28); oled.setCursorXY(1, 28);
oled.print(pet_all_species[pet_species]);
oled.setCursorXY(1, 40);
oled.print("Age: "); oled.print("Age: ");
oled.print(pet_age); oled.print(pet_age);
oled.setCursorXY(1, 40); oled.setCursorXY(1, 52);
oled.print("Lvl: "); oled.print("Lvl: ");
if(pet_lvl < 2){oled.print(pet_lvl);} if(pet_lvl < 2){oled.print(pet_lvl);}
else{oled.print("MAX");} else{oled.print("MAX");}
@@ -623,21 +732,14 @@ void draw(){
oled.rect(66 + (i*13), 18 + (34 - (34 * pet.happy)/100), 70 + (i*13), 52); oled.rect(66 + (i*13), 18 + (34 - (34 * pet.happy)/100), 70 + (i*13), 52);
} }
} }
//Draw Back Button
oled.rect(0, 55, 6, 63);
oled.setCursor(1, 7);
oled.invertText(true);
oled.print("<");
oled.invertText(false);
}
if (evolve_ani == true){
draw_explosion();
} }
oled.update(); oled.update();
} }
void draw_sex(uint8_t x, uint8_t y){
if (pet_sex == 0) {oled.drawBitmap(x, y, icon_fem, 8, 8);}
else {oled.drawBitmap(x, y, icon_male, 8, 8);}
}
//Solving the sprite puzzle //Solving the sprite puzzle
void draw_pet(){ void draw_pet(){
uint8_t animation_eyes; uint8_t animation_eyes;
@@ -713,9 +815,22 @@ void draw_explosion(){
} }
} }
void draw_letter_scroll(){
for(uint8_t i=0; i<8; i++){
if (i == name_letters_indicator){
oled.invertText(true);
}
oled.print(name_letters_array[i]);
oled.invertText(false);
}
}
void loop() { void loop() {
check_bt(); check_bt();
time_check();
if (timer_rolling == true){
time_check();
}
if(millis() > draw_tick + draw_tick_max){ //controls the screens refresh to save some battery if(millis() > draw_tick + draw_tick_max){ //controls the screens refresh to save some battery
draw_tick = millis(); draw_tick = millis();